diff --git a/pkg/apis/compute/cachedimage.go b/pkg/apis/compute/cachedimage.go index 7cbe4d2357..be484b9094 100644 --- a/pkg/apis/compute/cachedimage.go +++ b/pkg/apis/compute/cachedimage.go @@ -30,6 +30,12 @@ type CachedImageManagerCacheImageInput struct { ImageId string `json:"image_id"` } +type CachedimageUsage struct { + // 此镜像被使用次数 + // example: 0 + CachedCount int `json:"cached_count"` +} + type CachedimageDetails struct { apis.SharableVirtualResourceDetails @@ -56,7 +62,7 @@ type CachedimageDetails struct { // 此镜像被使用次数 // example: 0 - CachedCount int `json:"cached_count"` + CachedimageUsage } type CachedImageSetClassMetadataInput struct { diff --git a/pkg/apis/compute/host.go b/pkg/apis/compute/host.go index 09ec50d60f..ed284f197d 100644 --- a/pkg/apis/compute/host.go +++ b/pkg/apis/compute/host.go @@ -274,6 +274,9 @@ type HostResourceInfo struct { // 宿主机序列号 HostSN string `json:"host_sn"` + // 宿主是否启用 + HostEnabled bool `json:"host_enabled"` + // 宿主机状态 HostStatus string `json:"host_status"` diff --git a/pkg/compute/models/cachedimages.go b/pkg/compute/models/cachedimages.go index 9e5cc8300d..986b930381 100644 --- a/pkg/compute/models/cachedimages.go +++ b/pkg/compute/models/cachedimages.go @@ -26,6 +26,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/gotypes" "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/httputils" "yunion.io/x/pkg/util/rbacscope" @@ -103,12 +104,16 @@ func (self SCachedimage) GetGlobalId() string { return self.ExternalId } -func (self *SCachedimage) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error { - cnt, err := self.getStoragecacheCount() - if err != nil { - return httperrors.NewInternalServerError("ValidateDeleteCondition error %s", err) +func (self *SCachedimage) ValidateDeleteCondition(ctx context.Context, info *api.CachedimageDetails) error { + if gotypes.IsNil(info) { + info = &api.CachedimageDetails{} + count, err := CachedimageManager.TotalResourceCount([]string{self.Id}) + if err != nil { + return err + } + info.CachedimageUsage, _ = count[self.Id] } - if cnt > 0 { + if info.CachedCount > 0 { return httperrors.NewNotEmptyError("The image has been cached on storages") } if self.GetStatus() == api.CACHED_IMAGE_STATUS_ACTIVE && !self.isReferenceSessionExpire() { @@ -396,6 +401,54 @@ func (manager *SCachedimageManager) getImageInfo(ctx context.Context, userCred m return manager.getImageByName(ctx, userCred, imageId, refresh) } +func (cm *SCachedimageManager) query(manager db.IModelManager, field string, cacheIds []string, filter func(*sqlchemy.SQuery) *sqlchemy.SQuery) *sqlchemy.SSubQuery { + q := manager.Query() + + if filter != nil { + q = filter(q) + } + + sq := q.SubQuery() + + return sq.Query( + sq.Field("cachedimage_id"), + sqlchemy.COUNT(field), + ).In("cachedimage_id", cacheIds).GroupBy(sq.Field("cachedimage_id")).SubQuery() +} + +type CachedimageUsageCount struct { + Id string + api.CachedimageUsage +} + +func (manager *SCachedimageManager) TotalResourceCount(cacheIds []string) (map[string]api.CachedimageUsage, error) { + ret := map[string]api.CachedimageUsage{} + + scSQ := manager.query(StoragecachedimageManager, "cached_cnt", cacheIds, nil) + + caches := manager.Query().SubQuery() + cachesQ := caches.Query( + sqlchemy.SUM("cached_count", scSQ.Field("cached_cnt")), + ) + + cachesQ.AppendField(cachesQ.Field("id")) + + cachesQ = cachesQ.LeftJoin(scSQ, sqlchemy.Equals(cachesQ.Field("id"), scSQ.Field("cachedimage_id"))) + + cachesQ = cachesQ.Filter(sqlchemy.In(cachesQ.Field("id"), cacheIds)).GroupBy(cachesQ.Field("id")) + + counts := []CachedimageUsageCount{} + err := cachesQ.All(&counts) + if err != nil { + return nil, errors.Wrapf(err, "cachesQ.All") + } + for i := range counts { + ret[counts[i].Id] = counts[i].CachedimageUsage + } + + return ret, nil +} + func (manager *SCachedimageManager) FetchCustomizeColumns( ctx context.Context, userCred mcclient.TokenCredential, @@ -406,6 +459,7 @@ func (manager *SCachedimageManager) FetchCustomizeColumns( ) []api.CachedimageDetails { rows := make([]api.CachedimageDetails, len(objs)) virtRows := manager.SSharableVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + cacheIds := make([]string, len(objs)) for i := range rows { ci := objs[i].(*SCachedimage) rows[i] = api.CachedimageDetails{ @@ -415,7 +469,15 @@ func (manager *SCachedimageManager) FetchCustomizeColumns( OsVersion: ci.GetOSVersion(), Hypervisor: ci.GetHypervisor(), } - rows[i].CachedCount, _ = ci.getStoragecacheCount() + cacheIds[i] = ci.Id + } + usage, err := manager.TotalResourceCount(cacheIds) + if err != nil { + log.Errorf("TotalResourceCount error: %v", err) + return rows + } + for i := range rows { + rows[i].CachedimageUsage, _ = usage[cacheIds[i]] } return rows } diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 091a48cc16..5ee724a73d 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -27,6 +27,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/gotypes" "yunion.io/x/pkg/tristate" "yunion.io/x/pkg/util/billing" "yunion.io/x/pkg/util/compare" @@ -834,13 +835,22 @@ func (guest *SGuest) ValidatePurgeCondition(ctx context.Context) error { return guest.validateDeleteCondition(ctx, true) } -func (guest *SGuest) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error { - host, _ := guest.GetHost() - if host != nil && guest.GetHypervisor() != api.HYPERVISOR_BAREMETAL { - if !host.GetEnabled() { +func (guest *SGuest) ValidateDeleteCondition(ctx context.Context, info *api.ServerDetails) error { + if gotypes.IsNil(info) { + info = &api.ServerDetails{} + host, err := guest.GetHost() + if err != nil { + return err + } + info.HostType = host.HostType + info.HostEnabled = host.Enabled.Bool() + info.HostStatus = host.HostStatus + } + if len(info.HostType) > 0 && guest.GetHypervisor() != api.HYPERVISOR_BAREMETAL { + if !info.HostEnabled { return httperrors.NewInputParameterError("Cannot delete server on disabled host") } - if host.HostStatus != api.HOST_ONLINE { + if info.HostStatus != api.HOST_ONLINE { return httperrors.NewInputParameterError("Cannot delete server on offline host") } } diff --git a/pkg/compute/models/hostresource.go b/pkg/compute/models/hostresource.go index db1bd1dcd9..a4fbe57e02 100644 --- a/pkg/compute/models/hostresource.go +++ b/pkg/compute/models/hostresource.go @@ -105,6 +105,7 @@ func (manager *SHostResourceBaseManager) FetchCustomizeColumns( rows[i].Host = host.Name rows[i].HostSN = host.SN rows[i].HostStatus = host.Status + rows[i].HostEnabled = host.Enabled.Bool() rows[i].HostServiceStatus = host.HostStatus rows[i].HostType = host.HostType rows[i].ManagerId = host.ManagerId