Skip to content

Commit

Permalink
add cluster type license.
Browse files Browse the repository at this point in the history
  • Loading branch information
lingdie committed May 24, 2024
1 parent 007cc37 commit ae0c08d
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 336 deletions.
250 changes: 56 additions & 194 deletions controllers/go.work.sum

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions controllers/license/api/v1/license_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type LicenseStatus struct {
//+kubebuilder:default=Pending
Phase LicenseStatusPhase `json:"phase,omitempty"`
ActivationTime metav1.Time `json:"activationTime,omitempty"`
ExpirationTime metav1.Time `json:"expirationTime,omitempty"`
}

//+kubebuilder:object:root=true
Expand Down
1 change: 1 addition & 0 deletions controllers/license/api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion controllers/license/cmd/manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ func main() {
}
}()

if err = (&controller.LicenseReconciler{ClusterID: clusterID}).SetupWithManager(mgr, db, accountDB); err != nil {
if err = (&controller.LicenseReconciler{ClusterID: clusterID}).SetupWithManager(mgr, accountDB); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "License")
os.Exit(1)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ spec:
activationTime:
format: date-time
type: string
expirationTime:
format: date-time
type: string
phase:
default: Pending
enum:
Expand Down
3 changes: 3 additions & 0 deletions controllers/license/deploy/manifests/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ spec:
activationTime:
format: date-time
type: string
expirationTime:
format: date-time
type: string
phase:
default: Pending
enum:
Expand Down
21 changes: 14 additions & 7 deletions controllers/license/internal/controller/license_activator.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ import (
"context"
"fmt"
"strings"
"time"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"sigs.k8s.io/controller-runtime/pkg/client"

licensev1 "github.com/labring/sealos/controllers/license/api/v1"
Expand All @@ -37,16 +39,21 @@ type LicenseActivator struct {

func (l *LicenseActivator) Active(license *licensev1.License) error {
// TODO mv to active function
switch license.Spec.Type {
case licensev1.AccountLicenseType:
if license.Spec.Type == licensev1.AccountLicenseType {
if err := l.Recharge(license); err != nil {
return fmt.Errorf("recharge account failed: %w", err)
}
case licensev1.ClusterLicenseType:
license.Status.Phase = licensev1.LicenseStatusPhaseActive
if err := l.Client.Status().Update(context.Background(), license); err != nil {
return fmt.Errorf("update license status failed: %w", err)
}
}
exp, err := licenseutil.GetLicenseExpireTime(license)
if err != nil {
return err
}
license.Status.ExpirationTime = metav1.NewTime(exp)
license.Status.ActivationTime = metav1.NewTime(time.Now())
license.Status.Phase = licensev1.LicenseStatusPhaseActive

if err := l.Status().Update(context.Background(), license); err != nil {
return fmt.Errorf("update license status failed: %w", err)
}
return nil
}
Expand Down
69 changes: 16 additions & 53 deletions controllers/license/internal/controller/license_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,18 @@ package controller

import (
"context"
"errors"
"time"

"github.com/go-logr/logr"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/predicate"

licensev1 "github.com/labring/sealos/controllers/license/api/v1"
"github.com/labring/sealos/controllers/license/internal/util/database"
utilerrors "github.com/labring/sealos/controllers/license/internal/util/errors"
licenseutil "github.com/labring/sealos/controllers/license/internal/util/license"
database2 "github.com/labring/sealos/controllers/pkg/database"
)

Expand All @@ -46,10 +43,11 @@ type LicenseReconciler struct {
ClusterID string

validator *LicenseValidator
recorder *LicenseRecorder
activator *LicenseActivator
}

var requeueRes = ctrl.Result{RequeueAfter: time.Minute}

// +kubebuilder:rbac:groups=license.sealos.io,resources=licenses,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=license.sealos.io,resources=licenses/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=license.sealos.io,resources=licenses/finalizers,verbs=update
Expand All @@ -69,66 +67,36 @@ func (r *LicenseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct

func (r *LicenseReconciler) reconcile(ctx context.Context, license *licensev1.License) (ctrl.Result, error) {
r.Logger.V(1).Info("reconcile for license", "license", license.Namespace+"/"+license.Name)
// if license is active, do nothing and return
if license.Status.Phase == licensev1.LicenseStatusPhaseActive {
r.Logger.V(1).Info("license is active", "license", license.Namespace+"/"+license.Name)
return ctrl.Result{}, nil
}

// check if license is valid
valid, err := r.validator.Validate(license)
if errors.Is(err, utilerrors.ErrClusterIDNotMatch) {
r.Logger.V(1).Info("license clusterID not match", "license", license.Namespace+"/"+license.Name)
license.Status.Phase = licensev1.LicenseStatusPhaseFailed
_ = r.Status().Update(ctx, license)
return ctrl.Result{}, nil
}
if err != nil {
r.Logger.V(1).Error(err, "failed to validate license")
return ctrl.Result{}, err
return requeueRes, err
}
// if license is invalid, update license status to failed
if !valid {
r.Logger.V(1).Info("license is invalid", "license", license.Namespace+"/"+license.Name)
// TODO mv to a function
switch valid {
case licenseutil.ValidationClusterIDMismatch, licenseutil.ValidationClusterInfoMismatch, licenseutil.ValidationExpired:
// update license status to failed
license.Status.Phase = licensev1.LicenseStatusPhaseFailed
r.Logger.V(1).Info("license is invalid", "license", license.Namespace+"/"+license.Name)
_ = r.Status().Update(ctx, license)
return ctrl.Result{}, nil
return requeueRes, nil
default:
}

// check if license has been used
found, err := r.recorder.Find(ctx, license)
if err != nil {
r.Logger.V(1).Error(err, "failed to get license from database")
return ctrl.Result{}, err
}
// if license has been used, update license status to failed
if found {
r.Logger.V(1).Info("license has been used", "license", license.Namespace+"/"+license.Name)
license.Status.Phase = licensev1.LicenseStatusPhaseFailed
_ = r.Status().Update(ctx, license)
return ctrl.Result{}, nil
if license.Spec.Type == licensev1.AccountLicenseType && license.Status.Phase == licensev1.LicenseStatusPhaseActive {
r.Logger.V(1).Info("license is active, skip reconcile", "license", license.Namespace+"/"+license.Name)
return requeueRes, nil
}

if err := r.activator.Active(license); err != nil {
r.Logger.V(1).Error(err, "failed to active license")
return ctrl.Result{}, err
}

// update license status to active
license.Status.ActivationTime = metav1.NewTime(time.Now())
license.Status.Phase = licensev1.LicenseStatusPhaseActive
_ = r.Status().Update(ctx, license)
// record license token and key to database to prevent reuse
if err = r.recorder.Store(ctx, license); err != nil {
r.Logger.V(1).Error(err, "failed to store license in database")
return ctrl.Result{}, err
return requeueRes, err
}
return ctrl.Result{}, nil
return ctrl.Result{RequeueAfter: time.Minute * 30}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *LicenseReconciler) SetupWithManager(mgr ctrl.Manager, db *database.DataBase, accountDB database2.AccountV2) error {
func (r *LicenseReconciler) SetupWithManager(mgr ctrl.Manager, accountDB database2.AccountV2) error {
r.Logger = mgr.GetLogger().WithName("controller").WithName("License")
r.Client = mgr.GetClient()

Expand All @@ -137,11 +105,6 @@ func (r *LicenseReconciler) SetupWithManager(mgr ctrl.Manager, db *database.Data
ClusterID: r.ClusterID,
}

r.recorder = &LicenseRecorder{
Client: r.Client,
db: db,
}

r.activator = &LicenseActivator{
Client: r.Client,
accountDB: accountDB,
Expand Down
68 changes: 0 additions & 68 deletions controllers/license/internal/controller/license_recorder.go

This file was deleted.

4 changes: 2 additions & 2 deletions controllers/license/internal/controller/license_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ type LicenseValidator struct {
ClusterID string
}

func (v *LicenseValidator) Validate(license *licensev1.License) (bool, error) {
func (v *LicenseValidator) Validate(license *licensev1.License) (licenseutil.ValidationResult, error) {
nodeList := &v1.NodeList{}
if err := v.Client.List(context.Background(), nodeList); err != nil {
return false, err
return licenseutil.ValidationError, err
}
nodeCount := len(nodeList.Items)
totalCPU := resource.NewQuantity(0, resource.DecimalSI)
Expand Down
1 change: 1 addition & 0 deletions controllers/license/internal/util/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ var ErrLicenseTypeNotMatch = fmt.Errorf("the license type provided appears to be
var ErrClaimsConvent = fmt.Errorf("the claims data provided appears to be invalid")
var ErrClusterIDNotMatch = fmt.Errorf("the cluster id provided appears to be invalid")
var ErrClusterLicenseNotMatch = fmt.Errorf("the cluster license provided appears to be invalid")
var ErrLicenseExpired = fmt.Errorf("the license provided appears to be expired")
35 changes: 29 additions & 6 deletions controllers/license/internal/util/license/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package license

import (
"time"

"encoding/base64"
"github.com/golang-jwt/jwt/v4"

Expand All @@ -26,6 +28,16 @@ import (
"github.com/labring/sealos/controllers/pkg/crypto"
)

type ValidationResult int

const (
ValidationSuccess ValidationResult = iota
ValidationError
ValidationClusterIDMismatch
ValidationClusterInfoMismatch
ValidationExpired
)

func ParseLicenseToken(license *licensev1.License) (*jwt.Token, error) {
token, err := jwt.ParseWithClaims(license.Spec.Token, &utilclaims.Claims{},
func(token *jwt.Token) (interface{}, error) {
Expand Down Expand Up @@ -57,24 +69,35 @@ func GetClaims(license *licensev1.License) (*utilclaims.Claims, error) {
return claims, nil
}

func IsLicenseValid(license *licensev1.License, clusterInfo *cluster.Info, clusterID string) (bool, error) {
func IsLicenseValid(license *licensev1.License, clusterInfo *cluster.Info, clusterID string) (ValidationResult, error) {
token, err := ParseLicenseToken(license)
if err != nil {
return false, err
return ValidationError, err
}
if !token.Valid {
return ValidationExpired, nil
}
claims, err := GetClaims(license)
if err != nil {
return false, err
return ValidationError, err
}
// if clusterID is empty, it means this license is a super license.
if claims.ClusterID != "" && claims.ClusterID != clusterID {
return false, errors.ErrClusterIDNotMatch
return ValidationError, nil
}

if claims.Type == licensev1.ClusterLicenseType {
if !clusterInfo.CompareWithClaimData(&claims.Data) {
return false, errors.ErrClusterLicenseNotMatch
return ValidationClusterInfoMismatch, nil
}
}
return token.Valid, nil
return ValidationSuccess, nil
}

func GetLicenseExpireTime(license *licensev1.License) (time.Time, error) {
claims, err := GetClaims(license)
if err != nil {
return time.Time{}, err
}
return claims.ExpiresAt.UTC(), nil
}
Loading

0 comments on commit ae0c08d

Please sign in to comment.