diff --git a/cmd/climc/shell/images.go b/cmd/climc/shell/images.go index f127484b5d..46100e7c1c 100644 --- a/cmd/climc/shell/images.go +++ b/cmd/climc/shell/images.go @@ -450,4 +450,29 @@ func init() { return nil }) + type ImageChangeOwnerOptions struct { + ID string `help:"Image to change owner"` + PROJECT string `help:"Project ID or change"` + RawId bool `help:"User raw ID, instead of name"` + } + R(&ImageChangeOwnerOptions{}, "image-change-owner", "Change owner project of an image", func(s *mcclient.ClientSession, opts *ImageChangeOwnerOptions) error { + params := jsonutils.NewDict() + if opts.RawId { + projid, err := modules.Projects.GetId(s, opts.PROJECT, nil) + if err != nil { + return err + } + params.Add(jsonutils.NewString(projid), "tenant") + params.Add(jsonutils.JSONTrue, "raw_id") + } else { + params.Add(jsonutils.NewString(opts.PROJECT), "tenant") + } + srv, err := modules.Images.PerformAction(s, opts.ID, "change-owner", params) + if err != nil { + return err + } + printObject(srv) + return nil + }) + } diff --git a/cmd/climc/shell/quotas.go b/cmd/climc/shell/quotas.go index a58c4487e2..e54b4d5d97 100644 --- a/cmd/climc/shell/quotas.go +++ b/cmd/climc/shell/quotas.go @@ -7,7 +7,7 @@ import ( ) type QuotaBaseOptions struct { - Cpu int64 `help:"CPU count" json:"cpu:omitzero"` + Cpu int64 `help:"CPU count" json:"cpu,omitzero"` Memory int64 `help:"Memory size in MB" json:"memory,omitzero"` Storage int64 `help:"Storage size in MB" json:"storage,omitzero"` Port int64 `help:"Internal NIC count" json:"port,omitzero"` @@ -17,10 +17,7 @@ type QuotaBaseOptions struct { Ebw int64 `help:"External bandwidth in Mbps" json:"ebw,omitzero"` IsolatedDevice int64 `help:"Isolated device count" json:"isolated_device,omitzero"` Snapshot int64 `help:"Snapshot count" json:"snapshot,omitzero"` -} - -type ImageQuotaBaseOptions struct { - Image int64 `help:"Template count" json:"image,omitzero"` + Image int64 `help:"Template count" json:"image,omitzero"` } func init() { @@ -60,20 +57,6 @@ func init() { return nil }) - type ImageQuotaSetOptions struct { - Tenant string `help:"Tenant name or ID to set quota" json:"tenant,omitempty"` - ImageQuotaBaseOptions - } - R(&ImageQuotaSetOptions{}, "image-quota-set", "Set image quota for tenant", func(s *mcclient.ClientSession, args *ImageQuotaSetOptions) error { - params := jsonutils.Marshal(args) - result, e := modules.ImageQuotas.DoQuotaSet(s, params) - if e != nil { - return e - } - printObject(result) - return nil - }) - type QuotaCheckOptions struct { TENANT string `help:"Tenant name or ID to check quota" json:"tenant,omitempty"` QuotaBaseOptions @@ -88,18 +71,4 @@ func init() { return nil }) - type ImageQuotaCheckOptions struct { - TENANT string `help:"Tenant name or ID to check quota" json:"tenant,omitempty"` - ImageQuotaBaseOptions - } - R(&ImageQuotaCheckOptions{}, "image-quota-check", "Check quota for tenant", func(s *mcclient.ClientSession, args *ImageQuotaCheckOptions) error { - params := jsonutils.Marshal(args) - result, e := modules.ImageQuotas.DoQuotaCheck(s, params) - if e != nil { - return e - } - printObject(result) - return nil - }) - } diff --git a/cmd/climc/shell/usages.go b/cmd/climc/shell/usages.go index 0d7d3ea583..37d3942433 100644 --- a/cmd/climc/shell/usages.go +++ b/cmd/climc/shell/usages.go @@ -121,7 +121,7 @@ func init() { if args.Project != "" { params.Add(jsonutils.NewString(args.Project), "project") } - result, err := modules.ImageUsages.GetUsage(s, params) + result, err := modules.Images.GetUsage(s, params) if err != nil { return err } diff --git a/pkg/image/models/images.go b/pkg/image/models/images.go index bbe066ab51..6f36f73307 100644 --- a/pkg/image/models/images.go +++ b/pkg/image/models/images.go @@ -20,7 +20,6 @@ import ( "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/db" - "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/image/options" @@ -650,41 +649,79 @@ type SImageUsage struct { Size int64 } -func (manager *SImageManager) count(projectId string, isISO tristate.TriState, pendingDelete bool) SImageUsage { - images := manager.Query().SubQuery() - q := images.Query(sqlchemy.COUNT("count"), sqlchemy.SUM("size", images.Field("size"))) +func (manager *SImageManager) count(projectId string, status string, isISO tristate.TriState, pendingDelete bool) map[string]SImageUsage { + sq := manager.Query("id") if len(projectId) > 0 { - q = q.Equals("tenant_id", projectId) + sq = sq.Equals("tenant_id", projectId) + } + if len(status) > 0 { + sq = sq.Equals("status", status) } if pendingDelete { - q = q.IsTrue("pending_deleted") + sq = sq.IsTrue("pending_deleted") } else { - q = q.IsFalse("pending_deleted") + sq = sq.IsFalse("pending_deleted") } if isISO.IsTrue() { - q = q.Equals("disk_format", "iso") + sq = sq.Equals("disk_format", "iso") } else if isISO.IsFalse() { - q = q.NotEquals("disk_format", "iso") + sq = sq.NotEquals("disk_format", "iso") + } + subimages := ImageSubformatManager.Query().SubQuery() + q := subimages.Query(subimages.Field("format"), + sqlchemy.COUNT("count"), + sqlchemy.SUM("size", subimages.Field("size"))) + q = q.In("image_id", sq.SubQuery()) + q = q.GroupBy(subimages.Field("format")) + type sFormatImageUsage struct { + Format string + Count int64 + Size int64 + } + var usages []sFormatImageUsage + err := q.All(&usages) + if err != nil { + log.Errorf("query usage fail %s", err) + return nil + } + ret := make(map[string]SImageUsage) + totalSize := int64(0) + for _, u := range usages { + ret[u.Format] = SImageUsage{Count: u.Count, Size: u.Size} + totalSize += u.Size + } + ret["total"] = SImageUsage{Count: int64(sq.Count()), Size: totalSize} + return ret +} + +func expandUsageCount(usages map[string]int64, prefix, imgType, state string, count map[string]SImageUsage) { + for k, u := range count { + key := []string{} + if len(prefix) > 0 { + key = append(key, prefix) + } + key = append(key, imgType) + if len(state) > 0 { + key = append(key, state) + } + key = append(key, k) + countKey := strings.Join(append(key, "count"), ".") + sizeKey := strings.Join(append(key, "size"), ".") + usages[countKey] = u.Count + usages[sizeKey] = u.Size } - usage := SImageUsage{} - q.First(&usage) - return usage } func (manager *SImageManager) Usage(projectId string, prefix string) map[string]int64 { usages := make(map[string]int64) - count := manager.count(projectId, tristate.False, false) - usages[quotas.KeyName(prefix, "img.count")] = count.Count - usages[quotas.KeyName(prefix, "img.size")] = count.Size - count = manager.count(projectId, tristate.True, false) - usages[quotas.KeyName(prefix, "iso.count")] = count.Count - usages[quotas.KeyName(prefix, "iso.size")] = count.Size - count = manager.count(projectId, tristate.False, true) - usages[quotas.KeyName(prefix, "img.pending_delete.count")] = count.Count - usages[quotas.KeyName(prefix, "img.pending_delete.size")] = count.Size - count = manager.count(projectId, tristate.True, true) - usages[quotas.KeyName(prefix, "iso.pending_delete.count")] = count.Count - usages[quotas.KeyName(prefix, "iso.pending_delete.size")] = count.Size + count := manager.count(projectId, IMAGE_STATUS_ACTIVE, tristate.False, false) + expandUsageCount(usages, prefix, "img", "", count) + count = manager.count(projectId, IMAGE_STATUS_ACTIVE, tristate.True, false) + expandUsageCount(usages, prefix, "iso", "", count) + count = manager.count(projectId, "", tristate.False, true) + expandUsageCount(usages, prefix, "img", "pending_delete", count) + count = manager.count(projectId, "", tristate.True, true) + expandUsageCount(usages, prefix, "iso", "pending_delete", count) return usages } diff --git a/pkg/image/models/quotas.go b/pkg/image/models/quotas.go index 96a672cc14..681f3de5c5 100644 --- a/pkg/image/models/quotas.go +++ b/pkg/image/models/quotas.go @@ -17,7 +17,7 @@ func init() { dbStore := quotas.NewDBQuotaStore() pendingStore := quotas.NewMemoryQuotaStore() - QuotaManager = quotas.NewQuotaManager("image-quotas", SQuota{}, dbStore, pendingStore) + QuotaManager = quotas.NewQuotaManager("quotas", SQuota{}, dbStore, pendingStore) } var ( @@ -33,8 +33,8 @@ func (self *SQuota) FetchSystemQuota() { } func (self *SQuota) FetchUsage(ctx context.Context, projectId string) error { - count := ImageManager.count(projectId, tristate.None, false) - self.Image = int(count.Count) + count := ImageManager.count(projectId, "", tristate.None, false) + self.Image = int(count["total"].Count) return nil } diff --git a/pkg/image/usages/handler.go b/pkg/image/usages/handler.go index c95ba95e83..df98b0e640 100644 --- a/pkg/image/usages/handler.go +++ b/pkg/image/usages/handler.go @@ -61,7 +61,7 @@ func ReportGeneralUsage(ctx context.Context, w http.ResponseWriter, r *http.Requ var adminUsage map[string]int64 var projectUsage map[string]int64 if isAdmin { - adminUsage = models.ImageManager.Usage(userCred.GetProjectId(), "all") + adminUsage = models.ImageManager.Usage("", "all") } isProject := false diff --git a/pkg/mcclient/modules/mod_images.go b/pkg/mcclient/modules/mod_images.go index e4c03283ab..47d5ceece9 100644 --- a/pkg/mcclient/modules/mod_images.go +++ b/pkg/mcclient/modules/mod_images.go @@ -210,7 +210,7 @@ func (this *ImageManager) countUsage(session *mcclient.ClientSession, deleted bo } func (this *ImageManager) GetUsage(session *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { - body := jsonutils.NewDict() + /*body := jsonutils.NewDict() pendingDelete := jsonutils.NewDict() for deleted, data := range map[bool]*jsonutils.JSONDict{false: body, true: pendingDelete} { if ret, err := this.countUsage(session, deleted); err != nil { @@ -226,6 +226,8 @@ func (this *ImageManager) GetUsage(session *mcclient.ClientSession, params jsonu } body.Add(pendingDelete, "pending_delete") return body, nil + */ + return ImageUsages.GetUsage(session, params) } func setImageMeta(params jsonutils.JSONObject) (http.Header, error) { diff --git a/pkg/mcclient/modules/mod_quotas.go b/pkg/mcclient/modules/mod_quotas.go index 036760bd8b..2eec59e1fd 100644 --- a/pkg/mcclient/modules/mod_quotas.go +++ b/pkg/mcclient/modules/mod_quotas.go @@ -27,32 +27,55 @@ func (this *QuotaManager) getURL(params jsonutils.JSONObject) string { } func (this *QuotaManager) GetQuota(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { - return this._get(s, this.getURL(params), this.KeywordPlural) + computeQuota, err := this._get(s, this.getURL(params), this.KeywordPlural) + if err != nil { + return nil, err + } + imageQuota, err := ImageQuotas._get(s, ImageQuotas.getURL(params), ImageQuotas.KeywordPlural) + if err != nil { + return nil, err + } + computeQuotaDict := computeQuota.(*jsonutils.JSONDict) + computeQuotaDict.Update(imageQuota) + return computeQuotaDict, nil } -func (this *QuotaManager) DoQuotaSet(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { - url := this.getURL(params) +func (this *QuotaManager) doPost(s *mcclient.ClientSession, params jsonutils.JSONObject, url string) (jsonutils.JSONObject, error) { quotas, ok := params.(*jsonutils.JSONDict) if !ok { return nil, fmt.Errorf("Invalid input") } - data := quotas.Copy("tenant", "user") - body := jsonutils.NewDict() - body.Add(data, this.KeywordPlural) - return this._post(s, url, body, this.KeywordPlural) + data := quotas.CopyExcludes("tenant", "user", "image") + var err error + if data.Size() > 0 { + body := jsonutils.NewDict() + body.Add(data, this.KeywordPlural) + _, err = this._post(s, url, body, this.KeywordPlural) + if err != nil { + return nil, err + } + } + data = quotas.CopyIncludes("image") + if data.Size() > 0 { + body := jsonutils.NewDict() + body.Add(data, ImageQuotas.KeywordPlural) + _, err = ImageQuotas._post(s, url, body, ImageQuotas.KeywordPlural) + if err != nil { + return nil, err + } + } + return jsonutils.NewDict(), nil +} + +func (this *QuotaManager) DoQuotaSet(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + url := this.getURL(params) + return this.doPost(s, params, url) } func (this *QuotaManager) DoQuotaCheck(s *mcclient.ClientSession, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { url := this.getURL(params) url = fmt.Sprintf("%s/check_quota", url) - quotas, ok := params.(*jsonutils.JSONDict) - if !ok { - return nil, fmt.Errorf("Invalid input") - } - data := quotas.Copy("tenant", "user") - body := jsonutils.NewDict() - body.Add(data, this.KeywordPlural) - return this._post(s, url, body, this.KeywordPlural) + return this.doPost(s, params, url) } var ( @@ -66,8 +89,8 @@ func init() { []string{})} registerCompute(&Quotas) - ImageQuotas = QuotaManager{NewImageManager("image-quota", "image-quotas", + ImageQuotas = QuotaManager{NewImageManager("quota", "quotas", []string{}, []string{})} - registerV2(&ImageQuotas) + // registerV2(&ImageQuotas) }