From 2fdfd33526557b713fa72f5bac01bb6936666ca3 Mon Sep 17 00:00:00 2001 From: Eslam-Nawara Date: Tue, 12 Dec 2023 12:30:22 +0200 Subject: [PATCH] send notfication to user one day before expiration date --- server/app/app.go | 4 ++ server/app/user_handler.go | 30 +++++++------- server/app/voucher_handler.go | 20 ++++----- server/deployer/deployer.go | 72 ++++++++++++++++++++++++++++++++- server/deployer/k8s_deployer.go | 2 +- server/deployer/vms_deployer.go | 2 +- server/models/k8s.go | 2 +- server/models/vm.go | 2 +- server/models/voucher.go | 24 +++++------ 9 files changed, 115 insertions(+), 43 deletions(-) diff --git a/server/app/app.go b/server/app/app.go index 554ac2b3..1b535680 100644 --- a/server/app/app.go +++ b/server/app/app.go @@ -87,6 +87,10 @@ func (a *App) startBackgroundWorkers(ctx context.Context) { go a.deployer.PeriodicRequests(ctx, substrateBlockDiffInSeconds) go a.deployer.PeriodicDeploy(ctx, substrateBlockDiffInSeconds) + // send notification about vms and k8s expiration + go a.deployer.WarnUsersWithExpiredVMs(ctx) + go a.deployer.WarnUsersWithExpiredK8s(ctx) + // remove expired vms and k8s go a.deployer.CleanExpiredVMs(ctx) go a.deployer.CleanExpiredK8S(ctx) diff --git a/server/app/user_handler.go b/server/app/user_handler.go index fbc70250..595cff81 100644 --- a/server/app/user_handler.go +++ b/server/app/user_handler.go @@ -66,16 +66,16 @@ type EmailInput struct { // ApplyForVoucherInput struct for user to apply for voucher type ApplyForVoucherInput struct { - VMs int `json:"vms" binding:"required" validate:"min=0"` - PublicIPs int `json:"public_ips" binding:"required" validate:"min=0"` - Reason string `json:"reason" binding:"required" validate:"nonzero"` - VoucherDuration int `json:"voucher_duration" binding:"required"` + VMs int `json:"vms" binding:"required" validate:"min=0"` + PublicIPs int `json:"public_ips" binding:"required" validate:"min=0"` + Reason string `json:"reason" binding:"required" validate:"nonzero"` + VoucherDurationInMonth int `json:"voucher_duration_in_month" binding:"required"` } // AddVoucherInput struct for voucher applied by user type AddVoucherInput struct { - Voucher string `json:"voucher" binding:"required"` - RequestedDuration int `json:"requestedDuration" binding:"required"` + Voucher string `json:"voucher" binding:"required"` + VoucherDurationInMonth int `json:"voucher_duration_in_month" binding:"required"` } // SignUpHandler creates account for user @@ -574,19 +574,19 @@ func (a *App) ApplyForVoucherHandler(req *http.Request) (interface{}, Response) } // make sure the requested duration is less that the maximum allowed duration - if input.VoucherDuration > a.config.VouchersMaxDuration { + if input.VoucherDurationInMonth > a.config.VouchersMaxDuration { return nil, BadRequest(fmt.Errorf("invalid voucher duration, max duration is %d", a.config.VouchersMaxDuration)) } // generate voucher for user but can't use it until admin approves it v := internal.GenerateRandomVoucher(5) voucher := models.Voucher{ - Voucher: v, - UserID: userID, - VMs: input.VMs, - Reason: input.Reason, - PublicIPs: input.PublicIPs, - Duration: input.VoucherDuration, + Voucher: v, + UserID: userID, + VMs: input.VMs, + Reason: input.Reason, + PublicIPs: input.PublicIPs, + VoucherDurationInMonth: input.VoucherDurationInMonth, } err = a.db.CreateVoucher(&voucher) @@ -631,7 +631,7 @@ func (a *App) ActivateVoucherHandler(req *http.Request) (interface{}, Response) return nil, InternalServerError(errors.New(internalServerErrorMsg)) } - userQuotaVMs, err := a.db.GetUserQuotaVMs(quota.ID.String(), voucherQuota.Duration) + userQuotaVMs, err := a.db.GetUserQuotaVMs(quota.ID.String(), voucherQuota.VoucherDurationInMonth) if err == gorm.ErrRecordNotFound { return nil, NotFound(errors.New("user quota vms is not found")) } @@ -664,7 +664,7 @@ func (a *App) ActivateVoucherHandler(req *http.Request) (interface{}, Response) return nil, InternalServerError(errors.New(internalServerErrorMsg)) } - err = a.db.UpdateUserQuotaVMs(quota.ID.String(), voucherQuota.Duration, userQuotaVMs.Vms+voucherQuota.VMs) + err = a.db.UpdateUserQuotaVMs(quota.ID.String(), voucherQuota.VoucherDurationInMonth, userQuotaVMs.Vms+voucherQuota.VMs) if err != nil { log.Error().Err(err).Send() return nil, InternalServerError(errors.New(internalServerErrorMsg)) diff --git a/server/app/voucher_handler.go b/server/app/voucher_handler.go index 11fdd2ec..c6a48422 100644 --- a/server/app/voucher_handler.go +++ b/server/app/voucher_handler.go @@ -18,10 +18,10 @@ import ( // GenerateVoucherInput struct for data needed when user generate vouchers type GenerateVoucherInput struct { - Length int `json:"length" binding:"required" validate:"min=3,max=20"` - VMs int `json:"vms" binding:"required"` - PublicIPs int `json:"public_ips" binding:"required"` - VoucherDuration int `json:"voucher_duration" binding:"required"` + Length int `json:"length" binding:"required" validate:"min=3,max=20"` + VMs int `json:"vms" binding:"required"` + PublicIPs int `json:"public_ips" binding:"required"` + VoucherDurationInMonth int `json:"voucher_duration_in_month" binding:"required"` } // UpdateVoucherInput struct for data needed when user update voucher @@ -45,16 +45,16 @@ func (a *App) GenerateVoucherHandler(req *http.Request) (interface{}, Response) } voucher := internal.GenerateRandomVoucher(input.Length) - if input.VoucherDuration > a.config.VouchersMaxDuration { + if input.VoucherDurationInMonth > a.config.VouchersMaxDuration { return nil, BadRequest(fmt.Errorf("invalid voucher duration, max duration is %d", a.config.VouchersMaxDuration)) } v := models.Voucher{ - Voucher: voucher, - VMs: input.VMs, - PublicIPs: input.PublicIPs, - Approved: true, - Duration: input.VoucherDuration, + Voucher: voucher, + VMs: input.VMs, + PublicIPs: input.PublicIPs, + Approved: true, + VoucherDurationInMonth: input.VoucherDurationInMonth, } err = a.db.CreateVoucher(&v) diff --git a/server/deployer/deployer.go b/server/deployer/deployer.go index f32eeac3..4fd0feef 100644 --- a/server/deployer/deployer.go +++ b/server/deployer/deployer.go @@ -166,6 +166,74 @@ func (d *Deployer) CancelDeployment(contractID uint64, netContractID uint64, dlT return nil } +func (d *Deployer) WarnUsersWithExpiredVMs(ctx context.Context) { + ticker := time.NewTicker(24 * time.Hour) + for range ticker.C { + users, err := d.db.ListAllUsers() + if err != nil { + log.Error().Err(err).Msg("failed to get all users") + return + } + + for _, user := range users { + vms, err := d.db.GetAllVms(user.UserID) + if err != nil { + log.Error().Err(err).Msg("failed to get all user vms") + continue + } + + for _, vm := range vms { + if time.Now().Before(vm.ExpiresAt) && time.Until(vm.ExpiresAt) < time.Hour*24 { + notification := models.Notification{ + UserID: user.UserID, + Msg: fmt.Sprintf("Warning: vm with id %d expires in one day", vm.ID), + Type: models.VMsType, + } + + err = d.db.CreateNotification(¬ification) + if err != nil { + log.Error().Err(err).Msgf("failed to create notification: %+v", notification) + } + } + } + } + } +} + +func (d *Deployer) WarnUsersWithExpiredK8s(ctx context.Context) { + ticker := time.NewTicker(24 * time.Hour) + for range ticker.C { + users, err := d.db.ListAllUsers() + if err != nil { + log.Error().Err(err).Msg("failed to get all users") + return + } + + for _, user := range users { + k8s, err := d.db.GetAllK8s(user.UserID) + if err != nil { + log.Error().Err(err).Msg("failed to get all user k8s clusters") + continue + } + + for _, k := range k8s { + if time.Now().Before(k.ExpiresAt) && time.Until(k.ExpiresAt) < time.Hour*24 { + notification := models.Notification{ + UserID: user.UserID, + Msg: fmt.Sprintf("Warning: k8s cluster with id %d expires in one day", k.ID), + Type: models.K8sType, + } + + err = d.db.CreateNotification(¬ification) + if err != nil { + log.Error().Err(err).Msgf("failed to create notification: %+v", notification) + } + } + } + } + } +} + func (d *Deployer) CleanExpiredVMs(ctx context.Context) { ticker := time.NewTicker(24 * time.Hour) for range ticker.C { @@ -183,7 +251,7 @@ func (d *Deployer) CleanExpiredVMs(ctx context.Context) { } for _, vm := range vms { - if vm.ExpirationDate.Before(time.Now()) { + if vm.ExpiresAt.Before(time.Now()) { err = d.CancelDeployment(vm.ContractID, vm.NetworkContractID, "vm", vm.Name) if err != nil { log.Error().Err(err).Msg("failed to cancel contract of expired vm") @@ -215,7 +283,7 @@ func (d *Deployer) CleanExpiredK8S(ctx context.Context) { } for _, k := range k8s { - if k.ExpirationDate.Before(time.Now()) { + if k.ExpiresAt.Before(time.Now()) { err = d.CancelDeployment(uint64(k.ClusterContract), uint64(k.NetworkContract), "k8s", k.Master.Name) if err != nil { log.Error().Err(err).Msg("failed to cancel contract of expired k8s cluster") diff --git a/server/deployer/k8s_deployer.go b/server/deployer/k8s_deployer.go index ac448d42..829674a4 100644 --- a/server/deployer/k8s_deployer.go +++ b/server/deployer/k8s_deployer.go @@ -155,7 +155,7 @@ func (d *Deployer) loadK8s(k8sDeployInput models.K8sDeployInput, userID string, ClusterContract: int(k8sContractID), Master: master, Workers: workers, - ExpirationDate: time.Now().Add(time.Duration(k8sDeployInput.Duration) * 30 * 24 * time.Hour).Truncate(24 * time.Hour), + ExpiresAt: time.Now().Add(time.Duration(k8sDeployInput.Duration) * 30 * 24 * time.Hour).Truncate(24 * time.Hour), } return k8sCluster, nil diff --git a/server/deployer/vms_deployer.go b/server/deployer/vms_deployer.go index c4f7567b..37219ac1 100644 --- a/server/deployer/vms_deployer.go +++ b/server/deployer/vms_deployer.go @@ -160,7 +160,7 @@ func (d *Deployer) deployVMRequest(ctx context.Context, user models.User, input MRU: uint64(vm.Memory), ContractID: contractID, NetworkContractID: networkContractID, - ExpirationDate: time.Now().Add(time.Duration(quotaVMs.Duration) * 30 * 24 * time.Hour).Truncate(24 * time.Hour), + ExpiresAt: time.Now().Add(time.Duration(quotaVMs.Duration) * 30 * 24 * time.Hour).Truncate(24 * time.Hour), } err = d.db.CreateVM(&userVM) diff --git a/server/models/k8s.go b/server/models/k8s.go index d1b4a321..c1a84f6d 100644 --- a/server/models/k8s.go +++ b/server/models/k8s.go @@ -11,7 +11,7 @@ type K8sCluster struct { ClusterContract int `json:"contract_id"` Master Master `json:"master" gorm:"foreignKey:ClusterID"` Workers []Worker `json:"workers" gorm:"foreignKey:ClusterID"` - ExpirationDate time.Time `json:"expiration_date"` + ExpiresAt time.Time `json:"expires_at"` } // Master struct for kubernetes master data diff --git a/server/models/vm.go b/server/models/vm.go index a459b1d7..cdd0751f 100644 --- a/server/models/vm.go +++ b/server/models/vm.go @@ -17,7 +17,7 @@ type VM struct { MRU uint64 `json:"mru"` ContractID uint64 `json:"contractID"` NetworkContractID uint64 `json:"networkContractID"` - ExpirationDate time.Time `json:"expirationDate" binding:"required"` + ExpiresAt time.Time `json:"expires_at" binding:"required"` } // DeploymentsCount has the vms and ips reserved in the grid diff --git a/server/models/voucher.go b/server/models/voucher.go index 46fdca65..700da55a 100644 --- a/server/models/voucher.go +++ b/server/models/voucher.go @@ -5,16 +5,16 @@ import "time" // Voucher struct holds data of vouchers type Voucher struct { - ID int `json:"id" gorm:"primaryKey"` - UserID string `json:"user_id" binding:"required"` - Voucher string `json:"voucher" gorm:"unique"` - VMs int `json:"vms" binding:"required"` - PublicIPs int `json:"public_ips" binding:"required"` - Reason string `json:"reason" binding:"required"` - Used bool `json:"used" binding:"required"` - Approved bool `json:"approved" binding:"required"` - Rejected bool `json:"rejected" binding:"required"` - Duration int `json:"duration" binding:"required"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID int `json:"id" gorm:"primaryKey"` + UserID string `json:"user_id" binding:"required"` + Voucher string `json:"voucher" gorm:"unique"` + VMs int `json:"vms" binding:"required"` + PublicIPs int `json:"public_ips" binding:"required"` + Reason string `json:"reason" binding:"required"` + Used bool `json:"used" binding:"required"` + Approved bool `json:"approved" binding:"required"` + Rejected bool `json:"rejected" binding:"required"` + VoucherDurationInMonth int `json:"voucher_duration_in_month" binding:"required"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` }