Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(license): default to basic plan if license validation fails #6972

Merged
merged 13 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 33 additions & 125 deletions ee/query-service/license/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,19 +32,6 @@ func (r *Repo) InitDB(inputDB *sqlx.DB) error {
return sqlite.InitDB(inputDB)
}

func (r *Repo) GetLicenses(ctx context.Context) ([]model.License, error) {
licenses := []model.License{}

query := "SELECT key, activationId, planDetails, validationMessage FROM licenses"

err := r.db.Select(&licenses, query)
if err != nil {
return nil, fmt.Errorf("failed to get licenses from db: %v", err)
}

return licenses, nil
}

func (r *Repo) GetLicensesV3(ctx context.Context) ([]*model.LicenseV3, error) {
licensesData := []model.LicenseDB{}
licenseV3Data := []*model.LicenseV3{}
Expand Down Expand Up @@ -73,35 +60,6 @@ func (r *Repo) GetLicensesV3(ctx context.Context) ([]*model.LicenseV3, error) {
return licenseV3Data, nil
}

func (r *Repo) GetActiveLicenseV2(ctx context.Context) (*model.License, *basemodel.ApiError) {
var err error
licenses := []model.License{}

query := "SELECT key, activationId, planDetails, validationMessage FROM licenses"

err = r.db.Select(&licenses, query)
if err != nil {
return nil, basemodel.InternalError(fmt.Errorf("failed to get active licenses from db: %v", err))
}

var active *model.License
for _, l := range licenses {
l.ParsePlan()
if active == nil &&
(l.ValidFrom != 0) &&
(l.ValidUntil == -1 || l.ValidUntil > time.Now().Unix()) {
active = &l
}
if active != nil &&
l.ValidFrom > active.ValidFrom &&
(l.ValidUntil == -1 || l.ValidUntil > time.Now().Unix()) {
active = &l
}
}

return active, nil
}

// GetActiveLicense fetches the latest active license from DB.
// If the license is not present, expect a nil license and a nil error in the output.
func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, *basemodel.ApiError) {
Expand Down Expand Up @@ -156,50 +114,56 @@ func (r *Repo) GetActiveLicenseV3(ctx context.Context) (*model.LicenseV3, error)
return active, nil
}

// InsertLicense inserts a new license in db
func (r *Repo) InsertLicense(ctx context.Context, l *model.License) error {
// InsertLicenseV3 inserts a new license v3 in db
func (r *Repo) InsertLicenseV3(ctx context.Context, l *model.LicenseV3) *model.ApiError {

if l.Key == "" {
return fmt.Errorf("insert license failed: license key is required")
}
query := `INSERT INTO licenses_v3 (id, key, data) VALUES ($1, $2, $3)`

query := `INSERT INTO licenses
(key, planDetails, activationId, validationmessage)
VALUES ($1, $2, $3, $4)`
// licsense is the entity of zeus so putting the entire license here without defining schema
licenseData, err := json.Marshal(l.Data)
if err != nil {
return &model.ApiError{Typ: basemodel.ErrorBadData, Err: err}
}

_, err := r.db.ExecContext(ctx,
_, err = r.db.ExecContext(ctx,
query,
l.ID,
l.Key,
l.PlanDetails,
l.ActivationId,
l.ValidationMessage)
string(licenseData),
)

if err != nil {
if sqliteErr, ok := err.(sqlite3.Error); ok {
if sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique {
zap.L().Error("error in inserting license data: ", zap.Error(sqliteErr))
return &model.ApiError{Typ: model.ErrorConflict, Err: sqliteErr}
}
}
zap.L().Error("error in inserting license data: ", zap.Error(err))
return fmt.Errorf("failed to insert license in db: %v", err)
return &model.ApiError{Typ: basemodel.ErrorExec, Err: err}
}

return nil
}

// UpdatePlanDetails writes new plan details to the db
func (r *Repo) UpdatePlanDetails(ctx context.Context,
key,
planDetails string) error {

if key == "" {
return fmt.Errorf("update plan details failed: license key is required")
}
// UpdateLicenseV3 updates a new license v3 in db
func (r *Repo) UpdateLicenseV3(ctx context.Context, l *model.LicenseV3) error {

query := `UPDATE licenses
SET planDetails = $1,
updatedAt = $2
WHERE key = $3`
// the key and id for the license can't change so only update the data here!
query := `UPDATE licenses_v3 SET data=$1 WHERE id=$2;`

_, err := r.db.ExecContext(ctx, query, planDetails, time.Now(), key)
license, err := json.Marshal(l.Data)
if err != nil {
return fmt.Errorf("insert license failed: license marshal error")
}
_, err = r.db.ExecContext(ctx,
query,
license,
l.ID,
)

if err != nil {
zap.L().Error("error in updating license: ", zap.Error(err))
zap.L().Error("error in updating license data: ", zap.Error(err))
return fmt.Errorf("failed to update license in db: %v", err)
}

Expand Down Expand Up @@ -281,59 +245,3 @@ func (r *Repo) InitFeatures(req basemodel.FeatureSet) error {
}
return nil
}

// InsertLicenseV3 inserts a new license v3 in db
func (r *Repo) InsertLicenseV3(ctx context.Context, l *model.LicenseV3) *model.ApiError {

query := `INSERT INTO licenses_v3 (id, key, data) VALUES ($1, $2, $3)`

// licsense is the entity of zeus so putting the entire license here without defining schema
licenseData, err := json.Marshal(l.Data)
if err != nil {
return &model.ApiError{Typ: basemodel.ErrorBadData, Err: err}
}

_, err = r.db.ExecContext(ctx,
query,
l.ID,
l.Key,
string(licenseData),
)

if err != nil {
if sqliteErr, ok := err.(sqlite3.Error); ok {
if sqliteErr.ExtendedCode == sqlite3.ErrConstraintUnique {
zap.L().Error("error in inserting license data: ", zap.Error(sqliteErr))
return &model.ApiError{Typ: model.ErrorConflict, Err: sqliteErr}
}
}
zap.L().Error("error in inserting license data: ", zap.Error(err))
return &model.ApiError{Typ: basemodel.ErrorExec, Err: err}
}

return nil
}

// UpdateLicenseV3 updates a new license v3 in db
func (r *Repo) UpdateLicenseV3(ctx context.Context, l *model.LicenseV3) error {

// the key and id for the license can't change so only update the data here!
query := `UPDATE licenses_v3 SET data=$1 WHERE id=$2;`

license, err := json.Marshal(l.Data)
if err != nil {
return fmt.Errorf("insert license failed: license marshal error")
}
_, err = r.db.ExecContext(ctx,
query,
license,
l.ID,
)

if err != nil {
zap.L().Error("error in updating license data: ", zap.Error(err))
return fmt.Errorf("failed to update license in db: %v", err)
}

return nil
}
58 changes: 20 additions & 38 deletions ee/query-service/license/manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,19 @@ var LM *Manager
var validationFrequency = 24 * 60 * time.Minute

type Manager struct {
repo *Repo
mutex sync.Mutex

repo *Repo
mutex sync.Mutex
validatorRunning bool

// end the license validation, this is important to gracefully
// stopping validation and protect in-consistent updates
done chan struct{}

// terminated waits for the validate go routine to end
terminated chan struct{}

// last time the license was validated
lastValidated int64

// keep track of validation failure attempts
failedAttempts uint64

// keep track of active license and features
activeLicense *model.License
activeLicenseV3 *model.LicenseV3
activeFeatures basemodel.FeatureSet
}
Expand All @@ -58,18 +51,17 @@ func StartManager(db *sqlx.DB, features ...basemodel.Feature) (*Manager, error)

repo := NewLicenseRepo(db)
err := repo.InitDB(db)

if err != nil {
return nil, fmt.Errorf("failed to initiate license repo: %v", err)
}

m := &Manager{
repo: &repo,
}

if err := m.start(features...); err != nil {
return m, err
}

LM = m
return m, nil
}
Expand Down Expand Up @@ -119,6 +111,7 @@ func (lm *Manager) LoadActiveLicenseV3(features ...basemodel.Feature) error {
if err != nil {
return err
}

if active != nil {
lm.SetActiveV3(active, features...)
} else {
Expand All @@ -136,32 +129,6 @@ func (lm *Manager) LoadActiveLicenseV3(features ...basemodel.Feature) error {
return nil
}

func (lm *Manager) GetLicenses(ctx context.Context) (response []model.License, apiError *model.ApiError) {

licenses, err := lm.repo.GetLicenses(ctx)
if err != nil {
return nil, model.InternalError(err)
}

for _, l := range licenses {
l.ParsePlan()

if lm.activeLicense != nil && l.Key == lm.activeLicense.Key {
l.IsCurrent = true
}

if l.ValidUntil == -1 {
// for subscriptions, there is no end-date as such
// but for showing user some validity we default one year timespan
l.ValidUntil = l.ValidFrom + 31556926
}

response = append(response, l)
}

return
}

func (lm *Manager) GetLicensesV3(ctx context.Context) (response []*model.LicenseV3, apiError *model.ApiError) {

licenses, err := lm.repo.GetLicensesV3(ctx)
Expand All @@ -188,11 +155,11 @@ func (lm *Manager) GetLicensesV3(ctx context.Context) (response []*model.License
func (lm *Manager) ValidatorV3(ctx context.Context) {
zap.L().Info("ValidatorV3 started!")
defer close(lm.terminated)

tick := time.NewTicker(validationFrequency)
defer tick.Stop()

lm.ValidateV3(ctx)

for {
select {
case <-lm.done:
Expand Down Expand Up @@ -238,10 +205,25 @@ func (lm *Manager) ValidateV3(ctx context.Context) (reterr error) {
lm.lastValidated = time.Now().Unix()
if reterr != nil {
zap.L().Error("License validation completed with error", zap.Error(reterr))

atomic.AddUint64(&lm.failedAttempts, 1)
// default to basic plan if validation fails for three consecutive times
if atomic.LoadUint64(&lm.failedAttempts) > 3 {
lm.activeLicenseV3 = nil
lm.activeFeatures = model.BasicPlan
setDefaultFeatures(lm)
err := lm.InitFeatures(lm.activeFeatures)
if err != nil {
zap.L().Error("Couldn't initialize features", zap.Error(err))
}
zap.L().Info("License validation completed with error for three consecutive times, defaulting to basic plan")
vikrantgupta25 marked this conversation as resolved.
Show resolved Hide resolved
}

telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_CHECK_FAILED,
map[string]interface{}{"err": reterr.Error()}, "", true, false)
} else {
// reset the failed attempts counter
atomic.StoreUint64(&lm.failedAttempts, 0)
zap.L().Info("License validation completed with no errors")
}

Expand Down
32 changes: 0 additions & 32 deletions ee/query-service/model/license.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package model

import (
"encoding/base64"
"encoding/json"
"fmt"
"reflect"
Expand Down Expand Up @@ -61,37 +60,6 @@ type LicensePlan struct {
Status string `json:"status"`
}

func (l *License) ParsePlan() error {
l.LicensePlan = LicensePlan{}

planData, err := base64.StdEncoding.DecodeString(l.PlanDetails)
if err != nil {
return err
}

plan := LicensePlan{}
err = json.Unmarshal([]byte(planData), &plan)
if err != nil {
l.ValidationMessage = "failed to parse plan from license"
return errors.Wrap(err, "failed to parse plan from license")
}

l.LicensePlan = plan
l.ParseFeatures()
return nil
}

func (l *License) ParseFeatures() {
switch l.PlanKey {
case Pro:
l.FeatureSet = ProPlan
case Enterprise:
l.FeatureSet = EnterprisePlan
default:
l.FeatureSet = BasicPlan
}
}

type Licenses struct {
TrialStart int64 `json:"trialStart"`
TrialEnd int64 `json:"trialEnd"`
Expand Down
Loading