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

Optimize/debt resume speed #4658

Merged
merged 2 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion controllers/account/controllers/account_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,7 @@ func (r *AccountReconciler) DeletePayment(ctx context.Context) error {
if err != nil {
r.Logger.Error(err, "get payment details failed")
}
if status == pay.StatusSuccess {
if status == pay.PaymentSuccess {
r.Logger.Info("payment success, post delete payment cr", "payment", payment, "amount", amount)
}
// expire session
Expand Down
90 changes: 75 additions & 15 deletions controllers/account/controllers/debt_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ import (
"strings"
"time"

"github.com/labring/sealos/controllers/pkg/pay"

"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/source"

"gorm.io/gorm"

"github.com/labring/sealos/controllers/pkg/database/cockroach"
Expand Down Expand Up @@ -105,53 +110,76 @@ var DebtConfig = accountv1.DefaultDebtConfig
//+kubebuilder:rbac:groups=apps,resources=statefulsets,verbs=get;list;watch;create;update;patch;delete

func (r *DebtReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
payment := &accountv1.Payment{}
var reconcileErr error
if err := r.Get(ctx, client.ObjectKey{Namespace: req.Namespace, Name: req.Name}, payment); err == nil {
if payment.Status.Status != pay.PaymentSuccess {
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}
reconcileErr = r.reconcile(ctx, payment.Spec.UserID)
} else if client.IgnoreNotFound(err) != nil {
return ctrl.Result{}, fmt.Errorf("failed to get payment %s: %v", req.Name, err)
} else {
reconcileErr = r.reconcile(ctx, req.NamespacedName.Name)
}
if reconcileErr != nil {
if reconcileErr == ErrAccountNotExist {
return ctrl.Result{RequeueAfter: 10 * time.Minute}, nil
}
r.Logger.Error(reconcileErr, "reconcile debt error")
return ctrl.Result{}, reconcileErr
}
return ctrl.Result{RequeueAfter: r.DebtDetectionCycle}, nil
}

func (r *DebtReconciler) reconcile(ctx context.Context, owner string) error {
debt := &accountv1.Debt{}
owner := req.NamespacedName.Name
account, err := r.AccountV2.GetAccount(&pkgtypes.UserQueryOpts{Owner: owner})
if account == nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
userOwner := &userv1.User{}
if err := r.Get(ctx, types.NamespacedName{Name: owner, Namespace: r.accountSystemNamespace}, userOwner); err != nil {
// if user not exist, skip
if client.IgnoreNotFound(err) == nil {
return ctrl.Result{}, nil
return nil
}
return ctrl.Result{}, fmt.Errorf("failed to get user %s: %v", owner, err)
return fmt.Errorf("failed to get user %s: %v", owner, err)
}
// if user not exist, skip
if userOwner.CreationTimestamp.Add(20 * 24 * time.Hour).Before(time.Now()) {
return ctrl.Result{}, nil
return nil
}
}
r.Logger.Error(fmt.Errorf("account %s not exist", owner), "account not exist")
return ctrl.Result{RequeueAfter: 10 * time.Minute}, nil
return ErrAccountNotExist
}
// In a multi-region scenario, select the region where the account is created for SMS notification
smsEnable := account.CreateRegionID == r.LocalRegionID

r.Logger.Info("reconcile debt", "account", owner, "balance", account.Balance, "deduction balance", account.DeductionBalance)
if err := r.Get(ctx, client.ObjectKey{Name: GetDebtName(owner), Namespace: r.accountSystemNamespace}, debt); client.IgnoreNotFound(err) != nil {
return ctrl.Result{}, err
return err
} else if err != nil {
if err := r.syncDebt(ctx, owner, debt); err != nil {
return ctrl.Result{}, err
return err
}
r.Logger.Info("create or update debt success", "debt", debt)
}

nsList, err := getOwnNsList(r.Client, getUsername(owner))
if err != nil {
r.Logger.Error(err, "get own ns list error")
return ctrl.Result{}, fmt.Errorf("get own ns list error: %v", err)
return fmt.Errorf("get own ns list error: %v", err)
}
if err := r.reconcileDebtStatus(ctx, debt, account, nsList, smsEnable); err != nil {
r.Logger.Error(err, "reconcile debt status error")
return ctrl.Result{}, err
return err
}
//Debt Detection Cycle
return ctrl.Result{Requeue: true, RequeueAfter: r.DebtDetectionCycle}, nil
return nil
}

var ErrAccountNotExist = errors.New("account not exist")

/*
NormalPeriod -> WarningPeriod -> ApproachingDeletionPeriod -> ImmediateDeletePeriod -> FinalDeletePeriod
正常期:账户余额大于等于0
Expand Down Expand Up @@ -198,7 +226,9 @@ func (r *DebtReconciler) reconcileDebtStatus(ctx context.Context, debt *accountv
*/
if oweamount >= 0 {
update = SetDebtStatus(debt, accountv1.NormalPeriod)
//TODO 撤销警告消息通知
if err := r.readNotice(ctx, userNamespaceList, WarningNotice); err != nil {
r.Logger.Error(err, "readNotice WarningNotice error")
}
break
}
//上次更新时间小于临近删除时间
Expand All @@ -222,7 +252,9 @@ func (r *DebtReconciler) reconcileDebtStatus(ctx context.Context, debt *accountv
*/
if oweamount >= 0 {
update = SetDebtStatus(debt, accountv1.NormalPeriod)
//TODO 撤销临近删除消息通知
if err := r.readNotice(ctx, userNamespaceList, ApproachingDeletionNotice, WarningNotice); err != nil {
r.Logger.Error(err, "readNotice ApproachingDeletionNotice error")
}
break
}
if updateIntervalSeconds < DebtConfig[accountv1.ImminentDeletionPeriod] && account.Balance+oweamount > 0 {
Expand Down Expand Up @@ -250,7 +282,9 @@ func (r *DebtReconciler) reconcileDebtStatus(ctx context.Context, debt *accountv
if err := r.ResumeUserResource(ctx, userNamespaceList); err != nil {
return err
}
//TODO 撤销最终删除消息通知
if err := r.readNotice(ctx, userNamespaceList, ImminentDeletionNotice, ApproachingDeletionNotice, WarningNotice); err != nil {
r.Logger.Error(err, "readNotice ImminentDeletionNotice error")
}
break
}
//上次更新时间小于最终删除时间, 且欠费不大于总金额的两倍
Expand All @@ -271,6 +305,9 @@ func (r *DebtReconciler) reconcileDebtStatus(ctx context.Context, debt *accountv
最终删除期 -> 正常期:更新status 状态normal事件及更新时间
*/
if oweamount >= 0 {
if err := r.readNotice(ctx, userNamespaceList, FinalDeletionNotice, ImminentDeletionNotice, ApproachingDeletionNotice, WarningNotice); err != nil {
r.Logger.Error(err, "readNotice FinalDeletionNotice error")
}
//TODO 用户从欠费到正常,是否需要发送消息通知
update = SetDebtStatus(debt, accountv1.NormalPeriod)

Expand Down Expand Up @@ -352,6 +389,7 @@ const (
debtChoicePrefix = "debt-choice-"
readStatusLabel = "isRead"
falseStatus = "false"
trueStatus = "true"
)

var NoticeTemplateEN map[int]string
Expand Down Expand Up @@ -394,6 +432,27 @@ func (r *DebtReconciler) sendSMSNotice(user string, oweAmount int64, noticeType
})
}

func (r *DebtReconciler) readNotice(ctx context.Context, namespaces []string, noticeTypes ...int) error {
for i := range namespaces {
for j := range noticeTypes {
ntf := &v1.Notification{}
if err := r.Get(ctx, types.NamespacedName{Name: debtChoicePrefix + strconv.Itoa(noticeTypes[j]), Namespace: namespaces[i]}, ntf); client.IgnoreNotFound(err) != nil {
return err
} else if err != nil {
continue
}
if ntf.Labels != nil && ntf.Labels[readStatusLabel] == trueStatus {
continue
}
ntf.Labels[readStatusLabel] = trueStatus
if err := r.Client.Update(ctx, ntf); err != nil {
return err
}
}
}
return nil
}

func (r *DebtReconciler) sendNotice(ctx context.Context, user string, oweAmount int64, noticeType int, namespaces []string, smsEnable bool) error {
now := time.Now().UTC().Unix()
ntfTmp := &v1.Notification{
Expand Down Expand Up @@ -520,7 +579,7 @@ func (r *DebtReconciler) SetupWithManager(mgr ctrl.Manager, rateOpts controller.
r.Logger = ctrl.Log.WithName("DebtController")
r.accountSystemNamespace = env.GetEnvWithDefault(accountv1.AccountSystemNamespaceEnv, "account-system")
r.LocalRegionID = os.Getenv(cockroach.EnvLocalRegion)
debtDetectionCycleSecond := env.GetInt64EnvWithDefault(DebtDetectionCycleEnv, 60)
debtDetectionCycleSecond := env.GetInt64EnvWithDefault(DebtDetectionCycleEnv, 1800)
r.DebtDetectionCycle = time.Duration(debtDetectionCycleSecond) * time.Second

smsConfig, err := setupSmsConfig()
Expand All @@ -543,6 +602,7 @@ func (r *DebtReconciler) SetupWithManager(mgr ctrl.Manager, rateOpts controller.
"accountSystemNamespace", r.accountSystemNamespace)
return ctrl.NewControllerManagedBy(mgr).
For(&userv1.User{}, builder.WithPredicates(predicate.And(UserOwnerPredicate{}))).
Watches(&source.Kind{Type: &accountv1.Payment{}}, &handler.EnqueueRequestForObject{}).
WithOptions(rateOpts).
Complete(r)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ data:
ApproachingDeletionPeriod: '{{ .ApproachingDeletionPeriod | default "345600" }}'
ImminentDeletionPeriod: '{{ .ImminentDeletionPeriod | default "259200" }}'
FinalDeletionPeriod: '{{ .FinalDeletionPeriod | default "604800" }}'
DebtDetectionCycleSeconds: '{{ .DebtDetectionCycleSeconds | default "30" }}'
DebtDetectionCycleSeconds: '{{ .DebtDetectionCycleSeconds | default "1800" }}'
OSAdminSecret: '{{ .OSAdminSecret }}'
OSInternalEndpoint: '{{ .OSInternalEndpoint }}'
OSNamespace: '{{ .OSNamespace }}'
Expand Down
Loading