mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-14 06:05:58 +08:00
chore(license): default to basic plan if license validation fails (#6972)
* chore(license): default to basic plan if license validation fails 3 times * chore(license): revert the on-boot validation check * chore(license): reset the atomic counter * chore(license): revert the table creation removal * chore(license): remove verify issue workflow * chore(license): add proper log level * chore(license): add proper log level * chore(license): close the validation go routine post defaulting to basic plan * chore(license): set the validator running flag to false as well
This commit is contained in:
parent
f2be856f63
commit
6dfea14219
19
.github/workflows/pr_verify_linked_issue.yml
vendored
19
.github/workflows/pr_verify_linked_issue.yml
vendored
@ -1,19 +0,0 @@
|
|||||||
# This workflow will inspect a pull request to ensure there is a linked issue or a
|
|
||||||
# valid issue is mentioned in the body. If neither is present it fails the check and adds
|
|
||||||
# a comment alerting users of this missing requirement.
|
|
||||||
name: VerifyIssue
|
|
||||||
|
|
||||||
on:
|
|
||||||
pull_request:
|
|
||||||
types: [edited, opened]
|
|
||||||
check_run:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
verify_linked_issue:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Ensure Pull Request has a linked issue.
|
|
||||||
steps:
|
|
||||||
- name: Verify Linked Issue
|
|
||||||
uses: srikanthccv/verify-linked-issue-action@v0.71
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
@ -32,19 +32,6 @@ func (r *Repo) InitDB(inputDB *sqlx.DB) error {
|
|||||||
return sqlite.InitDB(inputDB)
|
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) {
|
func (r *Repo) GetLicensesV3(ctx context.Context) ([]*model.LicenseV3, error) {
|
||||||
licensesData := []model.LicenseDB{}
|
licensesData := []model.LicenseDB{}
|
||||||
licenseV3Data := []*model.LicenseV3{}
|
licenseV3Data := []*model.LicenseV3{}
|
||||||
@ -73,35 +60,6 @@ func (r *Repo) GetLicensesV3(ctx context.Context) ([]*model.LicenseV3, error) {
|
|||||||
return licenseV3Data, nil
|
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.
|
// 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.
|
// 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) {
|
func (r *Repo) GetActiveLicense(ctx context.Context) (*model.License, *basemodel.ApiError) {
|
||||||
@ -156,50 +114,56 @@ func (r *Repo) GetActiveLicenseV3(ctx context.Context) (*model.LicenseV3, error)
|
|||||||
return active, nil
|
return active, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// InsertLicense inserts a new license in db
|
// InsertLicenseV3 inserts a new license v3 in db
|
||||||
func (r *Repo) InsertLicense(ctx context.Context, l *model.License) error {
|
func (r *Repo) InsertLicenseV3(ctx context.Context, l *model.LicenseV3) *model.ApiError {
|
||||||
|
|
||||||
if l.Key == "" {
|
query := `INSERT INTO licenses_v3 (id, key, data) VALUES ($1, $2, $3)`
|
||||||
return fmt.Errorf("insert license failed: license key is required")
|
|
||||||
|
// 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}
|
||||||
}
|
}
|
||||||
|
|
||||||
query := `INSERT INTO licenses
|
_, err = r.db.ExecContext(ctx,
|
||||||
(key, planDetails, activationId, validationmessage)
|
|
||||||
VALUES ($1, $2, $3, $4)`
|
|
||||||
|
|
||||||
_, err := r.db.ExecContext(ctx,
|
|
||||||
query,
|
query,
|
||||||
|
l.ID,
|
||||||
l.Key,
|
l.Key,
|
||||||
l.PlanDetails,
|
string(licenseData),
|
||||||
l.ActivationId,
|
)
|
||||||
l.ValidationMessage)
|
|
||||||
|
|
||||||
if err != nil {
|
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))
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdatePlanDetails writes new plan details to the db
|
// UpdateLicenseV3 updates a new license v3 in db
|
||||||
func (r *Repo) UpdatePlanDetails(ctx context.Context,
|
func (r *Repo) UpdateLicenseV3(ctx context.Context, l *model.LicenseV3) error {
|
||||||
key,
|
|
||||||
planDetails string) error {
|
|
||||||
|
|
||||||
if key == "" {
|
// the key and id for the license can't change so only update the data here!
|
||||||
return fmt.Errorf("update plan details failed: license key is required")
|
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 := `UPDATE licenses
|
query,
|
||||||
SET planDetails = $1,
|
license,
|
||||||
updatedAt = $2
|
l.ID,
|
||||||
WHERE key = $3`
|
)
|
||||||
|
|
||||||
_, err := r.db.ExecContext(ctx, query, planDetails, time.Now(), key)
|
|
||||||
|
|
||||||
if err != nil {
|
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)
|
return fmt.Errorf("failed to update license in db: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -281,59 +245,3 @@ func (r *Repo) InitFeatures(req basemodel.FeatureSet) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
|
||||||
}
|
|
||||||
|
@ -27,26 +27,19 @@ var LM *Manager
|
|||||||
var validationFrequency = 24 * 60 * time.Minute
|
var validationFrequency = 24 * 60 * time.Minute
|
||||||
|
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
repo *Repo
|
repo *Repo
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
|
|
||||||
validatorRunning bool
|
validatorRunning bool
|
||||||
|
|
||||||
// end the license validation, this is important to gracefully
|
// end the license validation, this is important to gracefully
|
||||||
// stopping validation and protect in-consistent updates
|
// stopping validation and protect in-consistent updates
|
||||||
done chan struct{}
|
done chan struct{}
|
||||||
|
|
||||||
// terminated waits for the validate go routine to end
|
// terminated waits for the validate go routine to end
|
||||||
terminated chan struct{}
|
terminated chan struct{}
|
||||||
|
|
||||||
// last time the license was validated
|
// last time the license was validated
|
||||||
lastValidated int64
|
lastValidated int64
|
||||||
|
|
||||||
// keep track of validation failure attempts
|
// keep track of validation failure attempts
|
||||||
failedAttempts uint64
|
failedAttempts uint64
|
||||||
|
|
||||||
// keep track of active license and features
|
// keep track of active license and features
|
||||||
activeLicense *model.License
|
|
||||||
activeLicenseV3 *model.LicenseV3
|
activeLicenseV3 *model.LicenseV3
|
||||||
activeFeatures basemodel.FeatureSet
|
activeFeatures basemodel.FeatureSet
|
||||||
}
|
}
|
||||||
@ -58,7 +51,6 @@ func StartManager(db *sqlx.DB, features ...basemodel.Feature) (*Manager, error)
|
|||||||
|
|
||||||
repo := NewLicenseRepo(db)
|
repo := NewLicenseRepo(db)
|
||||||
err := repo.InitDB(db)
|
err := repo.InitDB(db)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to initiate license repo: %v", err)
|
return nil, fmt.Errorf("failed to initiate license repo: %v", err)
|
||||||
}
|
}
|
||||||
@ -66,10 +58,10 @@ func StartManager(db *sqlx.DB, features ...basemodel.Feature) (*Manager, error)
|
|||||||
m := &Manager{
|
m := &Manager{
|
||||||
repo: &repo,
|
repo: &repo,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.start(features...); err != nil {
|
if err := m.start(features...); err != nil {
|
||||||
return m, err
|
return m, err
|
||||||
}
|
}
|
||||||
|
|
||||||
LM = m
|
LM = m
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
@ -119,6 +111,7 @@ func (lm *Manager) LoadActiveLicenseV3(features ...basemodel.Feature) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if active != nil {
|
if active != nil {
|
||||||
lm.SetActiveV3(active, features...)
|
lm.SetActiveV3(active, features...)
|
||||||
} else {
|
} else {
|
||||||
@ -136,32 +129,6 @@ func (lm *Manager) LoadActiveLicenseV3(features ...basemodel.Feature) error {
|
|||||||
return nil
|
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) {
|
func (lm *Manager) GetLicensesV3(ctx context.Context) (response []*model.LicenseV3, apiError *model.ApiError) {
|
||||||
|
|
||||||
licenses, err := lm.repo.GetLicensesV3(ctx)
|
licenses, err := lm.repo.GetLicensesV3(ctx)
|
||||||
@ -188,11 +155,11 @@ func (lm *Manager) GetLicensesV3(ctx context.Context) (response []*model.License
|
|||||||
func (lm *Manager) ValidatorV3(ctx context.Context) {
|
func (lm *Manager) ValidatorV3(ctx context.Context) {
|
||||||
zap.L().Info("ValidatorV3 started!")
|
zap.L().Info("ValidatorV3 started!")
|
||||||
defer close(lm.terminated)
|
defer close(lm.terminated)
|
||||||
|
|
||||||
tick := time.NewTicker(validationFrequency)
|
tick := time.NewTicker(validationFrequency)
|
||||||
defer tick.Stop()
|
defer tick.Stop()
|
||||||
|
|
||||||
lm.ValidateV3(ctx)
|
lm.ValidateV3(ctx)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-lm.done:
|
case <-lm.done:
|
||||||
@ -238,10 +205,27 @@ func (lm *Manager) ValidateV3(ctx context.Context) (reterr error) {
|
|||||||
lm.lastValidated = time.Now().Unix()
|
lm.lastValidated = time.Now().Unix()
|
||||||
if reterr != nil {
|
if reterr != nil {
|
||||||
zap.L().Error("License validation completed with error", zap.Error(reterr))
|
zap.L().Error("License validation completed with error", zap.Error(reterr))
|
||||||
|
|
||||||
atomic.AddUint64(&lm.failedAttempts, 1)
|
atomic.AddUint64(&lm.failedAttempts, 1)
|
||||||
|
// default to basic plan if validation fails for three consecutive times
|
||||||
|
if atomic.LoadUint64(&lm.failedAttempts) > 3 {
|
||||||
|
zap.L().Error("License validation completed with error for three consecutive times, defaulting to basic plan", zap.String("license_id", lm.activeLicenseV3.ID), zap.Bool("license_validation", false))
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
lm.done <- struct{}{}
|
||||||
|
lm.validatorRunning = false
|
||||||
|
}
|
||||||
|
|
||||||
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_CHECK_FAILED,
|
telemetry.GetInstance().SendEvent(telemetry.TELEMETRY_LICENSE_CHECK_FAILED,
|
||||||
map[string]interface{}{"err": reterr.Error()}, "", true, false)
|
map[string]interface{}{"err": reterr.Error()}, "", true, false)
|
||||||
} else {
|
} else {
|
||||||
|
// reset the failed attempts counter
|
||||||
|
atomic.StoreUint64(&lm.failedAttempts, 0)
|
||||||
zap.L().Info("License validation completed with no errors")
|
zap.L().Info("License validation completed with no errors")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -61,37 +60,6 @@ type LicensePlan struct {
|
|||||||
Status string `json:"status"`
|
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 {
|
type Licenses struct {
|
||||||
TrialStart int64 `json:"trialStart"`
|
TrialStart int64 `json:"trialStart"`
|
||||||
TrialEnd int64 `json:"trialEnd"`
|
TrialEnd int64 `json:"trialEnd"`
|
||||||
|
Loading…
x
Reference in New Issue
Block a user