diff --git a/cmd/climc/shell/compute/elasticcache.go b/cmd/climc/shell/compute/elasticcache.go index 31bc1cbf9b..053e5aabd1 100644 --- a/cmd/climc/shell/compute/elasticcache.go +++ b/cmd/climc/shell/compute/elasticcache.go @@ -46,6 +46,10 @@ func init() { return err } + if len(opts.SecgroupIds) > 0 { + params.Set("secgroup_ids", jsonutils.NewStringArray(opts.SecgroupIds)) + } + result, err := modules.ElasticCache.Create(s, params) if err != nil { return err diff --git a/go.mod b/go.mod index 336e437810..d441392a49 100644 --- a/go.mod +++ b/go.mod @@ -141,7 +141,7 @@ require ( k8s.io/cluster-bootstrap v0.17.3 k8s.io/kubernetes v1.16.0 yunion.io/x/executor v0.0.0-20200227030256-a18417815e74 - yunion.io/x/jsonutils v0.0.0-20201014025429-7ce189228db6 + yunion.io/x/jsonutils v0.0.0-20201022101715-4e3add1ac4aa yunion.io/x/log v0.0.0-20200313080802-57a4ce5966b3 yunion.io/x/ovsdb v0.0.0-20200526071744-27bf0940cbc7 yunion.io/x/pkg v0.0.0-20200814072949-4f1b541857d6 diff --git a/go.sum b/go.sum index cec9d3787d..be8ab3744e 100644 --- a/go.sum +++ b/go.sum @@ -1124,10 +1124,9 @@ vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj yunion.io/x/executor v0.0.0-20200227030256-a18417815e74 h1:A15C6VdVRWvmQ9pAJHrUs9yan5qKlYH7uaRxHg1kRbk= yunion.io/x/executor v0.0.0-20200227030256-a18417815e74/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws= yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634= -yunion.io/x/jsonutils v0.0.0-20201014025429-7ce189228db6 h1:RPpnTSCZwa9VQuWAf7S2dRjnZlYTIM0NH/j5vvUpk9g= -yunion.io/x/jsonutils v0.0.0-20201014025429-7ce189228db6/go.mod h1:p0nyMqGA/apTxxyLIU/o1k4V7Vujl2O6ey30L594sYE= +yunion.io/x/jsonutils v0.0.0-20201022101715-4e3add1ac4aa h1:IbxIwaRpdLJhxAzLc8TzNvCWec79o9wCA8jXDcRszlA= +yunion.io/x/jsonutils v0.0.0-20201022101715-4e3add1ac4aa/go.mod h1:p0nyMqGA/apTxxyLIU/o1k4V7Vujl2O6ey30L594sYE= yunion.io/x/log v0.0.0-20190514041436-04ce53b17c6b/go.mod h1:+gauLs73omeJAPlsXcevLsJLKixV+sR/E7WSYTSx1fE= -yunion.io/x/log v0.0.0-20190629062853-9f6483a7103d h1:59zrDL7Ft+hDukguJRmLr/Gdu/9V75x+yX99ovZwfaA= yunion.io/x/log v0.0.0-20190629062853-9f6483a7103d/go.mod h1:LC6f/4FozL0iaAbnFt2eDX9jlsyo3WiOUPm03d7+U4U= yunion.io/x/log v0.0.0-20200313080802-57a4ce5966b3 h1:5Wc5hkB8PtMudmHuzCyok960RuOa9I55imIGrigSdjs= yunion.io/x/log v0.0.0-20200313080802-57a4ce5966b3/go.mod h1:LC6f/4FozL0iaAbnFt2eDX9jlsyo3WiOUPm03d7+U4U= diff --git a/pkg/apis/compute/elasticcache.go b/pkg/apis/compute/elasticcache.go index 9070b4da21..f40ea541f0 100644 --- a/pkg/apis/compute/elasticcache.go +++ b/pkg/apis/compute/elasticcache.go @@ -14,7 +14,11 @@ package compute -import "yunion.io/x/onecloud/pkg/apis" +import ( + "time" + + "yunion.io/x/onecloud/pkg/apis" +) type ElasticcacheDetails struct { apis.VirtualResourceDetails @@ -25,6 +29,9 @@ type ElasticcacheDetails struct { // IP子网名称 Network string `json:"network"` + + // 关联安全组列表 + Secgroups []apis.StandaloneShortDesc `json:"secgroups"` } type ElasticcacheResourceInfo struct { @@ -99,3 +106,112 @@ type ElasticcacheRemoteUpdateInput struct { // 是否覆盖替换所有标签 ReplaceTags *bool `json:"replace_tags" help:"replace all remote tags"` } + +//type SElasticcacheJointsBase struct { +// apis.SVirtualJointResourceBase +// // 弹性缓存实例(ID or Name) +// ElasticcacheId string `json:"elasticcache_id"` +//} + +type ElasticcacheJointResourceDetails struct { + apis.VirtualJointResourceBaseDetails + + // 弹性缓存实例名称 + Elasticcache string `json:"elasticcache"` + // 弹性缓存实例ID + ElasticcacheId string `json:"elasticcache_id"` +} + +type ElasticcacheJointsListInput struct { + apis.VirtualJointResourceBaseListInput + + ElasticcacheFilterListInput +} + +type ElasticcacheJointBaseUpdateInput struct { + apis.VirtualJointResourceBaseUpdateInput +} + +type ElasticcacheSecgroupsInput struct { + // 安全组Id列表 + // 实例必须处于运行状态 + // + // + // | 平台 | 最多绑定安全组数量 | + // |-------------|------------------- | + // | 腾讯云 | 10 | + // | 华为云 | 不支持安全组 | + // | 阿里云 | 不支持安全组 | + SecgroupIds []string `json:"secgroup_ids"` +} + +type ElasticcacheCreateInput struct { + apis.VirtualResourceCreateInput + + // 安全组列表 + // 腾讯云需要传此参数 + // required: false + ElasticcacheSecgroupsInput + + // 主可用区名称或Id + Zone string `json:"zone"` + + // Ip子网名称或Id,建议使用Id + // required: true + Network string `json:"network"` + + // 网络类型 + // enum: vpc, cLassic + // required: true + NetworkType string `json:"network_type"` + + // 弹性缓存Engine + // enum: redis, memcache + // required: true + Engine string `json:"engine"` + + // 弹性缓存Engine版本 + // required: false + EngineVersion string `json:"engine_version"` + + // 实例规格 + // required: false + InstanceType string `json:"instance_type"` + + // 初始密码 + // required: false + Password string `json:"password"` + + // 安全组名称或Id + // default: default + Secgroup string `json:"secgroup"` + + // 内网IP + // 阿里云、华为云此参数可选,其它公有云该参数无效 + // required: false + PrivateIp string `json:"private_ip"` + + // swagger:ignore + VpcId string + + // swagger:ignore + ManagerId string + + // 包年包月时间周期 + Duration string `json:"duration"` + + // swagger:ignore + ExpiredAt time.Time `json:"expired_at"` + + // 计费方式 + // enum: postpaid, prepaid + BillingType string + // swagger:ignore + BillingCycle string + + // 弹性缓存维护时间段 + // 华为云此参数可选,其它云该参数无效 + // enum: 22:00:00, 02:00:00, 06:00:00, 10:00:00, 14:00:00, 18:00:00 + // required: false + MaintainStartTime string `json:"maintain_start_time"` +} diff --git a/pkg/apis/compute/elasticcache_const.go b/pkg/apis/compute/elasticcache_const.go index 4054aa5c44..43e239aa37 100644 --- a/pkg/apis/compute/elasticcache_const.go +++ b/pkg/apis/compute/elasticcache_const.go @@ -29,6 +29,7 @@ const ( ELASTIC_CACHE_STATUS_INACTIVE = "inactive" //(被禁用) ELASTIC_CACHE_STATUS_FLUSHING = "flushing" //(清除中) ELASTIC_CACHE_STATUS_FLUSHING_FAILED = "flushing_failed" //(清除失败) + ELASTIC_CACHE_STATUS_RELEASING = "releasing" //(释放中) ELASTIC_CACHE_STATUS_RELEASED = "released" //(已释放) ELASTIC_CACHE_STATUS_RELEASE_FAILED = "release_failed" //(释放失败) ELASTIC_CACHE_STATUS_TRANSFORMING = "transforming" //(转换中) @@ -52,6 +53,7 @@ const ( ELASTIC_CACHE_ACCOUNT_STATUS_CREATE_FAILED = "create_failed" //(创建失败) ELASTIC_CACHE_ACCOUNT_STATUS_DELETING = "deleting" // 删除中 ELASTIC_CACHE_ACCOUNT_STATUS_DELETE_FAILED = "delete_failed" // 删除失败 + ELASTIC_CACHE_ACCOUNT_STATUS_DELETED = "deleted" // 已删除 ) const ( diff --git a/pkg/apis/compute/secgroup.go b/pkg/apis/compute/secgroup.go index aa35498d7f..7eacd47248 100644 --- a/pkg/apis/compute/secgroup.go +++ b/pkg/apis/compute/secgroup.go @@ -135,6 +135,7 @@ type SecgroupListInput struct { ServerFilterListInput DBInstanceResourceInput + ELasticcacheResourceInput // equals Equals string @@ -242,6 +243,11 @@ type GuestsecgroupListInput struct { SecgroupFilterListInput } +type ElasticcachesecgroupListInput struct { + ElasticcacheJointsListInput + SecgroupFilterListInput +} + type GuestsecgroupDetails struct { GuestJointResourceDetails @@ -251,6 +257,20 @@ type GuestsecgroupDetails struct { Secgroup string `json:"secgroup"` } +//type SElasticcachesecgroup struct { +// SElasticcacheJointsBase +// SSecurityGroupResourceBase +//} + +type ElasticcachesecgroupDetails struct { + ElasticcacheJointResourceDetails + + SElasticcachesecgroup + + // 安全组名称 + Secgroup string `json:"secgroup"` +} + type SecgroupMergeInput struct { // 安全组id列表 SecgroupIds []string `json:"secgroup_ids"` diff --git a/pkg/apis/compute/zz_generated.model.go b/pkg/apis/compute/zz_generated.model.go index c65a0019ab..505c983482 100644 --- a/pkg/apis/compute/zz_generated.model.go +++ b/pkg/apis/compute/zz_generated.model.go @@ -681,6 +681,12 @@ type SElasticcacheBackup struct { EndTime time.Time `json:"end_time"` } +// SElasticcacheJointsBase is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SElasticcacheJointsBase. +type SElasticcacheJointsBase struct { + apis.SVirtualJointResourceBase + SElasticcacheResourceBase +} + // SElasticcacheParameter is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SElasticcacheParameter. type SElasticcacheParameter struct { apis.SStatusStandaloneResourceBase @@ -755,6 +761,12 @@ type SElasticcacheSku struct { Provider string `json:"provider"` } +// SElasticcachesecgroup is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SElasticcachesecgroup. +type SElasticcachesecgroup struct { + SElasticcacheJointsBase + SSecurityGroupResourceBase +} + // SElasticip is an autogenerated struct via yunion.io/x/onecloud/pkg/compute/models.SElasticip. type SElasticip struct { apis.SVirtualResourceBase diff --git a/pkg/cloudprovider/elasticcache.go b/pkg/cloudprovider/elasticcache.go index c4a91f92cc..9dfd596811 100644 --- a/pkg/cloudprovider/elasticcache.go +++ b/pkg/cloudprovider/elasticcache.go @@ -34,7 +34,7 @@ type SCloudElasticCacheInput struct { Engine string // Redis|Memcache EngineVersion string // 版本类型 PrivateIpAddress string // 指定新实例的内网IP地址。 - SecurityGroupId string // 安全组ID + SecurityGroupIds []string // 安全组ID EipId string // 绑定弹性IP MaintainBegin string // 维护时间窗开始时间,格式为HH:mm:ss MaintainEnd string // 维护时间窗结束时间,格式为HH:mm:ss @@ -70,3 +70,7 @@ type SCloudElasticCacheBackupPolicyUpdateInput struct { PreferredBackupPeriod string // Monday(周一) / Tuesday(周二) / Wednesday(周三) / Thursday(周四) / Friday(周五) / Saturday(周六) / Sunday(周日) PreferredBackupTime string // 备份时间,格式:HH:mmZ-HH:mmZ } + +type SCloudElasticCacheFlushInstanceInput struct { + Password string // root账号密码. requied by qcloud +} diff --git a/pkg/cloudprovider/resources.go b/pkg/cloudprovider/resources.go index 3160ae6e03..3058d5b352 100644 --- a/pkg/cloudprovider/resources.go +++ b/pkg/cloudprovider/resources.go @@ -888,6 +888,7 @@ type ICloudElasticcache interface { GetMaintainEndTime() string GetAuthMode() string + GetSecurityGroupIds() ([]string, error) GetICloudElasticcacheAccounts() ([]ICloudElasticcacheAccount, error) GetICloudElasticcacheAcls() ([]ICloudElasticcacheAcl, error) @@ -907,13 +908,14 @@ type ICloudElasticcache interface { CreateAccount(account SCloudElasticCacheAccountInput) (ICloudElasticcacheAccount, error) CreateAcl(aclName, securityIps string) (ICloudElasticcacheAcl, error) - CreateBackup() (ICloudElasticcacheBackup, error) - FlushInstance() error + CreateBackup(desc string) (ICloudElasticcacheBackup, error) + FlushInstance(input SCloudElasticCacheFlushInstanceInput) error UpdateAuthMode(noPasswordAccess bool) error UpdateInstanceParameters(config jsonutils.JSONObject) error UpdateBackupPolicy(config SCloudElasticCacheBackupPolicyUpdateInput) error SetMetadata(tags map[string]string, replace bool) error + UpdateSecurityGroups(secgroupIds []string) error } type ICloudElasticcacheAccount interface { diff --git a/pkg/compute/models/cloudsyncelasticcache.go b/pkg/compute/models/cloudsyncelasticcache.go index fb239225f1..dbddff5379 100644 --- a/pkg/compute/models/cloudsyncelasticcache.go +++ b/pkg/compute/models/cloudsyncelasticcache.go @@ -18,6 +18,8 @@ import ( "context" "fmt" + "github.com/pkg/errors" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -53,6 +55,7 @@ func syncElasticcaches(ctx context.Context, userCred mcclient.TokenCredential, s syncElasticcacheAccounts(ctx, userCred, syncResults, &localInstances[i], remoteInstances[i]) syncElasticcacheAcls(ctx, userCred, syncResults, &localInstances[i], remoteInstances[i]) syncElasticcacheBackups(ctx, userCred, syncResults, &localInstances[i], remoteInstances[i]) + syncElasticcacheSecgroups(ctx, userCred, syncResults, &localInstances[i], remoteInstances[i]) }() } } @@ -97,7 +100,11 @@ func syncElasticcacheAcls(ctx context.Context, userCred mcclient.TokenCredential acls, err := remoteInstance.GetICloudElasticcacheAcls() if err != nil { msg := fmt.Sprintf("GetIElasticcacheAcls for dbinstance %s failed %s", remoteInstance.GetName(), err) - log.Errorf(msg) + if errors.Cause(err) == cloudprovider.ErrNotSupported { + log.Warningf(msg) + } else { + log.Errorf(msg) + } return } @@ -128,3 +135,25 @@ func syncElasticcacheBackups(ctx context.Context, userCred mcclient.TokenCredent return } } + +func syncElasticcacheSecgroups(ctx context.Context, userCred mcclient.TokenCredential, syncResults SSyncResultSet, localInstance *SElasticcache, remoteInstance cloudprovider.ICloudElasticcache) { + secgroupIds, err := remoteInstance.GetSecurityGroupIds() + if err != nil { + msg := fmt.Sprintf("Elasticcache.GetSecurityGroupIds for dbinstance %s failed %s", remoteInstance.GetName(), err) + if errors.Cause(err) == cloudprovider.ErrNotSupported { + log.Warningf(msg) + } else { + log.Errorf(msg) + } + return + } + + result := localInstance.SyncElasticcacheSecgroups(ctx, userCred, secgroupIds) + syncResults.Add(ElasticcacheAclManager, result) + + msg := result.Result() + log.Infof("SyncElasticcacheSecgroups for dbinstance %s result: %s", localInstance.Name, msg) + if result.IsError() { + return + } +} diff --git a/pkg/compute/models/elasticcache_accounts.go b/pkg/compute/models/elasticcache_accounts.go index c89c1e17ce..f11e4f5233 100644 --- a/pkg/compute/models/elasticcache_accounts.go +++ b/pkg/compute/models/elasticcache_accounts.go @@ -297,6 +297,29 @@ func (self *SElasticcacheAccount) GetCreateAliyunElasticcacheAccountParams() (cl return ret, nil } +func (self *SElasticcacheAccount) GetCreateQcloudElasticcacheAccountParams() (cloudprovider.SCloudElasticCacheAccountInput, error) { + ret := cloudprovider.SCloudElasticCacheAccountInput{} + ret.AccountName = self.Name + ret.Description = self.Description + passwd, err := self.GetDecodedPassword() + if err != nil { + return ret, err + } + + ret.AccountPassword = passwd + + switch self.AccountPrivilege { + case "read": + ret.AccountPrivilege = "r" + case "write": + ret.AccountPrivilege = "rw" + default: + return ret, fmt.Errorf("ElasticcacheAccount.GetUpdateQcloudElasticcacheAccountParams invalid account_privilege %s", self.AccountPrivilege) + } + + return ret, nil +} + func (self *SElasticcacheAccount) GetUpdateAliyunElasticcacheAccountParams(data jsonutils.JSONDict) (cloudprovider.SCloudElasticCacheAccountUpdateInput, error) { ret := cloudprovider.SCloudElasticCacheAccountUpdateInput{} @@ -363,6 +386,45 @@ func (self *SElasticcacheAccount) GetUpdateHuaweiElasticcacheAccountParams(data return ret, nil } +func (self *SElasticcacheAccount) GetUpdateQcloudElasticcacheAccountParams(data jsonutils.JSONDict) (cloudprovider.SCloudElasticCacheAccountUpdateInput, error) { + ret := cloudprovider.SCloudElasticCacheAccountUpdateInput{} + + if desc, _ := data.GetString("description"); len(desc) > 0 { + ret.Description = &desc + } + + if password, _ := data.GetString("password"); len(password) > 0 { + ret.Password = &password + } + + if ok := data.Contains("no_password_access"); ok { + passwordAccess, _ := data.Bool("no_password_access") + if self.AccountType == api.ELASTIC_CACHE_ACCOUNT_TYPE_ADMIN { + ret.NoPasswordAccess = &passwordAccess + } else { + if passwordAccess == false { + return ret, fmt.Errorf("ElasticcacheAccount.GetUpdateQcloudElasticcacheAccountParams normal account not support no auth access") + } + } + } + + if privilege, _ := data.GetString("account_privilege"); len(privilege) > 0 { + var p string + switch privilege { + case "read": + p = "r" + case "write": + p = "rw" + default: + return ret, fmt.Errorf("ElasticcacheAccount.GetUpdateQcloudElasticcacheAccountParams invalid account_privilege %s", privilege) + } + + ret.AccountPrivilege = &p + } + + return ret, nil +} + func (self *SElasticcacheAccount) SavePassword(passwd string) error { passwd, err := utils.EncryptAESBase64(self.Id, passwd) if err != nil { diff --git a/pkg/compute/models/elasticcache_instances.go b/pkg/compute/models/elasticcache_instances.go index 8894fc1e77..3cd8d099dc 100644 --- a/pkg/compute/models/elasticcache_instances.go +++ b/pkg/compute/models/elasticcache_instances.go @@ -16,6 +16,7 @@ package models import ( "context" + "database/sql" "fmt" "regexp" "strings" @@ -217,6 +218,7 @@ func (manager *SElasticcacheManager) FetchCustomizeColumns( zoneRows := manager.SZoneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) netIds := make([]string, len(objs)) + cacheIds := make([]string, len(objs)) for i := range rows { rows[i] = api.ElasticcacheDetails{ VirtualResourceDetails: virtRows[i], @@ -224,6 +226,7 @@ func (manager *SElasticcacheManager) FetchCustomizeColumns( ZoneResourceInfoBase: zoneRows[i].ZoneResourceInfoBase, } netIds[i] = objs[i].(*SElasticcache).NetworkId + cacheIds[i] = objs[i].(*SElasticcache).Id } networks := make(map[string]SNetwork) @@ -239,6 +242,19 @@ func (manager *SElasticcacheManager) FetchCustomizeColumns( } } + if len(fields) == 0 || fields.Contains("secgroups") || fields.Contains("secgroup") { + gsgs := fetchElasticcacheSecgroups(cacheIds) + if gsgs != nil { + for i := range rows { + if gsg, ok := gsgs[cacheIds[i]]; ok { + if len(fields) == 0 || fields.Contains("secgroups") { + rows[i].Secgroups = gsg + } + } + } + } + } + return rows } @@ -680,7 +696,7 @@ func (manager *SElasticcacheManager) AllowCreateItem(ctx context.Context, userCr } func (manager *SElasticcacheManager) BatchCreateValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { - input, err := manager.ValidateCreateData(ctx, userCred, ownerId, query, data) + input, err := manager.validateCreateData(ctx, userCred, ownerId, query, data) if err != nil { return nil, err } @@ -688,7 +704,12 @@ func (manager *SElasticcacheManager) BatchCreateValidateCreateData(ctx context.C return input, nil } -func (manager *SElasticcacheManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { +func (manager *SElasticcacheManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.ElasticcacheCreateInput) (*jsonutils.JSONDict, error) { + data := input.JSON(&input) + return manager.validateCreateData(ctx, userCred, ownerId, query, data) +} + +func (manager *SElasticcacheManager) validateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { var region *SCloudregion if id, _ := data.GetString("network"); len(id) > 0 { network, err := db.FetchByIdOrName(NetworkManager, userCred, strings.Split(id, ",")[0]) @@ -743,6 +764,10 @@ func (self *SElasticcache) PostCreate(ctx context.Context, userCred mcclient.Tok params := jsonutils.NewDict() params.Set("password", jsonutils.NewString(password)) + secgroupIds, err := data.Get("secgroup_ids") + if err == nil { + params.Set("secgroup_ids", secgroupIds) + } self.SetStatus(userCred, api.ELASTIC_CACHE_STATUS_DEPLOYING, "") if err := self.StartElasticcacheCreateTask(ctx, userCred, params, ""); err != nil { log.Errorf("Failed to create elastic cache error: %v", err) @@ -942,7 +967,7 @@ func (self *SElasticcache) GetCreateHuaweiElasticcacheParams(data *jsonutils.JSO return nil, errors.Wrap(fmt.Errorf("cached security group not found"), "elasticcache.GetCreateHuaweiElasticcacheParams.SecurityGroup") } - input.SecurityGroupId = sgCache.GetExternalId() + input.SecurityGroupIds = []string{sgCache.GetExternalId()} } if len(self.MaintainEndTime) > 0 { @@ -952,6 +977,71 @@ func (self *SElasticcache) GetCreateHuaweiElasticcacheParams(data *jsonutils.JSO return input, nil } +func (self *SElasticcache) GetCreateQCloudElasticcacheParams(data *jsonutils.JSONDict) (*cloudprovider.SCloudElasticCacheInput, error) { + input := &cloudprovider.SCloudElasticCacheInput{} + iregion, err := self.GetIRegion() + if err != nil { + return nil, fmt.Errorf("elastic cache %s(%s) region not found", self.Name, self.Id) + } else { + input.RegionId = iregion.GetId() + } + + input.InstanceType = self.InstanceType + input.InstanceName = self.GetName() + + if password, _ := data.GetString("password"); len(password) > 0 { + input.Password = password + } + + zone := self.GetZone() + if zone != nil { + izone, err := iregion.GetIZoneById(zone.ExternalId) + if err != nil { + return nil, errors.Wrap(err, "elasticcache.GetCreateHuaweiElasticcacheParams.Zone") + } + input.ZoneIds = []string{izone.GetId()} + } + + switch self.BillingType { + case billing.BILLING_TYPE_PREPAID: + input.ChargeType = "PrePaid" + billingCycle, err := bc.ParseBillingCycle(self.BillingCycle) + if err != nil { + return nil, errors.Wrap(err, "elasticcache.GetCreateHuaweiElasticcacheParams.BillingCycle") + } + input.BC = &billingCycle + default: + input.ChargeType = "PostPaid" + } + + switch self.NetworkType { + case api.LB_NETWORK_TYPE_CLASSIC: + input.NetworkType = "CLASSIC" + default: + input.NetworkType = "VPC" + } + + if ivpc, err := db.FetchById(VpcManager, self.VpcId); err != nil { + return nil, errors.Wrap(err, "elasticcache.GetCreateHuaweiElasticcacheParams.Vpc") + } else { + if ivpc != nil { + vpc := ivpc.(*SVpc) + input.VpcId = vpc.ExternalId + } + } + + if inetwork, err := db.FetchById(NetworkManager, self.NetworkId); err != nil { + return nil, errors.Wrap(err, "elasticcache.GetCreateHuaweiElasticcacheParams.Network") + } else { + if inetwork != nil { + network := inetwork.(*SNetwork) + input.NetworkId = network.ExternalId + } + } + + return input, nil +} + func (self *SElasticcache) AllowPerformRestart(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "restart") } @@ -1313,7 +1403,7 @@ func (self *SElasticcache) AllowPerformFlushInstance(ctx context.Context, userCr func (self *SElasticcache) PerformFlushInstance(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { self.SetStatus(userCred, api.ELASTIC_CACHE_STATUS_FLUSHING, "") - return nil, self.StartFlushInstanceTask(ctx, userCred, jsonutils.NewDict(), "") + return nil, self.StartFlushInstanceTask(ctx, userCred, data.(*jsonutils.JSONDict), "") } func (self *SElasticcache) StartFlushInstanceTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { @@ -1437,11 +1527,11 @@ func (self *SElasticcache) AllowPerformSync(ctx context.Context, userCred mcclie } func (self *SElasticcache) PerformSync(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - self.SetStatus(userCred, api.ELASTIC_CACHE_STATUS_SYNCING, "") return nil, self.StartSyncTask(ctx, userCred, data.(*jsonutils.JSONDict), "") } func (self *SElasticcache) StartSyncTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + self.SetStatus(userCred, api.ELASTIC_CACHE_STATUS_SYNCING, "") task, err := taskman.TaskManager.NewTask(ctx, "ElasticcacheSyncTask", self, userCred, params, parentTaskId, "", nil) if err != nil { return err @@ -1750,3 +1840,303 @@ func (self *SElasticcache) OnMetadataUpdated(ctx context.Context, userCred mccli log.Errorf("StartRemoteUpdateTask fail: %s", err) } } + +func (self *SElasticcache) getSecgroupsBySecgroupExternalIds(externalIds []string) ([]SSecurityGroup, error) { + vpc := self.GetVpc() + if vpc == nil { + return nil, errors.Wrap(errors.ErrNotFound, "GetVpc") + } + + return getSecgroupsBySecgroupExternalIds(vpc.ManagerId, externalIds) +} + +func (self *SElasticcache) GetElasticcacheSecgroups() ([]SElasticcachesecgroup, error) { + ess := []SElasticcachesecgroup{} + q := ElasticcachesecgroupManager.Query().Equals("elasticcache_id", self.Id) + err := db.FetchModelObjects(ElasticcachesecgroupManager, q, &ess) + if err != nil { + return nil, errors.Wrapf(err, "db.FetchModelObjects") + } + return ess, nil +} + +func (self *SElasticcache) validateSecgroupInput(secgroups []string) error { + if !utils.IsInStringArray(self.Status, []string{api.ELASTIC_CACHE_STATUS_RUNNING, api.ELASTIC_CACHE_STATUS_DEPLOYING}) { + return httperrors.NewInputParameterError("Cannot add security groups in status %s", self.Status) + } + + region := self.GetRegion() + if region == nil { + return httperrors.NewNotFoundError("region") + } + + driver := region.GetDriver() + if driver == nil { + return httperrors.NewNotFoundError("regiondriver") + } + + maxCount := driver.GetMaxElasticcacheSecurityGroupCount() + if !driver.IsSupportedElasticcacheSecgroup() || maxCount == 0 { + return httperrors.NewNotSupportedError("not supported bind security group") + } + + if len(secgroups) > maxCount { + return httperrors.NewOutOfLimitError("beyond security group quantity limit, max items %d.", maxCount) + } + + return nil +} + +func CheckingSecgroupIds(ctx context.Context, userCred mcclient.TokenCredential, secgroupIds []string) ([]string, error) { + secgroupNames := []string{} + for _, secgroupId := range secgroupIds { + secgrp, err := SecurityGroupManager.FetchByIdOrName(userCred, secgroupId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("secgroup", secgroupId) + } + return nil, httperrors.NewGeneralError(errors.Wrapf(err, "FetchByIdOrName(%s)", secgroupId)) + } + + err = SecurityGroupManager.ValidateName(secgrp.GetName()) + if err != nil { + return nil, httperrors.NewInputParameterError("The secgroup name %s does not meet the requirements, please change the name", secgrp.GetName()) + } + + secgroupNames = append(secgroupNames, secgrp.GetName()) + } + + return secgroupNames, nil +} + +func (self *SElasticcache) checkingSecgroupIds(ctx context.Context, userCred mcclient.TokenCredential, secgroupIds []string) ([]string, error) { + return CheckingSecgroupIds(ctx, userCred, secgroupIds) +} + +// 返回 本次更新后secgroup id的全集、本次更增的secgroup id, 本次删除的secgroup id, error +func (self *SElasticcache) cleanSecgroupIds(action string, secgroupIds []string) ([]string, []string, []string, error) { + ess, err := self.GetElasticcacheSecgroups() + if err != nil { + return nil, nil, nil, errors.Wrap(err, "GetElasticcacheSecgroups") + } + + currentIds := make([]string, len(ess)) + for i := range ess { + currentIds[i] = ess[i].SecgroupId + } + + all := []string{} + adds := []string{} + removes := []string{} + + switch action { + case "add": + all = currentIds + for i := range secgroupIds { + if !utils.IsInStringArray(secgroupIds[i], currentIds) && !utils.IsInStringArray(secgroupIds[i], adds) { + adds = append(adds, secgroupIds[i]) + all = append(all, secgroupIds[i]) + } + } + case "revoke": + for i := range secgroupIds { + if utils.IsInStringArray(secgroupIds[i], currentIds) && !utils.IsInStringArray(secgroupIds[i], adds) { + removes = append(removes, secgroupIds[i]) + } + } + + for i := range currentIds { + if !utils.IsInStringArray(currentIds[i], removes) { + all = append(all, currentIds[i]) + } + } + case "set": + for i := range secgroupIds { + if !utils.IsInStringArray(secgroupIds[i], all) { + all = append(all, secgroupIds[i]) + } + } + + for i := range currentIds { + if !utils.IsInStringArray(currentIds[i], all) { + removes = append(removes, currentIds[i]) + } + } + + for i := range all { + if !utils.IsInStringArray(all[i], currentIds) { + adds = append(adds, all[i]) + } + } + default: + return nil, nil, nil, fmt.Errorf("not supported cleanSecgroupIds action %s", action) + } + + return all, adds, removes, nil +} + +func (self *SElasticcache) addSecgroup(ctx context.Context, userCred mcclient.TokenCredential, secgroupId string) error { + es := &SElasticcachesecgroup{} + es.SetModelManager(GuestsecgroupManager, es) + es.ElasticcacheId = self.Id + es.SecgroupId = secgroupId + err := ElasticcachesecgroupManager.TableSpec().Insert(ctx, es) + if err != nil { + return errors.Wrap(err, "ElasticcachesecgroupManager.Insert") + } + + return nil +} + +func (self *SElasticcache) removeSecgroup(ctx context.Context, userCred mcclient.TokenCredential, secgroupId string) error { + q := ElasticcachesecgroupManager.Query().Equals("secgroup_id", secgroupId).Equals("elasticcache_id", self.GetId()) + ret := []SElasticcachesecgroup{} + err := db.FetchModelObjects(ElasticcachesecgroupManager, q, &ret) + if err != nil { + if errors.Cause(err) != sql.ErrNoRows { + return errors.Wrap(err, "FetchModelObjects") + } else { + return nil + } + } + + for i := range ret { + err = ret[i].Delete(ctx, userCred) + if err != nil { + return errors.Wrapf(err, "Delete elasticcache %s secgroup %s", self.Name, secgroupId) + } + } + + return nil +} + +func (self *SElasticcache) saveSecgroups(ctx context.Context, userCred mcclient.TokenCredential, adds []string, removes []string) compare.SyncResult { + saveResult := compare.SyncResult{} + + for i := range adds { + err := self.addSecgroup(ctx, userCred, adds[i]) + if err != nil { + saveResult.Error(errors.Wrap(err, "addSecgroup")) + return saveResult + } else { + saveResult.Add() + } + } + + for i := range removes { + err := self.removeSecgroup(ctx, userCred, removes[i]) + if err != nil { + saveResult.Error(errors.Wrap(err, "removeSecgroup")) + return saveResult + } else { + saveResult.Delete() + } + } + + return saveResult +} + +func (self *SElasticcache) ProcessElasticcacheSecgroupsInput(ctx context.Context, userCred mcclient.TokenCredential, action string, input *api.ElasticcacheSecgroupsInput) ([]string, error) { + all, adds, removes, err := self.cleanSecgroupIds(action, input.SecgroupIds) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + + if len(all) == 0 { + return nil, httperrors.NewInputParameterError("secgroups will be empty after update.") + } + + switch action { + case "add": + input.SecgroupIds = adds + case "revoke": + input.SecgroupIds = removes + case "set": + input.SecgroupIds = all + } + + err = self.validateSecgroupInput(all) + if err != nil { + return nil, err + } + + names, err := self.checkingSecgroupIds(ctx, userCred, input.SecgroupIds) + if err != nil { + return nil, err + } + + result := self.saveSecgroups(ctx, userCred, adds, removes) + if result.IsError() { + return nil, result.AllError() + } + + return names, nil +} + +func (self *SElasticcache) SyncSecgroup(ctx context.Context, userCred mcclient.TokenCredential, action string, input api.ElasticcacheSecgroupsInput) (jsonutils.JSONObject, error) { + names, err := self.ProcessElasticcacheSecgroupsInput(ctx, userCred, action, &input) + if err != nil { + return nil, err + } + + logclient.AddActionLogWithContext(ctx, self, logclient.ACT_SYNC_CONF, names, userCred, true) + return nil, self.StartSyncSecgroupsTask(ctx, userCred, nil, "") +} + +func (self *SElasticcache) AllowPerformAddSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "add-secgroup") +} + +func (self *SElasticcache) PerformAddSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ElasticcacheSecgroupsInput) (jsonutils.JSONObject, error) { + return self.SyncSecgroup(ctx, userCred, "add", input) +} + +func (self *SElasticcache) AllowPerformRevokeSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "revoke-secgroup") +} + +func (self *SElasticcache) PerformRevokeSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ElasticcacheSecgroupsInput) (jsonutils.JSONObject, error) { + return self.SyncSecgroup(ctx, userCred, "revoke", input) +} + +func (self *SElasticcache) AllowPerformSetSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool { + return self.IsOwner(userCred) || db.IsAdminAllowPerform(userCred, self, "set-secgroup") +} + +func (self *SElasticcache) PerformSetSecgroup(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input api.ElasticcacheSecgroupsInput) (jsonutils.JSONObject, error) { + return self.SyncSecgroup(ctx, userCred, "set", input) +} + +func (self *SElasticcache) SyncElasticcacheSecgroups(ctx context.Context, userCred mcclient.TokenCredential, externalIds []string) compare.SyncResult { + syncResult := compare.SyncResult{} + + secgroups, err := self.getSecgroupsBySecgroupExternalIds(externalIds) + if err != nil { + syncResult.Error(err) + return syncResult + } + + secgroupIds := []string{} + for _, secgroup := range secgroups { + secgroupIds = append(secgroupIds, secgroup.Id) + } + + _, adds, removes, err := self.cleanSecgroupIds("set", secgroupIds) + if err != nil { + syncResult.Error(err) + return syncResult + } + + return self.saveSecgroups(ctx, userCred, adds, removes) +} + +func (self *SElasticcache) StartSyncSecgroupsTask(ctx context.Context, userCred mcclient.TokenCredential, params *jsonutils.JSONDict, parentTaskId string) error { + self.SetStatus(userCred, api.ELASTIC_CACHE_STATUS_SYNCING, "") + task, err := taskman.TaskManager.NewTask(ctx, "ElasticcacheSyncsecgroupsTask", self, userCred, params, parentTaskId, "", nil) + if err != nil { + return err + } + + task.ScheduleRun(nil) + return nil +} diff --git a/pkg/compute/models/elasticcache_joints.go b/pkg/compute/models/elasticcache_joints.go new file mode 100644 index 0000000000..f06fa22c8c --- /dev/null +++ b/pkg/compute/models/elasticcache_joints.go @@ -0,0 +1,186 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/reflectutils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SElasticcacheJointsManager struct { + db.SVirtualJointResourceBaseManager + SElasticcacheResourceBaseManager +} + +func NewElasticcacheJointsManager(dt interface{}, tableName string, keyword string, keywordPlural string, slave db.IVirtualModelManager) SElasticcacheJointsManager { + return SElasticcacheJointsManager{ + SVirtualJointResourceBaseManager: db.NewVirtualJointResourceBaseManager( + dt, + tableName, + keyword, + keywordPlural, + ElasticcacheManager, + slave, + ), + } +} + +type SElasticcacheJointsBase struct { + db.SVirtualJointResourceBase + SElasticcacheResourceBase +} + +func (self *SElasticcacheJointsBase) getElasticcache() *SElasticcache { + ec, _ := ElasticcacheManager.FetchById(self.ElasticcacheId) + return ec.(*SElasticcache) +} + +func (manager *SElasticcacheJointsManager) GetMasterFieldName() string { + return "elasticcache_id" +} + +func (self *SElasticcacheJointsBase) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.ElasticcacheJointResourceDetails, error) { + return api.ElasticcacheJointResourceDetails{}, nil +} + +func (manager *SElasticcacheJointsManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.ElasticcacheJointResourceDetails { + rows := make([]api.ElasticcacheJointResourceDetails, len(objs)) + + jointRows := manager.SVirtualJointResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + + ecIds := make([]string, len(rows)) + for i := range rows { + rows[i] = api.ElasticcacheJointResourceDetails{ + VirtualJointResourceBaseDetails: jointRows[i], + } + var base *SElasticcacheJointsBase + reflectutils.FindAnonymouStructPointer(objs[i], &base) + if base != nil && len(base.ElasticcacheId) > 0 { + ecIds[i] = base.ElasticcacheId + } + } + + ecIdMaps, err := db.FetchIdNameMap2(ElasticcacheManager, ecIds) + if err != nil { + log.Errorf("db.FetchIdNameMap2 fail %s", err) + return rows + } + + for i := range rows { + if name, ok := ecIdMaps[ecIds[i]]; ok { + rows[i].Elasticcache = name + rows[i].ElasticcacheId = ecIds[i] + } + } + + return rows +} + +func (manager *SElasticcacheJointsManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.ElasticcacheJointsListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SVirtualJointResourceBaseManager.ListItemFilter(ctx, q, userCred, query.VirtualJointResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SVirtualJointResourceBaseManager.ListItemFilter") + } + q, err = manager.SElasticcacheResourceBaseManager.ListItemFilter(ctx, q, userCred, query.ElasticcacheFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SElasticcacheResourceBaseManager.ListItemFilter") + } + + return q, nil +} + +func (manager *SElasticcacheJointsManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.ElasticcacheJointsListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SVirtualJointResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.VirtualJointResourceBaseListInput) + if err != nil { + return nil, errors.Wrap(err, "SVirtualJointResourceBaseManager.OrderByExtraFields") + } + q, err = manager.SElasticcacheResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.ElasticcacheFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SElasticcacheResourceBaseManager.OrderByExtraFields") + } + + return q, nil +} + +func (self *SElasticcacheJointsBase) ValidateUpdateData( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + input api.ElasticcacheJointBaseUpdateInput, +) (api.ElasticcacheJointBaseUpdateInput, error) { + var err error + input.VirtualJointResourceBaseUpdateInput, err = self.SVirtualJointResourceBase.ValidateUpdateData(ctx, userCred, query, input.VirtualJointResourceBaseUpdateInput) + if err != nil { + return input, errors.Wrap(err, "SVirtualJointResourceBase.ValidateUpdateData") + } + return input, nil +} + +func (manager *SElasticcacheJointsManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SVirtualJointResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SVirtualJointResourceBaseManager.ListItemExportKeys") + } + if keys.ContainsAny(manager.SElasticcacheResourceBaseManager.GetExportKeys()...) { + q, err = manager.SElasticcacheResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SElasticcacheResourceBaseManager.ListItemExportKeys") + } + } + + return q, nil +} diff --git a/pkg/compute/models/elasticcache_secgroups.go b/pkg/compute/models/elasticcache_secgroups.go new file mode 100644 index 0000000000..3087e04e04 --- /dev/null +++ b/pkg/compute/models/elasticcache_secgroups.go @@ -0,0 +1,218 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package models + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/apis" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SElasticcachesecgroupManager struct { + SElasticcacheJointsManager + SSecurityGroupResourceBaseManager +} + +var ElasticcachesecgroupManager *SElasticcachesecgroupManager + +func init() { + db.InitManager(func() { + ElasticcachesecgroupManager = &SElasticcachesecgroupManager{ + SElasticcacheJointsManager: NewElasticcacheJointsManager( + SElasticcachesecgroup{}, + "elasticcachesecgroups_tbl", + "elasticcachesecgroup", + "elasticcachesecgroups", + SecurityGroupManager, + ), + } + ElasticcachesecgroupManager.SetVirtualObject(ElasticcachesecgroupManager) + }) +} + +type SElasticcachesecgroup struct { + SElasticcacheJointsBase + + SSecurityGroupResourceBase `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` +} + +func (manager *SElasticcachesecgroupManager) GetSlaveFieldName() string { + return "secgroup_id" +} + +func (self *SElasticcachesecgroup) getSecgroup() *SSecurityGroup { + secgrp, err := SecurityGroupManager.FetchById(self.SecgroupId) + if err != nil { + log.Errorf("failed to find secgroup %s", self.SecgroupId) + return nil + } + secgroup := secgrp.(*SSecurityGroup) + secgroup.SetModelManager(SecurityGroupManager, secgroup) + return secgroup +} + +func (self *SElasticcachesecgroup) Delete(ctx context.Context, userCred mcclient.TokenCredential) error { + return db.DeleteModel(ctx, userCred, self) +} + +func (manager *SElasticcachesecgroupManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.ElasticcachesecgroupListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SElasticcacheJointsManager.ListItemFilter(ctx, q, userCred, query.ElasticcacheJointsListInput) + if err != nil { + return nil, errors.Wrap(err, "SElasticcacheJointsManager.ListItemFilter") + } + q, err = manager.SSecurityGroupResourceBaseManager.ListItemFilter(ctx, q, userCred, query.SecgroupFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SSecurityGroupResourceBaseManager.ListItemFilter") + } + + return q, nil +} + +func (manager *SElasticcachesecgroupManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.ElasticcachesecgroupListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SElasticcacheJointsManager.OrderByExtraFields(ctx, q, userCred, query.ElasticcacheJointsListInput) + if err != nil { + return nil, errors.Wrap(err, "SElasticcacheJointsManager.OrderByExtraFields") + } + q, err = manager.SSecurityGroupResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.SecgroupFilterListInput) + if err != nil { + return nil, errors.Wrap(err, "SSecurityGroupResourceBaseManager.OrderByExtraFields") + } + + return q, nil +} + +func (manager *SElasticcachesecgroupManager) ListItemExportKeys(ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + keys stringutils2.SSortedStrings, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SElasticcacheJointsManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SElasticcacheJointsManager.ListItemExportKeys") + } + if keys.ContainsAny(manager.SSecurityGroupResourceBaseManager.GetExportKeys()...) { + q, err = manager.SSecurityGroupResourceBaseManager.ListItemExportKeys(ctx, q, userCred, keys) + if err != nil { + return nil, errors.Wrap(err, "SSecurityGroupResourceBaseManager.ListItemExportKeys") + } + } + + return q, nil +} + +func (self *SElasticcachesecgroup) GetExtraDetails( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + isList bool, +) (api.ElasticcachesecgroupDetails, error) { + return api.ElasticcachesecgroupDetails{}, nil +} + +func (manager *SElasticcachesecgroupManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.ElasticcachesecgroupDetails { + rows := make([]api.ElasticcachesecgroupDetails, len(objs)) + + ecRows := manager.SElasticcacheJointsManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + secgroupIds := make([]string, len(rows)) + for i := range rows { + rows[i].ElasticcacheJointResourceDetails = ecRows[i] + secgroupIds[i] = objs[i].(*SElasticcachesecgroup).SecgroupId + } + + secgroupIdMaps, err := db.FetchIdNameMap2(SecurityGroupManager, secgroupIds) + if err != nil { + log.Errorf("FetchIdNameMap2 fail %s", err) + return rows + } + + for i := range rows { + if name, ok := secgroupIdMaps[secgroupIds[i]]; ok { + rows[i].Secgroup = name + } + } + + return rows +} + +func fetchElasticcacheSecgroups(cacheIds []string) map[string][]apis.StandaloneShortDesc { + secgroups := SecurityGroupManager.Query().SubQuery() + cachesecgroups := ElasticcachesecgroupManager.Query().SubQuery() + q := cachesecgroups.Query( + cachesecgroups.Field("elasticcache_id", "elasticcache_id"), + cachesecgroups.Field("secgroup_id", "secgroup_id"), + secgroups.Field("name", "secgroup_name")) + q = q.Filter(sqlchemy.In(cachesecgroups.Field("elasticcache_id"), cacheIds)) + q = q.Join(secgroups, sqlchemy.Equals(q.Field("secgroup_id"), secgroups.Field("id"))) + + type secgroupInfo struct { + SecgroupId string + SecgroupName string + ElasticcacheId string + } + + gsgs := make([]secgroupInfo, 0) + err := q.All(&gsgs) + if err != nil { + log.Debugf("fetchElasticcacheSecgroups %s", err) + return nil + } + + ret := make(map[string][]apis.StandaloneShortDesc) + for i := range gsgs { + gsg, ok := ret[gsgs[i].ElasticcacheId] + if !ok { + gsg = make([]apis.StandaloneShortDesc, 0) + } + gsg = append(gsg, apis.StandaloneShortDesc{ + Id: gsgs[i].SecgroupId, + Name: gsgs[i].SecgroupName, + }) + ret[gsgs[i].ElasticcacheId] = gsg + } + + return ret +} diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index f320369c83..8c7eb558d7 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -4801,7 +4801,12 @@ func (self *SGuest) getSecgroupsBySecgroupExternalIds(externalIds []string) ([]S if host == nil { return nil, errors.Error("not found host for guest") } - sq := SecurityGroupCacheManager.Query("secgroup_id").In("external_id", externalIds).Equals("manager_id", host.ManagerId) + + return getSecgroupsBySecgroupExternalIds(host.ManagerId, externalIds) +} + +func getSecgroupsBySecgroupExternalIds(managerId string, externalIds []string) ([]SSecurityGroup, error) { + sq := SecurityGroupCacheManager.Query("secgroup_id").In("external_id", externalIds).Equals("manager_id", managerId) q := SecurityGroupManager.Query().In("id", sq.SubQuery()) secgroups := []SSecurityGroup{} err := db.FetchModelObjects(SecurityGroupManager, q, &secgroups) diff --git a/pkg/compute/models/regiondrivers.go b/pkg/compute/models/regiondrivers.go index 0cbc50c25e..7caeaf6254 100644 --- a/pkg/compute/models/regiondrivers.go +++ b/pkg/compute/models/regiondrivers.go @@ -174,18 +174,23 @@ type IDBInstanceDriver interface { type IElasticcacheDriver interface { IsSupportedElasticcache() bool + // capability + IsSupportedElasticcacheSecgroup() bool + GetMaxElasticcacheSecurityGroupCount() int + AllowCreateElasticcacheBackup(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, elasticcache *SElasticcache) error AllowUpdateElasticcacheAuthMode(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, elasticcache *SElasticcache) error ValidateCreateElasticcacheData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) ValidateCreateElasticcacheAccountData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) ValidateCreateElasticcacheAclData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) ValidateCreateElasticcacheBackupData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) - RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *SElasticcache, task taskman.ITask) error + RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *SElasticcache, task taskman.ITask, data *jsonutils.JSONDict) error RequestCreateElasticcacheAccount(ctx context.Context, userCred mcclient.TokenCredential, elasticcacheAccount *SElasticcacheAccount, task taskman.ITask) error RequestCreateElasticcacheAcl(ctx context.Context, userCred mcclient.TokenCredential, elasticcacheAcl *SElasticcacheAcl, task taskman.ITask) error RequestCreateElasticcacheBackup(ctx context.Context, userCred mcclient.TokenCredential, elasticcacheBackup *SElasticcacheBackup, task taskman.ITask) error RequestRestartElasticcache(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *SElasticcache, task taskman.ITask) error RequestSyncElasticcache(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *SElasticcache, task taskman.ITask) error + RequestSyncSecgroupsForElasticcache(ctx context.Context, userCred mcclient.TokenCredential, ec *SElasticcache, task taskman.ITask) error RequestDeleteElasticcache(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *SElasticcache, task taskman.ITask) error RequestDeleteElasticcacheAccount(ctx context.Context, userCred mcclient.TokenCredential, ea *SElasticcacheAccount, task taskman.ITask) error RequestDeleteElasticcacheAcl(ctx context.Context, userCred mcclient.TokenCredential, ea *SElasticcacheAcl, task taskman.ITask) error @@ -193,6 +198,7 @@ type IElasticcacheDriver interface { RequestSetElasticcacheMaintainTime(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *SElasticcache, task taskman.ITask) error RequestElasticcacheChangeSpec(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *SElasticcache, task taskman.ITask) error RequestUpdateElasticcacheAuthMode(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *SElasticcache, task taskman.ITask) error + RequestUpdateElasticcacheSecgroups(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *SElasticcache, task taskman.ITask) error RequestElasticcacheSetMaintainTime(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *SElasticcache, task taskman.ITask) error RequestElasticcacheAllocatePublicConnection(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *SElasticcache, task taskman.ITask) error RequestElasticcacheReleasePublicConnection(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *SElasticcache, task taskman.ITask) error diff --git a/pkg/compute/models/secgroups.go b/pkg/compute/models/secgroups.go index 0ccaaf7d49..e0834b531d 100644 --- a/pkg/compute/models/secgroups.go +++ b/pkg/compute/models/secgroups.go @@ -154,6 +154,12 @@ func (manager *SSecurityGroupManager) ListItemFilter( q = q.In("id", sq.SubQuery()) } + // elastic cache + q, err = manager.ListItemElasticcacheFilter(ctx, q, userCred, input) + if err != nil { + return nil, errors.Wrap(err, "ListItemElasticcacheFilter") + } + if len(input.Ip) > 0 || len(input.Ports) > 0 { sq := SecurityGroupRuleManager.Query("secgroup_id") if len(input.Ip) > 0 { @@ -171,6 +177,27 @@ func (manager *SSecurityGroupManager) ListItemFilter( return q, nil } +func (manager *SSecurityGroupManager) ListItemElasticcacheFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + input api.SecgroupListInput, +) (*sqlchemy.SQuery, error) { + cacheId := input.ElasticcacheId + if len(cacheId) > 0 { + cache, _, err := ValidateElasticcacheResourceInput(userCred, input.ELasticcacheResourceInput) + if err != nil { + return nil, errors.Wrap(err, "ValidateElasticcacheResourceInput") + } + cacheId := cache.GetId() + filters := []sqlchemy.ICondition{} + filters = append(filters, sqlchemy.In(q.Field("id"), ElasticcachesecgroupManager.Query("secgroup_id").Equals("elasticcache_id", cacheId).SubQuery())) + q = q.Filter(sqlchemy.OR(filters...)) + } + + return q, nil +} + func (manager *SSecurityGroupManager) OrderByExtraFields( ctx context.Context, q *sqlchemy.SQuery, diff --git a/pkg/compute/regiondrivers/aliyun.go b/pkg/compute/regiondrivers/aliyun.go index fd24ea0550..db7c787a87 100644 --- a/pkg/compute/regiondrivers/aliyun.go +++ b/pkg/compute/regiondrivers/aliyun.go @@ -1279,7 +1279,7 @@ func (self *SAliyunRegionDriver) ValidateCreateElasticcacheData(ctx context.Cont return data, nil } -func (self *SAliyunRegionDriver) RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, ec *models.SElasticcache, task taskman.ITask) error { +func (self *SAliyunRegionDriver) RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, ec *models.SElasticcache, task taskman.ITask, data *jsonutils.JSONDict) error { taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { iRegion, err := ec.GetIRegion() if err != nil { @@ -1479,7 +1479,7 @@ func (self *SAliyunRegionDriver) RequestCreateElasticcacheBackup(ctx context.Con backupIds = append(backupIds, oBackups[i].GetGlobalId()) } - _, err = iec.CreateBackup() + _, err = iec.CreateBackup("") if err != nil { return nil, errors.Wrap(err, "managedVirtualizationRegionDriver.CreateElasticcacheBackup.CreateBackup") } @@ -1576,3 +1576,11 @@ func (self *SAliyunRegionDriver) IsSupportedDBInstance() bool { func (self *SAliyunRegionDriver) IsSupportedElasticcache() bool { return true } + +func (self *SAliyunRegionDriver) IsSupportedElasticcacheSecgroup() bool { + return false +} + +func (self *SAliyunRegionDriver) GetMaxElasticcacheSecurityGroupCount() int { + return 0 +} diff --git a/pkg/compute/regiondrivers/base.go b/pkg/compute/regiondrivers/base.go index 59a00e363b..abbb191296 100644 --- a/pkg/compute/regiondrivers/base.go +++ b/pkg/compute/regiondrivers/base.go @@ -393,6 +393,10 @@ func (self *SBaseRegionDriver) RequestRemoteUpdateElasticcache(ctx context.Conte return nil } +func (self *SBaseRegionDriver) RequestSyncSecgroupsForElasticcache(ctx context.Context, userCred mcclient.TokenCredential, ec *models.SElasticcache, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestSyncSecgroupsForElasticcache") +} + func (self *SBaseRegionDriver) IsDBInstanceNeedSecgroup() bool { return false } diff --git a/pkg/compute/regiondrivers/huawei.go b/pkg/compute/regiondrivers/huawei.go index 66f3f72127..f8eda6711f 100644 --- a/pkg/compute/regiondrivers/huawei.go +++ b/pkg/compute/regiondrivers/huawei.go @@ -2472,7 +2472,7 @@ func (self *SHuaWeiRegionDriver) ValidateCreateElasticcacheData(ctx context.Cont return data, nil } -func (self *SHuaWeiRegionDriver) RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, ec *models.SElasticcache, task taskman.ITask) error { +func (self *SHuaWeiRegionDriver) RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, ec *models.SElasticcache, task taskman.ITask, data *jsonutils.JSONDict) error { taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { iRegion, err := ec.GetIRegion() if err != nil { @@ -2637,6 +2637,14 @@ func (self *SHuaWeiRegionDriver) IsSupportedElasticcache() bool { return true } +func (self *SHuaWeiRegionDriver) IsSupportedElasticcacheSecgroup() bool { + return false +} + +func (self *SHuaWeiRegionDriver) GetMaxElasticcacheSecurityGroupCount() int { + return 0 +} + func (self *SHuaWeiRegionDriver) GetBackendStatusForAdd() []string { return []string{api.VM_RUNNING, api.VM_READY} } diff --git a/pkg/compute/regiondrivers/kvm.go b/pkg/compute/regiondrivers/kvm.go index eb5f6beac8..35e459edb9 100644 --- a/pkg/compute/regiondrivers/kvm.go +++ b/pkg/compute/regiondrivers/kvm.go @@ -1146,7 +1146,7 @@ func (self *SKVMRegionDriver) ValidateCacheSecgroup(ctx context.Context, userCre return fmt.Errorf("No need to cache secgroup for onecloud region") } -func (self *SKVMRegionDriver) RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *models.SElasticcache, task taskman.ITask) error { +func (self *SKVMRegionDriver) RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *models.SElasticcache, task taskman.ITask, data *jsonutils.JSONDict) error { return nil } @@ -1182,6 +1182,10 @@ func (self *SKVMRegionDriver) RequestUpdateElasticcacheAuthMode(ctx context.Cont return nil } +func (self *SKVMRegionDriver) RequestUpdateElasticcacheSecgroups(ctx context.Context, userCred mcclient.TokenCredential, ec *models.SElasticcache, task taskman.ITask) error { + return nil +} + func (self *SKVMRegionDriver) RequestElasticcacheSetMaintainTime(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *models.SElasticcache, task taskman.ITask) error { return nil } @@ -1273,3 +1277,11 @@ func (self *SKVMRegionDriver) RequestSyncBucketStatus(ctx context.Context, userC }) return nil } + +func (self *SKVMRegionDriver) IsSupportedElasticcacheSecgroup() bool { + return false +} + +func (self *SKVMRegionDriver) GetMaxElasticcacheSecurityGroupCount() int { + return 0 +} diff --git a/pkg/compute/regiondrivers/managedvirtual.go b/pkg/compute/regiondrivers/managedvirtual.go index 4b72bebcb5..738db6a61e 100644 --- a/pkg/compute/regiondrivers/managedvirtual.go +++ b/pkg/compute/regiondrivers/managedvirtual.go @@ -47,6 +47,14 @@ type SManagedVirtualizationRegionDriver struct { SVirtualizationRegionDriver } +func (self *SManagedVirtualizationRegionDriver) IsSupportedElasticcacheSecgroup() bool { + return false +} + +func (self *SManagedVirtualizationRegionDriver) GetMaxElasticcacheSecurityGroupCount() int { + return 0 +} + func (self *SManagedVirtualizationRegionDriver) ValidateCreateLoadbalancerData(ctx context.Context, userCred mcclient.TokenCredential, owerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { return self.ValidateManagerId(ctx, userCred, data) } @@ -1770,7 +1778,7 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateDBInstance(ctx cont return nil } -func (self *SManagedVirtualizationRegionDriver) RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *models.SElasticcache, task taskman.ITask) error { +func (self *SManagedVirtualizationRegionDriver) RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, elasticcache *models.SElasticcache, task taskman.ITask, data *jsonutils.JSONDict) error { task.ScheduleRun(nil) return nil } @@ -1993,7 +2001,12 @@ func (self *SManagedVirtualizationRegionDriver) RequestUpdateElasticcacheAuthMod return errors.Wrap(err, "managedVirtualizationRegionDriver.RequestUpdateElasticcacheAuthMode.UpdatedbAuthMode") } - return cloudprovider.WaitStatusWithDelay(iec, api.ELASTIC_CACHE_STATUS_RUNNING, 10*time.Second, 10*time.Second, 300*time.Second) + return cloudprovider.WaitStatusWithDelay(iec, api.ELASTIC_CACHE_STATUS_RUNNING, 10*time.Second, 10*time.Second, 600*time.Second) +} + +func (self *SManagedVirtualizationRegionDriver) RequestUpdateElasticcacheSecgroups(ctx context.Context, userCred mcclient.TokenCredential, ec *models.SElasticcache, task taskman.ITask) error { + // todo: finish me + return nil } func (self *SManagedVirtualizationRegionDriver) RequestElasticcacheSetMaintainTime(ctx context.Context, userCred mcclient.TokenCredential, ec *models.SElasticcache, task taskman.ITask) error { @@ -2096,7 +2109,18 @@ func (self *SManagedVirtualizationRegionDriver) RequestElasticcacheFlushInstance return errors.Wrap(err, "managedVirtualizationRegionDriver.RequestElasticcacheFlushInstance.GetIElasticcacheById") } - err = iec.FlushInstance() + password, _ := task.GetParams().GetString("password") + input := cloudprovider.SCloudElasticCacheFlushInstanceInput{} + if len(password) > 0 { + input.Password = password + } else { + if info, err := ec.GetDetailsLoginInfo(ctx, userCred, jsonutils.NewDict()); err == nil && info != nil { + pwd, _ := info.GetString("password") + input.Password = pwd + } + } + + err = iec.FlushInstance(input) if err != nil { return errors.Wrap(err, "managedVirtualizationRegionDriver.RequestElasticcacheFlushInstance.FlushInstance") } @@ -2453,7 +2477,7 @@ func (self *SManagedVirtualizationRegionDriver) RequestCreateElasticcacheBackup( return nil, errors.Wrap(err, "managedVirtualizationRegionDriver.CreateElasticcacheBackup.GetIElasticcacheById") } - ieb, err := iec.CreateBackup() + ieb, err := iec.CreateBackup(eb.Name) if err != nil { return nil, errors.Wrap(err, "managedVirtualizationRegionDriver.CreateElasticcacheBackup.CreateBackup") } @@ -2832,3 +2856,7 @@ func (self *SManagedVirtualizationRegionDriver) RequestRemoteUpdateElasticcache( // nil ops return nil } + +func (self *SManagedVirtualizationRegionDriver) RequestSyncSecgroupsForElasticcache(ctx context.Context, userCred mcclient.TokenCredential, ec *models.SElasticcache, task taskman.ITask) error { + return fmt.Errorf("Not Implement RequestSyncSecgroupsForElasticcache") +} diff --git a/pkg/compute/regiondrivers/qcloud.go b/pkg/compute/regiondrivers/qcloud.go index e0b3fd9b8d..bfcfb372cd 100644 --- a/pkg/compute/regiondrivers/qcloud.go +++ b/pkg/compute/regiondrivers/qcloud.go @@ -18,6 +18,7 @@ import ( "context" "fmt" "regexp" + "time" "yunion.io/x/jsonutils" "yunion.io/x/log" @@ -26,6 +27,7 @@ import ( "yunion.io/x/pkg/utils" "yunion.io/x/sqlchemy" + 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/taskman" @@ -35,7 +37,9 @@ import ( "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/choices" "yunion.io/x/onecloud/pkg/util/rand" + "yunion.io/x/onecloud/pkg/util/seclib2" ) type SQcloudRegionDriver struct { @@ -1437,3 +1441,332 @@ func (self *SQcloudRegionDriver) ValidateDBInstanceAccountPrivilege(ctx context. } return nil } + +func (self *SQcloudRegionDriver) IsSupportedElasticcacheSecgroup() bool { + return true +} + +func (self *SQcloudRegionDriver) GetMaxElasticcacheSecurityGroupCount() int { + return 10 +} + +func (self *SQcloudRegionDriver) ValidateCreateElasticcacheData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + zoneV := validators.NewModelIdOrNameValidator("zone", "zone", ownerId) + networkV := validators.NewModelIdOrNameValidator("network", "network", ownerId) + instanceTypeV := validators.NewModelIdOrNameValidator("instance_type", "elasticcachesku", ownerId) + chargeTypeV := validators.NewStringChoicesValidator("billing_type", choices.NewChoices(billing_api.BILLING_TYPE_PREPAID, billing_api.BILLING_TYPE_POSTPAID)) + networkTypeV := validators.NewStringChoicesValidator("network_type", choices.NewChoices(api.LB_NETWORK_TYPE_VPC)).Default(api.LB_NETWORK_TYPE_VPC).Optional(true) + engineVersionV := validators.NewStringChoicesValidator("engine_version", choices.NewChoices("2.8", "3.0", "3.2", "4.0", "5.0")) + + keyV := map[string]validators.IValidator{ + "zone": zoneV, + "billing_type": chargeTypeV, + "network_type": networkTypeV, + "network": networkV, + "instance_type": instanceTypeV, + "engine_version": engineVersionV, + } + if err := RunValidators(keyV, data, false); err != nil { + return nil, err + } + + // validate password + if password, _ := data.GetString("password"); len(password) > 0 { + if !seclib2.MeetComplxity(password) { + return nil, httperrors.NewWeakPasswordError() + } + } + + zoneId, _ := data.GetString("zone_id") + billingType, _ := data.GetString("billing_type") + // validate sku + sku := instanceTypeV.Model.(*models.SElasticcacheSku) + if err := ValidateElasticcacheSku(zoneId, billingType, sku); err != nil { + return nil, err + } else { + data.Set("instance_type", jsonutils.NewString(sku.InstanceSpec)) + data.Set("node_type", jsonutils.NewString(sku.NodeType)) + data.Set("local_category", jsonutils.NewString(sku.LocalCategory)) + data.Set("capacity_mb", jsonutils.NewInt(int64(sku.MemorySizeMB))) + } + + // validate secgroups + secgroups := []string{} + err := data.Unmarshal(&secgroups, "secgroup_ids") + if err != nil { + log.Debugf("Unmarshal.security_groups %s", err) + secgroups = []string{api.SECGROUP_DEFAULT_ID} + } + + if len(secgroups) == 0 || len(secgroups) > 10 { + return nil, errors.Wrap(err, "secgroups id quantity should between 1 and 10.") + } + + _, err = models.CheckingSecgroupIds(ctx, userCred, secgroups) + if err != nil { + return nil, errors.Wrap(err, "CheckingSecgroupIds") + } + + // billing cycle + if billingType == billing_api.BILLING_TYPE_PREPAID { + billingCycle, err := data.GetString("billing_cycle") + if err != nil { + return nil, httperrors.NewMissingParameterError("billing_cycle") + } + + cycle, err := billing.ParseBillingCycle(billingCycle) + if err != nil { + return nil, httperrors.NewInputParameterError("invalid billing_cycle %s", billingCycle) + } + + data.Set("billing_cycle", jsonutils.NewString(cycle.String())) + } + + network := networkV.Model.(*models.SNetwork) + vpc := network.GetVpc() + if vpc == nil { + return nil, httperrors.NewNotFoundError("network %s related vpc not found", network.GetId()) + } + data.Set("engine", jsonutils.NewString("redis")) + data.Set("vpc_id", jsonutils.NewString(vpc.Id)) + data.Set("manager_id", jsonutils.NewString(vpc.ManagerId)) + data.Set("cloudregion_id", jsonutils.NewString(vpc.CloudregionId)) + return data, nil +} + +func (self *SQcloudRegionDriver) IsSupportedElasticcache() bool { + return true +} + +func (self *SQcloudRegionDriver) RequestCreateElasticcache(ctx context.Context, userCred mcclient.TokenCredential, ec *models.SElasticcache, task taskman.ITask, data *jsonutils.JSONDict) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + iRegion, err := ec.GetIRegion() + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcache.GetIRegion") + } + + provider := ec.GetCloudprovider() + if provider == nil { + return nil, errors.Wrap(httperrors.ErrInvalidStatus, "qcloudRegionDriver.CreateElasticcache.GetProvider") + } + + secgroups := []string{} + err = data.Unmarshal(&secgroups, "ext_secgroup_ids") + if err != nil { + return nil, errors.Wrap(err, "Unmarshal.ext_secgroup_ids") + } + + params, err := ec.GetCreateQCloudElasticcacheParams(task.GetParams()) + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcache.GetCreateHuaweiElasticcacheParams") + } + params.SecurityGroupIds = secgroups + iec, err := iRegion.CreateIElasticcaches(params) + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcache.CreateIElasticcaches") + } + + err = cloudprovider.WaitStatusWithDelay(iec, api.ELASTIC_CACHE_STATUS_RUNNING, 30*time.Second, 15*time.Second, 600*time.Second) + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcache.WaitStatusWithDelay") + } + + ec.SetModelManager(models.ElasticcacheManager, ec) + if err := db.SetExternalId(ec, userCred, iec.GetGlobalId()); err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcache.SetExternalId") + } + + if err := ec.SyncWithCloudElasticcache(ctx, userCred, provider, iec); err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcache.SyncWithCloudElasticcache") + } + + // sync accounts + { + iaccounts, err := iec.GetICloudElasticcacheAccounts() + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcache.GetICloudElasticcacheAccounts") + } + + result := models.ElasticcacheAccountManager.SyncElasticcacheAccounts(ctx, userCred, ec, iaccounts) + log.Debugf("qcloudRegionDriver.CreateElasticcache.SyncElasticcacheAccounts %s", result.Result()) + + account, err := ec.GetAdminAccount() + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcache.GetAdminAccount") + } + + err = account.SavePassword(params.Password) + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcache.SavePassword") + } + } + + // sync parameters + { + iparams, err := iec.GetICloudElasticcacheParameters() + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcache.GetICloudElasticcacheParameters") + } + + result := models.ElasticcacheParameterManager.SyncElasticcacheParameters(ctx, userCred, ec, iparams) + log.Debugf("qcloudRegionDriver.CreateElasticcache.SyncElasticcacheParameters %s", result.Result()) + } + + return nil, nil + }) + return nil +} + +func (self *SQcloudRegionDriver) RequestSyncSecgroupsForElasticcache(ctx context.Context, userCred mcclient.TokenCredential, ec *models.SElasticcache, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + // sync secgroups to cloud + secgroupExternalIds := []string{} + { + ess, err := ec.GetElasticcacheSecgroups() + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.GetElasticcacheSecgroups") + } + + vpc := ec.GetVpc() + for i := range ess { + externalId, err := self.RequestSyncSecurityGroup(ctx, task.GetUserCred(), vpc.GetId(), vpc, ess[i].GetSecGroup(), "") + if err != nil { + return nil, errors.Wrap(err, "RequestSyncSecurityGroup") + } + secgroupExternalIds = append(secgroupExternalIds, externalId) + } + } + + ret := jsonutils.NewDict() + ret.Set("ext_secgroup_ids", jsonutils.NewStringArray(secgroupExternalIds)) + return ret, nil + }) + return nil +} + +func (self *SQcloudRegionDriver) ValidateCreateElasticcacheAccountData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + elasticCacheV := validators.NewModelIdOrNameValidator("elasticcache", "elasticcache", ownerId) + accountTypeV := validators.NewStringChoicesValidator("account_type", choices.NewChoices("normal")).Default("normal") + accountPrivilegeV := validators.NewStringChoicesValidator("account_privilege", choices.NewChoices("read", "write")) + + keyV := map[string]validators.IValidator{ + "elasticcache": elasticCacheV, + "account_type": accountTypeV, + "account_privilege": accountPrivilegeV.Default("read"), + } + + for _, v := range keyV { + if err := v.Validate(data); err != nil { + return nil, err + } + } + + passwd, _ := data.GetString("password") + if !seclib2.MeetComplxity(passwd) { + return nil, httperrors.NewWeakPasswordError() + } + + return data, nil +} + +func (self *SQcloudRegionDriver) RequestCreateElasticcacheAccount(ctx context.Context, userCred mcclient.TokenCredential, ea *models.SElasticcacheAccount, task taskman.ITask) error { + taskman.LocalTaskRun(task, func() (jsonutils.JSONObject, error) { + _ec, err := db.FetchById(models.ElasticcacheManager, ea.ElasticcacheId) + if err != nil { + return nil, errors.Wrap(nil, "qcloudRegionDriver.CreateElasticcacheAccount.GetElasticcache") + } + + ec := _ec.(*models.SElasticcache) + iregion, err := ec.GetIRegion() + if err != nil { + return nil, errors.Wrap(nil, "qcloudRegionDriver.CreateElasticcacheAccount.GetIRegion") + } + + params, err := ea.GetCreateQcloudElasticcacheAccountParams() + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcacheAccount.GetCreateQcloudElasticcacheAccountParams") + } + + iec, err := iregion.GetIElasticcacheById(ec.GetExternalId()) + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcacheAccount.GetIElasticcacheById") + } + + iea, err := iec.CreateAccount(params) + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcacheAccount.CreateAccount") + } + + ea.SetModelManager(models.ElasticcacheAccountManager, ea) + if err := db.SetExternalId(ea, userCred, iea.GetGlobalId()); err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcacheAccount.SetExternalId") + } + + err = cloudprovider.WaitStatusWithDelay(iea, api.ELASTIC_CACHE_ACCOUNT_STATUS_AVAILABLE, 3*time.Second, 3*time.Second, 180*time.Second) + if err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcacheAccount.WaitStatusWithDelay") + } + + if err = ea.SyncWithCloudElasticcacheAccount(ctx, userCred, iea); err != nil { + return nil, errors.Wrap(err, "qcloudRegionDriver.CreateElasticcacheAccount.SyncWithCloudElasticcache") + } + + return nil, nil + }) + + return nil +} + +func (self *SQcloudRegionDriver) RequestElasticcacheAccountResetPassword(ctx context.Context, userCred mcclient.TokenCredential, ea *models.SElasticcacheAccount, task taskman.ITask) error { + iregion, err := ea.GetIRegion() + if err != nil { + return errors.Wrap(err, "qcloudRegionDriver.RequestElasticcacheAccountResetPassword.GetIRegion") + } + + _ec, err := db.FetchById(models.ElasticcacheManager, ea.ElasticcacheId) + if err != nil { + return errors.Wrap(err, "qcloudRegionDriver.RequestElasticcacheAccountResetPassword.FetchById") + } + + ec := _ec.(*models.SElasticcache) + iec, err := iregion.GetIElasticcacheById(ec.GetExternalId()) + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil + } else if err != nil { + return errors.Wrap(err, "qcloudRegionDriver.RequestElasticcacheAccountResetPassword.GetIElasticcacheById") + } + + iea, err := iec.GetICloudElasticcacheAccount(ea.GetExternalId()) + if err != nil { + return errors.Wrap(err, "qcloudRegionDriver.RequestElasticcacheAccountResetPassword.GetICloudElasticcacheBackup") + } + + data := task.GetParams() + if data == nil { + return errors.Wrap(fmt.Errorf("data is nil"), "qcloudRegionDriver.RequestElasticcacheAccountResetPassword.GetParams") + } + + input, err := ea.GetUpdateQcloudElasticcacheAccountParams(*data) + if err != nil { + return errors.Wrap(err, "qcloudRegionDriver.RequestElasticcacheAccountResetPassword.GetUpdateQcloudElasticcacheAccountParams") + } + + err = iea.UpdateAccount(input) + if err != nil { + return errors.Wrap(err, "qcloudRegionDriver.RequestElasticcacheAccountResetPassword.UpdateAccount") + } + + if input.Password != nil { + err = ea.SavePassword(*input.Password) + if err != nil { + return errors.Wrap(err, "qcloudRegionDriver.RequestElasticcacheAccountResetPassword.SavePassword") + } + } + + err = cloudprovider.WaitStatusWithDelay(iea, api.ELASTIC_CACHE_ACCOUNT_STATUS_AVAILABLE, 10*time.Second, 5*time.Second, 60*time.Second) + if err != nil { + return errors.Wrap(err, "qcloudRegionDriver.RequestElasticcacheAccountResetPassword.WaitStatusWithDelay") + } + + return ea.SyncWithCloudElasticcacheAccount(ctx, userCred, iea) +} diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index e9ef76fb7f..391906e835 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -222,6 +222,7 @@ func InitHandlers(app *appsrv.Application) { models.InstanceSnapshotJointManager, models.DnsZoneVpcManager, models.DBInstanceSecgroupManager, + models.ElasticcachesecgroupManager, } { db.RegisterModelManager(manager) handler := db.NewJointModelHandler(manager) diff --git a/pkg/compute/tasks/elasticcache_account_create_task.go b/pkg/compute/tasks/elasticcache_account_create_task.go index b4cd8747c0..5a75dfde3f 100644 --- a/pkg/compute/tasks/elasticcache_account_create_task.go +++ b/pkg/compute/tasks/elasticcache_account_create_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheAccountCreateTask) OnInit(ctx context.Context, obj db.IS self.SetStage("OnElasticcacheAccountCreateComplete", nil) if err := region.GetDriver().RequestCreateElasticcacheAccount(ctx, self.GetUserCred(), ea, self); err != nil { - self.taskFail(ctx, ea, jsonutils.Marshal(err)) + self.taskFail(ctx, ea, jsonutils.NewString(err.Error())) return } } diff --git a/pkg/compute/tasks/elasticcache_account_delete_task.go b/pkg/compute/tasks/elasticcache_account_delete_task.go index 3c02e1f30b..6ed7bc65a6 100644 --- a/pkg/compute/tasks/elasticcache_account_delete_task.go +++ b/pkg/compute/tasks/elasticcache_account_delete_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheAccountDeleteTask) OnInit(ctx context.Context, obj db.IS self.SetStage("OnElasticcacheAccountDeleteComplete", nil) if err := region.GetDriver().RequestDeleteElasticcacheAccount(ctx, self.GetUserCred(), ea, self); err != nil { - self.taskFail(ctx, ea, jsonutils.Marshal(err)) + self.taskFail(ctx, ea, jsonutils.NewString(err.Error())) return } else { err = db.DeleteModel(ctx, self.GetUserCred(), ea) diff --git a/pkg/compute/tasks/elasticcache_account_reset_password_task.go b/pkg/compute/tasks/elasticcache_account_reset_password_task.go index 294556269e..fc5f7aa31f 100644 --- a/pkg/compute/tasks/elasticcache_account_reset_password_task.go +++ b/pkg/compute/tasks/elasticcache_account_reset_password_task.go @@ -59,7 +59,7 @@ func (self *ElasticcacheAccountResetPasswordTask) OnInit(ctx context.Context, ob self.SetStage("OnElasticcacheAccountResetPasswordComplete", nil) if err := region.GetDriver().RequestElasticcacheAccountResetPassword(ctx, self.GetUserCred(), ea, self); err != nil { - self.taskFail(ctx, ea, jsonutils.Marshal(err)) + self.taskFail(ctx, ea, jsonutils.NewString(err.Error())) return } else { logclient.AddActionLogWithStartable(self, ea, logclient.ACT_RESET_PASSWORD, nil, self.UserCred, true) diff --git a/pkg/compute/tasks/elasticcache_acl_create_task.go b/pkg/compute/tasks/elasticcache_acl_create_task.go index 5fcd1430e2..0519410d84 100644 --- a/pkg/compute/tasks/elasticcache_acl_create_task.go +++ b/pkg/compute/tasks/elasticcache_acl_create_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheAclCreateTask) OnInit(ctx context.Context, obj db.IStand self.SetStage("OnElasticcacheAclCreateComplete", nil) if err := region.GetDriver().RequestCreateElasticcacheAcl(ctx, self.GetUserCred(), ea, self); err != nil { - self.taskFail(ctx, ea, jsonutils.Marshal(err)) + self.taskFail(ctx, ea, jsonutils.NewString(err.Error())) return } } diff --git a/pkg/compute/tasks/elasticcache_acl_delete_task.go b/pkg/compute/tasks/elasticcache_acl_delete_task.go index d6edbb61bd..804ebb489d 100644 --- a/pkg/compute/tasks/elasticcache_acl_delete_task.go +++ b/pkg/compute/tasks/elasticcache_acl_delete_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheAclDeleteTask) OnInit(ctx context.Context, obj db.IStand self.SetStage("OnElasticcacheAclDeleteComplete", nil) if err := region.GetDriver().RequestDeleteElasticcacheAcl(ctx, self.GetUserCred(), ea, self); err != nil { - self.taskFail(ctx, ea, jsonutils.Marshal(err)) + self.taskFail(ctx, ea, jsonutils.NewString(err.Error())) return } else { err = db.DeleteModel(ctx, self.GetUserCred(), ea) diff --git a/pkg/compute/tasks/elasticcache_acl_update_task.go b/pkg/compute/tasks/elasticcache_acl_update_task.go index 5d240aaa24..fd302ffdeb 100644 --- a/pkg/compute/tasks/elasticcache_acl_update_task.go +++ b/pkg/compute/tasks/elasticcache_acl_update_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheAclUpdateTask) OnInit(ctx context.Context, obj db.IStand self.SetStage("OnElasticcacheAclUpdateComplete", nil) if err := region.GetDriver().RequestElasticcacheAclUpdate(ctx, self.GetUserCred(), ea, self); err != nil { - self.taskFail(ctx, ea, jsonutils.Marshal(err)) + self.taskFail(ctx, ea, jsonutils.NewString(err.Error())) return } } diff --git a/pkg/compute/tasks/elasticcache_allocate_public_connection_task.go b/pkg/compute/tasks/elasticcache_allocate_public_connection_task.go index ddae60026b..d5091fc6a3 100644 --- a/pkg/compute/tasks/elasticcache_allocate_public_connection_task.go +++ b/pkg/compute/tasks/elasticcache_allocate_public_connection_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheAllocatePublicConnectionTask) OnInit(ctx context.Context self.SetStage("OnElasticcacheAllocatePublicConnectionComplete", nil) if err := region.GetDriver().RequestElasticcacheAllocatePublicConnection(ctx, self.GetUserCred(), elasticcache, self); err != nil { - self.OnElasticcacheAllocatePublicConnectionCompleteFailed(ctx, elasticcache, jsonutils.Marshal(err)) + self.OnElasticcacheAllocatePublicConnectionCompleteFailed(ctx, elasticcache, jsonutils.NewString(err.Error())) return } diff --git a/pkg/compute/tasks/elasticcache_backup_create_task.go b/pkg/compute/tasks/elasticcache_backup_create_task.go index 378777ac40..d69e531e0e 100644 --- a/pkg/compute/tasks/elasticcache_backup_create_task.go +++ b/pkg/compute/tasks/elasticcache_backup_create_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheBackupCreateTask) OnInit(ctx context.Context, obj db.ISt self.SetStage("OnElasticcacheBackupCreateComplete", nil) if err := region.GetDriver().RequestCreateElasticcacheBackup(ctx, self.GetUserCred(), eb, self); err != nil { - self.taskFail(ctx, eb, jsonutils.Marshal(err)) + self.taskFail(ctx, eb, jsonutils.NewString(err.Error())) return } } diff --git a/pkg/compute/tasks/elasticcache_backup_restore_instance_task.go b/pkg/compute/tasks/elasticcache_backup_restore_instance_task.go index 806f974eb2..3e33cb565a 100644 --- a/pkg/compute/tasks/elasticcache_backup_restore_instance_task.go +++ b/pkg/compute/tasks/elasticcache_backup_restore_instance_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheBackupRestoreInstanceTask) OnInit(ctx context.Context, o self.SetStage("OnElasticcacheBackupRestoreInstanceComplete", nil) if err := region.GetDriver().RequestElasticcacheBackupRestoreInstance(ctx, self.GetUserCred(), eb, self); err != nil { - self.OnElasticcacheBackupRestoreInstanceCompleteFailed(ctx, eb, jsonutils.Marshal(err)) + self.OnElasticcacheBackupRestoreInstanceCompleteFailed(ctx, eb, jsonutils.NewString(err.Error())) return } diff --git a/pkg/compute/tasks/elasticcache_change_spec_task.go b/pkg/compute/tasks/elasticcache_change_spec_task.go index 81e84f1602..734908a98c 100644 --- a/pkg/compute/tasks/elasticcache_change_spec_task.go +++ b/pkg/compute/tasks/elasticcache_change_spec_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheChangeSpecTask) OnInit(ctx context.Context, obj db.IStan self.SetStage("OnElasticcacheChangeSpecComplete", nil) if err := region.GetDriver().RequestElasticcacheChangeSpec(ctx, self.GetUserCred(), elasticcache, self); err != nil { - self.OnElasticcacheChangeSpecCompleteFailed(ctx, elasticcache, jsonutils.Marshal(err)) + self.OnElasticcacheChangeSpecCompleteFailed(ctx, elasticcache, jsonutils.NewString(err.Error())) return } diff --git a/pkg/compute/tasks/elasticcache_create_task.go b/pkg/compute/tasks/elasticcache_create_task.go index abe95c20af..76106d57d7 100644 --- a/pkg/compute/tasks/elasticcache_create_task.go +++ b/pkg/compute/tasks/elasticcache_create_task.go @@ -19,6 +19,7 @@ import ( "fmt" "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" @@ -54,14 +55,34 @@ func (self *ElasticcacheCreateTask) OnInit(ctx context.Context, obj db.IStandalo // sync security group here self.SetStage("OnSyncSecurityGroupComplete", data.(*jsonutils.JSONDict)) - self.OnSyncSecurityGroupComplete(ctx, elasticcache, data) + if region.GetDriver().IsSupportedElasticcacheSecgroup() { + secgroups := []string{} + err := self.GetParams().Unmarshal(&secgroups, "secgroup_ids") + if err != nil { + self.taskFail(ctx, elasticcache, jsonutils.Marshal(errors.Wrap(err, "Unmarshal.secgroup_ids"))) + return + } + secgroupInput := api.ElasticcacheSecgroupsInput{SecgroupIds: secgroups} + _, err = elasticcache.ProcessElasticcacheSecgroupsInput(ctx, self.UserCred, "set", &secgroupInput) + if err != nil { + self.taskFail(ctx, elasticcache, jsonutils.Marshal(errors.Wrap(err, "ProcessElasticcacheSecgroupsInput"))) + return + } + + if err := region.GetDriver().RequestSyncSecgroupsForElasticcache(ctx, self.UserCred, elasticcache, self); err != nil { + self.taskFail(ctx, elasticcache, jsonutils.NewString(err.Error())) + return + } + } else { + self.OnSyncSecurityGroupComplete(ctx, elasticcache, data) + } } func (self *ElasticcacheCreateTask) OnSyncSecurityGroupComplete(ctx context.Context, elasticcache *models.SElasticcache, data jsonutils.JSONObject) { region := elasticcache.GetRegion() self.SetStage("OnElasticcacheCreateComplete", nil) - if err := region.GetDriver().RequestCreateElasticcache(ctx, self.GetUserCred(), elasticcache, self); err != nil { - self.taskFail(ctx, elasticcache, jsonutils.Marshal(err)) + if err := region.GetDriver().RequestCreateElasticcache(ctx, self.GetUserCred(), elasticcache, self, data.(*jsonutils.JSONDict)); err != nil { + self.taskFail(ctx, elasticcache, jsonutils.NewString(err.Error())) return } } diff --git a/pkg/compute/tasks/elasticcache_flush_instance.go b/pkg/compute/tasks/elasticcache_flush_instance.go index 77d67ccc61..57fe53f780 100644 --- a/pkg/compute/tasks/elasticcache_flush_instance.go +++ b/pkg/compute/tasks/elasticcache_flush_instance.go @@ -54,7 +54,7 @@ func (self *ElasticcacheFlushInstanceTask) OnInit(ctx context.Context, obj db.IS self.SetStage("OnElasticcacheFlushInstanceComplete", nil) if err := region.GetDriver().RequestElasticcacheFlushInstance(ctx, self.GetUserCred(), elasticcache, self); err != nil { - self.OnElasticcacheFlushInstanceCompleteFailed(ctx, elasticcache, jsonutils.Marshal(err)) + self.OnElasticcacheFlushInstanceCompleteFailed(ctx, elasticcache, jsonutils.NewString(err.Error())) return } diff --git a/pkg/compute/tasks/elasticcache_parameter_update_task.go b/pkg/compute/tasks/elasticcache_parameter_update_task.go index 1ad8363389..3b827ddc07 100644 --- a/pkg/compute/tasks/elasticcache_parameter_update_task.go +++ b/pkg/compute/tasks/elasticcache_parameter_update_task.go @@ -60,7 +60,7 @@ func (self *ElasticcacheParameterUpdateTask) OnInit(ctx context.Context, obj db. self.SetStage("OnElasticcacheParameterUpdateComplete", nil) if err := region.GetDriver().RequestElasticcacheUpdateInstanceParameters(ctx, self.GetUserCred(), iec.(*models.SElasticcache), self); err != nil { - self.OnElasticcacheParameterUpdateCompleteFailed(ctx, ep, jsonutils.Marshal(err)) + self.OnElasticcacheParameterUpdateCompleteFailed(ctx, ep, jsonutils.NewString(err.Error())) return } diff --git a/pkg/compute/tasks/elasticcache_release_public_connection_task.go b/pkg/compute/tasks/elasticcache_release_public_connection_task.go index 7a3d1bd70c..3ef8e055d0 100644 --- a/pkg/compute/tasks/elasticcache_release_public_connection_task.go +++ b/pkg/compute/tasks/elasticcache_release_public_connection_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheReleasePublicConnectionTask) OnInit(ctx context.Context, self.SetStage("OnElasticcacheReleasePublicConnectionComplete", nil) if err := region.GetDriver().RequestElasticcacheReleasePublicConnection(ctx, self.GetUserCred(), elasticcache, self); err != nil { - self.OnElasticcacheReleasePublicConnectionCompleteFailed(ctx, elasticcache, jsonutils.Marshal(err)) + self.OnElasticcacheReleasePublicConnectionCompleteFailed(ctx, elasticcache, jsonutils.NewString(err.Error())) return } diff --git a/pkg/compute/tasks/elasticcache_restart_task.go b/pkg/compute/tasks/elasticcache_restart_task.go index 02ec74c695..8e596124c8 100644 --- a/pkg/compute/tasks/elasticcache_restart_task.go +++ b/pkg/compute/tasks/elasticcache_restart_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheRestartTask) OnInit(ctx context.Context, obj db.IStandal self.SetStage("OnElasticcacheRestartComplete", nil) if err := region.GetDriver().RequestRestartElasticcache(ctx, self.GetUserCred(), ec, self); err != nil { - self.taskFail(ctx, ec, jsonutils.Marshal(err)) + self.taskFail(ctx, ec, jsonutils.NewString(err.Error())) return } else { logclient.AddActionLogWithStartable(self, ec, logclient.ACT_VM_RESTART, nil, self.UserCred, true) diff --git a/pkg/compute/tasks/elasticcache_set_maintain_time_task.go b/pkg/compute/tasks/elasticcache_set_maintain_time_task.go index 67ef53d601..746abe91c2 100644 --- a/pkg/compute/tasks/elasticcache_set_maintain_time_task.go +++ b/pkg/compute/tasks/elasticcache_set_maintain_time_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheSetMaintainTimeTask) OnInit(ctx context.Context, obj db. self.SetStage("OnElasticcacheSetMaintainTimeComplete", nil) if err := region.GetDriver().RequestElasticcacheSetMaintainTime(ctx, self.GetUserCred(), elasticcache, self); err != nil { - self.OnElasticcacheSetMaintainTimeCompleteFailed(ctx, elasticcache, jsonutils.Marshal(err)) + self.OnElasticcacheSetMaintainTimeCompleteFailed(ctx, elasticcache, jsonutils.NewString(err.Error())) return } diff --git a/pkg/compute/tasks/elasticcache_sync_task.go b/pkg/compute/tasks/elasticcache_sync_task.go index 31d0c6e878..b6e4db48e1 100644 --- a/pkg/compute/tasks/elasticcache_sync_task.go +++ b/pkg/compute/tasks/elasticcache_sync_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheSyncTask) OnInit(ctx context.Context, obj db.IStandalone self.SetStage("OnElasticcacheSyncComplete", nil) if err := region.GetDriver().RequestSyncElasticcache(ctx, self.GetUserCred(), ec, self); err != nil { - self.taskFail(ctx, ec, jsonutils.Marshal(err)) + self.taskFail(ctx, ec, jsonutils.NewString(err.Error())) return } else { logclient.AddActionLogWithStartable(self, ec, logclient.ACT_SYNC_CONF, "", self.UserCred, true) diff --git a/pkg/compute/tasks/elasticcache_syncsecgroups_task.go b/pkg/compute/tasks/elasticcache_syncsecgroups_task.go new file mode 100644 index 0000000000..2072dfb62e --- /dev/null +++ b/pkg/compute/tasks/elasticcache_syncsecgroups_task.go @@ -0,0 +1,95 @@ +// 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" + "fmt" + + "yunion.io/x/jsonutils" + + 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 ElasticcacheSyncsecgroupsTask struct { + taskman.STask +} + +func init() { + taskman.RegisterTask(ElasticcacheSyncsecgroupsTask{}) +} + +func (self *ElasticcacheSyncsecgroupsTask) taskFailed(ctx context.Context, cache *models.SElasticcache, err jsonutils.JSONObject) { + cache.SetStatus(self.GetUserCred(), api.ELASTIC_CACHE_STATUS_SYNC_FAILED, err.String()) + self.SetStageFailed(ctx, err) + db.OpsLog.LogEvent(cache, db.ACT_SYNC_CONF, cache.GetShortDesc(ctx), self.GetUserCred()) + logclient.AddActionLogWithContext(ctx, cache, logclient.ACT_SYNC_CONF, err, self.UserCred, false) +} + +func (self *ElasticcacheSyncsecgroupsTask) OnInit(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + cache := obj.(*models.SElasticcache) + + region := cache.GetRegion() + if region == nil { + self.taskFailed(ctx, cache, jsonutils.NewString(fmt.Sprintf("failed to found cloudregion for elasticcache %s(%s)", cache.Name, cache.Id))) + return + } + + self.SetStage("OnElasticcacheSyncSecgroupsComplete", nil) + err := region.GetDriver().RequestSyncSecgroupsForElasticcache(ctx, self.GetUserCred(), cache, self) + if err != nil { + self.taskFailed(ctx, cache, jsonutils.Marshal(err)) + return + } +} + +// https://cloud.tencent.com/document/api/239/41256 +func (self *ElasticcacheSyncsecgroupsTask) OnElasticcacheSyncSecgroupsComplete(ctx context.Context, obj db.IStandaloneModel, data jsonutils.JSONObject) { + cache := obj.(*models.SElasticcache) + secgroups := []string{} + err := data.Unmarshal(&secgroups, "ext_secgroup_ids") + if err != nil { + self.taskFailed(ctx, cache, jsonutils.Marshal(err)) + return + } + + iregion, err := cache.GetIRegion() + if err != nil { + self.taskFailed(ctx, cache, jsonutils.Marshal(err)) + return + } + + iec, err := iregion.GetIElasticcacheById(cache.GetExternalId()) + if err != nil { + self.taskFailed(ctx, cache, jsonutils.Marshal(err)) + return + } + + err = iec.UpdateSecurityGroups(secgroups) + if err != nil { + self.taskFailed(ctx, cache, jsonutils.Marshal(err)) + return + } + + self.SetStageComplete(ctx, nil) +} + +func (self *ElasticcacheSyncsecgroupsTask) OnElasticcacheSyncSecgroupsCompleteFailed(ctx context.Context, cache *models.SElasticcache, data jsonutils.JSONObject) { + self.taskFailed(ctx, cache, data) +} diff --git a/pkg/compute/tasks/elasticcache_syncstatus_task.go b/pkg/compute/tasks/elasticcache_syncstatus_task.go index 54ed3869af..2d3ee12419 100644 --- a/pkg/compute/tasks/elasticcache_syncstatus_task.go +++ b/pkg/compute/tasks/elasticcache_syncstatus_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheSyncstatusTask) OnInit(ctx context.Context, obj db.IStan self.SetStage("OnElasticcacheSyncStatusComplete", nil) err := region.GetDriver().RequestSyncElasticcacheStatus(ctx, self.GetUserCred(), cache, self) if err != nil { - self.taskFailed(ctx, cache, jsonutils.Marshal(err)) + self.taskFailed(ctx, cache, jsonutils.NewString(err.Error())) return } } diff --git a/pkg/compute/tasks/elasticcache_update_auth_mode_task.go b/pkg/compute/tasks/elasticcache_update_auth_mode_task.go index d689087078..33c9197e08 100644 --- a/pkg/compute/tasks/elasticcache_update_auth_mode_task.go +++ b/pkg/compute/tasks/elasticcache_update_auth_mode_task.go @@ -54,7 +54,7 @@ func (self *ElasticcacheUpdateAuthModeTask) OnInit(ctx context.Context, obj db.I self.SetStage("OnElasticcacheUpdateAuthModeComplete", nil) if err := region.GetDriver().RequestUpdateElasticcacheAuthMode(ctx, self.GetUserCred(), elasticcache, self); err != nil { - self.OnElasticcacheUpdateAuthModeCompleteFailed(ctx, elasticcache, jsonutils.Marshal(err)) + self.OnElasticcacheUpdateAuthModeCompleteFailed(ctx, elasticcache, jsonutils.NewString(err.Error())) return } diff --git a/pkg/mcclient/options/elasticcache.go b/pkg/mcclient/options/elasticcache.go index 0a9c2b8383..3506b219d7 100644 --- a/pkg/mcclient/options/elasticcache.go +++ b/pkg/mcclient/options/elasticcache.go @@ -29,12 +29,13 @@ type ElasticCacheCreateOptions struct { Cloudregion string Zone string VpcId string - Network string `help:"network id"` - SecurityGroup string `help:"elastic cache security group. required by huawei."` - Engine string `choices:"redis"` - EngineVersion string `choices:"2.8|3.0|4.0|5.0"` - PrivateIP string `help:"private ip address in specificated network"` - Password string `help:"set auth password"` + Network string `help:"network id"` + SecurityGroup string `help:"elastic cache security group. required by huawei."` + SecgroupIds []string `help:"elastic cache security group. required by qcloud."` + Engine string `choices:"redis"` + EngineVersion string `choices:"2.8|3.0|3.2|4.0|5.0"` + PrivateIP string `help:"private ip address in specificated network"` + Password string `help:"set auth password"` InstanceType string CapacityMB string `help:"elastic cache capacity. required by huawei."` BillingType string `choices:"postpaid|prepaid" default:"postpaid"` diff --git a/pkg/multicloud/aliyun/elasticcache_instance.go b/pkg/multicloud/aliyun/elasticcache_instance.go index f72b3d7e62..0a8b2ac2b0 100644 --- a/pkg/multicloud/aliyun/elasticcache_instance.go +++ b/pkg/multicloud/aliyun/elasticcache_instance.go @@ -797,7 +797,7 @@ func (self *SElasticcache) UpdateInstanceParameters(config jsonutils.JSONObject) } // https://help.aliyun.com/document_detail/61075.html?spm=a2c4g.11186623.6.749.4cba126a2U9xNa -func (self *SElasticcache) CreateBackup() (cloudprovider.ICloudElasticcacheBackup, error) { +func (self *SElasticcache) CreateBackup(desc string) (cloudprovider.ICloudElasticcacheBackup, error) { params := make(map[string]string) params["InstanceId"] = self.GetId() @@ -826,7 +826,7 @@ func (self *SElasticcache) UpdateBackupPolicy(config cloudprovider.SCloudElastic } // https://help.aliyun.com/document_detail/60931.html?spm=a2c4g.11186623.6.728.5c57292920UKx3 -func (self *SElasticcache) FlushInstance() error { +func (self *SElasticcache) FlushInstance(input cloudprovider.SCloudElasticCacheFlushInstanceInput) error { params := make(map[string]string) params["InstanceId"] = self.GetId() @@ -873,6 +873,10 @@ func (self *SElasticcache) GetAuthMode() string { } } +func (self *SElasticcache) GetSecurityGroupIds() ([]string, error) { + return nil, cloudprovider.ErrNotSupported +} + func (self *SElasticcache) GetICloudElasticcacheAccount(accountId string) (cloudprovider.ICloudElasticcacheAccount, error) { segs := strings.Split(accountId, "/") if len(segs) < 2 { @@ -929,3 +933,7 @@ func (instance *SElasticcache) GetMetadata() *jsonutils.JSONDict { func (instance *SElasticcache) SetMetadata(tags map[string]string, replace bool) error { return instance.region.SetResourceTags("kvs", "INSTANCE", []string{instance.GetId()}, tags, replace) } + +func (self *SElasticcache) UpdateSecurityGroups(secgroupIds []string) error { + return errors.Wrap(cloudprovider.ErrNotSupported, "UpdateSecurityGroups") +} diff --git a/pkg/multicloud/huawei/elasticcache_instance.go b/pkg/multicloud/huawei/elasticcache_instance.go index 8b64ca37b6..c2ea37023d 100644 --- a/pkg/multicloud/huawei/elasticcache_instance.go +++ b/pkg/multicloud/huawei/elasticcache_instance.go @@ -439,7 +439,9 @@ func (self *SRegion) CreateIElasticcaches(ec *cloudprovider.SCloudElasticCacheIn params.Set("engine_version", jsonutils.NewString(ec.EngineVersion)) params.Set("capacity", jsonutils.NewInt(ec.CapacityGB)) params.Set("vpc_id", jsonutils.NewString(ec.VpcId)) - params.Set("security_group_id", jsonutils.NewString(ec.SecurityGroupId)) + if len(ec.SecurityGroupIds) > 0 { + params.Set("security_group_id", jsonutils.NewString(ec.SecurityGroupIds[0])) + } params.Set("subnet_id", jsonutils.NewString(ec.NetworkId)) params.Set("product_id", jsonutils.NewString(ec.InstanceType)) zones, err := self.zoneNameToDcsZoneIds(ec.ZoneIds) @@ -613,7 +615,7 @@ func (self *SElasticcache) UpdateInstanceParameters(config jsonutils.JSONObject) } // https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423033.html -func (self *SElasticcache) CreateBackup() (cloudprovider.ICloudElasticcacheBackup, error) { +func (self *SElasticcache) CreateBackup(desc string) (cloudprovider.ICloudElasticcacheBackup, error) { return nil, cloudprovider.ErrNotSupported } @@ -666,7 +668,7 @@ func (self *SElasticcache) UpdateBackupPolicy(config cloudprovider.SCloudElastic // https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423030.html // 当前版本,只有DCS2.0实例支持清空数据功能,即flush操作。 -func (self *SElasticcache) FlushInstance() error { +func (self *SElasticcache) FlushInstance(input cloudprovider.SCloudElasticCacheFlushInstanceInput) error { resp, err := self.region.ecsClient.Elasticcache.Flush(self.GetId()) if err != nil { return errors.Wrap(err, "elasticcache.FlushInstance") @@ -700,6 +702,10 @@ func (self *SElasticcache) GetAuthMode() string { } } +func (self *SElasticcache) GetSecurityGroupIds() ([]string, error) { + return nil, cloudprovider.ErrNotSupported +} + func (self *SElasticcache) GetICloudElasticcacheAccount(accountId string) (cloudprovider.ICloudElasticcacheAccount, error) { accounts, err := self.GetICloudElasticcacheAccounts() if err != nil { @@ -738,3 +744,7 @@ func (self *SElasticcache) GetICloudElasticcacheBackup(backupId string) (cloudpr func (instance *SElasticcache) SetMetadata(tags map[string]string, replace bool) error { return cloudprovider.ErrNotImplemented } + +func (self *SElasticcache) UpdateSecurityGroups(secgroupIds []string) error { + return errors.Wrap(cloudprovider.ErrNotSupported, "UpdateSecurityGroups") +} diff --git a/pkg/multicloud/qcloud/deal.go b/pkg/multicloud/qcloud/deal.go new file mode 100644 index 0000000000..753a73dc40 --- /dev/null +++ b/pkg/multicloud/qcloud/deal.go @@ -0,0 +1,55 @@ +package qcloud + +import ( + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SElasticcacheDeal struct { + region *SRegion + + DealID string `json:"DealId"` + DealName string `json:"DealName"` + ZoneID int64 `json:"ZoneId"` + GoodsNum int64 `json:"GoodsNum"` + Creater string `json:"Creater"` + CreatTime string `json:"CreatTime"` + OverdueTime string `json:"OverdueTime"` + EndTime string `json:"EndTime"` + Status int64 `json:"Status"` + Description string `json:"Description"` + Price int64 `json:"Price"` + InstanceIDS []string `json:"InstanceIds"` +} + +// https://cloud.tencent.com/document/product/239/30602 +func (self *SRegion) GetElasticcacheIdByDeal(dealId string) (string, error) { + params := map[string]string{} + params["DealIds.0"] = dealId + resp, err := self.redisRequest("DescribeInstanceDealDetail", params) + if err != nil { + return "", errors.Wrap(err, "DescribeInstanceDealDetail") + } + + ret := []SElasticcacheDeal{} + err = resp.Unmarshal(&ret, "DealDetails") + if err != nil { + return "", errors.Wrap(err, "DealDetails") + } + + if len(ret) == 0 { + return "", cloudprovider.ErrNotFound + } else if len(ret) > 1 { + log.Infof("%#v", ret) + return "", cloudprovider.ErrDuplicateId + } else { + if ret[0].InstanceIDS != nil && len(ret[0].InstanceIDS) == 1 { + return ret[0].InstanceIDS[0], nil + } else { + log.Infof("%#v", ret) + return "", cloudprovider.ErrNotFound + } + } +} diff --git a/pkg/multicloud/qcloud/elasticcache_account.go b/pkg/multicloud/qcloud/elasticcache_account.go new file mode 100644 index 0000000000..a08a3f35db --- /dev/null +++ b/pkg/multicloud/qcloud/elasticcache_account.go @@ -0,0 +1,152 @@ +package qcloud + +import ( + "fmt" + "strings" + + "github.com/pkg/errors" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SElasticcacheAccount struct { + multicloud.SElasticcacheAccountBase + + cacheDB *SElasticcache + + InstanceID string `json:"InstanceId"` + AccountName string `json:"AccountName"` + Remark string `json:"Remark"` + Privilege string `json:"Privilege"` + ReadonlyPolicy []string `json:"ReadonlyPolicy"` + Status int `json:"Status"` +} + +func (self *SElasticcacheAccount) GetId() string { + return fmt.Sprintf("%s/%s", self.InstanceID, self.AccountName) +} + +func (self *SElasticcacheAccount) GetName() string { + return self.AccountName +} + +func (self *SElasticcacheAccount) GetGlobalId() string { + return self.GetId() +} + +func (self *SElasticcacheAccount) GetStatus() string { + switch self.Status { + case 1: + return api.ELASTIC_CACHE_ACCOUNT_STATUS_CREATING + case 2: + return api.ELASTIC_CACHE_ACCOUNT_STATUS_AVAILABLE + case 4: + return api.ELASTIC_CACHE_ACCOUNT_STATUS_DELETED + default: + return api.ELASTIC_CACHE_ACCOUNT_STATUS_UNAVAILABLE + } +} + +func (self *SElasticcacheAccount) GetAccountType() string { + if strings.ToLower(self.AccountName) == "root" { + return api.ELASTIC_CACHE_ACCOUNT_TYPE_ADMIN + } else { + return api.ELASTIC_CACHE_ACCOUNT_TYPE_NORMAL + } +} + +func (self *SElasticcacheAccount) GetAccountPrivilege() string { + switch self.Privilege { + case "r": + return api.ELASTIC_CACHE_ACCOUNT_PRIVILEGE_READ + case "w": + return api.ELASTIC_CACHE_ACCOUNT_PRIVILEGE_WRITE + case "rw": + return api.ELASTIC_CACHE_ACCOUNT_PRIVILEGE_WRITE + default: + return self.Privilege + } +} + +func (self *SElasticcacheAccount) Refresh() error { + account, err := self.cacheDB.GetICloudElasticcacheAccount(self.GetGlobalId()) + if err != nil { + return errors.Wrap(err, "GetICloudElasticcacheAccount") + } + + err = jsonutils.Update(self, account) + if err != nil { + return errors.Wrap(err, "Update") + } + + return nil +} + +// https://cloud.tencent.com/document/product/239/38925 +func (self *SElasticcacheAccount) Delete() error { + params := map[string]string{} + params["InstanceId"] = self.cacheDB.GetId() + params["AccountName"] = self.AccountName + _, err := self.cacheDB.region.redisRequest("DeleteInstanceAccount", params) + if err != nil { + return errors.Wrap(err, "DeleteInstanceAccount") + } + + return nil +} + +// https://cloud.tencent.com/document/product/239/20014 +func (self *SElasticcacheAccount) ResetPassword(input cloudprovider.SCloudElasticCacheAccountResetPasswordInput) error { + params := map[string]string{} + params["InstanceId"] = self.cacheDB.GetId() + if input.NoPasswordAccess != nil && *input.NoPasswordAccess { + params["NoAuth"] = "true" + } else { + if len(input.NewPassword) > 0 { + params["Password"] = input.NewPassword + } else { + return nil + } + } + + _, err := self.cacheDB.region.redisRequest("ResetPassword", params) + if err != nil { + return errors.Wrap(err, "ResetPassword") + } + + return nil +} + +// https://cloud.tencent.com/document/product/239/38923 +func (self *SElasticcacheAccount) UpdateAccount(input cloudprovider.SCloudElasticCacheAccountUpdateInput) error { + params := map[string]string{} + params["InstanceId"] = self.cacheDB.GetId() + params["AccountName"] = self.AccountName + if input.NoPasswordAccess != nil && *input.NoPasswordAccess { + params["NoAuth"] = "true" + } else { + if input.Password != nil && len(*input.Password) > 0 { + params["AccountPassword"] = *input.Password + } + } + + if input.Description != nil && len(*input.Description) > 0 { + params["Remark"] = *input.Description + } + + if input.AccountPrivilege != nil && len(*input.AccountPrivilege) > 0 { + params["Privilege"] = *input.AccountPrivilege + params["ReadonlyPolicy.0"] = "master" + } + + _, err := self.cacheDB.region.redisRequest("ModifyInstanceAccount", params) + if err != nil { + return errors.Wrap(err, "ModifyInstanceAccount") + } + + return nil +} diff --git a/pkg/multicloud/qcloud/elasticcache_backup.go b/pkg/multicloud/qcloud/elasticcache_backup.go new file mode 100644 index 0000000000..34a4b55243 --- /dev/null +++ b/pkg/multicloud/qcloud/elasticcache_backup.go @@ -0,0 +1,170 @@ +package qcloud + +import ( + "strconv" + "strings" + "time" + + "github.com/pkg/errors" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SElasticcacheBackup struct { + multicloud.SElasticcacheBackupBase + + cacheDB *SElasticcache + + StartTime time.Time `json:"StartTime"` + BackupID string `json:"BackupId"` + BackupType string `json:"BackupType"` + Status int `json:"Status"` + Remark string `json:"Remark"` + Locked int64 `json:"Locked"` +} + +func (self *SElasticcacheBackup) GetId() string { + return self.BackupID +} + +func (self *SElasticcacheBackup) GetName() string { + if len(self.Remark) > 0 { + segs := strings.Split(self.Remark, "@") + return segs[0] + } + + return self.GetId() +} + +func (self *SElasticcacheBackup) GetGlobalId() string { + return self.GetId() +} + +/* + ELASTIC_CACHE_BACKUP_STATUS_CREATING = "creating" // 备份中 + ELASTIC_CACHE_BACKUP_STATUS_CREATE_EXPIRED = "expired" //(备份文件已过期) + ELASTIC_CACHE_BACKUP_STATUS_CREATE_DELETED = "deleted" //(备份文件已删除) + ELASTIC_CACHE_BACKUP_STATUS_DELETING = "deleting" // 删除中 + ELASTIC_CACHE_BACKUP_STATUS_SUCCESS = "success" // 备份成功 + ELASTIC_CACHE_BACKUP_STATUS_FAILED = "failed" // 备份失败 +*/ +func (self *SElasticcacheBackup) GetStatus() string { + switch self.Status { + case 1: + return api.ELASTIC_CACHE_BACKUP_STATUS_CREATING + case 2: + return api.ELASTIC_CACHE_BACKUP_STATUS_SUCCESS + case -1: + return api.ELASTIC_CACHE_BACKUP_STATUS_CREATE_EXPIRED + case -2: + return api.ELASTIC_CACHE_BACKUP_STATUS_CREATE_DELETED + case 3: + return api.ELASTIC_CACHE_BACKUP_STATUS_CREATING + case 4: + return api.ELASTIC_CACHE_BACKUP_STATUS_SUCCESS + default: + return strconv.Itoa(self.Status) + } +} + +func (self *SElasticcacheBackup) Refresh() error { + backup, err := self.cacheDB.GetICloudElasticcacheBackup(self.GetGlobalId()) + if err != nil { + return errors.Wrap(err, "GetICloudElasticcacheBackup") + } + + err = jsonutils.Update(self, backup) + if err != nil { + return errors.Wrap(err, "Update") + } + + return nil +} + +func (self *SElasticcacheBackup) GetBackupSizeMb() int { + return 0 +} + +func (self *SElasticcacheBackup) GetBackupType() string { + return api.ELASTIC_CACHE_BACKUP_TYPE_INCREMENTAL +} + +func (self *SElasticcacheBackup) GetBackupMode() string { + switch self.BackupType { + case "systemBackupInstance": + return api.ELASTIC_CACHE_BACKUP_MODE_AUTOMATED + case "manualBackupInstance": + return api.ELASTIC_CACHE_BACKUP_MODE_MANUAL + default: + return self.BackupType + } +} + +// https://cloud.tencent.com/document/api/239/34443 +func (self *SElasticcacheBackup) GetDownloadURL() string { + url, err := self.GetBackupDownloadURL() + if err != nil { + log.Debugf("GetBackupDownloadURL %s", err) + return "" + } + + return url +} + +func (self *SElasticcacheBackup) GetStartTime() time.Time { + return self.StartTime +} + +func (self *SElasticcacheBackup) GetEndTime() time.Time { + return time.Time{} +} + +func (self *SElasticcacheBackup) Delete() error { + return errors.Wrap(cloudprovider.ErrNotSupported, "Delete") +} + +// https://cloud.tencent.com/document/product/239/34435 +// todo: password +func (self *SElasticcacheBackup) RestoreInstance(instanceId string) error { + params := map[string]string{} + params["InstanceId"] = instanceId + params["BackupId"] = self.GetId() + //if len(Password) > 0 { + // params["Password"] = Password + //} + + _, err := self.cacheDB.region.redisRequest("RestoreInstance", params) + if err != nil { + return errors.Wrap(err, "RestoreInstance") + } + + return nil +} + +func (self *SElasticcacheBackup) GetBackupDownloadURL() (string, error) { + params := map[string]string{} + params["InstanceId"] = self.cacheDB.GetId() + params["BackupId"] = self.GetId() + client := self.cacheDB.region.client + resp, err := client.redisRequest("DescribeBackupUrl", params) + if err != nil { + return "", errors.Wrap(err, "DescribeBackupUrl") + } + + urls := []string{} + err = resp.Unmarshal(&urls, "DownloadUrl") + if err != nil { + return "", errors.Wrap(err, "Unmarshal.DownloadUrl") + } + + if len(urls) > 0 { + return urls[0], nil + } + + return "", nil +} diff --git a/pkg/multicloud/qcloud/elasticcache_instance.go b/pkg/multicloud/qcloud/elasticcache_instance.go new file mode 100644 index 0000000000..00642c6865 --- /dev/null +++ b/pkg/multicloud/qcloud/elasticcache_instance.go @@ -0,0 +1,1062 @@ +package qcloud + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/pkg/errors" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SElasticcache struct { + region *SRegion + + MaintenanceTime *MaintenanceTime `json:"maintenance_time"` + NoAuth *bool `json:"no_auth"` + Appid int64 `json:"Appid"` + AutoRenewFlag int64 `json:"AutoRenewFlag"` + BillingMode int64 `json:"BillingMode"` + CloseTime string `json:"CloseTime"` + Createtime time.Time `json:"Createtime"` + DeadlineTime string `json:"DeadlineTime"` + Engine string `json:"Engine"` + InstanceID string `json:"InstanceId"` + InstanceName string `json:"InstanceName"` + InstanceNode []interface{} `json:"InstanceNode"` + InstanceTags []InstanceTag `json:"InstanceTags"` + InstanceTitle string `json:"InstanceTitle"` + OfflineTime string `json:"OfflineTime"` + Port int `json:"Port"` + PriceID int64 `json:"PriceId"` + ProductType string `json:"ProductType"` + ProjectID int `json:"ProjectId"` + RedisReplicasNum int `json:"RedisReplicasNum"` + RedisShardNum int64 `json:"RedisShardNum"` + RedisShardSize int64 `json:"RedisShardSize"` // MB + DiskSize int64 `json:"disk_size"` + RegionID int64 `json:"RegionId"` + Size int `json:"Size"` + SizeUsed int64 `json:"SizeUsed"` + SlaveReadWeight int64 `json:"SlaveReadWeight"` + Status int `json:"Status"` + SubStatus int64 `json:"SubStatus"` + SubnetID int64 `json:"SubnetId"` + Tags []interface{} `json:"Tags"` + Type int `json:"Type"` + UniqSubnetID string `json:"UniqSubnetId"` + UniqVpcID string `json:"UniqVpcId"` + VpcID int64 `json:"VpcId"` + WANIP string `json:"WanIp"` + ZoneID int `json:"ZoneId"` +} + +func (self *SElasticcache) SetMetadata(tags map[string]string, replace bool) error { + return cloudprovider.ErrNotImplemented +} + +type InstanceTag struct { + TagKey string `json:"TagKey"` + TagValue string `json:"TagValue"` +} + +type MaintenanceTime struct { + StartTime time.Time `json:"start_time"` + EndTime time.Time `json:"end_time"` +} + +var zoneMaps = map[int]string{ + 100001: "ap-guangzhou-1", + 100002: "ap-guangzhou-2", + 100003: "ap-guangzhou-3", + 100004: "ap-guangzhou-4", + 110001: "ap-shenzhen-fsi-1", + 110002: "ap-shenzhen-fsi-2", + 110003: "ap-shenzhen-fsi-3", + 200001: "ap-shanghai-1", + 200002: "ap-shanghai-2", + 200003: "ap-shanghai-3", + 200004: "ap-shanghai-4", + 200005: "ap-shanghai-5", + 200006: "ap-shanghai-6", + 200007: "ap-shanghai-7", + 700001: "ap-shanghai-fsi-1", + 700002: "ap-shanghai-fsi-2", + 700003: "ap-shanghai-fsi-3", + 330001: "ap-nanjing-1", + 330002: "ap-nanjing-2", + 800001: "ap-beijing-1", + 800002: "ap-beijing-2", + 800003: "ap-beijing-3", + 800004: "ap-beijing-4", + 800005: "ap-beijing-5", + 460001: "ap-beijing-fsi-1", + 360001: "ap-tianjin-1", + 360002: "ap-tianjin-2", + 160001: "ap-chengdu-1", + 160002: "ap-chengdu-2", + 190001: "ap-chongqing-1", + 300001: "ap-hongkong-1", + 300002: "ap-hongkong-2", + 390001: "ap-taipei-1", + 900001: "ap-singapore-1", + 230001: "ap-bangkok-1", + 210001: "ap-mumbai-1", + 210002: "ap-mumbai-2", + 180001: "ap-seoul-1", + 250001: "ap-tokyo-1", + 150001: "na-siliconvalley-1", + 150002: "na-siliconvalley-2", + 220001: "na-ashburn-1", + 220002: "na-ashburn-2", + 400001: "na-toronto-1", + 170001: "eu-frankfurt-1", + 240001: "eu-moscow-1", +} + +var zoneIdMaps = map[string]int{ + "ap-guangzhou-1": 100001, + "ap-guangzhou-2": 100002, + "ap-guangzhou-3": 100003, + "ap-guangzhou-4": 100004, + "ap-shenzhen-fsi-1": 110001, + "ap-shenzhen-fsi-2": 110002, + "ap-shenzhen-fsi-3": 110003, + "ap-shanghai-1": 200001, + "ap-shanghai-2": 200002, + "ap-shanghai-3": 200003, + "ap-shanghai-4": 200004, + "ap-shanghai-5": 200005, + "ap-shanghai-6": 200006, + "ap-shanghai-7": 200007, + "ap-shanghai-fsi-1": 700001, + "ap-shanghai-fsi-2": 700002, + "ap-shanghai-fsi-3": 700003, + "ap-nanjing-1": 330001, + "ap-nanjing-2": 330002, + "ap-beijing-1": 800001, + "ap-beijing-2": 800002, + "ap-beijing-3": 800003, + "ap-beijing-4": 800004, + "ap-beijing-5": 800005, + "ap-beijing-fsi-1": 460001, + "ap-tianjin-1": 360001, + "ap-tianjin-2": 360002, + "ap-chengdu-1": 160001, + "ap-chengdu-2": 160002, + "ap-chongqing-1": 190001, + "ap-hongkong-1": 300001, + "ap-hongkong-2": 300002, + "ap-taipei-1": 390001, + "ap-singapore-1": 900001, + "ap-bangkok-1": 230001, + "ap-mumbai-1": 210001, + "ap-mumbai-2": 210002, + "ap-seoul-1": 180001, + "ap-tokyo-1": 250001, + "na-siliconvalley-1": 150001, + "na-siliconvalley-2": 150002, + "na-ashburn-1": 220001, + "na-ashburn-2": 220002, + "na-toronto-1": 400001, + "eu-frankfurt-1": 170001, + "eu-moscow-1": 240001, +} + +func (self *SElasticcache) GetId() string { + return self.InstanceID +} + +func (self *SElasticcache) GetName() string { + return self.InstanceName +} + +func (self *SElasticcache) GetGlobalId() string { + return self.GetId() +} + +// https://cloud.tencent.com/document/api/239/20022#InstanceSet +// 实例当前状态,0:待初始化;1:实例在流程中;2:实例运行中;-2:实例已隔离;-3:实例待删除 +func (self *SElasticcache) GetStatus() string { + switch self.Status { + case 2: + return api.ELASTIC_CACHE_STATUS_RUNNING + case 0: + return api.ELASTIC_CACHE_STATUS_DEPLOYING + case -3: + return api.ELASTIC_CACHE_STATUS_RELEASING + case -2: + return api.ELASTIC_CACHE_STATUS_UNAVAILABLE + case 1: + return api.ELASTIC_CACHE_STATUS_CHANGING + } + + return "" +} + +func (self *SElasticcache) Refresh() error { + cache, err := self.region.GetIElasticcacheById(self.GetId()) + if err != nil { + return errors.Wrap(err, "Elasticcache.Refresh.GetElasticCache") + } + + err = jsonutils.Update(self, cache) + if err != nil { + return errors.Wrap(err, "Elasticcache.Refresh.Update") + } + + return nil +} + +func (self *SElasticcache) IsEmulated() bool { + return false +} + +func (self *SElasticcache) GetMetadata() *jsonutils.JSONDict { + return nil +} + +func (self *SElasticcache) GetProjectId() string { + return strconv.Itoa(self.ProjectID) +} + +func (self *SElasticcache) GetBillingType() string { + // 计费模式:0-按量计费,1-包年包月 + if self.BillingMode == 1 { + return billing_api.BILLING_TYPE_PREPAID + } else { + return billing_api.BILLING_TYPE_POSTPAID + } +} + +func (self *SElasticcache) GetCreatedAt() time.Time { + return self.Createtime +} + +func (self *SElasticcache) GetExpiredAt() time.Time { + if self.DeadlineTime == "0000-00-00 00:00:00" || len(self.DeadlineTime) == 0 { + return time.Time{} + } + + t, err := time.Parse("2006-01-02 15:04:05", self.DeadlineTime) + if err != nil { + log.Debugf("GetExpiredAt.Parse %s", err) + return time.Time{} + } + + return t +} + +func (self *SElasticcache) SetAutoRenew(autoRenew bool) error { + panic("implement me") +} + +func (self *SElasticcache) IsAutoRenew() bool { + // 实例是否设置自动续费标识,1:设置自动续费;0:未设置自动续费 + return self.AutoRenewFlag == 1 +} + +// redis:master:s1:r5:m1g:v4.0 +// 实例类型:2 – Redis2.8内存版(标准架构),3 – CKV 3.2内存版(标准架构),4 – CKV 3.2内存版(集群架构) +// ,6 – Redis4.0内存版(标准架构),7 – Redis4.0内存版(集群架构), +// 8 – Redis5.0内存版(标准架构),9 – Redis5.0内存版(集群架构) +func (self *SElasticcache) GetInstanceType() string { + segs := make([]string, 6) + segs[0] = "redis" + switch self.Type { + case 2, 3, 6, 8: + segs[1] = "master" + case 4, 7, 9, 10: + segs[1] = "cluster" + case 5: + segs[1] = "single" + default: + segs[1] = strconv.Itoa(self.Type) + } + + segs[2] = fmt.Sprintf("s%d", self.RedisShardNum) + segs[3] = fmt.Sprintf("r%d", self.RedisReplicasNum) + if self.DiskSize > 0 { + segs[4] = fmt.Sprintf("m%dg-d%dg", self.Size/1024, self.DiskSize) + } else { + segs[4] = fmt.Sprintf("m%dg", self.Size/1024) + } + segs[5] = fmt.Sprintf("v%s", self.GetEngineVersion()) + + return strings.Join(segs, ":") +} + +func (self *SElasticcache) GetCapacityMB() int { + return self.Size +} + +func (self *SElasticcache) GetArchType() string { + // 产品类型:standalone – 标准版,cluster – 集群版 + switch self.ProductType { + case "standalone": + return api.ELASTIC_CACHE_ARCH_TYPE_MASTER + case "cluster": + return api.ELASTIC_CACHE_ARCH_TYPE_CLUSTER + } + + return "" +} + +func (self *SElasticcache) GetNodeType() string { + // single(单副本) | double(双副本) + switch self.RedisReplicasNum { + case 1: + return "single" + case 2: + return "double" + case 3: + return "three" + case 4: + return "four" + case 5: + return "five" + case 6: + return "six" + } + + return strconv.Itoa(self.RedisReplicasNum) +} + +func (self *SElasticcache) GetEngine() string { + return "redis" +} + +func (self *SElasticcache) GetEngineVersion() string { + switch self.Type { + case 1, 2, 5: + return "2.8" + case 3, 4: + return "3.2" + case 6, 7, 10: + return "4.0" + case 8, 9: + return "5.0" + } + + return "unknown" +} + +func (self *SElasticcache) GetVpcId() string { + return self.UniqVpcID +} + +// https://cloud.tencent.com/document/product/239/4106 +func (self *SElasticcache) GetZoneId() string { + zone := zoneMaps[self.ZoneID] + if len(zone) == 0 { + net, err := self.region.GetNetwork(self.UniqSubnetID) + if err != nil { + log.Warningf("zone %d not found. zone map needed update.", self.ZoneID) + log.Debugf("GetNetwork %s %s", self.UniqSubnetID, err) + return "" + } + + zone = net.Zone + } + + z, err := self.region.getZoneById(zone) + if err != nil { + log.Debugf("getZoneById %s", err) + return "" + } + + return z.GetGlobalId() +} + +func (self *SElasticcache) GetNetworkType() string { + if len(self.UniqVpcID) > 0 { + return api.LB_NETWORK_TYPE_VPC + } else { + return api.LB_NETWORK_TYPE_CLASSIC + } +} + +func (self *SElasticcache) GetNetworkId() string { + return self.UniqSubnetID +} + +func (self *SElasticcache) GetPrivateDNS() string { + return self.WANIP +} + +func (self *SElasticcache) GetPrivateIpAddr() string { + return self.WANIP +} + +func (self *SElasticcache) GetPrivateConnectPort() int { + return self.Port +} + +func (self *SElasticcache) GetPublicDNS() string { + return "" +} + +func (self *SElasticcache) GetPublicIpAddr() string { + return "" +} + +func (self *SElasticcache) GetPublicConnectPort() int { + return self.Port +} + +// https://cloud.tencent.com/document/api/239/46336 +func (self *SElasticcache) getMaintenanceTime() error { + params := map[string]string{} + params["InstanceId"] = self.GetId() + params["Region"] = self.region.GetId() + resp, err := self.region.client.redisRequest("DescribeMaintenanceWindow", params) + if err != nil { + return errors.Wrap(err, "DescribeMaintenanceWindow") + } + + self.MaintenanceTime = &MaintenanceTime{} + err = resp.Unmarshal(self.MaintenanceTime) + if err != nil { + return errors.Wrap(err, "err.MaintenanceTime") + } + + return nil +} + +func (self *SElasticcache) GetMaintainStartTime() string { + if self.MaintenanceTime == nil { + if err := self.getMaintenanceTime(); err != nil { + log.Debugf("getMaintenanceTime %s", err) + return "" + } + } + + return self.MaintenanceTime.StartTime.String() +} + +func (self *SElasticcache) GetMaintainEndTime() string { + if self.MaintenanceTime == nil { + if err := self.getMaintenanceTime(); err != nil { + log.Debugf("getMaintenanceTime %s", err) + return "" + } + } + + return self.MaintenanceTime.EndTime.String() +} + +func (self *SElasticcache) GetAuthMode() string { + if self.NoAuth != nil && *self.NoAuth == false { + return "on" + } + + return "off" +} + +func (self *SElasticcache) GetSecurityGroupIds() ([]string, error) { + ss, err := self.region.GetCloudElasticcacheSecurityGroups(self.GetId()) + if err != nil { + return nil, errors.Wrap(err, "GetCloudElasticcacheSecurityGroups") + } + + ret := make([]string, len(ss)) + for i := range ss { + ret[i] = ss[i].SecurityGroupID + } + + return ret, nil +} + +func (self *SElasticcache) GetICloudElasticcacheAccounts() ([]cloudprovider.ICloudElasticcacheAccount, error) { + accounts, err := self.region.GetCloudElasticcacheAccounts(self.GetId()) + if err != nil { + return nil, errors.Wrap(err, "GetCloudElasticcacheAccounts") + } + + iaccounts := make([]cloudprovider.ICloudElasticcacheAccount, len(accounts)) + for i := range accounts { + account := accounts[i] + account.cacheDB = self + iaccounts[i] = &account + } + + return iaccounts, nil +} + +func (self *SElasticcache) GetICloudElasticcacheAcls() ([]cloudprovider.ICloudElasticcacheAcl, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (self *SElasticcache) GetICloudElasticcacheBackups() ([]cloudprovider.ICloudElasticcacheBackup, error) { + backups, err := self.region.GetCloudElasticcacheBackups(self.GetId()) + if err != nil { + return nil, errors.Wrap(err, "GetCloudElasticcacheBackups") + } + + ibackups := make([]cloudprovider.ICloudElasticcacheBackup, len(backups)) + for i := range backups { + backup := backups[i] + backup.cacheDB = self + ibackups[i] = &backup + } + + return ibackups, nil +} + +func (self *SElasticcache) GetICloudElasticcacheParameters() ([]cloudprovider.ICloudElasticcacheParameter, error) { + parameters, err := self.region.GetCloudElasticcacheParameters(self.GetId()) + if err != nil { + return nil, errors.Wrap(err, "GetCloudElasticcacheParameters") + } + + ret := []cloudprovider.ICloudElasticcacheParameter{} + for i := range parameters { + parameter := parameters[i] + parameter.cacheDB = self + ret = append(ret, ¶meter) + } + + return ret, nil +} + +func (self *SElasticcache) GetICloudElasticcacheAccount(accountId string) (cloudprovider.ICloudElasticcacheAccount, error) { + accounts, err := self.region.GetCloudElasticcacheAccounts(self.GetId()) + if err != nil { + return nil, errors.Wrap(err, "GetCloudElasticcacheAccounts") + } + + for i := range accounts { + account := accounts[i] + if account.GetGlobalId() == accountId { + account.cacheDB = self + return &account, nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +func (self *SElasticcache) GetICloudElasticcacheAcl(aclId string) (cloudprovider.ICloudElasticcacheAcl, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (self *SElasticcache) GetICloudElasticcacheBackup(backupId string) (cloudprovider.ICloudElasticcacheBackup, error) { + backups, err := self.region.GetCloudElasticcacheBackups(self.InstanceID) + if err != nil { + return nil, errors.Wrap(err, "GetCloudElasticcacheBackups") + } + + for i := range backups { + backup := backups[i] + if backup.GetGlobalId() == backupId { + backup.cacheDB = self + return &backup, nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +func (self *SElasticcache) GetICloudElasticcacheBackupByReadmark(readmark string) (cloudprovider.ICloudElasticcacheBackup, error) { + backups, err := self.region.GetCloudElasticcacheBackups(self.InstanceID) + if err != nil { + return nil, errors.Wrap(err, "GetCloudElasticcacheBackups") + } + + for i := range backups { + backup := backups[i] + if backup.Remark == readmark { + backup.cacheDB = self + return &backup, nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +func (self *SElasticcache) Restart() error { + return errors.Wrap(cloudprovider.ErrNotSupported, "Restart") +} + +// https://cloud.tencent.com/document/product/239/34440 +func (self *SElasticcache) Delete() error { + err := self.Destroy() + if err != nil { + return errors.Wrap(err, "Destroy") + } + + err = cloudprovider.WaitStatus(self, api.ELASTIC_CACHE_STATUS_RELEASING, 5*time.Second, 180*time.Second) + if err != nil { + return errors.Wrap(err, "WaitStatus.releasing") + } + + err = self.CleanupInstance() + if err != nil { + return errors.Wrap(err, "CleanupInstance") + } + + return nil +} + +// https://cloud.tencent.com/document/product/239/34439 +func (self *SElasticcache) Destroy() error { + params := map[string]string{} + params["InstanceId"] = self.GetId() + _, err := self.region.redisRequest("DestroyPostpaidInstance", params) + if err != nil { + return errors.Wrap(err, "DestroyPostpaidInstance") + } + + return nil +} + +// https://cloud.tencent.com/document/product/239/34442 +func (self *SElasticcache) CleanupInstance() error { + params := map[string]string{} + params["InstanceId"] = self.GetId() + _, err := self.region.redisRequest("CleanUpInstance", params) + if err != nil { + return errors.Wrap(err, "CleanUpInstance") + } + + return nil +} + +// https://cloud.tencent.com/document/product/239/20013 +func (self *SElasticcache) ChangeInstanceSpec(spec string) error { + s, err := parseLocalInstanceSpec(spec) + if err != nil { + return errors.Wrap(err, "parseLocalInstanceSpec") + } + + params := map[string]string{} + params["InstanceId"] = self.GetId() + params["MemSize"] = fmt.Sprintf("%d", s.MemSizeMB) + params["RedisShardNum"] = s.RedisShardNum + params["RedisReplicasNum"] = s.RedisReplicasNum + _, err = self.region.redisRequest("UpgradeInstance", params) + if err != nil { + return errors.Wrap(err, "UpgradeInstance") + } + + return nil +} + +// https://cloud.tencent.com/document/product/239/46335 +func (self *SElasticcache) SetMaintainTime(maintainStartTime, maintainEndTime string) error { + params := map[string]string{} + params["InstanceId"] = self.GetId() + params["StartTime"] = maintainStartTime + params["EndTime"] = maintainEndTime + _, err := self.region.redisRequest("ModifyMaintenanceWindow", params) + if err != nil { + return errors.Wrap(err, "ModifyMaintenanceWindow") + } + + return nil +} + +func (self *SElasticcache) AllocatePublicConnection(port int) (string, error) { + return "", errors.Wrap(cloudprovider.ErrNotSupported, "AllocatePublicConnection") +} + +func (self *SElasticcache) ReleasePublicConnection() error { + return errors.Wrap(cloudprovider.ErrNotSupported, "ReleasePublicConnection") +} + +// https://cloud.tencent.com/document/product/239/38926 +func (self *SElasticcache) CreateAccount(account cloudprovider.SCloudElasticCacheAccountInput) (cloudprovider.ICloudElasticcacheAccount, error) { + params := map[string]string{} + params["InstanceId"] = self.GetId() + params["AccountName"] = account.AccountName + params["AccountPassword"] = account.AccountPassword + params["Privilege"] = account.AccountPrivilege + params["ReadonlyPolicy.0"] = "master" + if len(account.Description) > 0 { + params["Remark"] = account.Description + } + + _, err := self.region.redisRequest("CreateInstanceAccount", params) + if err != nil { + return nil, errors.Wrap(err, "CreateInstanceAccount") + } + + accountId := "" + cloudprovider.Wait(5*time.Second, 60*time.Second, func() (bool, error) { + accounts, err := self.GetICloudElasticcacheAccounts() + if err != nil { + log.Debugf("GetICloudElasticcacheAccounts %s", err) + return false, nil + } + + for i := range accounts { + if accounts[i].GetName() == account.AccountName { + accountId = accounts[i].GetGlobalId() + return true, nil + } + } + + return false, nil + }) + + return self.GetICloudElasticcacheAccount(accountId) +} + +func (self *SElasticcache) CreateAcl(aclName, securityIps string) (cloudprovider.ICloudElasticcacheAcl, error) { + return nil, errors.Wrap(cloudprovider.ErrNotSupported, "CreateAcl") +} + +// https://cloud.tencent.com/document/product/239/20010 +func (self *SElasticcache) CreateBackup(desc string) (cloudprovider.ICloudElasticcacheBackup, error) { + readmark := strings.Join([]string{desc, fmt.Sprintf("%d", time.Now().Unix())}, "@") + params := map[string]string{} + params["InstanceId"] = self.GetId() + params["Remark"] = readmark + resp, err := self.region.redisRequest("ManualBackupInstance", params) + if err != nil { + return nil, errors.Wrap(err, "ManualBackupInstance") + } + + taskId := 0 + err = resp.Unmarshal(&taskId, "TaskId") + if err != nil { + return nil, errors.Wrap(err, "Unmarshal") + } + + var task *SElasticcacheTask + err = cloudprovider.Wait(5*time.Second, 900*time.Second, func() (bool, error) { + task, err = self.region.DescribeTaskInfo(fmt.Sprintf("%d", taskId)) + if err != nil { + return false, nil + } + + if task != nil { + if task.Status == "failed" || task.Status == "error" { + return false, fmt.Errorf("CreateBackup failed %#v", task) + } + + if task.Status == "succeed" { + return true, nil + } + } + + return false, nil + }) + if err != nil { + return nil, errors.Wrap(err, "Wait.DescribeTaskInfo") + } + + if task == nil { + return nil, fmt.Errorf("CreateBackup failed task is empty") + } + + return self.GetICloudElasticcacheBackupByReadmark(readmark) +} + +// https://cloud.tencent.com/document/product/239/20021 +func (self *SElasticcache) FlushInstance(input cloudprovider.SCloudElasticCacheFlushInstanceInput) error { + params := map[string]string{} + params["InstanceId"] = self.GetId() + if self.NoAuth != nil && *self.NoAuth == false { + if len(input.Password) > 0 { + params["Password"] = input.Password + } else { + return fmt.Errorf("password required on auth mode while flush elastich cache instance") + } + } + _, err := self.region.redisRequest("ClearInstance", params) + if err != nil { + return errors.Wrap(err, "ClearInstance") + } + + return nil +} + +// https://cloud.tencent.com/document/product/239/38923 +// true表示将主账号切换为免密账号,这里只适用于主账号,子账号不可免密 +func (self *SElasticcache) UpdateAuthMode(noPasswordAccess bool) error { + account, err := self.getCloudElasticcacheAccount("root") + if err != nil { + return errors.Wrap(err, "getCloudElasticcacheAccount") + } + + input := cloudprovider.SCloudElasticCacheAccountUpdateInput{} + input.NoPasswordAccess = &noPasswordAccess + //if noPasswordAccess == false { + // input.Password = password + //} + + err = account.UpdateAccount(input) + if err != nil { + return errors.Wrap(err, "UpdateAccount") + } + + return nil +} + +// https://cloud.tencent.com/document/product/239/34445 +// todo: finish me +func (self *SElasticcache) UpdateInstanceParameters(config jsonutils.JSONObject) error { + params := map[string]string{} + params["InstanceId"] = self.GetId() + params["InstanceParams.0"] = config.String() + _, err := self.region.redisRequest("ModifyInstanceParams", params) + if err != nil { + return errors.Wrap(err, "ModifyInstanceParams") + } + + return nil +} + +func (self *SElasticcache) UpdateBackupPolicy(config cloudprovider.SCloudElasticCacheBackupPolicyUpdateInput) error { + panic("implement me") +} + +// https://cloud.tencent.com/document/api/239/38924 +func (self *SElasticcache) getCloudElasticcacheAccount(accountName string) (*SElasticcacheAccount, error) { + accounts, err := self.region.GetCloudElasticcacheAccounts(self.GetId()) + if err != nil { + return nil, errors.Wrap(err, "GetCloudElasticcacheAccounts") + } + + for i := range accounts { + if accounts[i].AccountName == accountName { + accounts[i].cacheDB = self + return &accounts[i], nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +// https://cloud.tencent.com/document/api/239/41256 +func (self *SElasticcache) UpdateSecurityGroups(secgroupIds []string) error { + params := map[string]string{} + params["InstanceId"] = self.GetId() + params["Product"] = "redis" + for i := range secgroupIds { + params[fmt.Sprintf("SecurityGroupIds.%d", i)] = secgroupIds[i] + } + + _, err := self.region.redisRequest("ModifyDBInstanceSecurityGroups", params) + if err != nil { + return errors.Wrap(err, "ModifyDBInstanceSecurityGroups") + } + + return nil +} + +// https://cloud.tencent.com/document/api/239/38924 +func (self *SRegion) GetCloudElasticcacheAccounts(instanceId string) ([]SElasticcacheAccount, error) { + params := map[string]string{} + params["Region"] = self.GetId() + params["InstanceId"] = instanceId + params["Limit"] = "1000" + params["Offset"] = "0" + resp, err := self.client.redisRequest("DescribeInstanceAccount", params) + if err != nil { + return nil, errors.Wrap(err, "DescribeInstanceAccount") + } + + ret := []SElasticcacheAccount{} + err = resp.Unmarshal(&ret, "Accounts") + if err != nil { + return nil, errors.Wrap(err, "Unmarshal") + } + + return ret, nil +} + +// https://cloud.tencent.com/document/api/239/20011 +func (self *SRegion) GetCloudElasticcacheBackups(instanceId string) ([]SElasticcacheBackup, error) { + params := map[string]string{} + params["Region"] = self.GetId() + params["InstanceId"] = instanceId + params["Limit"] = "100" + resp, err := self.client.redisRequest("DescribeInstanceBackups", params) + if err != nil { + return nil, errors.Wrap(err, "DescribeInstanceBackups") + } + + ret := []SElasticcacheBackup{} + err = resp.Unmarshal(&ret, "BackupSet") + if err != nil { + return nil, errors.Wrap(err, "Unmarshal") + } + + return ret, nil +} + +// https://cloud.tencent.com/document/api/239/41259 +func (self *SRegion) GetCloudElasticcacheSecurityGroups(instanceId string) ([]SElasticcacheSecgroup, error) { + params := map[string]string{} + params["Region"] = self.GetId() + params["InstanceId"] = instanceId + params["Product"] = "redis" + resp, err := self.client.redisRequest("DescribeDBSecurityGroups", params) + if err != nil { + return nil, errors.Wrap(err, "DescribeDBSecurityGroups") + } + + ret := []SElasticcacheSecgroup{} + err = resp.Unmarshal(&ret, "Groups") + if err != nil { + return nil, errors.Wrap(err, "Unmarshal") + } + + return ret, nil +} + +// https://cloud.tencent.com/document/api/239/34448 +func (self *SRegion) GetCloudElasticcacheParameters(instanceId string) ([]SElasticcacheParameter, error) { + params := map[string]string{} + params["Region"] = self.GetId() + params["InstanceId"] = instanceId + resp, err := self.client.redisRequest("DescribeInstanceParams", params) + if err != nil { + return nil, errors.Wrap(err, "DescribeInstanceParams") + } + + ret1 := []SElasticcacheParameter{} + err = resp.Unmarshal(&ret1, "InstanceEnumParam") + if err != nil { + return nil, errors.Wrap(err, "InstanceEnumParam") + } + + ret2 := []SElasticcacheParameter{} + err = resp.Unmarshal(&ret2, "InstanceIntegerParam") + if err != nil { + return nil, errors.Wrap(err, "InstanceIntegerParam") + } + + ret3 := []SElasticcacheParameter{} + err = resp.Unmarshal(&ret3, "InstanceMultiParam") + if err != nil { + return nil, errors.Wrap(err, "InstanceMultiParam") + } + + ret4 := []SElasticcacheParameter{} + err = resp.Unmarshal(&ret4, "InstanceTextParam") + if err != nil { + return nil, errors.Wrap(err, "InstanceTextParam") + } + + ret := []SElasticcacheParameter{} + ret = append(ret, ret1...) + ret = append(ret, ret2...) + ret = append(ret, ret3...) + ret = append(ret, ret4...) + return ret, nil +} + +// https://cloud.tencent.com/document/api/239/20018 +func (self *SRegion) GetCloudElasticcaches() ([]SElasticcache, error) { + params := map[string]string{} + params["Region"] = self.GetId() + params["Limit"] = "100" + resp, err := self.client.redisRequest("DescribeInstances", params) + if err != nil { + return nil, errors.Wrap(err, "DescribeInstances") + } + + ret := []SElasticcache{} + err = resp.Unmarshal(&ret, "InstanceSet") + if err != nil { + return nil, errors.Wrap(err, "Unmarshal") + } + + return ret, nil +} + +type elasticcachInstanceSpec struct { + TypeId string `json:"type_id"` + MemSizeMB int `json:"mem_size_mb"` + DiskSizeGB int `json:"disk_size_gb"` + RedisShardNum string `json:"redis_shard_num"` + RedisReplicasNum string `json:"redis_replicas_num"` +} + +// redis:master:s1:r5:m1g:v4.0 +func parseLocalInstanceSpec(s string) (elasticcachInstanceSpec, error) { + ret := elasticcachInstanceSpec{} + segs := strings.Split(s, ":") + if len(segs) != 6 { + return ret, fmt.Errorf("invalid instance spec %s", s) + } + if segs[1] == "master" { + switch segs[5] { + case "v2.8": + ret.TypeId = "2" + case "v3.2": + ret.TypeId = "3" + case "v4.0": + ret.TypeId = "6" + case "v5.0": + ret.TypeId = "8" + default: + return ret, fmt.Errorf("unknown master elastic cache version %s", segs[1]) + } + } else if segs[1] == "cluster" { + switch segs[5] { + case "v3.0": + ret.TypeId = "4" + case "v4.0": + ret.TypeId = "7" + case "v5.0": + if strings.Contains(segs[4], "-d") { + ret.TypeId = "10" + } else { + ret.TypeId = "9" + } + + default: + return ret, fmt.Errorf("unknown cluster elastic cache version %s", segs[1]) + } + } else if segs[1] == "single" { + switch segs[5] { + case "v2.8": + ret.TypeId = "5" + default: + return ret, fmt.Errorf("unknown single elastic cache version %s", segs[1]) + } + } else { + return ret, fmt.Errorf("unknown elastic cache type %s", segs[1]) + } + + // + sizes := strings.Split(segs[4], "-") + if len(sizes) == 2 { + ms, err := strconv.Atoi(strings.Trim(sizes[1], "dg")) + if err != nil { + return ret, errors.Wrap(err, "Atoi") + } + + ret.DiskSizeGB = ms + } + + sn, err := strconv.Atoi(strings.Trim(segs[2], "s")) + if err != nil { + return ret, errors.Wrap(err, "RedisShardNum") + } + + ms, err := strconv.Atoi(strings.Trim(sizes[0], "mdg")) + if err != nil { + return ret, errors.Wrap(err, "Atoi") + } + if strings.HasSuffix(sizes[0], "g") { + ret.MemSizeMB = ms * 1024 / sn + } else if strings.HasSuffix(sizes[0], "m") { + ret.MemSizeMB = ms / sn + } + + ret.RedisShardNum = strings.Trim(segs[2], "s") + ret.RedisReplicasNum = strings.Trim(segs[3], "r") + return ret, nil +} diff --git a/pkg/multicloud/qcloud/elasticcache_parameters.go b/pkg/multicloud/qcloud/elasticcache_parameters.go new file mode 100644 index 0000000000..02eeec20e8 --- /dev/null +++ b/pkg/multicloud/qcloud/elasticcache_parameters.go @@ -0,0 +1,77 @@ +package qcloud + +import ( + "fmt" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SElasticcacheParameters struct { + multicloud.SElasticcacheParameterBase + + cacheDB *SElasticcache + + InstanceEnumParam []SElasticcacheParameter `json:"InstanceEnumParam"` + InstanceIntegerParam []SElasticcacheParameter `json:"InstanceIntegerParam"` + InstanceTextParam []SElasticcacheParameter `json:"InstanceTextParam"` + RequestID string `json:"RequestId"` + TotalCount int64 `json:"TotalCount"` +} + +type SElasticcacheParameter struct { + multicloud.SElasticcacheParameterBase + + cacheDB *SElasticcache + + CurrentValue string `json:"CurrentValue"` + DefaultValue string `json:"DefaultValue"` + EnumValue []string `json:"EnumValue,omitempty"` + NeedRestart string `json:"NeedRestart"` + ParamName string `json:"ParamName"` + Tips string `json:"Tips"` + ValueType string `json:"ValueType"` + Max *string `json:"Max,omitempty"` + Min *string `json:"Min,omitempty"` + TextValue []string `json:"TextValue,omitempty"` +} + +func (self *SElasticcacheParameter) GetId() string { + return fmt.Sprintf("%s/%s", self.cacheDB.InstanceID, self.ParamName) +} + +func (self *SElasticcacheParameter) GetName() string { + return self.ParamName +} + +func (self *SElasticcacheParameter) GetGlobalId() string { + return self.GetId() +} + +func (self *SElasticcacheParameter) GetStatus() string { + return api.ELASTIC_CACHE_PARAMETER_STATUS_AVAILABLE +} + +func (self *SElasticcacheParameter) GetParameterKey() string { + return self.ParamName +} + +func (self *SElasticcacheParameter) GetParameterValue() string { + return self.CurrentValue +} + +func (self *SElasticcacheParameter) GetParameterValueRange() string { + return fmt.Sprintf("%s", self.EnumValue) +} + +func (self *SElasticcacheParameter) GetDescription() string { + return self.Tips +} + +func (self *SElasticcacheParameter) GetModifiable() bool { + return true +} + +func (self *SElasticcacheParameter) GetForceRestart() bool { + return self.NeedRestart == "true" +} diff --git a/pkg/multicloud/qcloud/elasticcache_secgroup.go b/pkg/multicloud/qcloud/elasticcache_secgroup.go new file mode 100644 index 0000000000..2591a21d26 --- /dev/null +++ b/pkg/multicloud/qcloud/elasticcache_secgroup.go @@ -0,0 +1,24 @@ +package qcloud + +import "yunion.io/x/onecloud/pkg/multicloud" + +type SElasticcacheSecgroup struct { + multicloud.SElasticcacheBackupBase + + cacheDB *SElasticcache + + CreateTime string `json:"CreateTime"` + InboundRule []BoundRule `json:"InboundRule"` + OutboundRule []BoundRule `json:"OutboundRule"` + ProjectID int64 `json:"ProjectId"` + SecurityGroupID string `json:"SecurityGroupId"` + SecurityGroupName string `json:"SecurityGroupName"` + SecurityGroupRemark string `json:"SecurityGroupRemark"` +} + +type BoundRule struct { + Action string `json:"Action"` + IP string `json:"Ip"` + Port string `json:"Port"` + Proto string `json:"Proto"` +} diff --git a/pkg/multicloud/qcloud/elasticcache_task.go b/pkg/multicloud/qcloud/elasticcache_task.go new file mode 100644 index 0000000000..4941e3febb --- /dev/null +++ b/pkg/multicloud/qcloud/elasticcache_task.go @@ -0,0 +1,30 @@ +package qcloud + +import "yunion.io/x/pkg/errors" + +type SElasticcacheTask struct { + Status string `json:"Status"` + StartTime string `json:"StartTime"` + TaskType string `json:"TaskType"` + InstanceID string `json:"InstanceId"` + TaskMessage string `json:"TaskMessage"` + RequestID string `json:"RequestId"` +} + +// https://cloud.tencent.com/document/product/239/30601 +func (self *SRegion) DescribeTaskInfo(taskId string) (*SElasticcacheTask, error) { + params := map[string]string{} + params["TaskId"] = taskId + resp, err := self.redisRequest("DescribeTaskInfo", params) + if err != nil { + return nil, errors.Wrap(err, "DescribeTaskInfo") + } + + ret := &SElasticcacheTask{} + err = resp.Unmarshal(ret) + if err != nil { + return nil, errors.Wrap(err, "Unmarshal") + } + + return ret, nil +} diff --git a/pkg/multicloud/qcloud/qcloud.go b/pkg/multicloud/qcloud/qcloud.go index 563b32b282..d14187b25b 100644 --- a/pkg/multicloud/qcloud/qcloud.go +++ b/pkg/multicloud/qcloud/qcloud.go @@ -55,6 +55,7 @@ const ( QCLOUD_MARIADB_API_VERSION = "2017-03-12" QCLOUD_POSTGRES_API_VERSION = "2017-03-12" QCLOUD_SQLSERVER_API_VERSION = "2018-03-28" + QCLOUD_REDIS_API_VERSION = "2018-04-12" ) type QcloudClientConfig struct { @@ -159,6 +160,12 @@ func accountRequest(client *common.Client, apiName string, params map[string]str return _phpJsonRequest(client, &wssJsonResponse{}, domain, "/v2/index.php", "", apiName, params, debug) } +// redis +func redisRequest(client *common.Client, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { + domain := apiDomain("redis", params) + return _jsonRequest(client, domain, QCLOUD_REDIS_API_VERSION, apiName, params, debug, true) +} + // loadbalancer服务 api 3.0 func clbRequest(client *common.Client, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) { domain := apiDomain("clb", params) @@ -550,6 +557,15 @@ func (client *SQcloudClient) cdbRequest(apiName string, params map[string]string return cdbRequest(cli, apiName, params, client.debug) } +func (client *SQcloudClient) redisRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + cli, err := client.getDefaultClient() + if err != nil { + return nil, err + } + + return redisRequest(cli, apiName, params, client.debug) +} + func (client *SQcloudClient) mariadbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { cli, err := client.getDefaultClient() if err != nil { @@ -892,6 +908,8 @@ func (self *SQcloudClient) GetCapabilities() []string { cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE, cloudprovider.CLOUD_CAPABILITY_RDS, // cloudprovider.CLOUD_CAPABILITY_CACHE, + // cloudprovider.CLOUD_CAPABILITY_RDS, + cloudprovider.CLOUD_CAPABILITY_CACHE, cloudprovider.CLOUD_CAPABILITY_EVENT, cloudprovider.CLOUD_CAPABILITY_CLOUDID, cloudprovider.CLOUD_CAPABILITY_DNSZONE, diff --git a/pkg/multicloud/qcloud/region.go b/pkg/multicloud/qcloud/region.go index 0efb722738..acfce9899e 100644 --- a/pkg/multicloud/qcloud/region.go +++ b/pkg/multicloud/qcloud/region.go @@ -17,6 +17,8 @@ package qcloud import ( "context" "fmt" + "strconv" + "strings" "time" "github.com/tencentyun/cos-go-sdk-v5" @@ -712,6 +714,11 @@ func (self *SRegion) wssRequest(apiName string, params map[string]string) (jsonu return self.client.wssRequest(apiName, params) } +func (self *SRegion) redisRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) { + params["Region"] = self.Region + return self.client.redisRequest(apiName, params) +} + func (self *SRegion) GetNetworks(ids []string, vpcId string, offset int, limit int) ([]SNetwork, int, error) { if limit > 50 || limit <= 0 { limit = 50 @@ -1001,3 +1008,112 @@ func (self *SRegion) CreateISecurityGroup(conf *cloudprovider.SecurityGroupCreat func (region *SRegion) GetCapabilities() []string { return region.client.GetCapabilities() } + +func (region *SRegion) GetIElasticcaches() ([]cloudprovider.ICloudElasticcache, error) { + caches, err := region.GetCloudElasticcaches() + if err != nil { + return nil, errors.Wrap(err, "GetCloudElasticcaches") + } + + ret := []cloudprovider.ICloudElasticcache{} + for i := range caches { + cache := caches[i] + cache.region = region + ret = append(ret, &cache) + } + + return ret, nil +} + +func (region *SRegion) GetIElasticcacheById(id string) (cloudprovider.ICloudElasticcache, error) { + caches, err := region.GetCloudElasticcaches() + if err != nil { + return nil, errors.Wrap(err, "GetCloudElasticcaches") + } + + for i := range caches { + if caches[i].GetGlobalId() == id { + caches[i].region = region + return &caches[i], nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +// https://cloud.tencent.com/document/product/239/20026 +func (r *SRegion) CreateIElasticcaches(ec *cloudprovider.SCloudElasticCacheInput) (cloudprovider.ICloudElasticcache, error) { + params := map[string]string{} + if len(ec.ZoneIds) == 0 { + return nil, fmt.Errorf("CreateIElasticcaches zone id should not be empty.") + } + + zoneId, ok := zoneIdMaps[ec.ZoneIds[0]] + if !ok { + return nil, fmt.Errorf("can't convert zone %s to integer id", ec.ZoneIds[0]) + } + + spec, err := parseLocalInstanceSpec(ec.InstanceType) + if err != nil { + return nil, errors.Wrap(err, "parseLocalInstanceSpec") + } + + params["InstanceName"] = ec.InstanceName + params["ProjectId"] = ec.ProjectId + params["ZoneId"] = fmt.Sprintf("%d", zoneId) + params["TypeId"] = spec.TypeId + params["MemSize"] = strconv.Itoa(spec.MemSizeMB) + params["RedisShardNum"] = spec.RedisShardNum + params["RedisReplicasNum"] = spec.RedisReplicasNum + params["GoodsNum"] = "1" + if strings.ToLower(ec.NetworkType) == api.LB_NETWORK_TYPE_VPC { + params["VpcId"] = ec.VpcId + params["SubnetId"] = ec.NetworkId + + for i := range ec.SecurityGroupIds { + params[fmt.Sprintf("SecurityGroupIdList.%d", i)] = ec.SecurityGroupIds[i] + } + } + params["Period"] = "1" + params["BillingMode"] = "0" + if ec.BC != nil && ec.BC.GetMonths() >= 1 { + params["Period"] = strconv.Itoa(ec.BC.GetMonths()) + params["BillingMode"] = "1" + // 自动续费 + if ec.BC.AutoRenew { + params["AutoRenew"] = "1" + } + } + + if len(ec.Password) > 0 { + params["NoAuth"] = "false" + params["Password"] = ec.Password + } else { + params["NoAuth"] = "true" + } + + resp, err := r.redisRequest("CreateInstances", params) + if err != nil { + return nil, errors.Wrap(err, "CreateInstances") + } + + instanceId, err := resp.GetString("DealId") + if err != nil { + return nil, errors.Wrap(err, "dealId") + } + + //instanceId := "" + //err = cloudprovider.Wait(5*time.Second, 900*time.Second, func() (bool, error) { + // instanceId, err = r.GetElasticcacheIdByDeal(dealId) + // if err != nil { + // return false, nil + // } + // + // return true, nil + //}) + //if err != nil { + // return nil, errors.Wrap(err, "Wait.GetElasticcacheIdByDeal") + //} + + return r.GetIElasticcacheById(instanceId) +} diff --git a/pkg/multicloud/qcloud/shell/elasticcache.go b/pkg/multicloud/qcloud/shell/elasticcache.go new file mode 100644 index 0000000000..0bf09b893a --- /dev/null +++ b/pkg/multicloud/qcloud/shell/elasticcache.go @@ -0,0 +1,71 @@ +package shell + +import ( + "yunion.io/x/onecloud/pkg/multicloud/qcloud" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type RedisListOptions struct { + } + shellutils.R(&RedisListOptions{}, "redis-list", "List redis", func(cli *qcloud.SRegion, args *RedisListOptions) error { + redis, err := cli.GetCloudElasticcaches() + if err != nil { + return err + } + printList(redis, 0, 0, 0, []string{}) + return nil + }) + + type RedisParameterListOptions struct { + INSTANCEID string `json:"instanceid"` + } + + shellutils.R(&RedisParameterListOptions{}, "redis-parameter-list", "List redis parameters", func(cli *qcloud.SRegion, args *RedisParameterListOptions) error { + parameters, err := cli.GetCloudElasticcacheParameters(args.INSTANCEID) + if err != nil { + return err + } + printList(parameters, 0, 0, 0, []string{}) + return nil + }) + + type RedisBackupListOptions struct { + INSTANCEID string `json:"instanceid"` + } + + shellutils.R(&RedisBackupListOptions{}, "redis-backup-list", "List redis backups", func(cli *qcloud.SRegion, args *RedisBackupListOptions) error { + backups, err := cli.GetCloudElasticcacheBackups(args.INSTANCEID) + if err != nil { + return err + } + printList(backups, 0, 0, 0, []string{}) + return nil + }) + + type RedisSecGroupListOptions struct { + INSTANCEID string `json:"instanceid"` + } + + shellutils.R(&RedisSecGroupListOptions{}, "redis-secgroup-list", "List redis secgroups", func(cli *qcloud.SRegion, args *RedisSecGroupListOptions) error { + secgroups, err := cli.GetCloudElasticcacheSecurityGroups(args.INSTANCEID) + if err != nil { + return err + } + printList(secgroups, 0, 0, 0, []string{}) + return nil + }) + + type RedisAccountListOptions struct { + INSTANCEID string `json:"instanceid"` + } + + shellutils.R(&RedisAccountListOptions{}, "redis-account-list", "List redis accounts", func(cli *qcloud.SRegion, args *RedisAccountListOptions) error { + accounts, err := cli.GetCloudElasticcacheAccounts(args.INSTANCEID) + if err != nil { + return err + } + printList(accounts, 0, 0, 0, []string{}) + return nil + }) +} diff --git a/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/response.go b/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/response.go index 8dc321dbb0..288f21bdf6 100644 --- a/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/response.go +++ b/vendor/github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http/response.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "io/ioutil" - //"log" "net/http" diff --git a/vendor/modules.txt b/vendor/modules.txt index 2e5a4de0d0..66bc82840c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1068,7 +1068,7 @@ sigs.k8s.io/yaml yunion.io/x/executor/apis yunion.io/x/executor/client yunion.io/x/executor/server -# yunion.io/x/jsonutils v0.0.0-20201014025429-7ce189228db6 +# yunion.io/x/jsonutils v0.0.0-20201022101715-4e3add1ac4aa yunion.io/x/jsonutils # yunion.io/x/log v0.0.0-20200313080802-57a4ce5966b3 yunion.io/x/log diff --git a/vendor/yunion.io/x/jsonutils/unmarshal.go b/vendor/yunion.io/x/jsonutils/unmarshal.go index dc5b008418..a59dd1bd42 100644 --- a/vendor/yunion.io/x/jsonutils/unmarshal.go +++ b/vendor/yunion.io/x/jsonutils/unmarshal.go @@ -28,6 +28,7 @@ import ( "strings" "time" + "yunion.io/x/log" "yunion.io/x/pkg/errors" "yunion.io/x/pkg/gotypes" "yunion.io/x/pkg/sortedmap" @@ -293,7 +294,7 @@ func (this *JSONString) unmarshalValue(val reflect.Value) error { if len(this.data) > 0 { tm, err = timeutils.ParseTimeStr(this.data) if err != nil { - return errors.Wrap(err, "timeutils.ParseTimeStr") + log.Warningf("timeutils.ParseTimeStr %s %s", this.data, err) } } else { tm = time.Time{}