diff --git a/cmd/climc/shell/compute/usages.go b/cmd/climc/shell/compute/usages.go index d4cc70320e..26ed5940bc 100644 --- a/cmd/climc/shell/compute/usages.go +++ b/cmd/climc/shell/compute/usages.go @@ -32,6 +32,8 @@ type GeneralUsageOptions struct { CloudEnv string `help:"show usage of specified cloudenv" choices:"public|private|onpremise"` Scope string `help:"show usage of specified privilege scope" choices:"system|domain|project"` + + Refresh bool `help:"force refresh usage statistics"` } func fetchHostTypeOptions(args *GeneralUsageOptions) *jsonutils.JSONDict { @@ -48,6 +50,9 @@ func fetchHostTypeOptions(args *GeneralUsageOptions) *jsonutils.JSONDict { if len(args.CloudEnv) > 0 { params.Add(jsonutils.NewString(args.CloudEnv), "cloud_env") } + if args.Refresh { + params.Add(jsonutils.JSONTrue, "refresh") + } return params } diff --git a/pkg/compute/usages/cache.go b/pkg/compute/usages/cache.go new file mode 100644 index 0000000000..9b859c3031 --- /dev/null +++ b/pkg/compute/usages/cache.go @@ -0,0 +1,85 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package usages + +import ( + "time" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/hashcache" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +var ( + usageCache = hashcache.NewCache(1024, time.Second*300) // 5 minutes, 1024 buckets cache +) + +func getCacheKey( + scope rbacutils.TRbacScope, + userCred mcclient.IIdentityProvider, + isOwner bool, + rangeObjs []db.IStandaloneModel, + hostTypes []string, + providers []string, + brands []string, + cloudEnv string, + includeSystem bool, +) string { + type RangeObject struct { + Resource string `json:"resource"` + Id string `json:"id"` + } + type KeyStruct struct { + Scope rbacutils.TRbacScope `json:"scope"` + Domain string `json:"domain"` + Project string `json:"project"` + IsOwner bool `json:"is_owner"` + Ranges []RangeObject `json:"ranges"` + HostTypes []string `json:"host_types"` + Providers []string `json:"providers"` + Brands []string `json:"brands"` + CloudEnv string `json:"cloud_env"` + System bool `json:"system"` + } + key := KeyStruct{} + key.Scope = scope + switch scope { + case rbacutils.ScopeSystem: + case rbacutils.ScopeDomain: + key.Domain = userCred.GetProjectDomainId() + case rbacutils.ScopeProject: + key.Project = userCred.GetProjectId() + } + if isOwner { + key.IsOwner = true + } + for _, obj := range rangeObjs { + robj := RangeObject{ + Resource: obj.Keyword(), + Id: obj.GetId(), + } + key.Ranges = append(key.Ranges, robj) + } + key.HostTypes = hostTypes + key.Providers = providers + key.Brands = brands + key.CloudEnv = cloudEnv + key.System = includeSystem + jsonObj := jsonutils.Marshal(key) + return jsonObj.QueryString() +} diff --git a/pkg/compute/usages/handler.go b/pkg/compute/usages/handler.go index 1c077ae99a..003fd9b648 100644 --- a/pkg/compute/usages/handler.go +++ b/pkg/compute/usages/handler.go @@ -122,11 +122,21 @@ func rangeObjHandler( if obj != nil { rangeObjs = []db.IStandaloneModel{obj} } + refresh := json.QueryBoolean(query, "refresh", false) + key := getCacheKey(scope, ownerId, isOwner, rangeObjs, hostTypes, providers, brands, cloudEnv, includeSystem) + if !refresh { + cached := usageCache.Get(key) + if cached != nil { + response(w, cached) + return + } + } usage, err := reporter(scope, ownerId, isOwner, rangeObjs, hostTypes, providers, brands, cloudEnv, includeSystem) if err != nil { httperrors.GeneralServerError(ctx, w, err) return } + usageCache.AtomicSet(key, usage) response(w, usage) } }