Skip to content
Open
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
214 changes: 214 additions & 0 deletions service/account/api/flush.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/gin-gonic/gin"
v1 "github.com/labring/sealos/controllers/pkg/notification/api/v1"
"github.com/labring/sealos/controllers/pkg/resources"
"github.com/labring/sealos/controllers/pkg/types"
"github.com/labring/sealos/service/account/dao"
"github.com/labring/sealos/service/account/helper"
Expand Down Expand Up @@ -403,6 +404,219 @@ func AdminFlushSubscriptionQuota(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"success": true})
}

func AdminFlushSubscriptionQuotaAll(c *gin.Context) {
err := authenticateAdminRequest(c)
if err != nil {
c.JSON(
http.StatusUnauthorized,
helper.ErrorMessage{Error: fmt.Sprintf("authenticate error : %v", err)},
)
return
}
req, err := helper.ParseAdminFlushSubscriptionQuotaAllReq(c)
if err != nil {
c.JSON(
http.StatusBadRequest,
helper.ErrorMessage{Error: fmt.Sprintf("failed to parse request: %v", err)},
)
return
}
if req.PlanName != "" {
if _, ok := dao.WorkspacePlanResQuota[req.PlanName]; !ok {
c.JSON(
http.StatusBadRequest,
helper.ErrorMessage{
Error: fmt.Sprintf("plan name is not in plan resource quota: %v", req.PlanName),
},
)
return
}
}

clt := dao.K8sManager.GetClient()
subNamespaces, err := listSubscriptionNamespaces(clt)
if err != nil {
c.JSON(
http.StatusInternalServerError,
helper.ErrorMessage{Error: fmt.Sprintf("list subscription namespaces failed: %v", err)},
)
return
}
if len(subNamespaces) == 0 {
c.JSON(http.StatusOK, gin.H{
"success": true,
"dryRun": req.DryRun,
"updated": 0,
"skipped": 0,
"unchanged": 0,
})
return
}

subscriptions, err := dao.DBClient.ListWorkspaceSubscriptionWorkspacePlan(req.PlanName)
if err != nil {
c.JSON(
http.StatusInternalServerError,
helper.ErrorMessage{Error: fmt.Sprintf("list workspace subscription failed: %v", err)},
)
return
}
subscriptionPlans := make(map[string]string, len(subscriptions))
for i := range subscriptions {
if subscriptions[i].Workspace == "" {
continue
}
subscriptionPlans[subscriptions[i].Workspace] = subscriptions[i].PlanName
}

quotaList := &corev1.ResourceQuotaList{}
if err := clt.List(context.Background(), quotaList); err != nil {
c.JSON(
http.StatusInternalServerError,
helper.ErrorMessage{Error: fmt.Sprintf("list resource quota failed: %v", err)},
)
return
}
quotaByNamespace := make(map[string]*corev1.ResourceQuota, len(quotaList.Items))
for i := range quotaList.Items {
quota := &quotaList.Items[i]
if quota.Name != "quota-"+quota.Namespace {
continue
}
quotaByNamespace[quota.Namespace] = quota
}

defaultQuota := resources.GetDefaultResourceQuota("default", "quota-default")
defaultHard := cloneResourceList(defaultQuota.Spec.Hard)
planQuotaCache := make(map[string]corev1.ResourceList)

updated := 0
skipped := 0
unchanged := 0

for namespace := range subNamespaces {
planName, ok := subscriptionPlans[namespace]
if !ok || planName == "" {
skipped++
continue
}
desiredQuota, ok := getWorkspacePlanResourceQuota(planName, defaultHard, planQuotaCache)
if !ok {
skipped++
continue
}
desiredHard := cloneResourceList(desiredQuota)
if currentQuota, ok := quotaByNamespace[namespace]; ok {
if resourceListEqual(currentQuota.Spec.Hard, desiredHard) {
unchanged++
continue
}
}
if req.DryRun {
updated++
continue
}
quota := resources.GetDefaultResourceQuota(namespace, "quota-"+namespace)
_, err := controllerutil.CreateOrUpdate(
context.Background(),
clt,
quota,
func() error {
quota.Spec.Hard = desiredHard
return nil
},
)
if err != nil {
c.JSON(
http.StatusInternalServerError,
helper.ErrorMessage{Error: fmt.Sprintf("update resource quota failed: %v", err)},
)
return
}
updated++
}

c.JSON(http.StatusOK, gin.H{
"success": true,
"dryRun": req.DryRun,
"updated": updated,
"skipped": skipped,
"unchanged": unchanged,
})
}

func listSubscriptionNamespaces(clt client.Client) (map[string]struct{}, error) {
nsList := &corev1.NamespaceList{}
if err := clt.List(context.Background(), nsList); err != nil {
return nil, err
}
subscriptionNamespaces := make(map[string]struct{})
for i := range nsList.Items {
namespace := &nsList.Items[i]
if namespace.Status.Phase == corev1.NamespaceTerminating {
continue
}
if namespace.Annotations == nil {
continue
}
if namespace.Annotations[types.WorkspaceSubscriptionStatusAnnoKey] == "" {
continue
}
subscriptionNamespaces[namespace.Name] = struct{}{}
}
return subscriptionNamespaces, nil
}

func cloneResourceList(src corev1.ResourceList) corev1.ResourceList {
if src == nil {
return nil
}
dst := make(corev1.ResourceList, len(src))
for key, quantity := range src {
dst[key] = quantity.DeepCopy()
}
return dst
}

func resourceListEqual(left, right corev1.ResourceList) bool {
if len(left) != len(right) {
return false
}
for key, leftQty := range left {
rightQty, ok := right[key]
if !ok {
return false
}
if leftQty.Cmp(rightQty) != 0 {
return false
}
}
return true
}

func getWorkspacePlanResourceQuota(
planName string,
defaultHard corev1.ResourceList,
cache map[string]corev1.ResourceList,
) (corev1.ResourceList, bool) {
if cached, ok := cache[planName]; ok {
return cached, true
}
planHard, ok := dao.WorkspacePlanResQuota[planName]
if !ok {
return nil, false
}
merged := planHard.DeepCopy()
for key, quantity := range defaultHard {
if _, exists := merged[key]; exists {
continue
}
merged[key] = quantity.DeepCopy()
}
cache[planName] = merged
return merged, true
}

// FlushSubscriptionQuota
// @Summary flush user quota with subscription
// @Description flush user quota with subscription
Expand Down
1 change: 1 addition & 0 deletions service/account/dao/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ type Interface interface {
GetWorkspaceSubscriptionTraffic(workspace, regionDomain string) (total, used int64, err error)
GetAIQuota(workspace, regionDomain string) (total, used int64, err error)
ListWorkspaceSubscription(userUID uuid.UUID) ([]types.WorkspaceSubscription, error)
ListWorkspaceSubscriptionWorkspacePlan(planName string) ([]types.WorkspaceSubscription, error)
ListWorkspaceSubscriptionWorkspace(userUID uuid.UUID) ([]string, error)
GetWorkspaceSubscriptionPlanList() ([]types.WorkspaceSubscriptionPlan, error)
GetWorkspaceSubscriptionPlan(planName string) (*types.WorkspaceSubscriptionPlan, error)
Expand Down
16 changes: 16 additions & 0 deletions service/account/dao/workspace_subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,22 @@ func (g *Cockroach) ListWorkspaceSubscription(
return g.ck.ListWorkspaceSubscription(userUID)
}

func (g *Cockroach) ListWorkspaceSubscriptionWorkspacePlan(
planName string,
) ([]types.WorkspaceSubscription, error) {
db := g.ck.GetGlobalDB().
Model(&types.WorkspaceSubscription{}).
Select("workspace", "plan_name")
if planName != "" {
db = db.Where("plan_name = ? AND subscription_status = ?", planName, types.SubscriptionStatusNormal)
}
var subscriptions []types.WorkspaceSubscription
if err := db.Find(&subscriptions).Error; err != nil {
return nil, fmt.Errorf("failed to list workspace subscriptions: %w", err)
}
return subscriptions, nil
}

func (g *Cockroach) GetWorkspaceSubscriptionPlanList() ([]types.WorkspaceSubscriptionPlan, error) {
return g.ck.GetWorkspaceSubscriptionPlanList()
}
Expand Down
1 change: 1 addition & 0 deletions service/account/helper/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const (
AdminActiveBilling = "/active-billing"
AdminGetUserRealNameInfo = "/real-name-info"
AdminFlushSubQuota = "/flush-sub-quota"
AdminFlushSubQuotaAll = "/flush-sub-quota-all"
AdminFlushDebtResourceStatus = "/flush-debt-resource-status"
AdminSuspendUserTraffic = "/suspend-user-traffic"
AdminResumeUserTraffic = "/resume-user-traffic"
Expand Down
17 changes: 17 additions & 0 deletions service/account/helper/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package helper
import (
"errors"
"fmt"
"io"
"strings"
"time"

Expand Down Expand Up @@ -673,6 +674,22 @@ func ParseAdminFlushSubscriptionQuotaReq(c *gin.Context) (*AdminFlushSubscriptio
return flushSubscriptionQuota, nil
}

type AdminFlushSubscriptionQuotaAllReq struct {
DryRun bool `json:"dryRun" bson:"dryRun"`
PlanName string `json:"planName" bson:"planName"`
}

func ParseAdminFlushSubscriptionQuotaAllReq(c *gin.Context) (*AdminFlushSubscriptionQuotaAllReq, error) {
flushSubscriptionQuota := &AdminFlushSubscriptionQuotaAllReq{}
if err := c.ShouldBindJSON(flushSubscriptionQuota); err != nil {
if errors.Is(err, io.EOF) {
return flushSubscriptionQuota, nil
}
return nil, fmt.Errorf("bind json error: %w", err)
}
return flushSubscriptionQuota, nil
}

type AdminFlushDebtResourceStatusReq struct {
UserUID uuid.UUID `json:"userUID" bson:"userUID"`
LastDebtStatus types.DebtStatusType `json:"lastDebtStatus" bson:"lastDebtStatus"`
Expand Down
1 change: 1 addition & 0 deletions service/account/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func RegisterPayRouter() {
POST(helper.AdminSubscriptionPlans, api.AdminSubscriptionPlans).
POST(helper.AdminSubscriptionPlanManage, api.AdminManageSubscriptionPlan).
POST(helper.AdminSubscriptionPlanDelete, api.AdminDeleteSubscriptionPlan).
POST(helper.AdminFlushSubQuotaAll, api.AdminFlushSubscriptionQuotaAll).
POST(helper.AdminReloadPropertyTypes, api.ReloadPropertyTypes)
paymentGroup := router.Group(helper.PaymentGroup).
POST(helper.CreatePay, api.CreateCardPay).
Expand Down
Loading