diff --git a/pkg/apis/compute/guest_const.go b/pkg/apis/compute/guest_const.go index 6612568e41..11e0384281 100644 --- a/pkg/apis/compute/guest_const.go +++ b/pkg/apis/compute/guest_const.go @@ -208,6 +208,11 @@ const ( VM_POWER_STATES_UNKNOWN = "unknown" ) +const ( + VM_SHUTDOWN_MODE_KEEP_CHARGING = "keep_charging" + VM_SHUTDOWN_MODE_STOP_CHARGING = "stop_charging" +) + const ( QGA_STATUS_UNKNOWN = "unknown" QGA_STATUS_EXCUTING = "executing" diff --git a/pkg/apis/compute/guests.go b/pkg/apis/compute/guests.go index 163121f230..f9781756db 100644 --- a/pkg/apis/compute/guests.go +++ b/pkg/apis/compute/guests.go @@ -137,6 +137,7 @@ type ServerRebuildRootInput struct { // swagger: ignore Image string `json:"image" yunion-deprecated-by:"image_id"` + // 关机且停机不收费情况下不允许重装系统 // 镜像 id // required: true ImageId string `json:"image_id"` @@ -726,6 +727,7 @@ type ServerDetachDiskInput struct { } type ServerChangeConfigInput struct { + // 关机且停机不收费情况下不允许调整配置 // 实例类型, 优先级高于vcpu_count和vmem_size InstanceType string `json:"instance_type"` // swagger: ignore diff --git a/pkg/compute/guestdrivers/aliyun.go b/pkg/compute/guestdrivers/aliyun.go index 3eac4ef642..19caf2d4c2 100644 --- a/pkg/compute/guestdrivers/aliyun.go +++ b/pkg/compute/guestdrivers/aliyun.go @@ -216,6 +216,10 @@ func (self *SAliyunGuestDriver) IsSupportedBillingCycle(bc billing.SBillingCycle return false } +func (self *SAliyunGuestDriver) IsSupportShutdownMode() bool { + return true +} + func (self *SAliyunGuestDriver) IsSupportPublicipToEip() bool { return true } diff --git a/pkg/compute/guestdrivers/base.go b/pkg/compute/guestdrivers/base.go index ac4dd20661..fb5b437cd0 100644 --- a/pkg/compute/guestdrivers/base.go +++ b/pkg/compute/guestdrivers/base.go @@ -301,6 +301,10 @@ func (self *SBaseGuestDriver) IsSupportPostpaidExpire() bool { return true } +func (self *SBaseGuestDriver) IsSupportShutdownMode() bool { + return false +} + func (self *SBaseGuestDriver) RequestRenewInstance(ctx context.Context, guest *models.SGuest, bc billing.SBillingCycle) (time.Time, error) { return time.Time{}, nil } diff --git a/pkg/compute/guestdrivers/kvm.go b/pkg/compute/guestdrivers/kvm.go index f1ddadffd5..970a075766 100644 --- a/pkg/compute/guestdrivers/kvm.go +++ b/pkg/compute/guestdrivers/kvm.go @@ -368,6 +368,10 @@ func (self *SKVMGuestDriver) IsSupportEip() bool { return true } +func (self *SKVMGuestDriver) IsSupportShutdownMode() bool { + return true +} + func (self *SKVMGuestDriver) ValidateCreateEip(ctx context.Context, userCred mcclient.TokenCredential, input api.ServerCreateEipInput) error { return nil } diff --git a/pkg/compute/guestdrivers/qcloud.go b/pkg/compute/guestdrivers/qcloud.go index 93554b088e..6ab21553f3 100644 --- a/pkg/compute/guestdrivers/qcloud.go +++ b/pkg/compute/guestdrivers/qcloud.go @@ -233,6 +233,10 @@ func (self *SQcloudGuestDriver) IsSupportdDcryptPasswordFromSecretKey() bool { return false } +func (self *SQcloudGuestDriver) IsSupportShutdownMode() bool { + return true +} + func (self *SQcloudGuestDriver) GetUserDataType() string { return cloudprovider.CLOUD_SHELL } diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index d47f47cca7..77f39e0fdc 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -1177,7 +1177,19 @@ func (self *SGuest) StartGuestStopTask(ctx context.Context, userCred mcclient.To if len(parentTaskId) > 0 { params.Add(jsonutils.JSONTrue, "subtask") } - return self.GetDriver().StartGuestStopTask(self, ctx, userCred, params, parentTaskId) + driver := self.GetDriver() + shutdownMode := api.VM_SHUTDOWN_MODE_KEEP_CHARGING + if stopCharging && driver.IsSupportShutdownMode() { + shutdownMode = api.VM_SHUTDOWN_MODE_STOP_CHARGING + } + _, err := db.Update(self, func() error { + self.ShutdownMode = shutdownMode + return nil + }) + if err != nil { + return errors.Wrapf(err, "db.Update") + } + return driver.StartGuestStopTask(self, ctx, userCred, params, parentTaskId) } func (self *SGuest) insertIso(imageId string, cdromOrdinal int64) bool { @@ -1618,6 +1630,10 @@ func (self *SGuest) PerformRebuildRoot(ctx context.Context, userCred mcclient.To return nil, httperrors.NewInvalidStatusError("Cannot reset root in status %s", self.Status) } + if self.Status == api.VM_READY && self.ShutdownMode == api.VM_SHUTDOWN_MODE_STOP_CHARGING { + return nil, httperrors.NewInvalidStatusError("Cannot reset root with %s", self.ShutdownMode) + } + autoStart := false if input.AutoStart != nil { autoStart = *input.AutoStart @@ -2556,6 +2572,10 @@ func (self *SGuest) PerformChangeConfig(ctx context.Context, userCred mcclient.T return nil, httperrors.NewInvalidStatusError("Cannot change config in %s for %s, requires %s", self.Status, self.GetHypervisor(), changeStatus) } + if self.Status == api.VM_READY && self.ShutdownMode == api.VM_SHUTDOWN_MODE_STOP_CHARGING { + return nil, httperrors.NewInvalidStatusError("Cannot change config with %s", self.ShutdownMode) + } + _, err = self.GetHost() if err != nil { return nil, errors.Wrapf(err, "GetHost") diff --git a/pkg/compute/models/guestdrivers.go b/pkg/compute/models/guestdrivers.go index a36427623b..9427feba2a 100644 --- a/pkg/compute/models/guestdrivers.go +++ b/pkg/compute/models/guestdrivers.go @@ -55,6 +55,7 @@ type IGuestDriver interface { IsSupportedBillingCycle(bc billing.SBillingCycle) bool IsSupportPostpaidExpire() bool + IsSupportShutdownMode() bool RequestRenewInstance(ctx context.Context, guest *SGuest, bc billing.SBillingCycle) (time.Time, error) diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 201212d85a..966b8a78af 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -133,6 +133,10 @@ type SGuest struct { // example: stop ShutdownBehavior string `width:"16" charset:"ascii" default:"stop" list:"user" update:"user" create:"optional"` + // 关机收费模式 + // example: keep_charging, stop_charging + ShutdownMode string `width:"16" charset:"ascii" default:"keep_charging" list:"user"` + // 秘钥对Id KeypairId string `width:"36" charset:"ascii" nullable:"true" list:"user" create:"optional"` @@ -5119,6 +5123,7 @@ func (self *SGuest) GetShortDesc(ctx context.Context) *jsonutils.JSONDict { desc.Set("cpu", jsonutils.NewInt(int64(self.VcpuCount))) desc.Set("status", jsonutils.NewString(self.Status)) + desc.Set("shutdown_mode", jsonutils.NewString(self.ShutdownMode)) address := jsonutils.NewString(strings.Join(self.GetRealIPs(), ",")) desc.Set("ip_addr", address)