From 8723164a1d0b0efc421badb4bcf34dc423d66187 Mon Sep 17 00:00:00 2001 From: Qu Xuan Date: Mon, 23 Mar 2020 11:50:00 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=AF=E6=8C=81=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E4=BA=91=E5=B9=B3=E5=8F=B0=E9=85=8D=E9=A2=9D=E4=BF=A1=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/climc/shell/cloudprovider_quota.go | 60 +++++ pkg/apis/compute/cloudprovider_quota.go | 36 +++ pkg/cloudprovider/cloudprovider.go | 5 + pkg/cloudprovider/resources.go | 10 + pkg/compute/models/cloudprovider_quotas.go | 252 ++++++++++++++++++ pkg/compute/models/cloudproviders.go | 1 + pkg/compute/models/cloudsync.go | 20 ++ pkg/compute/models/purge.go | 27 ++ pkg/compute/service/handlers.go | 1 + .../tasks/cloud_provider_sync_info_task.go | 132 +++------ .../modules/mod_cloudprovider_quotas.go | 33 +++ pkg/multicloud/aliyun/quota.go | 136 ++++++++++ pkg/multicloud/aliyun/shell/quota.go | 33 +++ pkg/multicloud/aws/shell/routetable.go | 2 +- pkg/multicloud/azure/shell/usage.go | 35 +++ pkg/multicloud/azure/usage.go | 81 ++++++ pkg/multicloud/google/quota.go | 60 +++++ pkg/multicloud/google/region.go | 1 + pkg/multicloud/huawei/client/client.go | 2 + .../huawei/client/modules/mod_quotas.go | 37 +++ pkg/multicloud/huawei/quota.go | 80 ++++++ pkg/multicloud/huawei/shell/quota.go | 34 +++ pkg/multicloud/openstack/quota.go | 119 ++++++++- pkg/multicloud/qcloud/quota.go | 131 +++++++++ pkg/multicloud/qcloud/shell/quota.go | 47 ++++ pkg/multicloud/region_base.go | 4 + pkg/multicloud/zstack/quota.go | 104 ++++++++ pkg/multicloud/zstack/shell/quota.go | 33 +++ pkg/multicloud/zstack/shell/user.go | 34 +++ 29 files changed, 1448 insertions(+), 102 deletions(-) create mode 100644 cmd/climc/shell/cloudprovider_quota.go create mode 100644 pkg/apis/compute/cloudprovider_quota.go create mode 100644 pkg/compute/models/cloudprovider_quotas.go create mode 100644 pkg/mcclient/modules/mod_cloudprovider_quotas.go create mode 100644 pkg/multicloud/aliyun/quota.go create mode 100644 pkg/multicloud/aliyun/shell/quota.go create mode 100644 pkg/multicloud/azure/shell/usage.go create mode 100644 pkg/multicloud/azure/usage.go create mode 100644 pkg/multicloud/google/quota.go create mode 100644 pkg/multicloud/huawei/client/modules/mod_quotas.go create mode 100644 pkg/multicloud/huawei/quota.go create mode 100644 pkg/multicloud/huawei/shell/quota.go create mode 100644 pkg/multicloud/qcloud/quota.go create mode 100644 pkg/multicloud/qcloud/shell/quota.go create mode 100644 pkg/multicloud/zstack/quota.go create mode 100644 pkg/multicloud/zstack/shell/quota.go create mode 100644 pkg/multicloud/zstack/shell/user.go diff --git a/cmd/climc/shell/cloudprovider_quota.go b/cmd/climc/shell/cloudprovider_quota.go new file mode 100644 index 0000000000..b3df0a4608 --- /dev/null +++ b/cmd/climc/shell/cloudprovider_quota.go @@ -0,0 +1,60 @@ +// 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 shell + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + type CloudproviderQuotaListOptions struct { + options.BaseListOptions + Cloudregion string `help:"ID or Name of Cloudregion"` + QuotaType string `help:"Quota type"` + QuotaRange string `help:"Quota range" choices:"cloudregion|cloudprovider"` + } + + R(&CloudproviderQuotaListOptions{}, "cloud-provider-quota-list", "List cloudprovider quota", func(s *mcclient.ClientSession, args *CloudproviderQuotaListOptions) error { + params, err := args.Params() + if err != nil { + return err + } + + if len(args.QuotaType) > 0 { + params.Add(jsonutils.NewString(args.QuotaType), "quota_type") + } + + if len(args.QuotaRange) > 0 { + params.Add(jsonutils.NewString(args.QuotaRange), "quota_range") + } + + if len(args.Cloudregion) > 0 { + params.Add(jsonutils.NewString(args.Cloudregion), "cloudregion") + } + + result, err := modules.CloudproviderQuotas.List(s, params) + if err != nil { + return err + } + + printList(result, modules.CloudproviderQuotas.GetColumns(s)) + return nil + }) + +} diff --git a/pkg/apis/compute/cloudprovider_quota.go b/pkg/apis/compute/cloudprovider_quota.go new file mode 100644 index 0000000000..f8a46fc847 --- /dev/null +++ b/pkg/apis/compute/cloudprovider_quota.go @@ -0,0 +1,36 @@ +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + CLOUD_PROVIDER_QUOTA_RANGE_CLOUDREGION = "cloudregion" + CLOUD_PROVIDER_QUOTA_RANGE_CLOUDPROVIDER = "cloudprovider" +) + +type CloudproviderQuotaListInput struct { + apis.StandaloneResourceListInput + apis.ExternalizedResourceBaseListInput + + ManagedResourceListInput + RegionalFilterListInput + + // 配额类型 + QuotaType string `json:"quota_type"` + + // 配额范围 + QuotaRange string `json:"quota_range"` +} diff --git a/pkg/cloudprovider/cloudprovider.go b/pkg/cloudprovider/cloudprovider.go index 78d9e5b194..739e50e70e 100644 --- a/pkg/cloudprovider/cloudprovider.go +++ b/pkg/cloudprovider/cloudprovider.go @@ -187,6 +187,7 @@ type ICloudProvider interface { GetStorageClasses(regionId string) []string GetCapabilities() []string + GetICloudQuotas() ([]ICloudQuota, error) } func IsSupportProject(prod ICloudProvider) bool { @@ -286,6 +287,10 @@ func (self *SBaseProvider) GetOnPremiseIRegion() (ICloudRegion, error) { return nil, ErrNotImplemented } +func (self *SBaseProvider) GetICloudQuotas() ([]ICloudQuota, error) { + return nil, ErrNotImplemented +} + func (self *SBaseProvider) GetCloudRegionExternalIdPrefix() string { return self.factory.GetId() } diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index bd2054a8fe..7190526f5b 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -138,6 +138,8 @@ type ICloudRegion interface { GetICloudEvents(start time.Time, end time.Time, withReadEvent bool) ([]ICloudEvent, error) //获取公有云操作日志接口 GetCapabilities() []string + + GetICloudQuotas() ([]ICloudQuota, error) } type ICloudZone interface { @@ -937,3 +939,11 @@ type ICloudEvent interface { GetCreatedAt() time.Time } + +type ICloudQuota interface { + GetGlobalId() string + GetDesc() string + GetQuotaType() string + GetMaxQuotaCount() int + GetCurrentQuotaUsedCount() int +} diff --git a/pkg/compute/models/cloudprovider_quotas.go b/pkg/compute/models/cloudprovider_quotas.go new file mode 100644 index 0000000000..f22973a76e --- /dev/null +++ b/pkg/compute/models/cloudprovider_quotas.go @@ -0,0 +1,252 @@ +// 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 models + +import ( + "context" + + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SCloudproviderQuotaManager struct { + db.SStandaloneResourceBaseManager + db.SExternalizedResourceBaseManager + SManagedResourceBaseManager + SCloudregionResourceBaseManager +} + +var CloudproviderQuotaManager *SCloudproviderQuotaManager + +func init() { + CloudproviderQuotaManager = &SCloudproviderQuotaManager{ + SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager( + SCloudproviderQuota{}, + "cloudprovider_quotas_tbl", + "cloudproviderquota", + "cloudproviderquotas", + ), + } + CloudproviderQuotaManager.SetVirtualObject(CloudproviderQuotaManager) +} + +type SCloudproviderQuota struct { + db.SStandaloneResourceBase + db.SExternalizedResourceBase + SManagedResourceBase + SCloudregionResourceBase + + // 配额范围 + // cloudregion: 区域级别 + // cloudprovider: 云订阅级别 + QuotaRange string `width:"32" charset:"ascii" list:"user"` + + // 已使用的配额 + // -1代表未从云平台拿到已使用配额信息 + UsedCount int `nullable:"false" default:"0" list:"user"` + + // 最大配额限制 + MaxCount int `nullable:"false" default:"0" list:"user"` + + // 配额类型 + QuotaType string `width:"32" charset:"ascii" list:"user"` +} + +func (manager *SCloudproviderQuotaManager) GetContextManagers() [][]db.IModelManager { + return [][]db.IModelManager{ + {CloudproviderManager}, + } +} + +func (man *SCloudproviderQuotaManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.CloudproviderQuotaListInput, +) (*sqlchemy.SQuery, error) { + + var err error + q, err = man.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.StandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.ListItemFilter") + } + q, err = man.SExternalizedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ExternalizedResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SExternalizedResourceBaseManager.ListItemFilter") + } + q, err = man.SManagedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ManagedResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SManagedResourceBaseManager.ListItemFilter") + } + q, err = man.SCloudregionResourceBaseManager.ListItemFilter(ctx, q, userCred, query.RegionalFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.ListItemFilter") + } + + if len(query.QuotaType) > 0 { + q = q.Equals("quota_type", query.QuotaType) + } + if len(query.QuotaRange) > 0 { + q = q.Equals("quota_range", query.QuotaRange) + } + + return q, nil +} + +func (man *SCloudproviderQuotaManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.CloudproviderQuotaListInput, +) (*sqlchemy.SQuery, error) { + q, err := man.SStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.StandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SStandaloneResourceBaseManager.OrderByExtraFields") + } + q, err = man.SCloudregionResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.RegionalFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.OrderByExtraFields") + } + q, err = man.SManagedResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ManagedResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SManagedResourceBaseManager.OrderByExtraFields") + } + return q, nil +} + +func (man *SCloudproviderQuotaManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + q, err := man.SStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = man.SCloudregionResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + q, err = man.SManagedResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + return q, httperrors.ErrNotFound +} + +func (manager *SCloudproviderQuotaManager) GetQuotas(provider *SCloudprovider, region *SCloudregion, quotaRange string) ([]SCloudproviderQuota, error) { + q := manager.Query() + if provider != nil { + q = q.Equals("manager_id", provider.Id) + } + if region != nil { + q = q.Equals("cloudregion_id", region.Id) + } + if len(quotaRange) > 0 { + q = q.Equals("quota_range", quotaRange) + } + quotas := []SCloudproviderQuota{} + err := db.FetchModelObjects(manager, q, "as) + if err != nil { + return nil, errors.Wrap(err, "db.FetchModelObjects") + } + return quotas, nil +} + +func (manager *SCloudproviderQuotaManager) SyncQuotas(ctx context.Context, userCred mcclient.TokenCredential, syncOwnerId mcclient.IIdentityProvider, provider *SCloudprovider, region *SCloudregion, quotaRange string, iQuotas []cloudprovider.ICloudQuota) compare.SyncResult { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, provider.GetOwnerId())) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, provider.GetOwnerId())) + result := compare.SyncResult{} + + dbQuotas, err := manager.GetQuotas(provider, region, quotaRange) + if err != nil { + result.Error(err) + return result + } + + removed := make([]SCloudproviderQuota, 0) + commondb := make([]SCloudproviderQuota, 0) + commonext := make([]cloudprovider.ICloudQuota, 0) + added := make([]cloudprovider.ICloudQuota, 0) + err = compare.CompareSets(dbQuotas, iQuotas, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(err) + return result + } + + for i := 0; i < len(removed); i++ { + err := removed[i].Delete(ctx, userCred) + if err != nil { + result.DeleteError(err) + } else { + result.Delete() + } + } + + for i := 0; i < len(commondb); i++ { + err := commondb[i].SyncWithCloudQuota(ctx, userCred, commonext[i]) + if err != nil { + result.UpdateError(err) + continue + } + result.Update() + } + + for i := 0; i < len(added); i++ { + err = manager.newFromCloudQuota(ctx, userCred, provider, region, quotaRange, added[i]) + if err != nil { + result.AddError(err) + continue + } + result.Add() + } + + return result +} + +func (self *SCloudproviderQuota) SyncWithCloudQuota(ctx context.Context, userCred mcclient.TokenCredential, iQuota cloudprovider.ICloudQuota) error { + _, err := db.UpdateWithLock(ctx, self, func() error { + self.UsedCount = iQuota.GetCurrentQuotaUsedCount() + self.MaxCount = iQuota.GetMaxQuotaCount() + return nil + }) + return err +} + +func (manager *SCloudproviderQuotaManager) newFromCloudQuota(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, region *SCloudregion, quotaRange string, iQuota cloudprovider.ICloudQuota) error { + lockman.LockClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + defer lockman.ReleaseClass(ctx, manager, db.GetLockClassKey(manager, userCred)) + + quota := SCloudproviderQuota{} + quota.SetModelManager(manager, "a) + + quota.Name = iQuota.GetQuotaType() + quota.Description = iQuota.GetDesc() + quota.ExternalId = iQuota.GetGlobalId() + quota.ManagerId = provider.Id + quota.QuotaType = iQuota.GetQuotaType() + quota.QuotaRange = quotaRange + quota.UsedCount = iQuota.GetCurrentQuotaUsedCount() + quota.MaxCount = iQuota.GetMaxQuotaCount() + if region != nil { + quota.CloudregionId = region.Id + } + + return manager.TableSpec().Insert("a) +} diff --git a/pkg/compute/models/cloudproviders.go b/pkg/compute/models/cloudproviders.go index 308819dd08..8cdd195b1c 100644 --- a/pkg/compute/models/cloudproviders.go +++ b/pkg/compute/models/cloudproviders.go @@ -1244,6 +1244,7 @@ func (self *SCloudprovider) RealDelete(ctx context.Context, userCred mcclient.To CloudproviderRegionManager, ExternalProjectManager, CloudregionManager, + CloudproviderQuotaManager, } { err = manager.purgeAll(ctx, userCred, self.Id) if err != nil { diff --git a/pkg/compute/models/cloudsync.go b/pkg/compute/models/cloudsync.go index f6d5f9d92b..a4ffde3fbe 100644 --- a/pkg/compute/models/cloudsync.go +++ b/pkg/compute/models/cloudsync.go @@ -87,6 +87,24 @@ func isInCache(pairs []sStoragecacheSyncPair, localCacheId string) bool { return false } +func syncRegionQuotas(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, driver cloudprovider.ICloudProvider, provider *SCloudprovider, localRegion *SCloudregion, remoteRegion cloudprovider.ICloudRegion) error { + quotas, err := remoteRegion.GetICloudQuotas() + if err != nil { + msg := fmt.Sprintf("GetICloudQuotas for region %s failed %s", remoteRegion.GetName(), err) + log.Errorf(msg) + return err + } + result := CloudproviderQuotaManager.SyncQuotas(ctx, userCred, provider.GetOwnerId(), provider, localRegion, api.CLOUD_PROVIDER_QUOTA_RANGE_CLOUDREGION, quotas) + syncResults.Add(CloudproviderQuotaManager, result) + msg := result.Result() + notes := fmt.Sprintf("SyncQuotas for region %s result: %s", localRegion.Name, msg) + log.Infof(notes) + if result.IsError() { + return fmt.Errorf(msg) + } + return nil +} + func syncRegionZones(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localRegion *SCloudregion, remoteRegion cloudprovider.ICloudRegion) ([]SZone, []cloudprovider.ICloudZone, error) { zones, err := remoteRegion.GetIZones() if err != nil { @@ -1013,6 +1031,8 @@ func syncPublicCloudProviderInfo( syncProjects(ctx, userCred, syncResults, driver, provider) } + syncRegionQuotas(ctx, userCred, syncResults, driver, provider, localRegion, remoteRegion) + localZones, remoteZones, _ := syncRegionZones(ctx, userCred, syncResults, provider, localRegion, remoteRegion) if !driver.GetFactory().NeedSyncSkuFromCloud() { diff --git a/pkg/compute/models/purge.go b/pkg/compute/models/purge.go index 2c85d3d160..d01872b233 100644 --- a/pkg/compute/models/purge.go +++ b/pkg/compute/models/purge.go @@ -1630,3 +1630,30 @@ func (manager *SSecurityGroupCacheManager) purgeAll(ctx context.Context, userCre } return nil } + +func (quota *SCloudproviderQuota) purge(ctx context.Context, userCred mcclient.TokenCredential) error { + lockman.LockObject(ctx, quota) + defer lockman.ReleaseObject(ctx, quota) + + err := quota.ValidateDeleteCondition(ctx) + if err != nil { + return err + } + + return quota.Delete(ctx, userCred) +} + +func (manager *SCloudproviderQuotaManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, providerId string) error { + quotas := []SCloudproviderQuota{} + err := fetchByManagerId(manager, providerId, "as) + if err != nil { + return err + } + for i := range quotas { + err := quotas[i].purge(ctx, userCred) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index d8dbc461d9..27394a502d 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -157,6 +157,7 @@ func InitHandlers(app *appsrv.Application) { models.GuestTemplateManager, models.ServiceCatalogManager, + models.CloudproviderQuotaManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/compute/tasks/cloud_provider_sync_info_task.go b/pkg/compute/tasks/cloud_provider_sync_info_task.go index 82b149241a..516a162ab4 100644 --- a/pkg/compute/tasks/cloud_provider_sync_info_task.go +++ b/pkg/compute/tasks/cloud_provider_sync_info_task.go @@ -16,12 +16,17 @@ package tasks import ( "context" + "fmt" "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/appsrv" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/compute/models" "yunion.io/x/onecloud/pkg/util/logclient" ) @@ -59,18 +64,47 @@ func taskFail(ctx context.Context, task *CloudProviderSyncInfoTask, provider *mo task.SetStageFailed(ctx, reason) } -func (self *CloudProviderSyncInfoTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { - provider := obj.(*models.SCloudprovider) - - db.OpsLog.LogEvent(provider, db.ACT_SYNCING_HOST, "", self.UserCred) - +func (self *CloudProviderSyncInfoTask) GetSyncRange() models.SSyncRange { syncRange := models.SSyncRange{} syncRangeJson, _ := self.Params.Get("sync_range") if syncRangeJson != nil { syncRangeJson.Unmarshal(&syncRange) } syncRange.Normalize() + return syncRange +} +func (self *CloudProviderSyncInfoTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + provider := obj.(*models.SCloudprovider) + + self.SetStage("OnSyncCloudProviderQuotaInfoComplete", nil) + + taskman.LocalTaskRun(self, func() (jsonutils.JSONObject, error) { + p, err := provider.GetProvider() + if err != nil { + return nil, errors.Wrap(err, "GetProvider") + } + quotas, err := p.GetICloudQuotas() + if err != nil { + if errors.Cause(err) != cloudprovider.ErrNotImplemented { + return nil, errors.Wrap(err, "GetICloudQuotas") + } + return nil, nil + } + result := models.CloudproviderQuotaManager.SyncQuotas(ctx, self.GetUserCred(), provider.GetOwnerId(), provider, nil, api.CLOUD_PROVIDER_QUOTA_RANGE_CLOUDPROVIDER, quotas) + msg := result.Result() + notes := fmt.Sprintf("SyncQuotas for provider %s result: %s", provider.Name, msg) + log.Infof(notes) + return nil, nil + }) + +} + +func (self *CloudProviderSyncInfoTask) OnSyncCloudProviderQuotaInfoComplete(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + provider := obj.(*models.SCloudprovider) + syncRange := self.GetSyncRange() + + db.OpsLog.LogEvent(provider, db.ACT_SYNCING_HOST, "", self.UserCred) self.SetStage("OnSyncCloudProviderInfoComplete", nil) taskman.LocalTaskRun(self, func() (jsonutils.JSONObject, error) { @@ -79,6 +113,11 @@ func (self *CloudProviderSyncInfoTask) OnInit(ctx context.Context, obj db.IStand }) } +func (self *CloudProviderSyncInfoTask) OnSyncCloudProviderQuotaInfoCompleteFailed(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + log.Errorf("faild to sync provider quotas %s", body.String()) + self.OnSyncCloudProviderQuotaInfoComplete(ctx, obj, body) +} + func (self *CloudProviderSyncInfoTask) OnSyncCloudProviderInfoComplete(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { provider := obj.(*models.SCloudprovider) // provider.MarkEndSync(self.UserCred) @@ -87,86 +126,3 @@ func (self *CloudProviderSyncInfoTask) OnSyncCloudProviderInfoComplete(ctx conte db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, "", self.UserCred) logclient.AddActionLogWithStartable(self, provider, getAction(self.Params), body, self.UserCred, true) } - -/*func logSyncFailed(provider *models.SCloudprovider, task taskman.ITask, reason string) { - db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, reason, task.GetUserCred()) - logclient.AddActionLogWithStartable(task, provider, getAction(task.GetParams()), reason, task.GetUserCred(), false) -} - -func syncCloudProviderInfo(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, driver cloudprovider.ICloudProvider, syncRange *models.SSyncRange) { - notes := fmt.Sprintf("Start sync host info ...") - log.Infof(notes) - db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_START, "", task.UserCred) - - if driver.GetFactory().IsOnPremise() { - syncOnPremiseCloudProviderInfo(ctx, provider, task, driver, syncRange) - } else { - syncOutOfPremiseCloudProviderInfo(ctx, provider, task, driver, syncRange) - } -} - -func syncOnPremiseCloudProviderInfo(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, driver cloudprovider.ICloudProvider, syncRange *models.SSyncRange) { - cpr := models.CloudproviderRegionManager.FetchByIdsOrCreate(provider.Id, models.DEFAULT_REGION_ID) - cpr.DoSync(ctx, task.UserCred, syncRange) -} - -func syncOutOfPremiseCloudProviderInfo(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, driver cloudprovider.ICloudProvider, syncRange *models.SSyncRange) { - regions := driver.GetIRegions() - - externalIdPrefix := driver.GetCloudRegionExternalIdPrefix() - localRegions, _, cloudProviderRegions, result := models.CloudregionManager.SyncRegions(ctx, task.UserCred, provider, externalIdPrefix, regions) - msg := result.Result() - log.Infof("SyncRegion result: %s", msg) - if result.IsError() { - logSyncFailed(provider, task, msg) - return - } - - db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, task.UserCred) - // logclient.AddActionLog(provider, getAction(task.Params), "", task.UserCred, true) - for i := 0; i < len(localRegions); i += 1 { - if len(syncRange.Region) > 0 && !utils.IsInStringArray(localRegions[i].Id, syncRange.Region) { - continue - } - - cloudProviderRegions[i].DoSync(ctx, task.UserCred, syncRange) - } -} - -func syncVMDisks(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, driver cloudprovider.ICloudProvider, host *models.SHost, localVM *models.SGuest, remoteVM cloudprovider.ICloudVM, syncRange *models.SSyncRange) { - disks, err := remoteVM.GetIDisks() - if err != nil { - msg := fmt.Sprintf("GetIDisks for VM %s failed %s", remoteVM.GetName(), err) - log.Errorf(msg) - logSyncFailed(provider, task, msg) - return - } - result := localVM.SyncVMDisks(ctx, task.UserCred, driver, host, disks, provider.ProjectId, syncRange.ProjectSync) - msg := result.Result() - notes := fmt.Sprintf("syncVMDisks for VM %s result: %s", localVM.Name, msg) - log.Infof(notes) - if result.IsError() { - logSyncFailed(provider, task, msg) - return - } - db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, task.UserCred) - // logclient.AddActionLog(provider, getAction(task.Params), notes, task.UserCred, true) -} - -func syncVMEip(ctx context.Context, provider *models.SCloudprovider, task *CloudProviderSyncInfoTask, localVM *models.SGuest, remoteVM cloudprovider.ICloudVM) { - eip, err := remoteVM.GetIEIP() - if err != nil { - msg := fmt.Sprintf("GetIEIP for VM %s failed %s", remoteVM.GetName(), err) - log.Errorf(msg) - logSyncFailed(provider, task, msg) - return - } - result := localVM.SyncVMEip(ctx, task.UserCred, provider, eip, provider.ProjectId) - msg := result.Result() - log.Infof("syncVMEip for VM %s result: %s", localVM.Name, msg) - if result.IsError() { - logSyncFailed(provider, task, msg) - return - } - db.OpsLog.LogEvent(provider, db.ACT_SYNC_HOST_COMPLETE, msg, task.UserCred) -}*/ diff --git a/pkg/mcclient/modules/mod_cloudprovider_quotas.go b/pkg/mcclient/modules/mod_cloudprovider_quotas.go new file mode 100644 index 0000000000..5d90598191 --- /dev/null +++ b/pkg/mcclient/modules/mod_cloudprovider_quotas.go @@ -0,0 +1,33 @@ +// 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 modules + +import "yunion.io/x/onecloud/pkg/mcclient/modulebase" + +type CloudproviderQuotasManager struct { + modulebase.ResourceManager +} + +var ( + CloudproviderQuotas CloudproviderQuotasManager +) + +func init() { + CloudproviderQuotas = CloudproviderQuotasManager{NewComputeManager("cloudproviderquota", "cloudproviderquotas", + []string{"Id", "Name", "Quota_Range", "Quota_Type", "Max_Count", "Used_Count"}, + []string{})} + + registerCompute(&CloudproviderQuotas) +} diff --git a/pkg/multicloud/aliyun/quota.go b/pkg/multicloud/aliyun/quota.go new file mode 100644 index 0000000000..8c98cd387e --- /dev/null +++ b/pkg/multicloud/aliyun/quota.go @@ -0,0 +1,136 @@ +// 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 aliyun + +import ( + "fmt" + "strconv" + "strings" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type ValueItem struct { + Value string + DiskCategory string +} + +type AttributeValues struct { + ValueItem []ValueItem +} + +type SAccountAttributeItem struct { + AttributeValues AttributeValues + AttributeName string +} + +type SQuota struct { + Name string + UsedCount int + MaxCount int +} + +func (q *SQuota) GetGlobalId() string { + return q.Name +} + +func (q *SQuota) GetDesc() string { + return q.Name +} + +func (q *SQuota) GetQuotaType() string { + return q.Name +} + +func (q *SQuota) GetMaxQuotaCount() int { + return q.MaxCount +} + +func (q *SQuota) GetCurrentQuotaUsedCount() int { + return q.UsedCount +} + +func (region *SRegion) GetAccountAttributes() ([]SAccountAttributeItem, error) { + params := map[string]string{ + "RegionId": region.RegionId, + } + resp, err := region.ecsRequest("DescribeAccountAttributes", params) + if err != nil { + return nil, errors.Wrap(err, "ecsRequest") + } + quotas := []SAccountAttributeItem{} + err = resp.Unmarshal("as, "AccountAttributeItems", "AccountAttributeItem") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return quotas, nil +} + +func (region *SRegion) GetQuotas() ([]SQuota, error) { + attrs, err := region.GetAccountAttributes() + if err != nil { + return nil, errors.Wrap(err, "GetAccountAttributes") + } + quotas := map[string]SQuota{} + for _, attr := range attrs { + for _, item := range attr.AttributeValues.ValueItem { + value, err := strconv.ParseInt(item.Value, 10, 64) + if err != nil { + continue + } + used := false + name := attr.AttributeName + if strings.HasPrefix(name, "used-") { + used = true + name = strings.TrimPrefix(name, "used-") + } + if len(item.DiskCategory) > 0 { + name = fmt.Sprintf("%s/%s", name, item.DiskCategory) + } + if _, ok := quotas[name]; !ok { + quotas[name] = SQuota{ + Name: name, + UsedCount: -1, + } + } + quota := quotas[name] + if used { + quota.UsedCount = int(value) + } else { + quota.MaxCount = int(value) + } + quotas[name] = quota + } + } + ret := []SQuota{} + for _, quota := range quotas { + ret = append(ret, quota) + } + return ret, nil +} + +func (region *SRegion) GetICloudQuotas() ([]cloudprovider.ICloudQuota, error) { + quotas, err := region.GetQuotas() + if err != nil { + return nil, errors.Wrap(err, "GetQuotas") + } + ret := []cloudprovider.ICloudQuota{} + for i := range quotas { + ret = append(ret, "as[i]) + } + return ret, nil +} diff --git a/pkg/multicloud/aliyun/shell/quota.go b/pkg/multicloud/aliyun/shell/quota.go new file mode 100644 index 0000000000..8948182c72 --- /dev/null +++ b/pkg/multicloud/aliyun/shell/quota.go @@ -0,0 +1,33 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type QuotaListOptions struct { + } + shellutils.R(&QuotaListOptions{}, "quota-list", "List quota", func(cli *aliyun.SRegion, args *QuotaListOptions) error { + quotas, err := cli.GetQuotas() + if err != nil { + return err + } + printList(quotas, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/aws/shell/routetable.go b/pkg/multicloud/aws/shell/routetable.go index 1c46f6b6d3..c5b32afd57 100644 --- a/pkg/multicloud/aws/shell/routetable.go +++ b/pkg/multicloud/aws/shell/routetable.go @@ -21,7 +21,7 @@ import ( func init() { type RouteTableListOptions struct { - VpcId string `vpc id` + VpcId string `help:"vpc id"` } shellutils.R(&RouteTableListOptions{}, "routetable-list", "List route tables", func(cli *aws.SRegion, args *RouteTableListOptions) error { routetables, err := cli.GetRouteTables(args.VpcId, false) diff --git a/pkg/multicloud/azure/shell/usage.go b/pkg/multicloud/azure/shell/usage.go new file mode 100644 index 0000000000..19914223d2 --- /dev/null +++ b/pkg/multicloud/azure/shell/usage.go @@ -0,0 +1,35 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/azure" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type UsageListOptions struct { + TYPE string `choices:"Microsoft.Network|Microsoft.Compute|Microsoft.Storage"` + } + shellutils.R(&UsageListOptions{}, "usage-list", "List usage", func(cli *azure.SRegion, args *UsageListOptions) error { + usage, err := cli.GetUsage(args.TYPE) + if err != nil { + return err + } + printList(usage, 0, 0, 0, []string{}) + return nil + }) + +} diff --git a/pkg/multicloud/azure/usage.go b/pkg/multicloud/azure/usage.go new file mode 100644 index 0000000000..e3f88efb50 --- /dev/null +++ b/pkg/multicloud/azure/usage.go @@ -0,0 +1,81 @@ +// 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 azure + +import ( + "fmt" + "strings" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type UsageName struct { + Value string + LocalizedValue string +} + +// {"value":[{"unit":"Count","currentValue":0,"limit":250,"name":{"value":"StorageAccounts","localizedValue":"Storage Accounts"}}]} +type SUsage struct { + Unit string + CurrentValue int + Limit int + Name UsageName +} + +func (u *SUsage) GetGlobalId() string { + return strings.ToLower(u.Name.Value) +} + +func (u *SUsage) GetQuotaType() string { + return u.Name.Value +} + +func (u *SUsage) GetDesc() string { + return u.Name.LocalizedValue +} + +func (u *SUsage) GetMaxQuotaCount() int { + return u.Limit +} + +func (u *SUsage) GetCurrentQuotaUsedCount() int { + return u.CurrentValue +} + +func (region *SRegion) GetUsage(resourceType string) ([]SUsage, error) { + usage := []SUsage{} + resource := fmt.Sprintf("%s/locations/%s/usages", resourceType, region.Name) + err := region.client.ListAll(resource, &usage) + if err != nil { + return nil, errors.Wrapf(err, "ListAll(%s)", resource) + } + return usage, nil +} + +func (region *SRegion) GetICloudQuotas() ([]cloudprovider.ICloudQuota, error) { + ret := []cloudprovider.ICloudQuota{} + for _, resourceType := range []string{"Microsoft.Network", "Microsoft.Storage", "Microsoft.Compute", "Microsoft.Storage"} { + usages, err := region.GetUsage(resourceType) + if err != nil { + return nil, errors.Wrap(err, "GetUsage") + } + for i := range usages { + ret = append(ret, &usages[i]) + } + } + return ret, nil +} diff --git a/pkg/multicloud/google/quota.go b/pkg/multicloud/google/quota.go new file mode 100644 index 0000000000..45e735b6e5 --- /dev/null +++ b/pkg/multicloud/google/quota.go @@ -0,0 +1,60 @@ +// 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 google + +import ( + "strings" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SQuota struct { + Metric string + Limit int + Usage int + Owner string +} + +func (q *SQuota) GetGlobalId() string { + return strings.ToLower(q.Metric) +} + +func (q *SQuota) GetName() string { + return q.Metric +} + +func (q *SQuota) GetDesc() string { + return q.Metric +} + +func (q *SQuota) GetQuotaType() string { + return q.Metric +} + +func (q *SQuota) GetMaxQuotaCount() int { + return q.Limit +} + +func (q *SQuota) GetCurrentQuotaUsedCount() int { + return q.Usage +} + +func (region *SRegion) GetICloudQuotas() ([]cloudprovider.ICloudQuota, error) { + ret := []cloudprovider.ICloudQuota{} + for i := range region.Quotas { + ret = append(ret, ®ion.Quotas[i]) + } + return ret, nil +} diff --git a/pkg/multicloud/google/region.go b/pkg/multicloud/google/region.go index f14ab4251c..ef423c3fe6 100644 --- a/pkg/multicloud/google/region.go +++ b/pkg/multicloud/google/region.go @@ -35,6 +35,7 @@ type SRegion struct { client *SGoogleClient capabilities []string + Quotas []SQuota Description string ID string diff --git a/pkg/multicloud/huawei/client/client.go b/pkg/multicloud/huawei/client/client.go index b88e6fa5ed..8547341d16 100644 --- a/pkg/multicloud/huawei/client/client.go +++ b/pkg/multicloud/huawei/client/client.go @@ -79,6 +79,7 @@ type Client struct { DBInstanceJob *modules.SDBInstanceJobManager Traces *modules.STraceManager CloudEye *modules.SCloudEyeManager + Quotas *modules.SQuotaManager } func (self *Client) Init() error { @@ -162,6 +163,7 @@ func (self *Client) initManagers() { self.DBInstanceJob = modules.NewDBInstanceJobManager(self.regionId, self.projectId, self.signer, self.debug) self.Traces = modules.NewTraceManager(self.regionId, self.projectId, self.signer, self.debug) self.CloudEye = modules.NewCloudEyeManager(self.regionId, self.projectId, self.signer, self.debug) + self.Quotas = modules.NewQuotaManager(self.regionId, self.projectId, self.signer, self.debug) } self.init = true diff --git a/pkg/multicloud/huawei/client/modules/mod_quotas.go b/pkg/multicloud/huawei/client/modules/mod_quotas.go new file mode 100644 index 0000000000..69ae72fdfc --- /dev/null +++ b/pkg/multicloud/huawei/client/modules/mod_quotas.go @@ -0,0 +1,37 @@ +// 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 modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" +) + +type SQuotaManager struct { + SResourceManager +} + +func NewQuotaManager(regionId string, projectId string, signer auth.Signer, debug bool) *SQuotaManager { + return &SQuotaManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(signer, debug), + ServiceName: ServiceNameEVS, + Region: regionId, + ProjectId: projectId, + version: "v1", + Keyword: "quotas", + KeywordPlural: "quotas", + + ResourceKeyword: "quotas", + }} +} diff --git a/pkg/multicloud/huawei/quota.go b/pkg/multicloud/huawei/quota.go new file mode 100644 index 0000000000..047ec3dfee --- /dev/null +++ b/pkg/multicloud/huawei/quota.go @@ -0,0 +1,80 @@ +// 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 huawei + +import ( + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SQuota struct { + Min int + Quota int + Type string + Used int +} + +func (q *SQuota) GetGlobalId() string { + return q.Type +} + +func (q *SQuota) GetQuotaType() string { + return q.Type +} + +func (q *SQuota) GetName() string { + return q.Type +} + +func (q *SQuota) GetDesc() string { + return "" +} + +func (q *SQuota) GetMaxQuotaCount() int { + return q.Quota +} + +func (q *SQuota) GetCurrentQuotaUsedCount() int { + return q.Used +} + +func (self *SRegion) GetQuotas() ([]SQuota, error) { + quotas := []SQuota{} + params := map[string]string{} + result, err := self.ecsClient.Quotas.Get("", params) + if err != nil { + return nil, errors.Wrap(err, "Quotas.List") + } + + err = result.Unmarshal("as, "resources") + if err != nil { + return nil, errors.Wrap(err, "result.Unmarshal") + } + + return quotas, nil +} + +func (region *SRegion) GetICloudQuotas() ([]cloudprovider.ICloudQuota, error) { + quotas, err := region.GetQuotas() + if err != nil { + return nil, errors.Wrap(err, "GetQuotas") + } + ret := []cloudprovider.ICloudQuota{} + for i := range quotas { + ret = append(ret, "as[i]) + } + return ret, nil +} diff --git a/pkg/multicloud/huawei/shell/quota.go b/pkg/multicloud/huawei/shell/quota.go new file mode 100644 index 0000000000..d1695785e0 --- /dev/null +++ b/pkg/multicloud/huawei/shell/quota.go @@ -0,0 +1,34 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huawei" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type QuotaListOptions struct { + } + shellutils.R(&QuotaListOptions{}, "quota-list", "List Quotas", func(cli *huawei.SRegion, args *QuotaListOptions) error { + quotas, err := cli.GetQuotas() + if err != nil { + return err + } + printList(quotas, 0, 0, 0, nil) + return nil + }) + +} diff --git a/pkg/multicloud/openstack/quota.go b/pkg/multicloud/openstack/quota.go index 7a408c5337..63cb701960 100644 --- a/pkg/multicloud/openstack/quota.go +++ b/pkg/multicloud/openstack/quota.go @@ -15,28 +15,66 @@ package openstack import ( - "yunion.io/x/jsonutils" + "fmt" + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/util/version" ) -type SQuota struct { - FixedIps int - Floatingips int - Networks int - Port int - RbacPolicy int - Router int - SecurityGroups int - SecurityGroupRules int +type QuotaDetail struct { + Reserved int + Limit int + InUse int } -func (region *SRegion) GetQuota() (*SQuota, error) { - _, resp, err := region.Get("compute", "/os-quota-sets/"+region.client.tokenCredential.GetTenantId(), "", nil) +type QuotaSet struct { + InjectedFileContentBytes QuotaDetail + MetadataItems QuotaDetail + ServerGroupMembers QuotaDetail + ServerGroups QuotaDetail + Ram QuotaDetail + FloatingIps QuotaDetail + KeyPairs QuotaDetail + Id string + Instances QuotaDetail + SecurityGroupRules QuotaDetail + InjectedFiles QuotaDetail + Cores QuotaDetail + FixedIps QuotaDetail + InjectedFilePathBytes QuotaDetail + SecurityGroups QuotaDetail +} + +type SQuota struct { + Cores int + Instances int + KeyPairs int + FixedIps int + MetadataItems int + Ram int + ServerGroups int + ServerGroupMembers int + InjectedFileContentBytes int + InjectedFilePathBytes int + InjectedFiles int + Floatingips int + Networks int + Port int + RbacPolicy int + Router int + SecurityGroups int + SecurityGroupRules int +} + +func (region *SRegion) GetQuota() (*QuotaSet, error) { + _, resp, err := region.Get("compute", fmt.Sprintf("/os-quota-sets/%s/detail", region.client.tokenCredential.GetTenantId()), "", nil) if err != nil { return nil, err } - quota := &SQuota{} + quota := &QuotaSet{} return quota, resp.Unmarshal(quota, "quota_set") } @@ -74,3 +112,58 @@ func (region *SRegion) SetQuota(quota *SQuota) error { _, _, err := region.Update("compute", "/os-quota-sets/"+region.client.tokenCredential.GetTenantId(), maxVersion, jsonutils.Marshal(params)) return err } + +type IQuota struct { + Name string + Limit int + InUse int +} + +func (iq *IQuota) GetGlobalId() string { + return iq.Name +} + +func (iq *IQuota) GetName() string { + return iq.Name +} + +func (iq *IQuota) GetDesc() string { + return "" +} + +func (iq *IQuota) GetQuotaType() string { + return iq.Name +} + +func (iq *IQuota) GetMaxQuotaCount() int { + return iq.Limit +} + +func (iq *IQuota) GetCurrentQuotaUsedCount() int { + return iq.InUse +} + +func (region *SRegion) GetICloudQuotas() ([]cloudprovider.ICloudQuota, error) { + quota, err := region.GetQuota() + if err != nil { + return nil, errors.Wrap(err, "GetQuota") + } + + ret := []cloudprovider.ICloudQuota{} + + ret = append(ret, &IQuota{Name: "injected_file_content_bytes", Limit: quota.InjectedFileContentBytes.Limit, InUse: quota.InjectedFileContentBytes.InUse}) + ret = append(ret, &IQuota{Name: "metadata_items", Limit: quota.MetadataItems.Limit, InUse: quota.MetadataItems.InUse}) + ret = append(ret, &IQuota{Name: "server_group_members", Limit: quota.ServerGroupMembers.Limit, InUse: quota.ServerGroupMembers.InUse}) + ret = append(ret, &IQuota{Name: "server_groups", Limit: quota.ServerGroups.Limit, InUse: quota.ServerGroups.InUse}) + ret = append(ret, &IQuota{Name: "ram", Limit: quota.Ram.Limit, InUse: quota.Ram.InUse}) + ret = append(ret, &IQuota{Name: "floating_ips", Limit: quota.FloatingIps.Limit, InUse: quota.FloatingIps.InUse}) + ret = append(ret, &IQuota{Name: "key_pairs", Limit: quota.KeyPairs.Limit, InUse: quota.KeyPairs.InUse}) + ret = append(ret, &IQuota{Name: "instances", Limit: quota.Instances.Limit, InUse: quota.Instances.InUse}) + ret = append(ret, &IQuota{Name: "security_group_rules", Limit: quota.SecurityGroupRules.Limit, InUse: quota.SecurityGroupRules.InUse}) + ret = append(ret, &IQuota{Name: "injected_files", Limit: quota.InjectedFiles.Limit, InUse: quota.InjectedFiles.InUse}) + ret = append(ret, &IQuota{Name: "cores", Limit: quota.Cores.Limit, InUse: quota.Cores.InUse}) + ret = append(ret, &IQuota{Name: "fixed_ips", Limit: quota.FixedIps.Limit, InUse: quota.FixedIps.InUse}) + ret = append(ret, &IQuota{Name: "injected_file_path_bytes", Limit: quota.InjectedFilePathBytes.Limit, InUse: quota.InjectedFilePathBytes.InUse}) + ret = append(ret, &IQuota{Name: "security_groups", Limit: quota.SecurityGroups.Limit, InUse: quota.SecurityGroups.InUse}) + return ret, nil +} diff --git a/pkg/multicloud/qcloud/quota.go b/pkg/multicloud/qcloud/quota.go new file mode 100644 index 0000000000..9a09528139 --- /dev/null +++ b/pkg/multicloud/qcloud/quota.go @@ -0,0 +1,131 @@ +// 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 qcloud + +import ( + "strings" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type ImageQuota struct { + region *SRegion + ImageNumQuota int +} + +func (iq *ImageQuota) GetGlobalId() string { + return "ImageNumQuota" +} + +func (iq *ImageQuota) GetName() string { + return "ImageNumQuota" +} + +func (iq *ImageQuota) GetDesc() string { + return "" +} + +func (iq *ImageQuota) GetQuotaType() string { + return "ImageNumQuota" +} + +func (iq *ImageQuota) GetMaxQuotaCount() int { + return iq.ImageNumQuota +} + +func (iq *ImageQuota) GetCurrentQuotaUsedCount() int { + _, count, err := iq.region.GetImages("", "PRIVATE_IMAGE", nil, "", 0, iq.ImageNumQuota) + if err != nil { + log.Errorf("get private image error: %v", err) + return -1 + } + return count +} + +func (region *SRegion) GetImageQuota() (*ImageQuota, error) { + params := map[string]string{} + resp, err := region.cvmRequest("DescribeImageQuota", params, true) + if err != nil { + return nil, errors.Wrap(err, "DescribeImageQuota") + } + imageQuota := &ImageQuota{region: region} + return imageQuota, resp.Unmarshal(imageQuota) +} + +type QuotaSet struct { + QuotaId string + QuotaCurrent int + QuotaLimit int +} + +func (qs *QuotaSet) GetGlobalId() string { + return strings.ToLower(qs.QuotaId) +} + +func (qs *QuotaSet) GetName() string { + return qs.QuotaId +} + +func (qs *QuotaSet) GetDesc() string { + return "" +} + +func (qs *QuotaSet) GetQuotaType() string { + return qs.QuotaId +} + +func (qs *QuotaSet) GetMaxQuotaCount() int { + return qs.QuotaLimit +} + +func (qs *QuotaSet) GetCurrentQuotaUsedCount() int { + return qs.QuotaCurrent +} + +func (region *SRegion) GetQuota(action string) ([]QuotaSet, error) { + params := map[string]string{} + resp, err := region.vpcRequest("DescribeAddressQuota", params) + if err != nil { + return nil, errors.Wrap(err, "DescribeAddressQuota") + } + quotas := []QuotaSet{} + err = resp.Unmarshal("as, "QuotaSet") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return quotas, nil +} + +func (region *SRegion) GetICloudQuotas() ([]cloudprovider.ICloudQuota, error) { + ret := []cloudprovider.ICloudQuota{} + imageQ, err := region.GetImageQuota() + if err != nil { + return nil, errors.Wrap(err, "GetImageQuota") + } + ret = append(ret, imageQ) + for _, action := range []string{"DescribeAddressQuota", "DescribeBandwidthPackageQuota"} { + quotas, err := region.GetQuota(action) + if err != nil { + return nil, errors.Wrapf(err, "GetQuota(%s)", action) + } + for i := range quotas { + ret = append(ret, "as[i]) + } + } + return ret, nil +} diff --git a/pkg/multicloud/qcloud/shell/quota.go b/pkg/multicloud/qcloud/shell/quota.go new file mode 100644 index 0000000000..1125e3215c --- /dev/null +++ b/pkg/multicloud/qcloud/shell/quota.go @@ -0,0 +1,47 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/qcloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ImageQuotaShowOptions struct { + } + shellutils.R(&ImageQuotaShowOptions{}, "image-quota", "Show image quota", func(cli *qcloud.SRegion, args *ImageQuotaShowOptions) error { + quota, err := cli.GetImageQuota() + if err != nil { + return err + } + printObject(quota) + return nil + }) + + type QuotaListOptions struct { + ACTION string `choices:"DescribeAddressQuota|DescribeBandwidthPackageQuota"` + } + + shellutils.R(&QuotaListOptions{}, "quota-list", "List quotas", func(cli *qcloud.SRegion, args *QuotaListOptions) error { + quotas, err := cli.GetQuota(args.ACTION) + if err != nil { + return err + } + printList(quotas, 0, 0, 0, nil) + return nil + }) + +} diff --git a/pkg/multicloud/region_base.go b/pkg/multicloud/region_base.go index 7350f56fb5..83b09b9422 100644 --- a/pkg/multicloud/region_base.go +++ b/pkg/multicloud/region_base.go @@ -138,3 +138,7 @@ func (self *SRegion) GetIElasticcacheById(id string) (cloudprovider.ICloudElasti func (self *SRegion) GetICloudEvents(start time.Time, end time.Time, withReadEvent bool) ([]cloudprovider.ICloudEvent, error) { return nil, fmt.Errorf("Not Implemented GetICloudEvnets") } + +func (self *SRegion) GetICloudQuotas() ([]cloudprovider.ICloudQuota, error) { + return nil, fmt.Errorf("Not Implemented GetICloudQuotas") +} diff --git a/pkg/multicloud/zstack/quota.go b/pkg/multicloud/zstack/quota.go new file mode 100644 index 0000000000..0930b2f08e --- /dev/null +++ b/pkg/multicloud/zstack/quota.go @@ -0,0 +1,104 @@ +// 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 zstack + +import ( + "net/url" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +/* + "uuid":"132e0ad32b2242c4a648440be917ec4a", + "name":"pci.num", + "identityUuid":"2dce5dc485554d21a3796500c1db007a", + "identityType":"AccountVO", + "value":0, + "lastOpDate":"Dec 28, 2019 2:26:03 PM", + "createDate":"Dec 28, 2019 2:26:03 PM" +*/ + +type SQuota struct { + Uuid string + Name string + IdentityUuid string + IdentityType string + Value int +} + +func (q *SQuota) GetGlobalId() string { + return q.Uuid +} + +func (q *SQuota) GetName() string { + return q.Name +} + +func (q *SQuota) GetDesc() string { + return "" +} + +func (q *SQuota) GetQuotaType() string { + return q.Name +} + +func (q *SQuota) GetMaxQuotaCount() int { + return q.Value +} + +func (q *SQuota) GetCurrentQuotaUsedCount() int { + return -1 +} + +func (region *SRegion) GetQuotas() ([]SQuota, error) { + quotas := []SQuota{} + params := url.Values{} + err := region.client.listAll("accounts/quotas", params, "as) + if err != nil { + return nil, err + } + return quotas, nil +} + +type SUserAccount struct { + Uuid string +} + +func (region *SRegion) GetUserAccount(name string) ([]SUserAccount, error) { + users := []SUserAccount{} + params := url.Values{} + if len(name) > 0 { + params.Add("q", "name="+name) + } + err := region.client.listAll("accounts/users", params, &users) + if err != nil { + return nil, errors.Wrap(err, "users.list") + } + return users, nil +} + +func (region *SRegion) GetICloudQuotas() ([]cloudprovider.ICloudQuota, error) { + quotas, err := region.GetQuotas() + if err != nil { + return nil, errors.Wrap(err, "GetQuotas") + } + ret := []cloudprovider.ICloudQuota{} + for i := range quotas { + ret = append(ret, "as[i]) + } + return ret, nil +} diff --git a/pkg/multicloud/zstack/shell/quota.go b/pkg/multicloud/zstack/shell/quota.go new file mode 100644 index 0000000000..671a5b1ba4 --- /dev/null +++ b/pkg/multicloud/zstack/shell/quota.go @@ -0,0 +1,33 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/zstack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type QuotaListOptions struct { + } + shellutils.R(&QuotaListOptions{}, "quota-list", "List quotas", func(cli *zstack.SRegion, args *QuotaListOptions) error { + quotas, err := cli.GetQuotas() + if err != nil { + return err + } + printList(quotas, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/zstack/shell/user.go b/pkg/multicloud/zstack/shell/user.go new file mode 100644 index 0000000000..4ab858233a --- /dev/null +++ b/pkg/multicloud/zstack/shell/user.go @@ -0,0 +1,34 @@ +// 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 shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/zstack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type UserListOptions struct { + Name string + } + shellutils.R(&UserListOptions{}, "user-list", "List users", func(cli *zstack.SRegion, args *UserListOptions) error { + users, err := cli.GetUserAccount(args.Name) + if err != nil { + return err + } + printList(users, 0, 0, 0, nil) + return nil + }) +}