diff --git a/controllers/account/controllers/account_controller.go b/controllers/account/controllers/account_controller.go index 601841990d0..9a1643001d1 100644 --- a/controllers/account/controllers/account_controller.go +++ b/controllers/account/controllers/account_controller.go @@ -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 diff --git a/controllers/account/controllers/debt_controller.go b/controllers/account/controllers/debt_controller.go index 2feb9e176fe..e7719af1935 100644 --- a/controllers/account/controllers/debt_controller.go +++ b/controllers/account/controllers/debt_controller.go @@ -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" @@ -105,8 +110,30 @@ 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) { @@ -114,27 +141,27 @@ func (r *DebtReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. 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) } @@ -142,16 +169,17 @@ func (r *DebtReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. 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 @@ -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 } //上次更新时间小于临近删除时间 @@ -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 { @@ -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 } //上次更新时间小于最终删除时间, 且欠费不大于总金额的两倍 @@ -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) @@ -352,6 +389,7 @@ const ( debtChoicePrefix = "debt-choice-" readStatusLabel = "isRead" falseStatus = "false" + trueStatus = "true" ) var NoticeTemplateEN map[int]string @@ -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{ @@ -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() @@ -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) } diff --git a/controllers/account/deploy/manifests/account-manager-config.yaml.tmpl b/controllers/account/deploy/manifests/account-manager-config.yaml.tmpl index 1fdacff101c..f407d48bcc5 100644 --- a/controllers/account/deploy/manifests/account-manager-config.yaml.tmpl +++ b/controllers/account/deploy/manifests/account-manager-config.yaml.tmpl @@ -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 }}'