Skip to content

Commit

Permalink
CostCenter api update (#4932)
Browse files Browse the repository at this point in the history
* optimize monitoring app store

* add get app instance role

* add app instance billing

* add test

* fix

* add CheckPermission with kc

* add get-regions api

* change regions to slice

* add GetCostAppList GetCostOverview api

* fix check-auth user

* fix divisor to zero

* fix get cost overview

* add app type list api

* add init region info

* fix docs get cost app type list

* add service account ingress

* add check region auth

* fix region auth

* add skip tls client get api

* add debug mod

* add basic cost distribution api

* remove deprecated encrypt account field

* set default time range

* add GetAppCostTimeRange api

* add dao test

* make format && fix semgrep ci

* make format && fix semgrep ci

* fix GetCostAppList

* optimize account reconcile

* ignore user not found

* add used_amount result for get app cost

* Ignore that a user is not created for a long time, cause a lot of errors

* add app cost sort with time

* optimize cost app api

* optimize cost app api

* optimize cost app api

* optimize cost app api

* get namespace id and name

* remove debug print

* remove debug print

* terminal named by `app.kubernetes.io/part-of` label
  • Loading branch information
bxy4543 authored Aug 6, 2024
1 parent 0109752 commit 594f3af
Show file tree
Hide file tree
Showing 35 changed files with 3,538 additions and 626 deletions.
37 changes: 32 additions & 5 deletions controllers/account/controllers/account_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"strings"
"time"

"sigs.k8s.io/controller-runtime/pkg/event"

"go.mongodb.org/mongo-driver/bson/primitive"

"github.com/google/uuid"
Expand Down Expand Up @@ -112,6 +114,9 @@ func (r *AccountReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
// determine the resource quota created by the owner user and the resource quota initialized by the account user,
// and only the resource quota created by the team user
_, err = r.syncAccount(ctx, owner, "ns-"+user.Name)
if errors.Is(err, gorm.ErrRecordNotFound) && user.CreationTimestamp.Add(20*24*time.Hour).Before(time.Now()) {
return ctrl.Result{}, nil
}
return ctrl.Result{}, err
} else if client.IgnoreNotFound(err) != nil {
return ctrl.Result{}, err
Expand Down Expand Up @@ -203,10 +208,7 @@ func (r *AccountReconciler) syncAccount(ctx context.Context, owner string, userN
}
account, err := r.AccountV2.NewAccount(&pkgtypes.UserQueryOpts{Owner: owner})
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, fmt.Errorf("failed to create %s account: %v", owner, err)
return nil, err
}
return account, nil
}
Expand Down Expand Up @@ -290,11 +292,36 @@ func (r *AccountReconciler) SetupWithManager(mgr ctrl.Manager, rateOpts controll
r.AccountSystemNamespace = env.GetEnvWithDefault(ACCOUNTNAMESPACEENV, DEFAULTACCOUNTNAMESPACE)
return ctrl.NewControllerManagedBy(mgr).
For(&userv1.User{}, builder.WithPredicates(OnlyCreatePredicate{})).
Watches(&accountv1.Payment{}, &handler.EnqueueRequestForObject{}).
Watches(&accountv1.Payment{}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(PaymentPredicate{})).
WithOptions(rateOpts).
Complete(r)
}

type PaymentPredicate struct{}

func (PaymentPredicate) Create(e event.CreateEvent) bool {
if payment, ok := e.Object.(*accountv1.Payment); ok {
fmt.Println("payment create", payment.Status.TradeNO, payment.Status.Status)
return payment.Status.TradeNO != "" && payment.Status.Status != pay.PaymentSuccess
}
return false
}

func (PaymentPredicate) Update(e event.UpdateEvent) bool {
if payment, ok := e.ObjectNew.(*accountv1.Payment); ok {
return payment.Status.TradeNO != "" && payment.Status.Status != pay.PaymentSuccess
}
return false
}

func (PaymentPredicate) Delete(_ event.DeleteEvent) bool {
return false
}

func (PaymentPredicate) Generic(_ event.GenericEvent) bool {
return false
}

func RawParseRechargeConfig() (activities pkgtypes.Activities, discountsteps []int64, discountratios []float64, returnErr error) {
// local test
//config, err := clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG"))
Expand Down
4 changes: 4 additions & 0 deletions controllers/account/controllers/debt_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,10 @@ func (r *DebtReconciler) reconcile(ctx context.Context, owner string) error {
account, err := r.AccountV2.GetAccount(&pkgtypes.UserQueryOpts{Owner: owner})
if account == nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
_, err = r.AccountV2.NewAccount(&pkgtypes.UserQueryOpts{Owner: owner})
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("failed to create account %s: %v", owner, err)
}
userOwner := &userv1.User{}
if err := r.Get(ctx, types.NamespacedName{Name: owner, Namespace: r.accountSystemNamespace}, userOwner); err != nil {
// if user not exist, skip
Expand Down
52 changes: 1 addition & 51 deletions controllers/pkg/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import (
"strconv"
)

const defaultEncryptionKey = "0123456789ABCDEF0123456789ABCDEF"
const defaultEncryptionKey = "Bg1c3Dd5e9e0F84bdF0A5887cF43aB63"

var encryptionKey = defaultEncryptionKey

Expand Down Expand Up @@ -79,56 +79,6 @@ func DecryptInt64(in string) (int64, error) {
return strconv.ParseInt(string(out), 10, 64)
}

func DecryptFloat64(in string) (float64, error) {
out, err := Decrypt(in)
if err != nil {
return 0, fmt.Errorf("failed to decrpt balance: %w", err)
}
return strconv.ParseFloat(string(out), 64)
}

func EncryptFloat64(in float64) (*string, error) {
out, err := Encrypt([]byte(strconv.FormatFloat(in, 'f', -1, 64)))
return &out, err
}

// DecryptInt64WithKey decrypts the given ciphertext using AES-GCM.
func DecryptInt64WithKey(in string, encryptionKey []byte) (int64, error) {
out, err := DecryptWithKey(in, encryptionKey)
if err != nil {
return 0, fmt.Errorf("failed to decrpt balance: %w", err)
}
return strconv.ParseInt(string(out), 10, 64)
}

func RechargeBalance(rawBalance *string, amount int64) error {
balanceInt, err := DecryptInt64(*rawBalance)
if err != nil {
return fmt.Errorf("failed to recharge balance: %w", err)
}
balanceInt += amount
encryptBalance, err := EncryptInt64(balanceInt)
if err != nil {
return fmt.Errorf("failed to recharge balance: %w", err)
}
*rawBalance = *encryptBalance
return nil
}

func DeductBalance(balance *string, amount int64) error {
balanceInt, err := DecryptInt64(*balance)
if err != nil {
return fmt.Errorf("failed to deduct balance: %w", err)
}
balanceInt -= amount
encryptBalance, err := EncryptInt64(balanceInt)
if err != nil {
return fmt.Errorf("failed to deduct balance: %w", err)
}
*balance = *encryptBalance
return nil
}

// Decrypt decrypts the given ciphertext using AES-GCM.
func Decrypt(ciphertextBase64 string) ([]byte, error) {
return DecryptWithKey(ciphertextBase64, []byte(encryptionKey))
Expand Down
95 changes: 51 additions & 44 deletions controllers/pkg/database/cockroach/accountv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ func (c *Cockroach) GetUser(ops *types.UserQueryOpts) (*types.User, error) {
} else if ops.Owner != "" {
userCr, err := c.GetUserCr(ops)
if err != nil {
return nil, fmt.Errorf("failed to get user: %v", err)
return nil, fmt.Errorf("failed to get user cr: %v", err)
}
queryUser.UID = userCr.UserUID
}
Expand Down Expand Up @@ -160,6 +160,17 @@ func (c *Cockroach) GetUserUID(ops *types.UserQueryOpts) (uuid.UUID, error) {
return userCr.UserUID, nil
}

func (c *Cockroach) GetWorkspace(namespaces ...string) ([]types.Workspace, error) {
if len(namespaces) == 0 {
return nil, fmt.Errorf("empty namespaces")
}
var workspaces []types.Workspace
if err := c.Localdb.Where("id IN ?", namespaces).Find(&workspaces).Error; err != nil {
return nil, fmt.Errorf("failed to get workspaces: %v", err)
}
return workspaces, nil
}

func checkOps(ops *types.UserQueryOpts) error {
if ops.Owner == "" && ops.UID == uuid.Nil && ops.ID == "" {
return fmt.Errorf("empty query opts")
Expand Down Expand Up @@ -276,18 +287,8 @@ func (c *Cockroach) getAccount(ops *types.UserQueryOpts) (*types.Account, error)
if ops.IgnoreEmpty && errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil
}
return nil, fmt.Errorf("failed to search account from db: %w", err)
}
balance, err := crypto.DecryptInt64(account.EncryptBalance)
if err != nil {
return nil, fmt.Errorf("failed to descrypt balance: %v", err)
}
deductionBalance, err := crypto.DecryptInt64(account.EncryptDeductionBalance)
if err != nil {
return nil, fmt.Errorf("failed to descrypt deduction balance: %v", err)
return nil, err
}
account.Balance = balance
account.DeductionBalance = deductionBalance
return &account, nil
}

Expand Down Expand Up @@ -317,52 +318,35 @@ func (c *Cockroach) updateBalance(tx *gorm.DB, ops *types.UserQueryOpts, amount
}
ops.UID = user.UserUID
}
var account types.Account
//TODO update UserUid = ?
if err := tx.Where(&types.Account{UserUID: ops.UID}).First(&account).Error; err != nil {
return fmt.Errorf("failed to get account: %w", err)
var account = &types.Account{}
if err := tx.Where(&types.Account{UserUID: ops.UID}).First(account).Error; err != nil {
// if not found, create a new account and retry
if !errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("failed to get account: %w", err)
}
if account, err = c.NewAccount(ops); err != nil {
return fmt.Errorf("failed to create account: %v", err)
}
}

if err := c.updateWithAccount(isDeduction, add, &account, amount); err != nil {
if err := c.updateWithAccount(isDeduction, add, account, amount); err != nil {
return err
}
if err := tx.Save(&account).Error; err != nil {
if err := tx.Save(account).Error; err != nil {
return fmt.Errorf("failed to update account balance: %w", err)
}
return nil
}

func (c *Cockroach) updateWithAccount(isDeduction bool, add bool, account *types.Account, amount int64) error {
var fieldToUpdate string
balancePtr := &account.Balance
if isDeduction {
fieldToUpdate = account.EncryptDeductionBalance
} else {
fieldToUpdate = account.EncryptBalance
}

currentBalance, err := crypto.DecryptInt64(fieldToUpdate)
if err != nil {
return fmt.Errorf("failed to decrypt balance: %w", err)
balancePtr = &account.DeductionBalance
}

if add {
currentBalance += amount
*balancePtr += amount
} else {
currentBalance -= amount
*balancePtr -= amount
}

newEncryptBalance, err := crypto.EncryptInt64(currentBalance)
if err != nil {
return fmt.Errorf("failed to encrypt balance: %v", err)
}
if isDeduction {
account.EncryptDeductionBalance = *newEncryptBalance
account.DeductionBalance = currentBalance
} else {
account.EncryptBalance = *newEncryptBalance
account.Balance = currentBalance
}

return nil
}

Expand Down Expand Up @@ -618,6 +602,29 @@ func (c *Cockroach) payment(payment *types.Payment, updateBalance bool) error {
})
}

func (c *Cockroach) GetRegions() ([]types.Region, error) {
var regions []types.Region
if err := c.DB.Find(&regions).Error; err != nil {
return nil, fmt.Errorf("failed to get regions: %v", err)
}
return regions, nil
}

func (c *Cockroach) GetLocalRegion() types.Region {
if c.LocalRegion.Domain == "" {
regions, err := c.GetRegions()
if err == nil {
for i := range regions {
if regions[i].UID == c.LocalRegion.UID {
c.LocalRegion = &regions[i]
return *c.LocalRegion
}
}
}
}
return *c.LocalRegion
}

func (c *Cockroach) GetPayment(ops *types.UserQueryOpts, startTime, endTime time.Time) ([]types.Payment, error) {
userUID, err := c.GetUserUID(ops)
if err != nil {
Expand Down
10 changes: 6 additions & 4 deletions controllers/pkg/database/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ type Account interface {
GetUnsettingBillingHandler(owner string) ([]resources.BillingHandler, error)
UpdateBillingStatus(orderID string, status resources.BillingStatus) error
GetUpdateTimeForCategoryAndPropertyFromMetering(category string, property string) (time.Time, error)
GetAllPricesMap() (map[string]resources.Price, error)
GetAllPayment() ([]resources.Billing, error)
InitDefaultPropertyTypeLS() error
SavePropertyTypes(types []resources.PropertyType) error
Expand Down Expand Up @@ -90,10 +89,14 @@ type AccountV2 interface {
Close() error
GetUserCr(user *types.UserQueryOpts) (*types.RegionUserCr, error)
GetUser(ops *types.UserQueryOpts) (*types.User, error)
CreateUser(oAuth *types.OauthProvider, regionUserCr *types.RegionUserCr, user *types.User, workspace *types.Workspace, userWorkspace *types.UserWorkspace) error
GetAccount(user *types.UserQueryOpts) (*types.Account, error)
SetAccountCreateLocalRegion(account *types.Account, region string) error
GetRegions() ([]types.Region, error)
GetLocalRegion() types.Region
GetUserOauthProvider(ops *types.UserQueryOpts) ([]types.OauthProvider, error)
GetWorkspace(namespaces ...string) ([]types.Workspace, error)
GetUserAccountRechargeDiscount(user *types.UserQueryOpts) (*types.RechargeDiscount, error)
SetAccountCreateLocalRegion(account *types.Account, region string) error
CreateUser(oAuth *types.OauthProvider, regionUserCr *types.RegionUserCr, user *types.User, workspace *types.Workspace, userWorkspace *types.UserWorkspace) error
AddBalance(user *types.UserQueryOpts, balance int64) error
ReduceBalance(ops *types.UserQueryOpts, amount int64) error
ReduceDeductionBalance(ops *types.UserQueryOpts, amount int64) error
Expand All @@ -106,7 +109,6 @@ type AccountV2 interface {
TransferAccount(from, to *types.UserQueryOpts, amount int64) error
TransferAccountAll(from, to *types.UserQueryOpts) error
TransferAccountV1(owner string, account *types.Account) (*types.Account, error)
GetUserAccountRechargeDiscount(user *types.UserQueryOpts) (*types.RechargeDiscount, error)
AddDeductionBalance(user *types.UserQueryOpts, balance int64) error
AddDeductionBalanceWithFunc(ops *types.UserQueryOpts, amount int64, preDo, postDo func() error) error
}
Expand Down
Loading

0 comments on commit 594f3af

Please sign in to comment.