diff --git a/pkg/cloudcommon/db/db_joint_dispatcher.go b/pkg/cloudcommon/db/db_joint_dispatcher.go index f846e942c4..af7ccc1b4b 100644 --- a/pkg/cloudcommon/db/db_joint_dispatcher.go +++ b/pkg/cloudcommon/db/db_joint_dispatcher.go @@ -206,6 +206,11 @@ func (dispatcher *DBJointModelDispatcher) Update(ctx context.Context, id1 string userCred := fetchUserCredential(ctx) master, slave, item, err := fetchJointItem(dispatcher, ctx, userCred, id1, id2, query) if err == sql.ErrNoRows { + if jsonutils.QueryBoolean(query, "auto_create", false) { + queryDict := query.(*jsonutils.JSONDict) + queryDict.Remove("auto_create") + return dispatcher.Attach(ctx, id1, id2, query, data) + } return nil, httperrors.NewResourceNotFoundError2(dispatcher.modelManager.Keyword(), id1+"-"+id2) } else if err != nil { return nil, httperrors.NewGeneralError(err) diff --git a/pkg/compute/models/disks.go b/pkg/compute/models/disks.go index 6dafa9d63c..f74fd78f5e 100644 --- a/pkg/compute/models/disks.go +++ b/pkg/compute/models/disks.go @@ -790,6 +790,19 @@ func (self *SDisk) validateDeleteCondition(ctx context.Context, isPurge bool) er return self.SSharableVirtualResourceBase.ValidateDeleteCondition(ctx) } +func (self *SDisk) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + overridePendingDelete := false + purge := false + if query != nil { + overridePendingDelete = jsonutils.QueryBoolean(query, "override_pending_delete", false) + purge = jsonutils.QueryBoolean(query, "purge", false) + } + if (overridePendingDelete || purge) && !db.IsAdminAllowDelete(userCred, self) { + return false + } + return self.IsOwner(userCred) || db.IsAdminAllowDelete(userCred, self) +} + func (self *SDisk) GetTemplateId() string { return self.TemplateId } @@ -1615,3 +1628,23 @@ func (self *SDisk) SaveRenewInfo(ctx context.Context, userCred mcclient.TokenCre db.OpsLog.LogEvent(self, db.ACT_RENEW, self.GetShortDesc(ctx), userCred) return nil } + +func (self *SDisk) IsDetachable() bool { + storage := self.GetStorage() + if storage == nil { + return true + } + if storage.IsLocal() { + return false + } + if self.BillingType == BILLING_TYPE_PREPAID { + return false + } + if utils.IsInStringArray(self.DiskType, []string{DISK_TYPE_SYS, DISK_TYPE_SWAP}) { + return false + } + if self.AutoDelete { + return false + } + return true +} diff --git a/pkg/compute/models/guest_actions.go b/pkg/compute/models/guest_actions.go index 64d3ad3af7..d3c119ade5 100644 --- a/pkg/compute/models/guest_actions.go +++ b/pkg/compute/models/guest_actions.go @@ -129,6 +129,9 @@ func (self *SGuest) AllowPerformSync(ctx context.Context, userCred mcclient.Toke } func (self *SGuest) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if !utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING}) { + return nil, httperrors.NewResourceBusyError("Cannot sync in status %s", self.Status) + } if err := self.StartSyncTask(ctx, userCred, false, ""); err != nil { return nil, err } @@ -424,9 +427,7 @@ func (self *SGuest) PerformAttachdisk(ctx context.Context, userCred mcclient.Tok } func (self *SGuest) StartSyncTask(ctx context.Context, userCred mcclient.TokenCredential, fw_only bool, parentTaskId string) error { - if !utils.IsInStringArray(self.Status, []string{VM_READY, VM_RUNNING}) { - return httperrors.NewResourceBusyError("Cannot sync in status %s", self.Status) - } + data := jsonutils.NewDict() if fw_only { data.Add(jsonutils.JSONTrue, "fw_only") @@ -848,7 +849,7 @@ func (self *SGuest) PerformSetSecgroup(ctx context.Context, userCred mcclient.To setSecgroupNames = append(setSecgroupNames, secgrp.GetName()) } - if err := self.revokeAllSecgroups(ctx, userCred); err != nil { + if err := self.RevokeAllSecgroups(ctx, userCred); err != nil { return nil, err } @@ -1125,14 +1126,8 @@ func (self *SGuest) PerformDetachdisk(ctx context.Context, userCred mcclient.Tok return nil, httperrors.NewInputParameterError("Cannot keep detached disk") } if utils.IsInStringArray(self.Status, detachDiskStatus) { - if disk.Status == DISK_INIT { - disk.SetStatus(userCred, DISK_DETACHING, "") - } - taskData := jsonutils.NewDict() - taskData.Add(jsonutils.NewString(disk.Id), "disk_id") - taskData.Add(jsonutils.NewBool(keepDisk), "keep_disk") - self.GetDriver().StartGuestDetachdiskTask(ctx, userCred, self, taskData, "") - return nil, nil + err = self.StartGuestDetachdiskTask(ctx, userCred, disk, keepDisk, "") + return nil, err } else { return nil, httperrors.NewInvalidStatusError("Server in %s not able to detach disk", self.Status) } @@ -1143,6 +1138,16 @@ func (self *SGuest) PerformDetachdisk(ctx context.Context, userCred mcclient.Tok return nil, httperrors.NewResourceNotFoundError("Disk %s not found", diskId) } +func (self *SGuest) StartGuestDetachdiskTask(ctx context.Context, userCred mcclient.TokenCredential, disk *SDisk, keepDisk bool, parentTaskId string) error { + if disk.Status == DISK_INIT { + disk.SetStatus(userCred, DISK_DETACHING, "") + } + taskData := jsonutils.NewDict() + taskData.Add(jsonutils.NewString(disk.Id), "disk_id") + taskData.Add(jsonutils.NewBool(keepDisk), "keep_disk") + return self.GetDriver().StartGuestDetachdiskTask(ctx, userCred, self, taskData, "") +} + func (self *SGuest) AllowPerformDetachIsolatedDevice(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "detach-isolated-device") } @@ -1528,12 +1533,12 @@ func (self *SGuest) StartChangeConfigTask(ctx context.Context, userCred mcclient return nil } -func (self *SGuest) revokeAllSecgroups(ctx context.Context, userCred mcclient.TokenCredential) error { - err := self.revokeSecgroup(ctx, userCred, self.getSecgroup()) +func (self *SGuest) RevokeAllSecgroups(ctx context.Context, userCred mcclient.TokenCredential) error { + err := GuestsecgroupManager.DeleteGuestSecgroup(ctx, userCred, self, nil) if err != nil { return err } - err = GuestsecgroupManager.DeleteGuestSecgroup(ctx, userCred, self, nil) + err = self.revokeSecgroup(ctx, userCred, self.getSecgroup()) if err != nil { return err } @@ -1545,16 +1550,13 @@ func (self *SGuest) DoPendingDelete(ctx context.Context, userCred mcclient.Token if eip != nil { eip.DoPendingDelete(ctx, userCred) } - // revoke all secgroups - // TODO: sync revoked secgroups to remote cloud - self.revokeAllSecgroups(ctx, userCred) - // remove detachable disks + for _, guestdisk := range self.GetDisks() { disk := guestdisk.GetDisk() - storage := disk.GetStorage() - if storage.IsLocal() || disk.BillingType == BILLING_TYPE_PREPAID || utils.IsInStringArray(disk.DiskType, []string{DISK_TYPE_SYS, DISK_TYPE_SWAP}) || (utils.IsInStringArray(self.Hypervisor, PUBLIC_CLOUD_HYPERVISORS) && disk.AutoDelete) { + if !disk.IsDetachable() { disk.DoPendingDelete(ctx, userCred) } else { + log.Warningf("detachable disk on pending delete guests!!! should be removed earlier") self.DetachDisk(ctx, disk, userCred) } } diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 3bfef2176a..d1dc92bd30 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -2638,9 +2638,9 @@ func (self *SGuest) RealDelete(ctx context.Context, userCred mcclient.TokenCrede func (self *SGuest) AllowDeleteItem(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { overridePendingDelete := false purge := false - if data != nil { - overridePendingDelete = jsonutils.QueryBoolean(data, "override_pending_delete", false) - purge = jsonutils.QueryBoolean(data, "purge", false) + if query != nil { + overridePendingDelete = jsonutils.QueryBoolean(query, "override_pending_delete", false) + purge = jsonutils.QueryBoolean(query, "purge", false) } if (overridePendingDelete || purge) && !db.IsAdminAllowDelete(userCred, self) { return false diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index fd1df2e48d..67f7ce4272 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -140,9 +140,9 @@ type SHost struct { CpuReserved int8 `nullable:"true" default:"0" list:"admin" update:"admin" create:"admin_optional"` // Column(TINYINT, nullable=True, default=0) CpuCmtbound float32 `nullable:"true" default:"8.0" list:"admin" update:"admin" create:"admin_optional"` // = Column(Float, nullable=True) - MemSize int `nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # memory size in MB - MemReserved int `nullable:"true" default:"0" list:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True, default=0) # memory reserved in MB - MemCmtbound float32 `nullable:"true" default:"1.0" update:"admin" create:"admin_optional"` // = Column(Float, nullable=True) + MemSize int `nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # memory size in MB + MemReserved int `nullable:"true" default:"0" list:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True, default=0) # memory reserved in MB + MemCmtbound float32 `nullable:"true" default:"1.0" list:"admin" update:"admin" create:"admin_optional"` // = Column(Float, nullable=True) StorageSize int `nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(Integer, nullable=True) # storage size in MB StorageType string `width:"20" charset:"ascii" nullable:"true" list:"admin" update:"admin" create:"admin_optional"` // Column(VARCHAR(20, charset='ascii'), nullable=True) diff --git a/pkg/compute/tasks/disk_delete_task.go b/pkg/compute/tasks/disk_delete_task.go index c185b6eca8..cad04bafc1 100644 --- a/pkg/compute/tasks/disk_delete_task.go +++ b/pkg/compute/tasks/disk_delete_task.go @@ -29,7 +29,14 @@ func (self *DiskDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, db.OpsLog.LogEvent(disk, db.ACT_DELOCATE_FAIL, reason, self.UserCred) return } - if options.Options.EnablePendingDelete && !disk.PendingDeleted && !jsonutils.QueryBoolean(self.Params, "purge", false) && !jsonutils.QueryBoolean(self.Params, "override_pending_delete", false) { + + isPurge := jsonutils.QueryBoolean(self.Params, "purge", false) + overridePendingDelete := jsonutils.QueryBoolean(self.Params, "override_pending_delete", false) + if options.Options.EnablePendingDelete && !isPurge && !overridePendingDelete { + if disk.PendingDeleted { + self.SetStageComplete(ctx, nil) + return + } self.startPendingDeleteDisk(ctx, disk) } else { self.startDeleteDisk(ctx, disk) diff --git a/pkg/compute/tasks/guest_delete_task.go b/pkg/compute/tasks/guest_delete_task.go index b34e28d2ec..8a7746daca 100644 --- a/pkg/compute/tasks/guest_delete_task.go +++ b/pkg/compute/tasks/guest_delete_task.go @@ -80,20 +80,69 @@ func (self *GuestDeleteTask) OnGuestStopComplete(ctx context.Context, obj db.ISt } } +func (self *GuestDeleteTask) OnGuestStopCompleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) { + self.OnGuestStopComplete(ctx, obj, err) // ignore stop error +} + func (self *GuestDeleteTask) OnEipDissociateCompleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) { guest := obj.(*models.SGuest) self.OnFailed(ctx, guest, err) } func (self *GuestDeleteTask) OnEipDissociateComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + self.SetStage("OnDiskDetachComplete", nil) + self.OnDiskDetachComplete(ctx, obj, data) +} + +// remove detachable disks +func (self *GuestDeleteTask) OnDiskDetachComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + log.Debugf("OnDiskDetachComplete") guest := obj.(*models.SGuest) - if options.Options.EnablePendingDelete && !guest.PendingDeleted && - !jsonutils.QueryBoolean(self.Params, "purge", false) && - !jsonutils.QueryBoolean(self.Params, "override_pending_delete", false) { + guestdisks := guest.GetDisks() + if len(guestdisks) == 0 { + self.doClearSecurityGroupComplete(ctx, guest) + return + } + lastDisk := guestdisks[len(guestdisks)-1].GetDisk() // remove last detachable disk + log.Debugf("lastDisk IsDetachable?? %v", lastDisk.IsDetachable()) + if !lastDisk.IsDetachable() { + self.doClearSecurityGroupComplete(ctx, guest) + return + } + guest.StartGuestDetachdiskTask(ctx, self.UserCred, lastDisk, true, self.GetTaskId()) +} + +func (self *GuestDeleteTask) OnDiskDetachCompleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + self.OnFailed(ctx, guest, err) +} + +// revoke all secgroups +func (self *GuestDeleteTask) doClearSecurityGroupComplete(ctx context.Context, guest *models.SGuest) { + log.Debugf("doClearSecurityGroupComplete") + models.IsolatedDeviceManager.ReleaseDevicesOfGuest(ctx, guest, self.UserCred) + guest.RevokeAllSecgroups(ctx, self.UserCred) + // sync revoked secgroups to remote cloud + self.SetStage("OnSyncConfigComplete", nil) + guest.StartSyncTask(ctx, self.UserCred, false, self.GetTaskId()) +} + +func (self *GuestDeleteTask) OnSyncConfigComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + + isPurge := jsonutils.QueryBoolean(self.Params, "purge", false) + overridePendingDelete := jsonutils.QueryBoolean(self.Params, "override_pending_delete", false) + + if options.Options.EnablePendingDelete && !isPurge && !overridePendingDelete { + if guest.PendingDeleted { + self.SetStageComplete(ctx, nil) + return + } log.Debugf("XXXXXXX Do guest pending delete... XXXXXXX") guestStatus, _ := self.Params.GetString("guest_status") - if !utils.IsInStringArray(guestStatus, []string{models.VM_SCHEDULE_FAILED, models.VM_NETWORK_FAILED, models.VM_DISK_FAILED, + if !utils.IsInStringArray(guestStatus, []string{ + models.VM_SCHEDULE_FAILED, models.VM_NETWORK_FAILED, models.VM_DISK_FAILED, models.VM_CREATE_FAILED, models.VM_DEVICE_FAILED}) { self.StartPendingDeleteGuest(ctx, guest) return @@ -103,8 +152,9 @@ func (self *GuestDeleteTask) OnEipDissociateComplete(ctx context.Context, obj db self.doStartDeleteGuest(ctx, guest) } -func (self *GuestDeleteTask) OnGuestStopCompleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) { - self.OnGuestStopComplete(ctx, obj, err) // ignore stop error +func (self *GuestDeleteTask) OnSyncConfigCompleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) { + guest := obj.(*models.SGuest) + self.OnFailed(ctx, guest, err) } func (self *GuestDeleteTask) OnGuestDeleteFailed(ctx context.Context, obj db.IStandaloneModel, err jsonutils.JSONObject) { @@ -121,7 +171,6 @@ func (self *GuestDeleteTask) doStartDeleteGuest(ctx context.Context, obj db.ISta func (self *GuestDeleteTask) StartPendingDeleteGuest(ctx context.Context, guest *models.SGuest) { guest.DoPendingDelete(ctx, self.UserCred) - models.IsolatedDeviceManager.ReleaseDevicesOfGuest(ctx, guest, self.UserCred) self.SetStage("on_pending_delete_complete", nil) guest.StartSyncstatus(ctx, self.UserCred, self.GetTaskId()) }