From 69a6aedf176ebe81ae574577cecb4e7bc9ef64a8 Mon Sep 17 00:00:00 2001 From: Qu Xuan Date: Fri, 2 Jul 2021 09:57:35 +0800 Subject: [PATCH] feat(region): add mongodb --- cmd/climc/shell/compute/mongodb.go | 32 + pkg/apis/compute/mongodb.go | 126 +++ pkg/apis/compute/mongodb_const.go | 38 + pkg/cloudprovider/consts.go | 1 + pkg/cloudprovider/mongodb.go | 55 ++ pkg/cloudprovider/resources.go | 27 + pkg/compute/models/cloudproviders.go | 1 + pkg/compute/models/cloudsync.go | 23 + pkg/compute/models/mongodb.go | 782 +++++++++++++++++++ pkg/compute/models/purge.go | 15 + pkg/compute/models/regionquota.go | 26 +- pkg/compute/options/options.go | 1 + pkg/compute/service/handlers.go | 2 + pkg/compute/tasks/mongodb_delete_task.go | 70 ++ pkg/compute/tasks/mongodb_syncstatus_task.go | 62 ++ pkg/compute/usages/handler.go | 15 + pkg/mcclient/modules/mod_mongodb.go | 33 + pkg/mcclient/options/compute/mongodb.go | 59 ++ pkg/multicloud/aliyun/aliyun.go | 24 +- pkg/multicloud/aliyun/mongodb.go | 416 ++++++++++ pkg/multicloud/aliyun/mongodb_backup.go | 104 +++ pkg/multicloud/aliyun/region.go | 14 +- pkg/multicloud/aliyun/shell/mongodb.go | 87 +++ pkg/multicloud/qcloud/mongodb.go | 387 +++++++++ pkg/multicloud/qcloud/qcloud.go | 17 + pkg/multicloud/qcloud/region.go | 5 + pkg/multicloud/qcloud/shell/memcached.go | 14 + pkg/multicloud/qcloud/shell/mongodb.go | 53 ++ pkg/multicloud/region_base.go | 8 + 29 files changed, 2483 insertions(+), 14 deletions(-) create mode 100644 cmd/climc/shell/compute/mongodb.go create mode 100644 pkg/apis/compute/mongodb.go create mode 100644 pkg/apis/compute/mongodb_const.go create mode 100644 pkg/cloudprovider/mongodb.go create mode 100644 pkg/compute/models/mongodb.go create mode 100644 pkg/compute/tasks/mongodb_delete_task.go create mode 100644 pkg/compute/tasks/mongodb_syncstatus_task.go create mode 100644 pkg/mcclient/modules/mod_mongodb.go create mode 100644 pkg/mcclient/options/compute/mongodb.go create mode 100644 pkg/multicloud/aliyun/mongodb.go create mode 100644 pkg/multicloud/aliyun/mongodb_backup.go create mode 100644 pkg/multicloud/aliyun/shell/mongodb.go create mode 100644 pkg/multicloud/qcloud/mongodb.go create mode 100644 pkg/multicloud/qcloud/shell/mongodb.go diff --git a/cmd/climc/shell/compute/mongodb.go b/cmd/climc/shell/compute/mongodb.go new file mode 100644 index 0000000000..fec6d004b8 --- /dev/null +++ b/cmd/climc/shell/compute/mongodb.go @@ -0,0 +1,32 @@ +// 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/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" + "yunion.io/x/onecloud/pkg/mcclient/options/compute" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.MongoDB) + cmd.List(&compute.MongoDBListOptions{}) + cmd.Update(&compute.MongoDBUpdateOptions{}) + cmd.Show(&options.BaseIdOptions{}) + cmd.Delete(&options.BaseIdOptions{}) + cmd.Get("backups", &options.BaseIdOptions{}) + cmd.Perform("syncstatus", &options.BaseIdOptions{}) +} diff --git a/pkg/apis/compute/mongodb.go b/pkg/apis/compute/mongodb.go new file mode 100644 index 0000000000..c2e1b4206d --- /dev/null +++ b/pkg/apis/compute/mongodb.go @@ -0,0 +1,126 @@ +// 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" +) + +type MongoDBCreateInput struct { + apis.VirtualResourceCreateInput + DeletePreventableCreateInput +} + +type SMongoDBChangeConfigInput struct { + apis.Meta + + InstanceType string + DiskSizeGB int +} + +type MongoDBListInput struct { + apis.VirtualResourceListInput + apis.ExternalizedResourceBaseListInput + apis.DeletePreventableResourceBaseListInput + + VpcFilterListInput + + ZoneResourceInput + + VcpuCount int `json:"vcpu_count"` + + VmemSizeMb int `json:"vmem_size_mb"` + + Category string `json:"category"` + + Engine string `json:"engine"` + + EngineVersion string `json:"engine_version"` + + InstanceType string `json:"instance_type"` +} + +type MongoDBDetails struct { + apis.VirtualResourceDetails + CloudregionResourceInfo + ManagedResourceInfo + + VpcResourceInfoBase + + // IP子网名称 + // example: test-network + Network string `json:"network"` +} + +type MongoDBResourceInfoBase struct { + // MongoDB实例名称 + MongoDB string `json:"mongodb"` +} + +type MongoDBResourceInfo struct { + MongoDBResourceInfoBase + + // 归属VPC ID + VpcId string `json:"vpc_id"` + + VpcResourceInfo +} + +type MongoDBResourceInput struct { + // MongoDB实例(ID or Name) + MongoDBId string `json:"mongodb_id"` + + // swagger:ignore + // Deprecated + MongoDB string `json:"mongodb" yunion-deprecated-by:"mongodb_id"` +} + +type MongoDBFilterListInputBase struct { + MongoDBResourceInput + + // 以MongoDB实例名字排序 + OrderByMongoDB string `json:"order_by_mongodb"` +} + +type MongoDBFilterListInput struct { + MongoDBFilterListInputBase + + VpcFilterListInput +} + +type MongoDBJoinListInput struct { + apis.VirtualJointResourceBaseListInput + MongoDBFilterListInput +} + +type MongoDBRemoteUpdateInput struct { + // 是否覆盖替换所有标签 + ReplaceTags *bool `json:"replace_tags" help:"replace all remote tags"` +} + +type MongoDBNetworkListInput struct { + MongoDBJoinListInput + + NetworkFilterListInput +} + +type MongoDBAutoRenewInput struct { + // 是否自动续费 + AutoRenew bool `json:"auto_renew"` +} + +type MongoDBSetSecgroupInput struct { + SecgroupIds []string `json:"secgroup_ids"` +} diff --git a/pkg/apis/compute/mongodb_const.go b/pkg/apis/compute/mongodb_const.go new file mode 100644 index 0000000000..d5c2887159 --- /dev/null +++ b/pkg/apis/compute/mongodb_const.go @@ -0,0 +1,38 @@ +// 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 + +const ( + MONGO_DB_STATUS_CREATING = "creating" + MONGO_DB_STATUS_RUNNING = "running" + MONGO_DB_STATUS_DEPLOY = "deploy" + MONGO_DB_STATUS_CHANGE_CONFIG = "change_config" + MONGO_DB_STATUS_DELETING = "deleting" + MONGO_DB_STATUS_DELETE_FAILED = "delete_failed" + MONGO_DB_STATUS_REBOOTING = "rebooting" + MONGO_DB_STATUS_UNKNOWN = "unknown" + + MONGO_DB_ENGINE_WIRED_TIGER = "WiredTiger" + MONGO_DB_ENGINE_ROCKS = "Rocks" + + MONGO_DB_ENGINE_VERSION_40 = "4.0" + MONGO_DB_ENGINE_VERSION_36 = "3.6" + MONGO_DB_ENGINE_VERSION_32 = "3.2" + + // 分片 + MONGO_DB_CATEGORY_SHARDING = "sharding" + // 副本集 + MONGO_DB_CATEGORY_REPLICATE = "replicate" +) diff --git a/pkg/cloudprovider/consts.go b/pkg/cloudprovider/consts.go index e9b20039f8..5a28b071f3 100644 --- a/pkg/cloudprovider/consts.go +++ b/pkg/cloudprovider/consts.go @@ -57,6 +57,7 @@ const ( CLOUD_CAPABILITY_NAT = "nat" // NAT网关 CLOUD_CAPABILITY_NAS = "nas" // NAS CLOUD_CAPABILITY_WAF = "waf" // WAF + CLOUD_CAPABILITY_MONGO_DB = "mongodb" // MongoDB ) const ( diff --git a/pkg/cloudprovider/mongodb.go b/pkg/cloudprovider/mongodb.go new file mode 100644 index 0000000000..e6a961e2ef --- /dev/null +++ b/pkg/cloudprovider/mongodb.go @@ -0,0 +1,55 @@ +// 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 cloudprovider + +import "time" + +// 备份状态 +type TMongoDBBackupStatus string + +// 备份方法 +type TMongoDBBackupMethod string + +// 备份方式 +type TMongoDBBackupType string + +const ( + MongoDBBackupStatusCreating = TMongoDBBackupStatus("creating") + MongoDBBackupStatusAvailable = TMongoDBBackupStatus("available") + MongoDBBackupStatusFailed = TMongoDBBackupStatus("failed") + MongoDBBackupStatusUnknown = TMongoDBBackupStatus("unknown") + + MongoDBBackupMethodPhysical = TMongoDBBackupMethod("physical") + MongoDBBackupMethodLogical = TMongoDBBackupMethod("logical") + + MongoDBBackupTypeAuto = TMongoDBBackupType("auto") + MongoDBBackupTypeManual = TMongoDBBackupType("manual") +) + +type SMongoDBBackup struct { + Name string + Description string + StartTime time.Time + EndTime time.Time + Status TMongoDBBackupStatus + BackupMethod TMongoDBBackupMethod + BackupType TMongoDBBackupType + BackupSizeKb int +} + +type SMongoDBBackups struct { + Data []SMongoDBBackup + Total int +} diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 83839050c0..3265884161 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -174,6 +174,9 @@ type ICloudRegion interface { GetICloudWafInstanceById(id string) (ICloudWafInstance, error) CreateICloudWafInstance(opts *WafCreateOptions) (ICloudWafInstance, error) GetICloudWafRuleGroups() ([]ICloudWafRuleGroup, error) + + GetICloudMongoDBs() ([]ICloudMongoDB, error) + GetICloudMongoDBById(id string) (ICloudMongoDB, error) } type ICloudZone interface { @@ -1345,3 +1348,27 @@ type ICloudWafRule interface { Update(opts *SWafRule) error Delete() error } + +type ICloudMongoDB interface { + IVirtualResource + IBillingResource + + GetVpcId() string + GetNetworkId() string + GetIpAddr() string + GetVcpuCount() int + GetVmemSizeMb() int + GetDiskSizeMb() int + GetZoneId() string + GetReplicationNum() int + GetCategory() string + GetEngine() string + GetEngineVersion() string + GetInstanceType() string + GetMaintainTime() string + GetPort() int + + GetIBackups() ([]SMongoDBBackup, error) + + Delete() error +} diff --git a/pkg/compute/models/cloudproviders.go b/pkg/compute/models/cloudproviders.go index b8af6ac454..cd29102985 100644 --- a/pkg/compute/models/cloudproviders.go +++ b/pkg/compute/models/cloudproviders.go @@ -1482,6 +1482,7 @@ func (self *SCloudprovider) RealDelete(ctx context.Context, userCred mcclient.To WafInstanceManager, VpcManager, ElasticipManager, + MongoDBManager, NetworkInterfaceManager, CloudproviderRegionManager, CloudregionManager, diff --git a/pkg/compute/models/cloudsync.go b/pkg/compute/models/cloudsync.go index 9165f48723..f3a0719374 100644 --- a/pkg/compute/models/cloudsync.go +++ b/pkg/compute/models/cloudsync.go @@ -1115,6 +1115,25 @@ func syncWafRegexSets(ctx context.Context, userCred mcclient.TokenCredential, sy return nil } +func syncMongoDBs(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localRegion *SCloudregion, remoteRegion cloudprovider.ICloudRegion) error { + dbs, err := remoteRegion.GetICloudMongoDBs() + if err != nil { + msg := fmt.Sprintf("GetICloudMongoDBs for region %s failed %s", remoteRegion.GetName(), err) + log.Errorf(msg) + return err + } + + _, _, result := localRegion.SyncMongoDBs(ctx, userCred, provider, dbs) + syncResults.Add(MongoDBManager, result) + msg := result.Result() + log.Infof("SyncMongoDBs for region %s result: %s", localRegion.Name, msg) + if result.IsError() { + return result.AllError() + } + + return nil +} + func syncWafInstances(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, provider *SCloudprovider, localRegion *SCloudregion, remoteRegion cloudprovider.ICloudRegion) error { wafIns, err := remoteRegion.GetICloudWafInstances() if err != nil { @@ -1349,6 +1368,10 @@ func syncPublicCloudProviderInfo( syncWafInstances(ctx, userCred, syncResults, provider, localRegion, remoteRegion) } + if utils.IsInStringArray(cloudprovider.CLOUD_CAPABILITY_MONGO_DB, driver.GetCapabilities()) { + syncMongoDBs(ctx, userCred, syncResults, provider, localRegion, remoteRegion) + } + if cloudprovider.IsSupportCompute(driver) { log.Debugf("storageCachePairs count %d", len(storageCachePairs)) for i := range storageCachePairs { diff --git a/pkg/compute/models/mongodb.go b/pkg/compute/models/mongodb.go new file mode 100644 index 0000000000..bc7a707d77 --- /dev/null +++ b/pkg/compute/models/mongodb.go @@ -0,0 +1,782 @@ +// 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" + "fmt" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/compare" + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/apis" + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + 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/cloudcommon/db/quotas" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/billing" + "yunion.io/x/onecloud/pkg/util/rbacutils" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SMongoDBManager struct { + db.SVirtualResourceBaseManager + db.SExternalizedResourceBaseManager + SDeletePreventableResourceBaseManager + + SCloudregionResourceBaseManager + SZoneResourceBaseManager + SManagedResourceBaseManager + SVpcResourceBaseManager +} + +var MongoDBManager *SMongoDBManager + +func init() { + MongoDBManager = &SMongoDBManager{ + SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager( + SMongoDB{}, + "mongodbs_tbl", + "mongodb", + "mongodbs", + ), + } + MongoDBManager.SetVirtualObject(MongoDBManager) +} + +type SMongoDB struct { + db.SVirtualResourceBase + db.SExternalizedResourceBase + SManagedResourceBase + SBillingResourceBase + + SCloudregionResourceBase + SZoneResourceBase + SDeletePreventableResourceBase + + // CPU数量 + // example: 1 + VcpuCount int `nullable:"false" default:"1" list:"user" create:"optional"` + // 内存大小 + // example: 1024 + VmemSizeMb int `nullable:"false" list:"user" create:"required"` + // 存储大小, 单位Mb + // example: 10240 + DiskSizeMb int `nullable:"false" list:"user" create:"required"` + // 端口 + // example: 3306 + Port int `nullable:"false" list:"user" create:"optional"` + // 实例类型 + // example: ha + Category string `nullable:"false" list:"user" create:"optional"` + + // 分片数量 + // example: 3 + ReplicationNum int `nullable:"false" default:"0" list:"user" create:"optional"` + + // 实例IP地址 + IpAddr string `nullable:"false" list:"user"` + + // 引擎 + // example: MySQL + Engine string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"` + // 引擎版本 + // example: 5.7 + EngineVersion string `width:"16" charset:"ascii" nullable:"false" list:"user" create:"required"` + // 套餐名称 + // example: mysql.x4.large.2c + InstanceType string `width:"64" charset:"utf8" nullable:"true" list:"user" create:"optional"` + + // 维护时间 + MaintainTime string `width:"64" charset:"ascii" nullable:"true" list:"user" create:"optional"` + + // 虚拟私有网络Id + // example: ed20d84e-3158-41b1-870c-1725e412e8b6 + VpcId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` + + // 所属网络ID + NetworkId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional" json:"network_id"` +} + +func (manager *SMongoDBManager) GetContextManagers() [][]db.IModelManager { + return [][]db.IModelManager{ + {CloudregionManager}, + } +} + +// MongoDB实例列表 +func (man *SMongoDBManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.MongoDBListInput, +) (*sqlchemy.SQuery, error) { + var err error + q, err = man.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, query.VirtualResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SVirtualResourceBaseManager.ListItemFilter") + } + q, err = man.SExternalizedResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ExternalizedResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SExternalizedResourceBaseManager.ListItemFilter") + } + q, err = man.SDeletePreventableResourceBaseManager.ListItemFilter(ctx, q, userCred, query.DeletePreventableResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SDeletePreventableResourceBaseManager.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") + } + q, err = man.SVpcResourceBaseManager.ListItemFilter(ctx, q, userCred, query.VpcFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SVpcResourceBaseManager.ListItemFilter") + } + + if query.VcpuCount > 0 { + q = q.Equals("vcpu_count", query.VcpuCount) + } + if query.VmemSizeMb > 0 { + q = q.Equals("vmem_size_mb", query.VmemSizeMb) + } + if len(query.Category) > 0 { + q = q.Equals("category", query.Category) + } + if len(query.Engine) > 0 { + q = q.Equals("engine", query.Engine) + } + if len(query.EngineVersion) > 0 { + q = q.Equals("engine_version", query.EngineVersion) + } + if len(query.InstanceType) > 0 { + q = q.Equals("instance_type", query.InstanceType) + } + + return q, nil +} + +func (man *SMongoDBManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.MongoDBListInput, +) (*sqlchemy.SQuery, error) { + q, err := man.SVirtualResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VirtualResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SVirtualResourceBaseManager.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") + } + q, err = man.SVpcResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VpcFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SVpcResourceBaseManager.OrderByExtraFields") + } + return q, nil +} + +func (man *SMongoDBManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + q, err := man.SVirtualResourceBaseManager.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 + } + q, err = man.SVpcResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + return q, httperrors.ErrNotFound +} + +func (manager *SMongoDBManager) BatchCreateValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input *api.MongoDBCreateInput) (*api.MongoDBCreateInput, error) { + return input, httperrors.NewNotImplementedError("Not Implemented") +} + +func (man *SMongoDBManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.MongoDBCreateInput) (api.MongoDBCreateInput, error) { + return input, httperrors.NewNotImplementedError("Not Implemented") +} + +func (manager *SMongoDBManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.MongoDBDetails { + rows := make([]api.MongoDBDetails, len(objs)) + virtRows := manager.SVirtualResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + manRows := manager.SManagedResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + regRows := manager.SCloudregionResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + + rdsIds := make([]string, len(rows)) + vpcIds := make([]string, len(rows)) + for i := range rows { + rows[i] = api.MongoDBDetails{ + VirtualResourceDetails: virtRows[i], + ManagedResourceInfo: manRows[i], + CloudregionResourceInfo: regRows[i], + } + instance := objs[i].(*SMongoDB) + rdsIds[i] = instance.Id + vpcIds[i] = instance.VpcId + } + + vpcs := make(map[string]SVpc) + + err := db.FetchStandaloneObjectsByIds(VpcManager, vpcIds, &vpcs) + if err != nil { + log.Errorf("db.FetchStandaloneObjectsByIds fail %s", err) + return rows + } + + for i := range rows { + if vpc, ok := vpcs[vpcIds[i]]; ok { + rows[i].Vpc = vpc.Name + rows[i].VpcExtId = vpc.ExternalId + } + } + + return rows +} + +func (self *SMongoDB) GetIMongoDB() (cloudprovider.ICloudMongoDB, error) { + if len(self.ExternalId) == 0 { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "empty external id") + } + iregion, err := self.GetIRegion() + if err != nil { + return nil, errors.Wrap(err, "self.GetIRegion") + } + iMongoDB, err := iregion.GetICloudMongoDBById(self.ExternalId) + if err != nil { + return nil, errors.Wrapf(err, "GetICloudMongoDBById(%s)", self.ExternalId) + } + return iMongoDB, nil +} + +// 同步MongoDB实例状态 +func (self *SMongoDB) AllowPerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "syncstatus") +} + +func (self *SMongoDB) PerformSyncstatus(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + var openTask = true + count, err := taskman.TaskManager.QueryTasksOfObject(self, time.Now().Add(-3*time.Minute), &openTask).CountWithError() + if err != nil { + return nil, err + } + if count > 0 { + return nil, httperrors.NewBadRequestError("MongoDB has %d task active, can't sync status", count) + } + + return nil, StartResourceSyncStatusTask(ctx, userCred, self, "MongoDBSyncstatusTask", "") +} + +func (self *SMongoDB) SetAutoRenew(autoRenew bool) error { + _, err := db.Update(self, func() error { + self.AutoRenew = autoRenew + return nil + }) + return err +} + +func (self *SMongoDB) SaveRenewInfo( + ctx context.Context, userCred mcclient.TokenCredential, + bc *billing.SBillingCycle, expireAt *time.Time, billingType string, +) error { + _, err := db.Update(self, func() error { + if billingType == "" { + billingType = billing_api.BILLING_TYPE_PREPAID + } + if self.BillingType == "" { + self.BillingType = billingType + } + if expireAt != nil && !expireAt.IsZero() { + self.ExpiredAt = *expireAt + } else { + self.BillingCycle = bc.String() + self.ExpiredAt = bc.EndAt(self.ExpiredAt) + } + return nil + }) + if err != nil { + return errors.Wrapf(err, "db.Update") + } + db.OpsLog.LogEvent(self, db.ACT_RENEW, self.GetShortDesc(ctx), userCred) + return nil +} + +func (self *SMongoDB) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + log.Infof("mongodb delete do nothing") + return nil +} + +func (self *SMongoDB) RealDelete(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.SVirtualResourceBase.Delete(ctx, userCred) +} + +func (self *SMongoDB) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error { + return self.StartDeleteTask(ctx, userCred, "") +} + +func (self *SMongoDB) StartDeleteTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { + var err = func() error { + task, err := taskman.TaskManager.NewTask(ctx, "MongoDBDeleteTask", self, userCred, nil, parentTaskId, "", nil) + if err != nil { + return errors.Wrapf(err, "NewTask") + } + return task.ScheduleRun(nil) + }() + if err != nil { + self.SetStatus(userCred, api.MONGO_DB_STATUS_DELETE_FAILED, err.Error()) + return err + } + return self.SetStatus(userCred, api.MONGO_DB_STATUS_DELETING, "") +} + +func (self *SCloudregion) GetMongoDBs(managerId string) ([]SMongoDB, error) { + q := MongoDBManager.Query().Equals("cloudregion_id", self.Id) + if len(managerId) > 0 { + q = q.Equals("manager_id", managerId) + } + dbs := []SMongoDB{} + err := db.FetchModelObjects(MongoDBManager, q, &dbs) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return dbs, nil +} + +func (self *SCloudregion) SyncMongoDBs(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, cloudMongoDBs []cloudprovider.ICloudMongoDB) ([]SMongoDB, []cloudprovider.ICloudMongoDB, compare.SyncResult) { + lockman.LockRawObject(ctx, "mongodbs", fmt.Sprintf("%s-%s", provider.Id, self.Id)) + defer lockman.ReleaseRawObject(ctx, "mongodbs", fmt.Sprintf("%s-%s", provider.Id, self.Id)) + + localMongoDBs := []SMongoDB{} + remoteMongoDBs := []cloudprovider.ICloudMongoDB{} + result := compare.SyncResult{} + + dbInstances, err := self.GetMongoDBs(provider.Id) + if err != nil { + result.Error(err) + return nil, nil, result + } + + removed := make([]SMongoDB, 0) + commondb := make([]SMongoDB, 0) + commonext := make([]cloudprovider.ICloudMongoDB, 0) + added := make([]cloudprovider.ICloudMongoDB, 0) + err = compare.CompareSets(dbInstances, cloudMongoDBs, &removed, &commondb, &commonext, &added) + if err != nil { + result.Error(err) + return nil, nil, result + } + + for i := 0; i < len(removed); i++ { + err := removed[i].syncRemoveCloudMongoDB(ctx, userCred) + if err != nil { + result.DeleteError(err) + continue + } + result.Delete() + } + + for i := 0; i < len(commondb); i++ { + err := commondb[i].SyncWithCloudMongoDB(ctx, userCred, commonext[i]) + if err != nil { + result.UpdateError(err) + continue + } + localMongoDBs = append(localMongoDBs, commondb[i]) + remoteMongoDBs = append(remoteMongoDBs, commonext[i]) + result.Update() + } + + for i := 0; i < len(added); i++ { + instance, err := self.newFromCloudMongoDB(ctx, userCred, provider, added[i]) + if err != nil { + result.AddError(err) + continue + } + localMongoDBs = append(localMongoDBs, *instance) + remoteMongoDBs = append(remoteMongoDBs, added[i]) + result.Add() + } + return localMongoDBs, remoteMongoDBs, result +} + +func (self *SMongoDB) syncRemoveCloudMongoDB(ctx context.Context, userCred mcclient.TokenCredential) error { + return self.RealDelete(ctx, userCred) +} + +func (self *SMongoDB) ValidateDeleteCondition(ctx context.Context) error { + if self.DisableDelete.IsTrue() { + return httperrors.NewInvalidStatusError("MongoDB is locked, cannot delete") + } + return self.SStatusStandaloneResourceBase.ValidateDeleteCondition(ctx) +} + +func (self *SMongoDB) SyncAllWithCloudMongoDB(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, ext cloudprovider.ICloudMongoDB) error { + err := self.SyncWithCloudMongoDB(ctx, userCred, ext) + if err != nil { + return errors.Wrapf(err, "SyncWithCloudMongoDB") + } + return nil +} + +func (self *SMongoDB) SyncWithCloudMongoDB(ctx context.Context, userCred mcclient.TokenCredential, ext cloudprovider.ICloudMongoDB) error { + diff, err := db.UpdateWithLock(ctx, self, func() error { + self.ExternalId = ext.GetGlobalId() + self.IpAddr = ext.GetIpAddr() + self.VcpuCount = ext.GetVcpuCount() + self.VmemSizeMb = ext.GetVmemSizeMb() + self.DiskSizeMb = ext.GetDiskSizeMb() + self.ReplicationNum = ext.GetReplicationNum() + self.Engine = ext.GetEngine() + self.EngineVersion = ext.GetEngineVersion() + self.Category = ext.GetCategory() + self.InstanceType = ext.GetInstanceType() + self.MaintainTime = ext.GetMaintainTime() + self.Status = ext.GetStatus() + self.Port = ext.GetPort() + return nil + }) + if err != nil { + return errors.Wrapf(err, "db.Update") + } + syncVirtualResourceMetadata(ctx, userCred, self, ext) + if provider := self.GetCloudprovider(); provider != nil { + SyncCloudProject(userCred, self, provider.GetOwnerId(), ext, provider.Id) + } + db.OpsLog.LogSyncUpdate(self, diff, userCred) + return nil +} + +func (self *SCloudregion) newFromCloudMongoDB(ctx context.Context, userCred mcclient.TokenCredential, provider *SCloudprovider, ext cloudprovider.ICloudMongoDB) (*SMongoDB, error) { + ins := SMongoDB{} + ins.SetModelManager(MongoDBManager, &ins) + + ins.ExternalId = ext.GetGlobalId() + ins.CloudregionId = self.Id + ins.ManagerId = provider.Id + ins.Status = ext.GetStatus() + ins.IpAddr = ext.GetIpAddr() + ins.VcpuCount = ext.GetVcpuCount() + ins.VmemSizeMb = ext.GetVmemSizeMb() + ins.DiskSizeMb = ext.GetDiskSizeMb() + ins.Engine = ext.GetEngine() + ins.EngineVersion = ext.GetEngineVersion() + ins.Category = ext.GetCategory() + ins.InstanceType = ext.GetInstanceType() + ins.MaintainTime = ext.GetMaintainTime() + ins.Port = ext.GetPort() + ins.ReplicationNum = ext.GetReplicationNum() + + if zoneId := ext.GetZoneId(); len(zoneId) > 0 { + _zone, err := db.FetchByExternalId(ZoneManager, zoneId) + if err != nil { + return nil, errors.Wrapf(err, "") + } + ins.ZoneId = _zone.GetId() + } + + createdAt := ext.GetCreatedAt() + if !createdAt.IsZero() { + ins.CreatedAt = createdAt + } + + ins.BillingType = ext.GetBillingType() + if ins.BillingType == billing_api.BILLING_TYPE_PREPAID { + expiredAt := ext.GetExpiredAt() + if !expiredAt.IsZero() { + ins.ExpiredAt = expiredAt + } + ins.AutoRenew = ext.IsAutoRenew() + } + + if vpcId := ext.GetVpcId(); len(vpcId) > 0 { + vpc, err := db.FetchByExternalIdAndManagerId(VpcManager, vpcId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + return q.Equals("manager_id", provider.Id) + }) + if err != nil { + log.Errorf("FetchVpcId(%s) error: %v", vpcId, err) + } else { + ins.VpcId = vpc.GetId() + } + } + + if networkId := ext.GetNetworkId(); len(networkId) > 0 { + network, err := db.FetchByExternalIdAndManagerId(NetworkManager, networkId, func(q *sqlchemy.SQuery) *sqlchemy.SQuery { + wire := WireManager.Query().SubQuery() + vpc := VpcManager.Query().SubQuery() + return q.Join(wire, sqlchemy.Equals(wire.Field("id"), q.Field("wire_id"))). + Join(vpc, sqlchemy.Equals(vpc.Field("id"), wire.Field("vpc_id"))). + Filter(sqlchemy.Equals(vpc.Field("manager_id"), provider.Id)) + }) + if err != nil { + return nil, errors.Wrapf(err, "ext.FetchNetworkId") + } + ins.NetworkId = network.GetId() + } + + var err error + err = func() error { + lockman.LockRawObject(ctx, MongoDBManager.Keyword(), "name") + defer lockman.ReleaseRawObject(ctx, MongoDBManager.Keyword(), "name") + + ins.Name, err = db.GenerateName(ctx, MongoDBManager, provider.GetOwnerId(), ext.GetName()) + if err != nil { + return errors.Wrapf(err, "db.GenerateName") + } + return MongoDBManager.TableSpec().Insert(ctx, &ins) + }() + if err != nil { + return nil, errors.Wrapf(err, "newFromCloudMongoDB.Insert") + } + + syncVirtualResourceMetadata(ctx, userCred, &ins, ext) + SyncCloudProject(userCred, &ins, provider.GetOwnerId(), ext, provider.Id) + db.OpsLog.LogEvent(&ins, db.ACT_CREATE, ins.GetShortDesc(ctx), userCred) + + return &ins, nil +} + +type SMongoDBCountStat struct { + TotalMongodbCount int + TotalCpuCount int + TotalMemSizeMb int +} + +func (man *SMongoDBManager) TotalCount( + scope rbacutils.TRbacScope, + ownerId mcclient.IIdentityProvider, + rangeObjs []db.IStandaloneModel, + providers []string, brands []string, cloudEnv string, +) (SMongoDBCountStat, error) { + sq := man.Query().SubQuery() + q := sq.Query(sqlchemy.COUNT("total_mongodb_count"), + sqlchemy.SUM("total_cpu_count", sq.Field("vcpu_count")), + sqlchemy.SUM("total_mem_size_mb", sq.Field("vmem_size_mb"))) + + q = scopeOwnerIdFilter(q, scope, ownerId) + q = CloudProviderFilter(q, q.Field("manager_id"), providers, brands, cloudEnv) + q = RangeObjectsFilter(q, rangeObjs, q.Field("cloudregion_id"), nil, q.Field("manager_id"), nil, nil) + + stat := SMongoDBCountStat{} + row := q.Row() + err := q.Row2Struct(row, &stat) + return stat, err +} + +func (self *SMongoDB) GetQuotaKeys() quotas.IQuotaKeys { + return fetchRegionalQuotaKeys( + rbacutils.ScopeProject, + self.GetOwnerId(), + self.GetRegion(), + self.GetCloudprovider(), + ) +} + +func (self *SMongoDB) GetUsages() []db.IUsage { + if self.PendingDeleted || self.Deleted { + return nil + } + usage := SRegionQuota{Rds: 1} + keys := self.GetQuotaKeys() + usage.SetKeys(keys) + return []db.IUsage{ + &usage, + } +} + +func (self *SMongoDB) GetIRegion() (cloudprovider.ICloudRegion, error) { + region := self.GetRegion() + if region == nil { + return nil, errors.Wrap(httperrors.ErrInvalidStatus, "no valid cloudregion") + } + provider, err := self.GetDriver() + if err != nil { + return nil, errors.Wrap(err, "self.GetDriver") + } + return provider.GetIRegionById(region.GetExternalId()) +} + +func (manager *SMongoDBManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SVirtualResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SVirtualResourceBaseManager.ListItemExportKeys") + } + + if keys.ContainsAny(manager.SManagedResourceBaseManager.GetExportKeys()...) { + q, err = manager.SManagedResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SManagedResourceBaseManager.ListItemExportKeys") + } + } + + if keys.ContainsAny(manager.SCloudregionResourceBaseManager.GetExportKeys()...) { + q, err = manager.SCloudregionResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SCloudregionResourceBaseManager.ListItemExportKeys") + } + } + + if keys.Contains("vpc") { + q, err = manager.SVpcResourceBaseManager.ListItemExportKeys(ctx, q, userCred, stringutils2.NewSortedStrings([]string{"vpc"})) + if err != nil { + return nil, errors.Wrap(err, "SVpcResourceBaseManager.ListItemExportKeys") + } + } + + return q, nil +} + +func (self *SMongoDB) AllowPerformPostpaidExpire(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "postpaid-expire") +} + +func (self *SMongoDB) PerformPostpaidExpire(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input apis.PostpaidExpireInput) (jsonutils.JSONObject, error) { + if self.BillingType != billing_api.BILLING_TYPE_POSTPAID { + return nil, httperrors.NewBadRequestError("self billing type is %s", self.BillingType) + } + + bc, err := ParseBillingCycleInput(&self.SBillingResourceBase, input) + if err != nil { + return nil, err + } + + err = self.SaveRenewInfo(ctx, userCred, bc, nil, billing_api.BILLING_TYPE_POSTPAID) + return nil, err +} + +func (self *SMongoDB) AllowPerformCancelExpire(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "cancel-expire") +} + +func (self *SMongoDB) PerformCancelExpire(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if err := self.CancelExpireTime(ctx, userCred); err != nil { + return nil, err + } + + return nil, nil +} + +func (self *SMongoDB) CancelExpireTime(ctx context.Context, userCred mcclient.TokenCredential) error { + if self.BillingType != billing_api.BILLING_TYPE_POSTPAID { + return httperrors.NewBadRequestError("self billing type %s not support cancel expire", self.BillingType) + } + + _, err := sqlchemy.GetDB().Exec( + fmt.Sprintf( + "update %s set expired_at = NULL and billing_cycle = NULL where id = ?", + MongoDBManager.TableSpec().Name(), + ), self.Id, + ) + if err != nil { + return errors.Wrap(err, "self cancel expire time") + } + db.OpsLog.LogEvent(self, db.ACT_RENEW, "self cancel expire time", userCred) + return nil +} + +func (self *SMongoDB) AllowPerformRemoteUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "remote-update") +} + +func (self *SMongoDB) PerformRemoteUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.MongoDBRemoteUpdateInput) (jsonutils.JSONObject, error) { + err := self.StartRemoteUpdateTask(ctx, userCred, (input.ReplaceTags != nil && *input.ReplaceTags), "") + if err != nil { + return nil, errors.Wrap(err, "StartRemoteUpdateTask") + } + return nil, nil +} + +func (self *SMongoDB) StartRemoteUpdateTask(ctx context.Context, userCred mcclient.TokenCredential, replaceTags bool, parentTaskId string) error { + data := jsonutils.NewDict() + if replaceTags { + data.Add(jsonutils.JSONTrue, "replace_tags") + } + if task, err := taskman.TaskManager.NewTask(ctx, "MongoDBRemoteUpdateTask", self, userCred, data, parentTaskId, "", nil); err != nil { + log.Errorln(err) + return errors.Wrap(err, "Start ElasticcacheRemoteUpdateTask") + } else { + self.SetStatus(userCred, api.DBINSTANCE_UPDATE_TAGS, "StartRemoteUpdateTask") + task.ScheduleRun(nil) + } + return nil +} + +func (self *SMongoDB) OnMetadataUpdated(ctx context.Context, userCred mcclient.TokenCredential) { + if len(self.ExternalId) == 0 { + return + } + err := self.StartRemoteUpdateTask(ctx, userCred, true, "") + if err != nil { + log.Errorf("StartRemoteUpdateTask fail: %s", err) + } +} + +func (self *SMongoDB) AllowGetDetailsBackups(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowGetSpec(userCred, self, "backups") +} + +// 获取备份列表 +func (self *SMongoDB) GetDetailsBackups(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*cloudprovider.SMongoDBBackups, error) { + if self.Status != api.MONGO_DB_STATUS_RUNNING { + return nil, httperrors.NewInvalidStatusError("invalid mongodb status %s for query backups", self.Status) + } + ret := &cloudprovider.SMongoDBBackups{} + iMongoDB, err := self.GetIMongoDB() + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrapf(err, "GetIMongoDB")) + } + ret.Data, err = iMongoDB.GetIBackups() + if err != nil { + return nil, httperrors.NewGeneralError(errors.Wrapf(err, "GetIBackups")) + } + ret.Total = len(ret.Data) + return ret, nil +} diff --git a/pkg/compute/models/purge.go b/pkg/compute/models/purge.go index d82b7d96ef..1c5f7ca69f 100644 --- a/pkg/compute/models/purge.go +++ b/pkg/compute/models/purge.go @@ -1984,3 +1984,18 @@ func (manager *SWafInstanceManager) purgeAll(ctx context.Context, userCred mccli } return nil } + +func (manager *SMongoDBManager) purgeAll(ctx context.Context, userCred mcclient.TokenCredential, providerId string) error { + dbs := []SMongoDB{} + err := fetchByManagerId(manager, providerId, &dbs) + if err != nil { + return errors.Wrapf(err, "fetchByManagerId") + } + for i := range dbs { + err := dbs[i].RealDelete(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "cache delete") + } + } + return nil +} diff --git a/pkg/compute/models/regionquota.go b/pkg/compute/models/regionquota.go index 61018934dd..a70d0467ca 100644 --- a/pkg/compute/models/regionquota.go +++ b/pkg/compute/models/regionquota.go @@ -86,8 +86,9 @@ type SRegionQuota struct { ObjectGB int `default:"-1" allow_zero:"true" json:"object_gb"` ObjectCnt int `default:"-1" allow_zero:"true" json:"object_cnt"` - Rds int `default:"-1" allow_zero:"true" json:"rds"` - Cache int `default:"-1" allow_zero:"true" json:"cache"` + Rds int `default:"-1" allow_zero:"true" json:"rds"` + Cache int `default:"-1" allow_zero:"true" json:"cache"` + Mongodb int `default:"-1" allow_zero:"true" json:"mongodb"` Loadbalancer int `default:"-1" allow_zero:"true" json:"loadbalancer"` } @@ -138,6 +139,7 @@ func (self *SRegionQuota) FetchSystemQuota() { self.ObjectCnt = defaultValue(options.Options.DefaultObjectCntQuota) self.Rds = defaultValue(options.Options.DefaultRdsQuota) self.Cache = defaultValue(options.Options.DefaultCacheQuota) + self.Mongodb = defaultValue(options.Options.DefaultMongodbQuota) self.Loadbalancer = defaultValue(options.Options.DefaultLoadbalancerQuota) } @@ -204,6 +206,8 @@ func (self *SRegionQuota) FetchUsage(ctx context.Context) error { rdsUsage, _ := DBInstanceManager.TotalCount(scope, ownerId, rangeObjs, providers, brands, regionKeys.CloudEnv) self.Rds = rdsUsage.TotalRdsCount self.Cache, _ = ElasticcacheManager.TotalCount(scope, ownerId, rangeObjs, providers, brands, regionKeys.CloudEnv) + mongodbUsage, _ := MongoDBManager.TotalCount(scope, ownerId, rangeObjs, providers, brands, regionKeys.CloudEnv) + self.Mongodb = mongodbUsage.TotalMongodbCount self.Loadbalancer, _ = LoadbalancerManager.TotalCount(scope, ownerId, rangeObjs, providers, brands, regionKeys.CloudEnv) @@ -247,6 +251,9 @@ func (self *SRegionQuota) ResetNegative() { if self.Cache < 0 { self.Cache = 0 } + if self.Mongodb < 0 { + self.Mongodb = 0 + } if self.Loadbalancer < 0 { self.Loadbalancer = 0 } @@ -289,6 +296,9 @@ func (self *SRegionQuota) IsEmpty() bool { if self.Cache > 0 { return false } + if self.Mongodb > 0 { + return false + } if self.Loadbalancer > 0 { return false } @@ -309,6 +319,7 @@ func (self *SRegionQuota) Add(quota quotas.IQuota) { self.ObjectCnt = self.ObjectCnt + quotas.NonNegative(squota.ObjectCnt) self.Rds = self.Rds + quotas.NonNegative(squota.Rds) self.Cache = self.Cache + quotas.NonNegative(squota.Cache) + self.Mongodb = self.Mongodb + quotas.NonNegative(squota.Mongodb) self.Loadbalancer = self.Loadbalancer + quotas.NonNegative(squota.Loadbalancer) } @@ -326,6 +337,7 @@ func (self *SRegionQuota) Sub(quota quotas.IQuota) { self.ObjectCnt = nonNegative(self.ObjectCnt - squota.ObjectCnt) self.Rds = nonNegative(self.Rds - squota.Rds) self.Cache = nonNegative(self.Cache - squota.Cache) + self.Mongodb = nonNegative(self.Mongodb - squota.Mongodb) self.Loadbalancer = nonNegative(self.Loadbalancer - squota.Loadbalancer) } @@ -368,6 +380,9 @@ func (self *SRegionQuota) Allocable(request quotas.IQuota) int { if self.Cache >= 0 && squota.Cache > 0 && (cnt < 0 || cnt > self.Cache/squota.Cache) { cnt = self.Cache / squota.Cache } + if self.Mongodb >= 0 && squota.Mongodb > 0 && (cnt < 0 || cnt > self.Mongodb/squota.Mongodb) { + cnt = self.Mongodb / squota.Mongodb + } if self.Loadbalancer >= 0 && squota.Loadbalancer > 0 && (cnt < 0 || cnt > self.Loadbalancer/squota.Loadbalancer) { cnt = self.Loadbalancer / squota.Loadbalancer } @@ -412,6 +427,9 @@ func (self *SRegionQuota) Update(quota quotas.IQuota) { if squota.Cache > 0 { self.Cache = squota.Cache } + if squota.Mongodb > 0 { + self.Mongodb = squota.Mongodb + } if squota.Loadbalancer > 0 { self.Loadbalancer = squota.Loadbalancer } @@ -457,6 +475,9 @@ func (used *SRegionQuota) Exceed(request quotas.IQuota, quota quotas.IQuota) err if quotas.Exceed(used.Cache, sreq.Cache, squota.Cache) { err.Add(used, "cache", squota.Cache, used.Cache, sreq.Cache) } + if quotas.Exceed(used.Mongodb, sreq.Mongodb, squota.Mongodb) { + err.Add(used, "mongodb", squota.Mongodb, used.Mongodb, sreq.Mongodb) + } if quotas.Exceed(used.Loadbalancer, sreq.Loadbalancer, squota.Loadbalancer) { err.Add(used, "loadbalancer", squota.Loadbalancer, used.Loadbalancer, sreq.Loadbalancer) } @@ -481,6 +502,7 @@ func (self *SRegionQuota) ToJSON(prefix string) jsonutils.JSONObject { ret.Add(jsonutils.NewInt(int64(self.ObjectCnt)), keyName(prefix, "object_cnt")) ret.Add(jsonutils.NewInt(int64(self.Rds)), keyName(prefix, "rds")) ret.Add(jsonutils.NewInt(int64(self.Cache)), keyName(prefix, "cache")) + ret.Add(jsonutils.NewInt(int64(self.Mongodb)), keyName(prefix, "mongodb")) ret.Add(jsonutils.NewInt(int64(self.Loadbalancer)), keyName(prefix, "loadbalancer")) return ret } diff --git a/pkg/compute/options/options.go b/pkg/compute/options/options.go index efe663d3a9..697af5c371 100644 --- a/pkg/compute/options/options.go +++ b/pkg/compute/options/options.go @@ -78,6 +78,7 @@ type ComputeOptions struct { DefaultLoadbalancerQuota int `default:"10" help:"Common loadbalancer quota per tenant, default 10"` DefaultRdsQuota int `default:"10" help:"Common RDS quota per tenant, default 10"` DefaultCacheQuota int `default:"10" help:"Common ElasticCache quota per tenant, default 10"` + DefaultMongodbQuota int `default:"10" help:"Common MongoDB quota per tenant, default 10"` DefaultGlobalvpcQuota int `default:"10" help:"Common global Vpc quota per domain, default 10"` DefaultCloudaccountQuota int `default:"20" help:"Common cloud account quota per domain, default 20"` diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index ed56be68af..a6002d1d74 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -226,6 +226,8 @@ func InitHandlers(app *appsrv.Application) { models.WafRegexSetCacheManager, models.WafInstanceManager, models.WafRuleManager, + + models.MongoDBManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/compute/tasks/mongodb_delete_task.go b/pkg/compute/tasks/mongodb_delete_task.go new file mode 100644 index 0000000000..7807cda14d --- /dev/null +++ b/pkg/compute/tasks/mongodb_delete_task.go @@ -0,0 +1,70 @@ +// 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 fsreed 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 langufse governing permissions and +// limitations under the License. + +package tasks + +import ( + "context" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "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" +) + +type MongoDBDeleteTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(MongoDBDeleteTask{}) +} + +func (self *MongoDBDeleteTask) taskFailed(ctx context.Context, mongodb *models.SMongoDB, err error) { + mongodb.SetStatus(self.UserCred, api.MONGO_DB_STATUS_DELETE_FAILED, err.Error()) + logclient.AddActionLogWithStartable(self, mongodb, logclient.ACT_DELOCATE, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *MongoDBDeleteTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) { + mongodb := obj.(*models.SMongoDB) + + iMongoDB, err := mongodb.GetIMongoDB() + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + self.taskComplete(ctx, mongodb) + return + } + self.taskFailed(ctx, mongodb, errors.Wrapf(err, "mongodb.GetIMongoDB")) + return + } + err = iMongoDB.Delete() + if err != nil { + self.taskFailed(ctx, mongodb, errors.Wrapf(err, "iMongoDB.Delete")) + return + } + cloudprovider.WaitDeleted(iMongoDB, time.Second*10, time.Minute*5) + self.taskComplete(ctx, mongodb) +} + +func (self *MongoDBDeleteTask) taskComplete(ctx context.Context, mongodb *models.SMongoDB) { + mongodb.RealDelete(ctx, self.GetUserCred()) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/tasks/mongodb_syncstatus_task.go b/pkg/compute/tasks/mongodb_syncstatus_task.go new file mode 100644 index 0000000000..09d6cc51db --- /dev/null +++ b/pkg/compute/tasks/mongodb_syncstatus_task.go @@ -0,0 +1,62 @@ +// 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 tasks + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/util/logclient" +) + +type MongoDBSyncstatusTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(MongoDBSyncstatusTask{}) +} + +func (self *MongoDBSyncstatusTask) taskFail(ctx context.Context, mongodb *models.SMongoDB, err error) { + mongodb.SetStatus(self.UserCred, api.MONGO_DB_STATUS_UNKNOWN, err.Error()) + db.OpsLog.LogEvent(mongodb, db.ACT_SYNC_STATUS, err, self.GetUserCred()) + logclient.AddActionLogWithStartable(self, mongodb, logclient.ACT_SYNC_STATUS, err, self.UserCred, false) + self.SetStageFailed(ctx, jsonutils.NewString(err.Error())) +} + +func (self *MongoDBSyncstatusTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + mongodb := obj.(*models.SMongoDB) + + ext, err := mongodb.GetIMongoDB() + if err != nil { + self.taskFail(ctx, mongodb, errors.Wrapf(err, "GetIMongoDB")) + return + } + + err = mongodb.SyncWithCloudMongoDB(ctx, self.UserCred, ext) + if err != nil { + self.taskFail(ctx, mongodb, errors.Wrapf(err, "SyncWithCloudMongoDB")) + return + } + + logclient.AddActionLogWithStartable(self, mongodb, logclient.ACT_SYNC_STATUS, nil, self.UserCred, true) + self.SetStageComplete(ctx, nil) +} diff --git a/pkg/compute/usages/handler.go b/pkg/compute/usages/handler.go index f6d5f8ef97..07604a530c 100644 --- a/pkg/compute/usages/handler.go +++ b/pkg/compute/usages/handler.go @@ -319,6 +319,8 @@ func getSystemGeneralUsage(userCred mcclient.IIdentityProvider, rangeObjs []db.I DBInstanceUsage(rbacutils.ScopeSystem, nil, rangeObjs, providers, brands, cloudEnv), + MongoDBUsage(rbacutils.ScopeSystem, nil, rangeObjs, providers, brands, cloudEnv), + ElasticCacheUsage(rbacutils.ScopeSystem, nil, rangeObjs, providers, brands, cloudEnv), ) @@ -409,6 +411,8 @@ func getDomainGeneralUsage(scope rbacutils.TRbacScope, cred mcclient.IIdentityPr DBInstanceUsage(scope, cred, rangeObjs, providers, brands, cloudEnv), + MongoDBUsage(scope, cred, rangeObjs, providers, brands, cloudEnv), + ElasticCacheUsage(scope, cred, rangeObjs, providers, brands, cloudEnv), ) return count, nil @@ -457,6 +461,8 @@ func getProjectGeneralUsage(scope rbacutils.TRbacScope, cred mcclient.IIdentityP DBInstanceUsage(scope, cred, rangeObjs, providers, brands, cloudEnv), + MongoDBUsage(scope, cred, rangeObjs, providers, brands, cloudEnv), + ElasticCacheUsage(scope, cred, rangeObjs, providers, brands, cloudEnv), ) @@ -949,6 +955,15 @@ func DBInstanceUsage(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvi return count } +func MongoDBUsage(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, rangeObjs []db.IStandaloneModel, providers []string, brands []string, cloudEnv string) Usage { + cnt, _ := models.MongoDBManager.TotalCount(scope, ownerId, rangeObjs, providers, brands, cloudEnv) + count := make(map[string]interface{}) + count[getKey(scope, "mongodb")] = cnt.TotalMongodbCount + count[getKey(scope, "mongodb.cpu")] = cnt.TotalCpuCount + count[getKey(scope, "mongodb.memory")] = cnt.TotalMemSizeMb + return count +} + func ElasticCacheUsage(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, rangeObjs []db.IStandaloneModel, providers []string, brands []string, cloudEnv string) Usage { cnt, _ := models.ElasticcacheManager.TotalCount(scope, ownerId, rangeObjs, providers, brands, cloudEnv) count := make(map[string]interface{}) diff --git a/pkg/mcclient/modules/mod_mongodb.go b/pkg/mcclient/modules/mod_mongodb.go new file mode 100644 index 0000000000..1da93ed049 --- /dev/null +++ b/pkg/mcclient/modules/mod_mongodb.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 MongoDBManager struct { + modulebase.ResourceManager +} + +var ( + MongoDB MongoDBManager +) + +func init() { + MongoDB = MongoDBManager{NewComputeManager("mongodb", "mongodbs", + []string{}, + []string{})} + + registerCompute(&MongoDB) +} diff --git a/pkg/mcclient/options/compute/mongodb.go b/pkg/mcclient/options/compute/mongodb.go new file mode 100644 index 0000000000..c638dd707c --- /dev/null +++ b/pkg/mcclient/options/compute/mongodb.go @@ -0,0 +1,59 @@ +// 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/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type MongoDBListOptions struct { + options.BaseListOptions +} + +func (opts *MongoDBListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(opts) +} + +type MongoDBUpdateOptions struct { + options.BaseIdOptions + Name string + Description string + Delete string `help:"Lock or not lock dbinstance" choices:"enable|disable"` +} + +func (opts *MongoDBUpdateOptions) Params() (jsonutils.JSONObject, error) { + params, err := options.StructToParams(opts) + if err != nil { + return nil, err + } + if len(opts.Delete) > 0 { + if opts.Delete == "disable" { + params.Add(jsonutils.JSONTrue, "disable_delete") + } else { + params.Add(jsonutils.JSONFalse, "disable_delete") + } + } + return params, nil +} + +type MongoDBBackupListOptions struct { + options.BaseIdOptions +} + +func (opts *MongoDBBackupListOptions) Property() string { + return "backups" +} diff --git a/pkg/multicloud/aliyun/aliyun.go b/pkg/multicloud/aliyun/aliyun.go index 4c16d6cf3f..81b3a405d6 100644 --- a/pkg/multicloud/aliyun/aliyun.go +++ b/pkg/multicloud/aliyun/aliyun.go @@ -55,17 +55,18 @@ const ( ALIYUN_BSS_API_VERSION = "2017-12-14" - ALIYUN_RAM_API_VERSION = "2015-05-01" - ALIYUN_API_VERION_RDS = "2014-08-15" - ALIYUN_RM_API_VERSION = "2020-03-31" - ALIYUN_STS_API_VERSION = "2015-04-01" - ALIYUN_PVTZ_API_VERSION = "2018-01-01" - ALIYUN_ALIDNS_API_VERSION = "2015-01-09" - ALIYUN_CBN_API_VERSION = "2017-09-12" - ALIYUN_CDN_API_VERSION = "2018-05-10" - ALIYUN_IMS_API_VERSION = "2019-08-15" - ALIYUN_NAS_API_VERSION = "2017-06-26" - ALIYUN_WAF_API_VERSION = "2019-09-10" + ALIYUN_RAM_API_VERSION = "2015-05-01" + ALIYUN_RDS_API_VERSION = "2014-08-15" + ALIYUN_RM_API_VERSION = "2020-03-31" + ALIYUN_STS_API_VERSION = "2015-04-01" + ALIYUN_PVTZ_API_VERSION = "2018-01-01" + ALIYUN_ALIDNS_API_VERSION = "2015-01-09" + ALIYUN_CBN_API_VERSION = "2017-09-12" + ALIYUN_CDN_API_VERSION = "2018-05-10" + ALIYUN_IMS_API_VERSION = "2019-08-15" + ALIYUN_NAS_API_VERSION = "2017-06-26" + ALIYUN_WAF_API_VERSION = "2019-09-10" + ALIYUN_MONGO_DB_API_VERSION = "2015-12-01" ALIYUN_SERVICE_ECS = "ecs" ALIYUN_SERVICE_VPC = "vpc" @@ -644,6 +645,7 @@ func (region *SAliyunClient) GetCapabilities() []string { cloudprovider.CLOUD_CAPABILITY_NAT, cloudprovider.CLOUD_CAPABILITY_NAS, cloudprovider.CLOUD_CAPABILITY_WAF, + cloudprovider.CLOUD_CAPABILITY_MONGO_DB, } return caps } diff --git a/pkg/multicloud/aliyun/mongodb.go b/pkg/multicloud/aliyun/mongodb.go new file mode 100644 index 0000000000..bb3e5a4d22 --- /dev/null +++ b/pkg/multicloud/aliyun/mongodb.go @@ -0,0 +1,416 @@ +// 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" + "regexp" + "strconv" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SMongoDB struct { + region *SRegion + multicloud.AliyunTags + multicloud.SBillingBase + multicloud.SResourceBase + + ChargeType TChargeType `json:"ChargeType"` + LockMode string `json:"LockMode"` + DBInstanceClass string `json:"DBInstanceClass"` + ResourceGroupId string `json:"ResourceGroupId"` + DBInstanceId string `json:"DBInstanceId"` + ZoneId string `json:"ZoneId"` + MongosList struct { + MongosAttribute []struct { + NodeId string `json:"NodeId"` + NodeClass string `json:"NodeClass"` + } `json:"MongosAttribute"` + } `json:"MongosList"` + DBInstanceDescription string `json:"DBInstanceDescription"` + Engine string `json:"Engine"` + CreationTime time.Time `json:"CreationTime"` + NetworkType string `json:"NetworkType"` + ExpireTime time.Time `json:"ExpireTime"` + DBInstanceType string `json:"DBInstanceType"` + RegionId string `json:"RegionId"` + ShardList struct { + ShardAttribute []struct { + NodeId string `json:"NodeId"` + NodeClass string `json:"NodeClass"` + NodeStorage int `json:"NodeStorage"` + } `json:"ShardAttribute"` + } `json:"ShardList"` + EngineVersion string `json:"EngineVersion"` + DBInstanceStatus string `json:"DBInstanceStatus"` + + DBInstanceStorage int `json:"DBInstanceStorage"` + MaintainStartTime string `json:"MaintainStartTime"` + MaintainEndTime string `json:"MaintainEndTime"` + StorageEngine string `json:"StorageEngine"` + VpcId string `json:"VPCId"` + VSwitchId string `json:"VSwitchId"` + VpcAuthMode string `json:"VpcAuthMode"` + ReplicationFactor string `json:"ReplicationFactor"` +} + +var mongoSpec = map[string]struct { + VcpuCount int + VmemSizeGb int +}{} + +func (self *SMongoDB) GetName() string { + if len(self.DBInstanceDescription) > 0 { + return self.DBInstanceDescription + } + return self.DBInstanceId +} + +func (self *SMongoDB) GetId() string { + return self.DBInstanceId +} + +func (self *SMongoDB) GetGlobalId() string { + return self.DBInstanceId +} + +func (self *SMongoDB) GetStatus() string { + switch self.DBInstanceStatus { + case "Creating": + return api.MONGO_DB_STATUS_CREATING + case "DBInstanceClassChanging": + return api.MONGO_DB_STATUS_CHANGE_CONFIG + case "DBInstanceNetTypeChanging", "EngineVersionUpgrading", "GuardSwitching", "HASwitching", "Importing", "ImportingFromOthers", "LinkSwitching", "MinorVersionUpgrading", "NET_CREATING", "NET_DELETING", "NodeCreating", "NodeDeleting", "Restoring", "SSLModifying", "TempDBInstanceCreating", "Transing", "TransingToOthers": + return api.MONGO_DB_STATUS_DEPLOY + case "Deleting": + return api.MONGO_DB_STATUS_DELETING + case "Rebooting": + return api.MONGO_DB_STATUS_REBOOTING + case "Running": + return api.MONGO_DB_STATUS_RUNNING + default: + return strings.ToLower(self.DBInstanceStatus) + } +} + +func (self *SMongoDB) GetProjectId() string { + return self.ResourceGroupId +} + +func (self *SMongoDB) Refresh() error { + db, err := self.region.GetMongoDB(self.DBInstanceId) + if err != nil { + return errors.Wrapf(err, "GetMongoDB") + } + return jsonutils.Update(self, db) +} + +func (self *SMongoDB) GetCreatedAt() time.Time { + return self.CreationTime +} + +func (self *SMongoDB) GetExpiredAt() time.Time { + return self.ExpireTime +} + +func (self *SMongoDB) GetIpAddr() string { + return "" +} + +func (self *SMongoDB) GetEngine() string { + if len(self.StorageEngine) == 0 { + self.Refresh() + } + return self.StorageEngine +} + +func (self *SMongoDB) GetEngineVersion() string { + return self.EngineVersion +} + +func (self *SMongoDB) GetVpcId() string { + if self.NetworkType != "VPC" { + return "" + } + if len(self.VpcId) == 0 { + self.Refresh() + } + return self.VpcId +} + +func (self *SMongoDB) GetNetworkId() string { + if self.NetworkType != "VPC" { + return "" + } + if len(self.VSwitchId) == 0 { + self.Refresh() + } + return self.VSwitchId +} + +func (self *SMongoDB) GetZoneId() string { + if strings.Contains(self.ZoneId, ",") { + return self.ZoneId + } + if info := strings.Split(self.ZoneId, "-"); len(info) == 3 { + return strings.Join([]string{info[0], info[1], string(info[2][strings.Index(info[2], ",")-1])}, "-") + } + return "" +} + +func (self *SMongoDB) Delete() error { + return self.region.DeleteMongoDB(self.DBInstanceId) +} + +func (self *SMongoDB) GetBillingType() string { + return convertChargeType(self.ChargeType) +} + +func (self *SMongoDB) GetCategory() string { + return self.DBInstanceType +} + +func (self *SMongoDB) GetDiskSizeMb() int { + if self.DBInstanceStorage == 0 { + self.Refresh() + } + return self.DBInstanceStorage * 1024 +} + +func (self *SMongoDB) GetInstanceType() string { + return self.DBInstanceClass +} + +func (self *SMongoDB) GetMaintainTime() string { + return fmt.Sprintf("%s-%s", self.MaintainStartTime, self.MaintainEndTime) +} + +func (self *SMongoDB) GetPort() int { + return 3717 +} + +func (self *SMongoDB) GetReplicationNum() int { + if len(self.ReplicationFactor) == 0 { + self.Refresh() + } + num, _ := strconv.Atoi(self.ReplicationFactor) + return int(num) +} + +func (self *SMongoDB) GetVcpuCount() int { + self.region.GetchMongoSkus() + sku, ok := self.region.mongoSkus[self.DBInstanceClass] + if ok { + return sku.CpuCount + } + return 0 +} + +func (self *SMongoDB) GetVmemSizeMb() int { + self.region.GetchMongoSkus() + sku, ok := self.region.mongoSkus[self.DBInstanceClass] + if ok { + return sku.MemSizeGb * 1024 + } + return 0 +} + +func (self *SRegion) GetICloudMongoDBs() ([]cloudprovider.ICloudMongoDB, error) { + dbs := []SMongoDB{} + for { + part, total, err := self.GetMongoDBs(100, len(dbs)/100) + if err != nil { + return nil, errors.Wrapf(err, "GetMongoDB") + } + dbs = append(dbs, part...) + if len(dbs) >= total { + break + } + } + ret := []cloudprovider.ICloudMongoDB{} + for i := range dbs { + dbs[i].region = self + ret = append(ret, &dbs[i]) + } + return ret, nil +} + +func (self *SRegion) GetMongoDBs(pageSize int, pageNum int) ([]SMongoDB, int, error) { + if pageSize < 1 || pageSize > 100 { + pageSize = 100 + } + if pageNum < 1 { + pageNum = 1 + } + + params := map[string]string{ + "PageSize": fmt.Sprintf("%d", pageSize), + "PageNumber": fmt.Sprintf("%d", pageNum), + } + resp, err := self.mongodbRequest("DescribeDBInstances", params) + if err != nil { + return nil, 0, errors.Wrapf(err, "DescribeDBInstances") + } + ret := []SMongoDB{} + err = resp.Unmarshal(&ret, "DBInstances", "DBInstance") + if err != nil { + return nil, 0, errors.Wrapf(err, "resp.Unmarshal") + } + totalCount, _ := resp.Int("TotalCount") + return ret, int(totalCount), nil +} + +func (self *SRegion) GetMongoDB(id string) (*SMongoDB, error) { + params := map[string]string{ + "DBInstanceId": id, + } + resp, err := self.mongodbRequest("DescribeDBInstanceAttribute", params) + if err != nil { + return nil, errors.Wrapf(err, "DescribeDBInstanceAttribute") + } + ret := []SMongoDB{} + err = resp.Unmarshal(&ret, "DBInstances", "DBInstance") + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + if len(ret) == 1 { + ret[0].region = self + return &ret[0], nil + } + return nil, errors.Wrapf(cloudprovider.ErrNotFound, id) +} + +func (self *SRegion) DeleteMongoDB(id string) error { + params := map[string]string{ + "DBInstanceId": id, + "ClientToken": utils.GenRequestId(20), + } + _, err := self.mongodbRequest("DeleteDBInstance", params) + return errors.Wrapf(err, "DeleteDBInstance") +} + +type SMongoDBAvaibaleResource struct { + SupportedDBTypes struct { + SupportedDBType []struct { + DbType string + AvailableZones struct { + AvailableZone []struct { + ZoneId string + RegionId string + SupportedEngineVersions struct { + SupportedEngineVersion []struct { + Version string + SupportedEngines struct { + SupportedEngine []struct { + SupportedNodeTypes struct { + SupportedNodeType []struct { + NetworkTypes string + NodeType string + AvailableResources struct { + AvailableResource []struct { + InstanceClassRemark string + InstanceClass string + } + } + } + } + } + } + } + } + } + } + } + } +} + +func (self *SRegion) GetchMongoSkus() (map[string]struct { + CpuCount int + MemSizeGb int +}, error) { + if len(self.mongoSkus) > 0 { + return self.mongoSkus, nil + } + self.mongoSkus = map[string]struct { + CpuCount int + MemSizeGb int + }{} + res, err := self.GetMongoDBAvailableResource() + if err != nil { + return nil, err + } + for _, dbType := range res.SupportedDBTypes.SupportedDBType { + for _, zone := range dbType.AvailableZones.AvailableZone { + for _, version := range zone.SupportedEngineVersions.SupportedEngineVersion { + for _, engine := range version.SupportedEngines.SupportedEngine { + for _, nodeType := range engine.SupportedNodeTypes.SupportedNodeType { + for _, sku := range nodeType.AvailableResources.AvailableResource { + _, ok := self.mongoSkus[sku.InstanceClass] + if !ok { + self.mongoSkus[sku.InstanceClass] = getMongoDBSkuDetails(sku.InstanceClassRemark) + } + } + } + } + } + } + } + return self.mongoSkus, nil +} + +func getMongoDBSkuDetails(remark string) struct { + CpuCount int + MemSizeGb int +} { + ret := struct { + CpuCount int + MemSizeGb int + }{} + r, _ := regexp.Compile(`(\d{1,3})核(\d{1,3})G+`) + result := r.FindSubmatch([]byte(remark)) + if len(result) > 2 { + cpu, _ := strconv.Atoi(string(result[1])) + ret.CpuCount = int(cpu) + mem, _ := strconv.Atoi(string(result[2])) + ret.MemSizeGb = int(mem) + } else { + log.Warningf("not match sku remark %s", remark) + } + return ret +} + +func (self *SRegion) GetMongoDBAvailableResource() (*SMongoDBAvaibaleResource, error) { + params := map[string]string{} + resp, err := self.mongodbRequest("DescribeAvailableResource", params) + if err != nil { + return nil, errors.Wrapf(err, "DescribeAvailableResource") + } + ret := &SMongoDBAvaibaleResource{} + err = resp.Unmarshal(ret) + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return ret, nil +} diff --git a/pkg/multicloud/aliyun/mongodb_backup.go b/pkg/multicloud/aliyun/mongodb_backup.go new file mode 100644 index 0000000000..7284a8e71f --- /dev/null +++ b/pkg/multicloud/aliyun/mongodb_backup.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 aliyun + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SMongoDBBackup struct { + BackupDBNames string + BackupDownloadURL string + BackupEndTime time.Time + BackupStartTime time.Time + BackupId string + BackupIntranetDownloadURL string + BackupMethod string + BackupMode string + BackupSize int + BackupStatus string + BackupType string +} + +func (self *SRegion) GetMongoDBBackups(id string, start time.Time, end time.Time, pageSize, pageNum int) ([]SMongoDBBackup, int, error) { + if pageSize < 1 || pageSize > 100 { + pageSize = 100 + } + if pageNum < 1 { + pageNum = 1 + } + params := map[string]string{ + "StartTime": start.String(), + "EndTime": end.String(), + "DBInstanceId": id, + "PageSize": fmt.Sprintf("%d", pageSize), + "PageNumber": fmt.Sprintf("%d", pageNum), + } + resp, err := self.mongodbRequest("DescribeBackups", params) + if err != nil { + return nil, 0, errors.Wrapf(err, "DescribeBackups") + } + ret := []SMongoDBBackup{} + err = resp.Unmarshal(&ret, "Backups", "Backup") + if err != nil { + return nil, 0, errors.Wrapf(err, "resp.Unmarshal") + } + totalCount, _ := resp.Int("TotalCount") + return ret, int(totalCount), nil +} + +func (self *SMongoDB) GetIBackups() ([]cloudprovider.SMongoDBBackup, error) { + backups := []SMongoDBBackup{} + now := time.Now().Add(time.Minute * -1) + for { + part, total, err := self.region.GetMongoDBBackups(self.DBInstanceId, self.CreationTime, now, 100, len(backups)/100) + if err != nil { + return nil, errors.Wrapf(err, "GetMongoDBBackups") + } + backups = append(backups, part...) + if len(backups) >= total { + break + } + } + ret := []cloudprovider.SMongoDBBackup{} + for _, res := range backups { + backup := cloudprovider.SMongoDBBackup{} + backup.Name = res.BackupId + backup.StartTime = res.BackupStartTime + backup.EndTime = res.BackupEndTime + backup.BackupSizeKb = res.BackupSize / 1024 + switch res.BackupStatus { + case "Success": + backup.Status = cloudprovider.MongoDBBackupStatusAvailable + case "Failed": + backup.Status = cloudprovider.MongoDBBackupStatusFailed + default: + backup.Status = cloudprovider.TMongoDBBackupStatus(strings.ToLower(res.BackupStatus)) + } + backup.BackupMethod = cloudprovider.TMongoDBBackupMethod(strings.ToLower(res.BackupMethod)) + backup.BackupType = cloudprovider.MongoDBBackupTypeAuto + if res.BackupMode == "Manual" { + backup.BackupType = cloudprovider.MongoDBBackupTypeManual + } + ret = append(ret, backup) + } + return ret, nil +} diff --git a/pkg/multicloud/aliyun/region.go b/pkg/multicloud/aliyun/region.go index 3c4934a0f9..e07cfcc576 100644 --- a/pkg/multicloud/aliyun/region.go +++ b/pkg/multicloud/aliyun/region.go @@ -40,6 +40,10 @@ type SRegion struct { client *SAliyunClient sdkClient *sdk.Client ossClient *oss.Client + mongoSkus map[string]struct { + CpuCount int + MemSizeGb int + } RegionId string LocalName string @@ -153,7 +157,15 @@ func (self *SRegion) rdsRequest(apiName string, params map[string]string) (jsonu if err != nil { return nil, err } - return jsonRequest(client, "rds.aliyuncs.com", ALIYUN_API_VERION_RDS, apiName, params, self.client.debug) + return jsonRequest(client, "rds.aliyuncs.com", ALIYUN_RDS_API_VERSION, apiName, params, self.client.debug) +} + +func (self *SRegion) mongodbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + client, err := self.getSdkClient() + if err != nil { + return nil, err + } + return jsonRequest(client, "mongodb.aliyuncs.com", ALIYUN_MONGO_DB_API_VERSION, apiName, params, self.client.debug) } func (self *SRegion) vpcRequest(action string, params map[string]string) (jsonutils.JSONObject, error) { diff --git a/pkg/multicloud/aliyun/shell/mongodb.go b/pkg/multicloud/aliyun/shell/mongodb.go new file mode 100644 index 0000000000..ff5740ea48 --- /dev/null +++ b/pkg/multicloud/aliyun/shell/mongodb.go @@ -0,0 +1,87 @@ +// 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 ( + "time" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/multicloud/aliyun" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type MongoDBListOptions struct { + Id string + Offset int + Limit int + } + shellutils.R(&MongoDBListOptions{}, "mongodb-list", "List mongodb", func(cli *aliyun.SRegion, args *MongoDBListOptions) error { + dbs, _, err := cli.GetMongoDBs(args.Limit, args.Offset) + if err != nil { + return err + } + printList(dbs, 0, 0, 0, []string{}) + return nil + }) + + type MongoDBIdOptions struct { + ID string + } + + shellutils.R(&MongoDBIdOptions{}, "mongodb-show", "Show mongodb", func(cli *aliyun.SRegion, args *MongoDBIdOptions) error { + db, err := cli.GetMongoDB(args.ID) + if err != nil { + return errors.Wrapf(err, "GetMongoDB(%s)", args.ID) + } + printObject(db) + return nil + }) + + shellutils.R(&MongoDBIdOptions{}, "mongodb-delete", "Delete mongodb", func(cli *aliyun.SRegion, args *MongoDBIdOptions) error { + return cli.DeleteMongoDB(args.ID) + }) + + type MongoDBBackupListOptions struct { + ID string + START time.Time + END time.Time + PageSize int + PageNumber int + } + + shellutils.R(&MongoDBBackupListOptions{}, "mongodb-backup-list", "List mongodb backups", func(cli *aliyun.SRegion, args *MongoDBBackupListOptions) error { + backups, _, err := cli.GetMongoDBBackups(args.ID, args.START, args.END, args.PageSize, args.PageNumber) + if err != nil { + return err + } + printList(backups, 0, 0, 0, nil) + return nil + }) + + type MongoDBSkuListOptions struct { + } + + shellutils.R(&MongoDBSkuListOptions{}, "mongodb-sku-list", "List mongodb skus", func(cli *aliyun.SRegion, args *MongoDBSkuListOptions) error { + skus, err := cli.GetchMongoSkus() + if err != nil { + return err + } + printObject(skus) + return nil + }) + +} diff --git a/pkg/multicloud/qcloud/mongodb.go b/pkg/multicloud/qcloud/mongodb.go new file mode 100644 index 0000000000..999d8db944 --- /dev/null +++ b/pkg/multicloud/qcloud/mongodb.go @@ -0,0 +1,387 @@ +// 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 ( + "fmt" + "strings" + "time" + + sdkerrors "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SMongoDB struct { + multicloud.QcloudTags + multicloud.SVirtualResourceBase + multicloud.SBillingBase + region *SRegion + + AutoRenewFlag int `json:"AutoRenewFlag"` + CloneInstances []interface{} `json:"CloneInstances"` + ClusterType int `json:"ClusterType"` + ClusterVer int `json:"ClusterVer"` + ConfigServerCpuNum int `json:"ConfigServerCpuNum"` + ConfigServerMemory int `json:"ConfigServerMemory"` + ConfigServerNodeNum int `json:"ConfigServerNodeNum"` + ConfigServerVolume int `json:"ConfigServerVolume"` + CpuNum int `json:"CpuNum"` + CreateTime time.Time `json:"CreateTime"` + DeadLine string `json:"DeadLine"` + InstanceId string `json:"InstanceId"` + InstanceName string `json:"InstanceName"` + InstanceStatusDesc string `json:"InstanceStatusDesc"` + InstanceTaskDesc string `json:"InstanceTaskDesc"` + InstanceTaskId int `json:"InstanceTaskId"` + InstanceType int `json:"InstanceType"` + InstanceVer int `json:"InstanceVer"` + MachineType string `json:"MachineType"` + MaintenanceEnd string `json:"MaintenanceEnd"` + MaintenanceStart string `json:"MaintenanceStart"` + Memory int `json:"Memory"` + MongoVersion string `json:"MongoVersion"` + MongosCpuNum int `json:"MongosCpuNum"` + MongosMemory int `json:"MongosMemory"` + MongosNodeNum int `json:"MongosNodeNum"` + NetType int `json:"NetType"` + PayMode int `json:"PayMode"` + ProjectId int `json:"ProjectId"` + Protocol int `json:"Protocol"` + Readonlyinstances []interface{} `json:"ReadonlyInstances"` + RealInstanceId string `json:"RealInstanceId"` + Region string `json:"Region"` + Relatedinstance struct { + InstanceId string `json:"InstanceId"` + Region string `json:"Region"` + } `json:"RelatedInstance"` + Replicasets []struct { + Memory int `json:"Memory"` + OplogSize int `json:"OplogSize"` + RealReplicasetId string `json:"RealReplicaSetId"` + ReplicaSetId string `json:"ReplicaSetId"` + ReplicaSetName string `json:"ReplicaSetName"` + SecondaryNum int `json:"SecondaryNum"` + UsedVolume int `json:"UsedVolume"` + Volume int `json:"Volume"` + } `json:"ReplicaSets"` + ReplicationSetNum int `json:"ReplicationSetNum"` + SecondaryNum int `json:"SecondaryNum"` + StandbyInstances []interface{} `json:"StandbyInstances"` + Status int `json:"Status"` + SubnetId string `json:"SubnetId"` + UsedVolume int `json:"UsedVolume"` + Vip string `json:"Vip"` + Volume int `json:"Volume"` + VpcId string `json:"VpcId"` + Vport int `json:"Vport"` + Zone string `json:"Zone"` +} + +func (self *SMongoDB) GetGlobalId() string { + return self.InstanceId +} + +func (self *SMongoDB) GetId() string { + return self.InstanceId +} + +func (self *SMongoDB) GetName() string { + return self.InstanceName +} + +func (self *SMongoDB) GetStatus() string { + switch self.Status { + case 0, 1: + return api.MONGO_DB_STATUS_CREATING + case 2: + return api.MONGO_DB_STATUS_RUNNING + case -2, -3: + return api.MONGO_DB_STATUS_DELETING + } + return fmt.Sprintf("%d", self.Status) +} + +func (self *SMongoDB) Refresh() error { + ins, err := self.region.GetMongoDB(self.InstanceId) + if err != nil { + return errors.Wrapf(err, "GetMongoDB") + } + return jsonutils.Update(self, ins) +} + +func (self *SMongoDB) GetProjectId() string { + return fmt.Sprintf("%d", self.ProjectId) +} + +func (self *SMongoDB) GetVpcId() string { + return self.VpcId +} + +func (self *SMongoDB) GetNetworkId() string { + return self.SubnetId +} + +func (self *SMongoDB) GetCreatedAt() time.Time { + return self.CreateTime.Add(time.Hour * -8) +} + +func (self *SMongoDB) GetExpiredAt() time.Time { + return time.Time{} +} + +func (self *SMongoDB) GetIpAddr() string { + return self.Vip +} + +func (self *SMongoDB) GetVcpuCount() int { + return self.CpuNum +} + +func (self *SMongoDB) GetVmemSizeMb() int { + return self.Memory +} + +func (self *SMongoDB) GetReplicationNum() int { + return self.ReplicationSetNum +} + +func (self *SMongoDB) GetDiskSizeMb() int { + return self.Volume +} + +func (self *SMongoDB) GetZoneId() string { + zone, err := self.region.getZoneById(self.Zone) + if err != nil { + return "" + } + return zone.GetGlobalId() +} + +func (self *SMongoDB) GetBillingType() string { + // 计费模式:0-按量计费,1-包年包月 + if self.PayMode == 1 { + return billing_api.BILLING_TYPE_PREPAID + } else { + return billing_api.BILLING_TYPE_POSTPAID + } +} + +func (self *SMongoDB) IsAutoRenew() bool { + return self.AutoRenewFlag == 1 +} + +func (self *SMongoDB) GetCategory() string { + switch self.ClusterType { + case 0: + return api.MONGO_DB_CATEGORY_REPLICATE + case 1: + return api.MONGO_DB_CATEGORY_SHARDING + default: + return fmt.Sprintf("%d", self.ClusterType) + } +} + +func (self *SMongoDB) GetEngine() string { + if utils.IsInStringArray("WT", strings.Split(self.MongoVersion, "_")) { + return api.MONGO_DB_ENGINE_WIRED_TIGER + } + return api.MONGO_DB_ENGINE_ROCKS +} + +func (self *SMongoDB) GetEngineVersion() string { + vers := strings.Split(self.MongoVersion, "_") + if len(vers) > 1 { + return strings.Join(strings.Split(vers[1], ""), ".") + } + return "" +} + +func (self *SMongoDB) GetInstanceType() string { + return self.MachineType +} + +func (self *SMongoDB) GetMaintainTime() string { + return fmt.Sprintf("%s-%s", self.MaintenanceStart, self.MaintenanceEnd) +} + +func (self *SMongoDB) GetPort() int { + return self.Vport +} + +func (self *SMongoDB) Delete() error { + return self.region.DeleteMongoDB(self.InstanceId) +} + +func (self *SRegion) DeleteMongoDB(id string) error { + err := self.IsolateMongoDB(id) + if err != nil { + return errors.Wrapf(err, "IsolateDBInstance") + } + return cloudprovider.Wait(time.Second*10, time.Minute*3, func() (bool, error) { + err = self.OfflineIsolatedMongoDB(id) + if err == nil { + return true, nil + } + if e, ok := errors.Cause(err).(*sdkerrors.TencentCloudSDKError); ok && e.Code == "InvalidParameterValue.LockFailed" { + return false, nil + } + return true, err + }) +} + +func (self *SMongoDB) GetIBackups() ([]cloudprovider.SMongoDBBackup, error) { + return self.region.GetMongoDBBackups(self.InstanceId) +} + +func (self *SRegion) IsolateMongoDB(id string) error { + params := map[string]string{ + "InstanceId": id, + } + _, err := self.mongodbRequest("IsolateDBInstance", params) + return errors.Wrapf(err, "IsolateDBInstance") +} + +func (self *SRegion) OfflineIsolatedMongoDB(id string) error { + params := map[string]string{ + "InstanceId": id, + } + _, err := self.mongodbRequest("OfflineIsolatedDBInstance", params) + return errors.Wrapf(err, "OfflineIsolatedDBInstance") +} + +func (self *SRegion) GetMongoDBs(ids []string, limit, offset int) ([]SMongoDB, int, error) { + if limit < 1 || limit > 100 { + limit = 100 + } + params := map[string]string{ + "Limit": fmt.Sprintf("%d", limit), + "Offset": fmt.Sprintf("%d", offset), + } + for i, id := range ids { + params[fmt.Sprintf("InstanceIds.%d", i)] = id + } + resp, err := self.mongodbRequest("DescribeDBInstances", params) + if err != nil { + return nil, 0, errors.Wrapf(err, "DescribeDBInstances") + } + dbs := []SMongoDB{} + err = resp.Unmarshal(&dbs, "InstanceDetails") + if err != nil { + return nil, 0, errors.Wrapf(err, "resp.Unmarshal") + } + totalCount, _ := resp.Float("TotalCount") + return dbs, int(totalCount), nil +} + +func (self *SRegion) GetICloudMongoDBs() ([]cloudprovider.ICloudMongoDB, error) { + dbs := []SMongoDB{} + for { + part, total, err := self.GetMongoDBs(nil, 100, len(dbs)) + if err != nil { + return nil, errors.Wrapf(err, "GetMongoDBs") + } + dbs = append(dbs, part...) + if len(dbs) >= total { + break + } + } + ret := []cloudprovider.ICloudMongoDB{} + for i := range dbs { + dbs[i].region = self + ret = append(ret, &dbs[i]) + } + return ret, nil +} + +func (self *SRegion) GetMongoDBBackups(id string) ([]cloudprovider.SMongoDBBackup, error) { + params := map[string]string{ + "InstanceId": id, + } + resp, err := self.mongodbRequest("DescribeDBBackups", params) + if err != nil { + return nil, errors.Wrapf(err, "DescribeDBBackups") + } + backups := []struct { + InstanceId string + BackupType int + BackupName string + BackupDesc string + BackupSize int + StartTime time.Time + EndTime time.Time + Status int + BackupMethod int + }{} + err = resp.Unmarshal(&backups, "BackupList") + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + ret := []cloudprovider.SMongoDBBackup{} + for _, backup := range backups { + b := cloudprovider.SMongoDBBackup{ + Name: backup.BackupName, + Description: backup.BackupDesc, + BackupSizeKb: backup.BackupSize, + } + b.StartTime = backup.StartTime.Add(time.Hour * -8) + b.EndTime = backup.EndTime.Add(time.Hour * -8) + switch backup.Status { + case 1: + b.Status = cloudprovider.MongoDBBackupStatusCreating + case 2: + b.Status = cloudprovider.MongoDBBackupStatusAvailable + default: + b.Status = cloudprovider.MongoDBBackupStatusUnknown + } + b.BackupMethod = cloudprovider.MongoDBBackupMethodLogical + if backup.BackupMethod == 0 { + b.BackupMethod = cloudprovider.MongoDBBackupMethodPhysical + } + b.BackupType = cloudprovider.MongoDBBackupTypeAuto + if backup.BackupType == 1 { + b.BackupType = cloudprovider.MongoDBBackupTypeManual + } + ret = append(ret, b) + } + return ret, nil +} + +func (self *SRegion) GetMongoDB(id string) (*SMongoDB, error) { + dbs, _, err := self.GetMongoDBs([]string{id}, 1, 0) + if err != nil { + return nil, errors.Wrapf(err, "GetMongoDB(%s)", id) + } + for i := range dbs { + dbs[i].region = self + return &dbs[i], nil + } + return nil, errors.Wrapf(cloudprovider.ErrNotFound, id) +} + +func (self *SRegion) GetICloudMongoDBById(id string) (cloudprovider.ICloudMongoDB, error) { + db, err := self.GetMongoDB(id) + if err != nil { + return nil, errors.Wrapf(err, "GetMongoDB") + } + return db, nil +} diff --git a/pkg/multicloud/qcloud/qcloud.go b/pkg/multicloud/qcloud/qcloud.go index 3be7384ad8..cbac810930 100644 --- a/pkg/multicloud/qcloud/qcloud.go +++ b/pkg/multicloud/qcloud/qcloud.go @@ -63,6 +63,7 @@ const ( QCLOUD_MEMCACHED_API_VERSION = "2019-03-18" QCLOUD_SSL_API_VERSION = "2019-12-05" QCLOUD_CDN_API_VERSION = "2018-06-06" + QCLOUD_MONGODB_API_VERSION = "2019-07-25" ) type QcloudClientConfig struct { @@ -177,6 +178,12 @@ func redisRequest(client *common.Client, apiName string, params map[string]strin return _jsonRequest(client, domain, QCLOUD_REDIS_API_VERSION, apiName, params, debug, true) } +// mongodb +func mongodbRequest(client *common.Client, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { + domain := apiDomain("mongodb", params) + return _jsonRequest(client, domain, QCLOUD_MONGODB_API_VERSION, apiName, params, debug, true) +} + // memcached func memcachedRequest(client *common.Client, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { domain := apiDomain("memcached", params) @@ -630,6 +637,15 @@ func (client *SQcloudClient) redisRequest(apiName string, params map[string]stri return redisRequest(cli, apiName, params, client.debug) } +func (client *SQcloudClient) mongodbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := client.getDefaultClient() + if err != nil { + return nil, err + } + + return mongodbRequest(cli, apiName, params, client.debug) +} + func (client *SQcloudClient) memcachedRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { cli, err := client.getDefaultClient() if err != nil { @@ -1027,6 +1043,7 @@ func (self *SQcloudClient) GetCapabilities() []string { cloudprovider.CLOUD_CAPABILITY_PUBLIC_IP, cloudprovider.CLOUD_CAPABILITY_INTERVPCNETWORK, cloudprovider.CLOUD_CAPABILITY_SAML_AUTH, + cloudprovider.CLOUD_CAPABILITY_MONGO_DB, } return caps } diff --git a/pkg/multicloud/qcloud/region.go b/pkg/multicloud/qcloud/region.go index fb3f8b264c..11076d50ec 100644 --- a/pkg/multicloud/qcloud/region.go +++ b/pkg/multicloud/qcloud/region.go @@ -686,6 +686,11 @@ func (self *SRegion) redisRequest(apiName string, params map[string]string) (jso return self.client.redisRequest(apiName, params) } +func (self *SRegion) mongodbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + params["Region"] = self.Region + return self.client.mongodbRequest(apiName, params) +} + func (self *SRegion) memcachedRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { params["Region"] = self.Region return self.client.memcachedRequest(apiName, params) diff --git a/pkg/multicloud/qcloud/shell/memcached.go b/pkg/multicloud/qcloud/shell/memcached.go index 35127ff8e2..cbcdd9e1aa 100644 --- a/pkg/multicloud/qcloud/shell/memcached.go +++ b/pkg/multicloud/qcloud/shell/memcached.go @@ -1,3 +1,17 @@ +// 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 ( diff --git a/pkg/multicloud/qcloud/shell/mongodb.go b/pkg/multicloud/qcloud/shell/mongodb.go new file mode 100644 index 0000000000..66beb5a982 --- /dev/null +++ b/pkg/multicloud/qcloud/shell/mongodb.go @@ -0,0 +1,53 @@ +// 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 MongoDBListOptions struct { + Ids []string + Offset int + Limit int + } + shellutils.R(&MongoDBListOptions{}, "mongodb-list", "List mongodb", func(cli *qcloud.SRegion, args *MongoDBListOptions) error { + dbs, _, err := cli.GetMongoDBs(args.Ids, args.Limit, args.Offset) + if err != nil { + return err + } + printList(dbs, 0, 0, 0, []string{}) + return nil + }) + + type MongoDBIdOptions struct { + ID string + } + + shellutils.R(&MongoDBIdOptions{}, "mongodb-isoloate", "Isolate mongodb", func(cli *qcloud.SRegion, args *MongoDBIdOptions) error { + return cli.IsolateMongoDB(args.ID) + }) + + shellutils.R(&MongoDBIdOptions{}, "mongodb-offline", "Offlie mongodb", func(cli *qcloud.SRegion, args *MongoDBIdOptions) error { + return cli.OfflineIsolatedMongoDB(args.ID) + }) + + shellutils.R(&MongoDBIdOptions{}, "mongodb-delete", "Delete mongodb", func(cli *qcloud.SRegion, args *MongoDBIdOptions) error { + return cli.DeleteMongoDB(args.ID) + }) + +} diff --git a/pkg/multicloud/region_base.go b/pkg/multicloud/region_base.go index 2951fd18fe..3386165648 100644 --- a/pkg/multicloud/region_base.go +++ b/pkg/multicloud/region_base.go @@ -198,3 +198,11 @@ func (self *SRegion) CreateICloudWafInstance(opts *cloudprovider.WafCreateOption func (self *SRegion) GetICloudWafRuleGroups() ([]cloudprovider.ICloudWafRuleGroup, error) { return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetICloudWafRuleGroups") } + +func (self *SRegion) GetICloudMongoDBs() ([]cloudprovider.ICloudMongoDB, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetICloudMongoDBs") +} + +func (self *SRegion) GetICloudMongoDBById(id string) (cloudprovider.ICloudMongoDB, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotImplemented, "GetICloudMongoDBById") +}