Skip to content

Commit

Permalink
add expiration date to vms and k8s
Browse files Browse the repository at this point in the history
  • Loading branch information
Eslam-Nawara committed Dec 11, 2023
1 parent 089c837 commit 80da291
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 82 deletions.
2 changes: 2 additions & 0 deletions server/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ func (a *App) startBackgroundWorkers(ctx context.Context) {
go a.deployer.PeriodicRequests(ctx, substrateBlockDiffInSeconds)
go a.deployer.PeriodicDeploy(ctx, substrateBlockDiffInSeconds)

// remove expired vms and k8s

// check pending deployments
a.deployer.ConsumeVMRequest(ctx, true)
a.deployer.ConsumeK8sRequest(ctx, true)
Expand Down
11 changes: 10 additions & 1 deletion server/app/k8s_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,16 @@ func (a *App) K8sDeployHandler(req *http.Request) (interface{}, Response) {
return nil, InternalServerError(errors.New(internalServerErrorMsg))
}

_, err = deployer.ValidateK8sQuota(k8sDeployInput, quota.Vms, quota.PublicIPs)
allQuotaVMs, err := a.db.ListUserQuotaVMs(quota.ID.String())
if err == gorm.ErrRecordNotFound {
return nil, NotFound(errors.New("user quota vms are not found"))
}
if err != nil {
log.Error().Err(err).Send()
return nil, InternalServerError(errors.New(internalServerErrorMsg))
}

_, _, err = deployer.ValidateK8sQuota(k8sDeployInput, allQuotaVMs, quota.PublicIPs)
if err != nil {
log.Error().Err(err).Send()
return nil, BadRequest(errors.New(err.Error()))
Expand Down
2 changes: 0 additions & 2 deletions server/app/quota_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"
"net/http"
"testing"
"time"

"github.com/codescalers/cloud4students/internal"
"github.com/codescalers/cloud4students/models"
Expand Down Expand Up @@ -45,7 +44,6 @@ func TestQuotaRouter(t *testing.T) {
err = app.db.CreateQuota(
&models.Quota{
UserID: user.ID.String(),
Vms: map[time.Time]int{time.Now().Add(time.Hour): 10},
PublicIPs: 1,
},
)
Expand Down
25 changes: 18 additions & 7 deletions server/app/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"net/http/httptest"
"os"
"path/filepath"

"testing"

c4sDeployer "github.com/codescalers/cloud4students/deployer"
Expand Down Expand Up @@ -73,23 +72,35 @@ func SetUp(t testing.TB) *App {
`, dbPath)

err := os.WriteFile(configPath, []byte(config), 0644)
assert.NoError(t, err)
if !assert.NoError(t, err) {
return &App{}
}

configuration, err := internal.ReadConfFile(configPath)
assert.NoError(t, err)
if !assert.NoError(t, err) {
return &App{}
}

db := models.NewDB()
err = db.Connect(configuration.Database.File)
assert.NoError(t, err)
if !assert.NoError(t, err) {
return &App{}
}

err = db.Migrate()
assert.NoError(t, err)
if !assert.NoError(t, err) {
return &App{}
}

tfPluginClient, err := deployer.NewTFPluginClient(configuration.Account.Mnemonics, "sr25519", configuration.Account.Network, "", "", "", 0, false)
assert.NoError(t, err)
if !assert.NoError(t, err) {
return &App{}
}

newDeployer, err := c4sDeployer.NewDeployer(db, streams.RedisClient{}, tfPluginClient)
assert.NoError(t, err)
if !assert.NoError(t, err) {
return &App{}
}

app := &App{
config: configuration,
Expand Down
19 changes: 9 additions & 10 deletions server/app/user_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -573,19 +573,20 @@ func (a *App) ApplyForVoucherHandler(req *http.Request) (interface{}, Response)
return nil, BadRequest(errors.New("invalid voucher data"))
}

// make sure the requested duration is less that the maximum allowed duration
if input.VoucherDuration > 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,
VoucherDuration: input.VoucherDuration,
Voucher: v,
UserID: userID,
VMs: input.VMs,
Reason: input.Reason,
PublicIPs: input.PublicIPs,
Duration: input.VoucherDuration,
}

err = a.db.CreateVoucher(&voucher)
Expand Down Expand Up @@ -630,9 +631,7 @@ func (a *App) ActivateVoucherHandler(req *http.Request) (interface{}, Response)
return nil, InternalServerError(errors.New(internalServerErrorMsg))
}

expirationDate := time.Now().Add(time.Duration(voucherQuota.VoucherDuration) * 30 * 24 * time.Hour)

userQuotaVMs, err := a.db.GetUserQuotaVMs(quota.ID.String(), expirationDate)
userQuotaVMs, err := a.db.GetUserQuotaVMs(quota.ID.String(), voucherQuota.Duration)
if err == gorm.ErrRecordNotFound {
return nil, NotFound(errors.New("user quota vms is not found"))
}
Expand Down Expand Up @@ -665,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(), expirationDate, userQuotaVMs.Vms+voucherQuota.VMs)
err = a.db.UpdateUserQuotaVMs(quota.ID.String(), voucherQuota.Duration, userQuotaVMs.Vms+voucherQuota.VMs)
if err != nil {
log.Error().Err(err).Send()
return nil, InternalServerError(errors.New(internalServerErrorMsg))
Expand Down
10 changes: 5 additions & 5 deletions server/app/voucher_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ func (a *App) GenerateVoucherHandler(req *http.Request) (interface{}, Response)
}

v := models.Voucher{
Voucher: voucher,
VMs: input.VMs,
PublicIPs: input.PublicIPs,
VoucherDuration: input.VoucherDuration,
Approved: true,
Voucher: voucher,
VMs: input.VMs,
PublicIPs: input.PublicIPs,
Approved: true,
Duration: input.VoucherDuration,
}

err = a.db.CreateVoucher(&v)
Expand Down
54 changes: 43 additions & 11 deletions server/deployer/k8s_deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,28 +199,31 @@ func (d *Deployer) getK8sAvailableNode(ctx context.Context, k models.K8sDeployIn
}

// ValidateK8sQuota validates the quota a k8s deployment need
func ValidateK8sQuota(k models.K8sDeployInput, availableResourcesQuota, availablePublicIPsQuota int) (int, error) {
func ValidateK8sQuota(k models.K8sDeployInput, availableResourcesQuota []models.QuotaVM, availablePublicIPsQuota int) (int, int, error) {
neededQuota, err := calcNeededQuota(k.Resources)
if err != nil {
return 0, err
return 0, 0, err
}

if k.Public && availablePublicIPsQuota < publicQuota {
return 0, 0, fmt.Errorf("no available quota %d for public ips", availablePublicIPsQuota)
}

for _, worker := range k.Workers {
workerQuota, err := calcNeededQuota(worker.Resources)
if err != nil {
return 0, err
return 0, 0, err
}
neededQuota += workerQuota
}

if availableResourcesQuota < neededQuota {
return 0, fmt.Errorf("no available quota %d for kubernetes deployment, you can request a new voucher", availableResourcesQuota)
}
if k.Public && availablePublicIPsQuota < publicQuota {
return 0, fmt.Errorf("no available quota %d for public ips", availablePublicIPsQuota)
for _, quotaVMs := range availableResourcesQuota {
if quotaVMs.Duration >= k.Duration && quotaVMs.Vms >= neededQuota {
return quotaVMs.Duration, neededQuota, nil
}
}

return neededQuota, nil
return 0, 0, fmt.Errorf("no available quota %v for kubernetes deployment, you can request a new voucher", availableResourcesQuota)
}

func (d *Deployer) deployK8sRequest(ctx context.Context, user models.User, k8sDeployInput models.K8sDeployInput, adminSSHKey string) (int, error) {
Expand All @@ -235,12 +238,30 @@ func (d *Deployer) deployK8sRequest(ctx context.Context, user models.User, k8sDe
return http.StatusInternalServerError, errors.New(internalServerErrorMsg)
}

neededQuota, err := ValidateK8sQuota(k8sDeployInput, quota.Vms, quota.PublicIPs)
allQuotaVMs, err := d.db.ListUserQuotaVMs(quota.ID.String())
if err == gorm.ErrRecordNotFound {
return http.StatusNotFound, errors.New("user quota vms are not found")
}
if err != nil {
log.Error().Err(err).Send()
return http.StatusInternalServerError, errors.New(internalServerErrorMsg)
}

neededQuotaDuration, neededQuota, err := ValidateK8sQuota(k8sDeployInput, allQuotaVMs, quota.PublicIPs)
if err != nil {
log.Error().Err(err).Send()
return http.StatusBadRequest, err
}

quotaVMs, err := d.db.GetUserQuotaVMs(quota.ID.String(), neededQuotaDuration)
if err == gorm.ErrRecordNotFound {
return http.StatusNotFound, errors.New("user quota vm is not found")
}
if err != nil {
log.Error().Err(err).Send()
return http.StatusInternalServerError, errors.New(internalServerErrorMsg)
}

// deploy network and cluster
node, networkContractID, k8sContractID, err := d.deployK8sClusterWithNetwork(ctx, k8sDeployInput, user.SSHKey, adminSSHKey)
if err != nil {
Expand All @@ -253,12 +274,14 @@ func (d *Deployer) deployK8sRequest(ctx context.Context, user models.User, k8sDe
log.Error().Err(err).Send()
return http.StatusInternalServerError, errors.New(internalServerErrorMsg)
}

publicIPsQuota := quota.PublicIPs
if k8sDeployInput.Public {
publicIPsQuota -= publicQuota
}

// update quota
err = d.db.UpdateUserQuota(user.ID.String(), quota.Vms-neededQuota, publicIPsQuota)
err = d.db.UpdateUserQuota(user.ID.String(), publicIPsQuota)
if err == gorm.ErrRecordNotFound {
return http.StatusNotFound, errors.New("user quota is not found")
}
Expand All @@ -267,6 +290,15 @@ func (d *Deployer) deployK8sRequest(ctx context.Context, user models.User, k8sDe
return http.StatusInternalServerError, errors.New(internalServerErrorMsg)
}

err = d.db.UpdateUserQuotaVMs(quota.ID.String(), neededQuotaDuration, quotaVMs.Vms-neededQuota)
if err == gorm.ErrRecordNotFound {
return http.StatusNotFound, errors.New("User quota vms is not found")
}
if err != nil {
log.Error().Err(err).Send()
return http.StatusInternalServerError, errors.New(internalServerErrorMsg)
}

err = d.db.CreateK8s(&k8sCluster)
if err != nil {
log.Error().Err(err).Send()
Expand Down
33 changes: 21 additions & 12 deletions server/deployer/vms_deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,24 +88,23 @@ func (d *Deployer) deployVM(ctx context.Context, vmInput models.DeployVMInput, s
}

// ValidateVMQuota validates the quota a vm deployment need
func ValidateVMQuota(vm models.DeployVMInput, availableResourcesQuota []models.QuotaVM, availablePublicIPsQuota int) (time.Time, int, error) {
func ValidateVMQuota(vm models.DeployVMInput, availableResourcesQuota []models.QuotaVM, availablePublicIPsQuota int) (int, int, error) {
neededQuota, err := calcNeededQuota(vm.Resources)
if err != nil {
return time.Now(), 0, err
return 0, 0, err
}

if vm.Public && availablePublicIPsQuota < publicQuota {
return time.Now(), 0, fmt.Errorf("no available quota %d for public ips", availablePublicIPsQuota)
return 0, 0, fmt.Errorf("no available quota %d for public ips", availablePublicIPsQuota)
}

requestedExpirationDate := time.Now().Add(time.Duration(vm.Duration) * 30 * 24 * time.Hour)
for _, vms := range availableResourcesQuota {
if requestedExpirationDate.Before(vms.ExpirationDate) && neededQuota <= vms.Vms {
return vms.ExpirationDate, vms.Vms - neededQuota, nil
for _, quotaVMs := range availableResourcesQuota {
if quotaVMs.Duration >= vm.Duration && quotaVMs.Vms >= neededQuota {
return quotaVMs.Duration, neededQuota, nil
}
}

return time.Now(), 0, fmt.Errorf("no available quota %v for deployment for resources %s, you can request a new voucher", availableResourcesQuota, vm.Resources)
return 0, 0, fmt.Errorf("no available quota %v for deployment for resources %s, you can request a new voucher", availableResourcesQuota, vm.Resources)
}

func (d *Deployer) deployVMRequest(ctx context.Context, user models.User, input models.DeployVMInput, adminSSHKey string) (int, error) {
Expand All @@ -119,7 +118,7 @@ func (d *Deployer) deployVMRequest(ctx context.Context, user models.User, input
return http.StatusInternalServerError, errors.New(internalServerErrorMsg)
}

quotaVM, err := d.db.ListUserQuotaVMs(quota.ID.String())
allQuotaVMs, err := d.db.ListUserQuotaVMs(quota.ID.String())
if err == gorm.ErrRecordNotFound {
return http.StatusNotFound, errors.New("user quota vm is not found")
}
Expand All @@ -128,11 +127,21 @@ func (d *Deployer) deployVMRequest(ctx context.Context, user models.User, input
return http.StatusInternalServerError, errors.New(internalServerErrorMsg)
}

expirationDate, newQuotaVMs, err := ValidateVMQuota(input, quotaVM, quota.PublicIPs)
neededQuotaDuration, neededQuota, err := ValidateVMQuota(input, allQuotaVMs, quota.PublicIPs)
if err != nil {
return http.StatusBadRequest, err
}

quotaVMs, err := d.db.GetUserQuotaVMs(quota.ID.String(), neededQuotaDuration)
if err == gorm.ErrRecordNotFound {
return http.StatusNotFound, errors.New("user quota vm is not found")
}
if err != nil {
log.Error().Err(err).Send()
return http.StatusInternalServerError, errors.New(internalServerErrorMsg)
}

// deploy network and vm
vm, contractID, networkContractID, diskSize, err := d.deployVM(ctx, input, user.SSHKey, adminSSHKey)
if err != nil {
log.Error().Err(err).Send()
Expand All @@ -151,7 +160,7 @@ func (d *Deployer) deployVMRequest(ctx context.Context, user models.User, input
MRU: uint64(vm.Memory),
ContractID: contractID,
NetworkContractID: networkContractID,
ExpirationDate: expirationDate,
ExpirationDate: time.Now().Add(time.Duration(quotaVMs.Duration) * 30 * 24 * time.Hour).Truncate(24 * time.Hour),
}

err = d.db.CreateVM(&userVM)
Expand All @@ -174,7 +183,7 @@ func (d *Deployer) deployVMRequest(ctx context.Context, user models.User, input
return http.StatusInternalServerError, errors.New(internalServerErrorMsg)
}

err = d.db.UpdateUserQuotaVMs(quota.ID.String(), expirationDate, newQuotaVMs)
err = d.db.UpdateUserQuotaVMs(quota.ID.String(), neededQuotaDuration, quotaVMs.Vms-neededQuota)
if err == gorm.ErrRecordNotFound {
return http.StatusNotFound, errors.New("User quota vms is not found")
}
Expand Down
Loading

0 comments on commit 80da291

Please sign in to comment.