diff --git a/cmd/climc/shell/compute/cloudaccounts.go b/cmd/climc/shell/compute/cloudaccounts.go index a74221acdf..d90de16a6c 100644 --- a/cmd/climc/shell/compute/cloudaccounts.go +++ b/cmd/climc/shell/compute/cloudaccounts.go @@ -37,6 +37,7 @@ func init() { cmd.CreateWithKeyword("create-aws", &options.SAWSCloudAccountCreateOptions{}) cmd.CreateWithKeyword("create-openstack", &options.SOpenStackCloudAccountCreateOptions{}) cmd.CreateWithKeyword("create-huawei", &options.SHuaweiCloudAccountCreateOptions{}) + cmd.CreateWithKeyword("create-huaweicloudstack", &options.SHuaweiCloudStackAccountCreateOptions{}) cmd.CreateWithKeyword("create-ucloud", &options.SUcloudCloudAccountCreateOptions{}) cmd.CreateWithKeyword("create-zstack", &options.SZStackCloudAccountCreateOptions{}) cmd.CreateWithKeyword("create-s3", &options.SS3CloudAccountCreateOptions{}) @@ -56,6 +57,7 @@ func init() { cmd.UpdateWithKeyword("update-aws", &options.SAWSCloudAccountUpdateOptions{}) cmd.UpdateWithKeyword("update-openstack", &options.SOpenStackCloudAccountUpdateOptions{}) cmd.UpdateWithKeyword("update-huawei", &options.SHuaweiCloudAccountUpdateOptions{}) + cmd.UpdateWithKeyword("update-huaweicloudstack", &options.SHuaweiCloudStackAccountUpdateOptions{}) cmd.UpdateWithKeyword("update-ucloud", &options.SUcloudCloudAccountUpdateOptions{}) cmd.UpdateWithKeyword("update-zstack", &options.SZStackCloudAccountUpdateOptions{}) cmd.UpdateWithKeyword("update-s3", &options.SS3CloudAccountUpdateOptions{}) @@ -73,6 +75,7 @@ func init() { cmd.PerformWithKeyword("update-credential-aws", "update-credential", &options.SAWSCloudAccountUpdateCredentialOptions{}) cmd.PerformWithKeyword("update-credential-openstack", "update-credential", &options.SOpenStackCloudAccountUpdateCredentialOptions{}) cmd.PerformWithKeyword("update-credential-huawei", "update-credential", &options.SHuaweiCloudAccountUpdateCredentialOptions{}) + cmd.PerformWithKeyword("update-credential-huaweicloudstack", "update-credential", &options.SHuaweiCloudStackAccountUpdateCredentialOptions{}) cmd.PerformWithKeyword("update-credential-ucloud", "update-credential", &options.SUcloudCloudAccountUpdateCredentialOptions{}) cmd.PerformWithKeyword("update-credential-zstack", "update-credential", &options.SZStackCloudAccountUpdateCredentialOptions{}) cmd.PerformWithKeyword("update-credential-s3", "update-credential", &options.SS3CloudAccountUpdateCredentialOptions{}) diff --git a/cmd/huaweistackcli/main.go b/cmd/huaweistackcli/main.go new file mode 100644 index 0000000000..1533bebf75 --- /dev/null +++ b/cmd/huaweistackcli/main.go @@ -0,0 +1,147 @@ +// 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 main + +import ( + "fmt" + "os" + + "yunion.io/x/structarg" + + "yunion.io/x/onecloud/pkg/cloudprovider" + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + _ "yunion.io/x/onecloud/pkg/multicloud/huaweistack/shell" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +type BaseOptions struct { + cloudprovider.SHuaweiCloudStackEndpoints + Help bool `help:"Show help" default:"false"` + Debug bool `help:"Show debug" default:"false"` + AccessKey string `help:"Access key" default:"$HUAWEI_ACCESS_KEY" metavar:"HUAWEI_ACCESS_KEY"` + Secret string `help:"Secret" default:"$HUAWEI_SECRET" metavar:"HUAWEI_SECRET"` + RegionId string `help:"RegionId" default:"$HUAWEI_REGION" metavar:"HUAWEI_REGION"` + ProjectId string `help:"ProjectId" default:"$HUAWEI_PROJECT" metavar:"HUAWEI_PROJECT"` + SUBCOMMAND string `help:"huaweicli subcommand" subcommand:"true"` +} + +func getSubcommandParser() (*structarg.ArgumentParser, error) { + parse, e := structarg.NewArgumentParser(&BaseOptions{}, + "huaweicli", + "Command-line interface to huawei API.", + `See "huaweicli help COMMAND" for help on a specific command.`) + + if e != nil { + return nil, e + } + + subcmd := parse.GetSubcommand() + if subcmd == nil { + return nil, fmt.Errorf("No subcommand argument.") + } + type HelpOptions struct { + SUBCOMMAND string `help:"sub-command name"` + } + shellutils.R(&HelpOptions{}, "help", "Show help of a subcommand", func(args *HelpOptions) error { + helpstr, e := subcmd.SubHelpString(args.SUBCOMMAND) + if e != nil { + return e + } else { + fmt.Print(helpstr) + return nil + } + }) + for _, v := range shellutils.CommandTable { + _, e := subcmd.AddSubParser(v.Options, v.Command, v.Desc, v.Callback) + if e != nil { + return nil, e + } + } + return parse, nil +} + +func showErrorAndExit(e error) { + fmt.Fprintf(os.Stderr, "%s", e) + fmt.Fprintln(os.Stderr) + os.Exit(1) +} + +func newClient(options *BaseOptions) (*huawei.SRegion, error) { + if len(options.AccessKey) == 0 { + return nil, fmt.Errorf("Missing accessKey") + } + + if len(options.Secret) == 0 { + return nil, fmt.Errorf("Missing secret") + } + + cli, err := huawei.NewHuaweiClient( + huawei.NewHuaweiClientConfig( + options.AccessKey, + options.Secret, + options.ProjectId, + &options.SHuaweiCloudStackEndpoints, + ).Debug(options.Debug), + ) + if err != nil { + return nil, err + } + + region := cli.GetRegion(options.RegionId) + if region == nil { + return nil, fmt.Errorf("No such region %s", options.RegionId) + } + + return region, nil +} + +func main() { + parser, e := getSubcommandParser() + if e != nil { + showErrorAndExit(e) + } + e = parser.ParseArgs(os.Args[1:], false) + options := parser.Options().(*BaseOptions) + + if options.Help { + fmt.Print(parser.HelpString()) + } else { + subcmd := parser.GetSubcommand() + subparser := subcmd.GetSubParser() + if e != nil { + if subparser != nil { + fmt.Print(subparser.Usage()) + } else { + fmt.Print(parser.Usage()) + } + showErrorAndExit(e) + } else { + suboptions := subparser.Options() + if options.SUBCOMMAND == "help" { + e = subcmd.Invoke(suboptions) + } else { + var region *huawei.SRegion + region, e = newClient(options) + if e != nil { + showErrorAndExit(e) + } + e = subcmd.Invoke(region, suboptions) + } + if e != nil { + showErrorAndExit(e) + } + } + } +} diff --git a/pkg/apis/compute/cloudaccount_const.go b/pkg/apis/compute/cloudaccount_const.go index 1ac316cb24..dc3a70c81c 100644 --- a/pkg/apis/compute/cloudaccount_const.go +++ b/pkg/apis/compute/cloudaccount_const.go @@ -36,22 +36,23 @@ const ( CLOUD_PROVIDER_SYNC_STATUS_IDLE = "idle" CLOUD_PROVIDER_SYNC_STATUS_ERROR = "error" - CLOUD_PROVIDER_ONECLOUD = "OneCloud" - CLOUD_PROVIDER_VMWARE = "VMware" - CLOUD_PROVIDER_ALIYUN = "Aliyun" - CLOUD_PROVIDER_APSARA = "Apsara" - CLOUD_PROVIDER_QCLOUD = "Qcloud" - CLOUD_PROVIDER_AZURE = "Azure" - CLOUD_PROVIDER_AWS = "Aws" - CLOUD_PROVIDER_HUAWEI = "Huawei" - CLOUD_PROVIDER_OPENSTACK = "OpenStack" - CLOUD_PROVIDER_UCLOUD = "Ucloud" - CLOUD_PROVIDER_ZSTACK = "ZStack" - CLOUD_PROVIDER_GOOGLE = "Google" - CLOUD_PROVIDER_CTYUN = "Ctyun" - CLOUD_PROVIDER_ECLOUD = "Ecloud" - CLOUD_PROVIDER_JDCLOUD = "JDcloud" - CLOUD_PROVIDER_CLOUDPODS = "Cloudpods" + CLOUD_PROVIDER_ONECLOUD = "OneCloud" + CLOUD_PROVIDER_VMWARE = "VMware" + CLOUD_PROVIDER_ALIYUN = "Aliyun" + CLOUD_PROVIDER_APSARA = "Apsara" + CLOUD_PROVIDER_QCLOUD = "Qcloud" + CLOUD_PROVIDER_AZURE = "Azure" + CLOUD_PROVIDER_AWS = "Aws" + CLOUD_PROVIDER_HUAWEI = "Huawei" + CLOUD_PROVIDER_HUAWEI_CLOUD_STACK = "HuaweiCloudStack" + CLOUD_PROVIDER_OPENSTACK = "OpenStack" + CLOUD_PROVIDER_UCLOUD = "Ucloud" + CLOUD_PROVIDER_ZSTACK = "ZStack" + CLOUD_PROVIDER_GOOGLE = "Google" + CLOUD_PROVIDER_CTYUN = "Ctyun" + CLOUD_PROVIDER_ECLOUD = "Ecloud" + CLOUD_PROVIDER_JDCLOUD = "JDcloud" + CLOUD_PROVIDER_CLOUDPODS = "Cloudpods" CLOUD_PROVIDER_GENERICS3 = "S3" CLOUD_PROVIDER_CEPH = "Ceph" @@ -97,7 +98,7 @@ const ( var ( CLOUD_PROVIDER_VALID_STATUS = []string{CLOUD_PROVIDER_CONNECTED} CLOUD_PROVIDER_VALID_HEALTH_STATUS = []string{CLOUD_PROVIDER_HEALTH_NORMAL, CLOUD_PROVIDER_HEALTH_NO_PERMISSION} - PRIVATE_CLOUD_PROVIDERS = []string{CLOUD_PROVIDER_ZSTACK, CLOUD_PROVIDER_OPENSTACK, CLOUD_PROVIDER_APSARA, CLOUD_PROVIDER_CLOUDPODS} + PRIVATE_CLOUD_PROVIDERS = []string{CLOUD_PROVIDER_ZSTACK, CLOUD_PROVIDER_OPENSTACK, CLOUD_PROVIDER_APSARA, CLOUD_PROVIDER_HUAWEI_CLOUD_STACK} CLOUD_PROVIDERS = []string{ CLOUD_PROVIDER_ONECLOUD, @@ -108,6 +109,7 @@ var ( CLOUD_PROVIDER_AZURE, CLOUD_PROVIDER_AWS, CLOUD_PROVIDER_HUAWEI, + CLOUD_PROVIDER_HUAWEI_CLOUD_STACK, CLOUD_PROVIDER_OPENSTACK, CLOUD_PROVIDER_UCLOUD, CLOUD_PROVIDER_ZSTACK, @@ -145,6 +147,9 @@ var ( CLOUD_PROVIDER_HUAWEI: []string{ HOST_TYPE_HUAWEI, }, + CLOUD_PROVIDER_HUAWEI_CLOUD_STACK: { + HOST_TYPE_HUAWEI_CLOUD_STACK, + }, CLOUD_PROVIDER_OPENSTACK: []string{ HOST_TYPE_OPENSTACK, }, diff --git a/pkg/apis/compute/guest_const.go b/pkg/apis/compute/guest_const.go index e7956aad7e..7f5437d375 100644 --- a/pkg/apis/compute/guest_const.go +++ b/pkg/apis/compute/guest_const.go @@ -153,20 +153,21 @@ const ( HYPERVISOR_HYPERV = "hyperv" HYPERVISOR_XEN = "xen" - HYPERVISOR_ALIYUN = "aliyun" - HYPERVISOR_APSARA = "apsara" - HYPERVISOR_QCLOUD = "qcloud" - HYPERVISOR_AZURE = "azure" - HYPERVISOR_AWS = "aws" - HYPERVISOR_HUAWEI = "huawei" - HYPERVISOR_OPENSTACK = "openstack" - HYPERVISOR_UCLOUD = "ucloud" - HYPERVISOR_ZSTACK = "zstack" - HYPERVISOR_GOOGLE = "google" - HYPERVISOR_CTYUN = "ctyun" - HYPERVISOR_ECLOUD = "ecloud" - HYPERVISOR_JDCLOUD = "jdcloud" - HYPERVISOR_CLOUDPODS = "cloudpods" + HYPERVISOR_ALIYUN = "aliyun" + HYPERVISOR_APSARA = "apsara" + HYPERVISOR_QCLOUD = "qcloud" + HYPERVISOR_AZURE = "azure" + HYPERVISOR_AWS = "aws" + HYPERVISOR_HUAWEI = "huawei" + HYPERVISOR_HUAWEI_CLOUD_STACK = "huaweicloudstack" + HYPERVISOR_OPENSTACK = "openstack" + HYPERVISOR_UCLOUD = "ucloud" + HYPERVISOR_ZSTACK = "zstack" + HYPERVISOR_GOOGLE = "google" + HYPERVISOR_CTYUN = "ctyun" + HYPERVISOR_ECLOUD = "ecloud" + HYPERVISOR_JDCLOUD = "jdcloud" + HYPERVISOR_CLOUDPODS = "cloudpods" // HYPERVISOR_DEFAULT = HYPERVISOR_KVM HYPERVISOR_DEFAULT = HYPERVISOR_KVM @@ -191,6 +192,7 @@ var HYPERVISORS = []string{ HYPERVISOR_AWS, HYPERVISOR_QCLOUD, HYPERVISOR_HUAWEI, + HYPERVISOR_HUAWEI_CLOUD_STACK, HYPERVISOR_OPENSTACK, HYPERVISOR_UCLOUD, HYPERVISOR_ZSTACK, @@ -225,50 +227,53 @@ var PRIVATE_CLOUD_HYPERVISORS = []string{ HYPERVISOR_OPENSTACK, HYPERVISOR_APSARA, HYPERVISOR_CLOUDPODS, + HYPERVISOR_HUAWEI_CLOUD_STACK, } // var HYPERVISORS = []string{HYPERVISOR_ALIYUN} var HYPERVISOR_HOSTTYPE = map[string]string{ - HYPERVISOR_KVM: HOST_TYPE_HYPERVISOR, - HYPERVISOR_BAREMETAL: HOST_TYPE_BAREMETAL, - HYPERVISOR_ESXI: HOST_TYPE_ESXI, - HYPERVISOR_CONTAINER: HOST_TYPE_KUBELET, - HYPERVISOR_ALIYUN: HOST_TYPE_ALIYUN, - HYPERVISOR_APSARA: HOST_TYPE_APSARA, - HYPERVISOR_AZURE: HOST_TYPE_AZURE, - HYPERVISOR_AWS: HOST_TYPE_AWS, - HYPERVISOR_QCLOUD: HOST_TYPE_QCLOUD, - HYPERVISOR_HUAWEI: HOST_TYPE_HUAWEI, - HYPERVISOR_OPENSTACK: HOST_TYPE_OPENSTACK, - HYPERVISOR_UCLOUD: HOST_TYPE_UCLOUD, - HYPERVISOR_ZSTACK: HOST_TYPE_ZSTACK, - HYPERVISOR_GOOGLE: HOST_TYPE_GOOGLE, - HYPERVISOR_CTYUN: HOST_TYPE_CTYUN, - HYPERVISOR_ECLOUD: HOST_TYPE_ECLOUD, - HYPERVISOR_JDCLOUD: HOST_TYPE_JDCLOUD, - HYPERVISOR_CLOUDPODS: HOST_TYPE_CLOUDPODS, + HYPERVISOR_KVM: HOST_TYPE_HYPERVISOR, + HYPERVISOR_BAREMETAL: HOST_TYPE_BAREMETAL, + HYPERVISOR_ESXI: HOST_TYPE_ESXI, + HYPERVISOR_CONTAINER: HOST_TYPE_KUBELET, + HYPERVISOR_ALIYUN: HOST_TYPE_ALIYUN, + HYPERVISOR_APSARA: HOST_TYPE_APSARA, + HYPERVISOR_AZURE: HOST_TYPE_AZURE, + HYPERVISOR_AWS: HOST_TYPE_AWS, + HYPERVISOR_QCLOUD: HOST_TYPE_QCLOUD, + HYPERVISOR_HUAWEI: HOST_TYPE_HUAWEI, + HYPERVISOR_HUAWEI_CLOUD_STACK: HOST_TYPE_HUAWEI_CLOUD_STACK, + HYPERVISOR_OPENSTACK: HOST_TYPE_OPENSTACK, + HYPERVISOR_UCLOUD: HOST_TYPE_UCLOUD, + HYPERVISOR_ZSTACK: HOST_TYPE_ZSTACK, + HYPERVISOR_GOOGLE: HOST_TYPE_GOOGLE, + HYPERVISOR_CTYUN: HOST_TYPE_CTYUN, + HYPERVISOR_ECLOUD: HOST_TYPE_ECLOUD, + HYPERVISOR_JDCLOUD: HOST_TYPE_JDCLOUD, + HYPERVISOR_CLOUDPODS: HOST_TYPE_CLOUDPODS, } var HOSTTYPE_HYPERVISOR = map[string]string{ - HOST_TYPE_HYPERVISOR: HYPERVISOR_KVM, - HOST_TYPE_BAREMETAL: HYPERVISOR_BAREMETAL, - HOST_TYPE_ESXI: HYPERVISOR_ESXI, - HOST_TYPE_KUBELET: HYPERVISOR_CONTAINER, - HOST_TYPE_ALIYUN: HYPERVISOR_ALIYUN, - HOST_TYPE_APSARA: HYPERVISOR_APSARA, - HOST_TYPE_AZURE: HYPERVISOR_AZURE, - HOST_TYPE_AWS: HYPERVISOR_AWS, - HOST_TYPE_QCLOUD: HYPERVISOR_QCLOUD, - HOST_TYPE_HUAWEI: HYPERVISOR_HUAWEI, - HOST_TYPE_OPENSTACK: HYPERVISOR_OPENSTACK, - HOST_TYPE_UCLOUD: HYPERVISOR_UCLOUD, - HOST_TYPE_ZSTACK: HYPERVISOR_ZSTACK, - HOST_TYPE_GOOGLE: HYPERVISOR_GOOGLE, - HOST_TYPE_CTYUN: HYPERVISOR_CTYUN, - HOST_TYPE_ECLOUD: HYPERVISOR_ECLOUD, - HOST_TYPE_JDCLOUD: HYPERVISOR_JDCLOUD, - HOST_TYPE_CLOUDPODS: HYPERVISOR_CLOUDPODS, + HOST_TYPE_HYPERVISOR: HYPERVISOR_KVM, + HOST_TYPE_BAREMETAL: HYPERVISOR_BAREMETAL, + HOST_TYPE_ESXI: HYPERVISOR_ESXI, + HOST_TYPE_KUBELET: HYPERVISOR_CONTAINER, + HOST_TYPE_ALIYUN: HYPERVISOR_ALIYUN, + HOST_TYPE_APSARA: HYPERVISOR_APSARA, + HOST_TYPE_AZURE: HYPERVISOR_AZURE, + HOST_TYPE_AWS: HYPERVISOR_AWS, + HOST_TYPE_QCLOUD: HYPERVISOR_QCLOUD, + HOST_TYPE_HUAWEI: HYPERVISOR_HUAWEI, + HOST_TYPE_HUAWEI_CLOUD_STACK: HYPERVISOR_HUAWEI_CLOUD_STACK, + HOST_TYPE_OPENSTACK: HYPERVISOR_OPENSTACK, + HOST_TYPE_UCLOUD: HYPERVISOR_UCLOUD, + HOST_TYPE_ZSTACK: HYPERVISOR_ZSTACK, + HOST_TYPE_GOOGLE: HYPERVISOR_GOOGLE, + HOST_TYPE_CTYUN: HYPERVISOR_CTYUN, + HOST_TYPE_ECLOUD: HYPERVISOR_ECLOUD, + HOST_TYPE_JDCLOUD: HYPERVISOR_JDCLOUD, + HOST_TYPE_CLOUDPODS: HYPERVISOR_CLOUDPODS, } const ( diff --git a/pkg/apis/compute/host_const.go b/pkg/apis/compute/host_const.go index e30811d0dc..157c9c9038 100644 --- a/pkg/apis/compute/host_const.go +++ b/pkg/apis/compute/host_const.go @@ -23,20 +23,21 @@ const ( HOST_TYPE_HYPERV = "hyperv" // # Microsoft Hyper-V HOST_TYPE_XEN = "xen" // # XenServer - HOST_TYPE_ALIYUN = "aliyun" - HOST_TYPE_APSARA = "apsara" - HOST_TYPE_AWS = "aws" - HOST_TYPE_QCLOUD = "qcloud" - HOST_TYPE_AZURE = "azure" - HOST_TYPE_HUAWEI = "huawei" - HOST_TYPE_OPENSTACK = "openstack" - HOST_TYPE_UCLOUD = "ucloud" - HOST_TYPE_ZSTACK = "zstack" - HOST_TYPE_GOOGLE = "google" - HOST_TYPE_CTYUN = "ctyun" - HOST_TYPE_ECLOUD = "ecloud" - HOST_TYPE_JDCLOUD = "jdcloud" - HOST_TYPE_CLOUDPODS = "cloudpods" + HOST_TYPE_ALIYUN = "aliyun" + HOST_TYPE_APSARA = "apsara" + HOST_TYPE_AWS = "aws" + HOST_TYPE_QCLOUD = "qcloud" + HOST_TYPE_AZURE = "azure" + HOST_TYPE_HUAWEI = "huawei" + HOST_TYPE_HUAWEI_CLOUD_STACK = "huaweicloudstack" + HOST_TYPE_OPENSTACK = "openstack" + HOST_TYPE_UCLOUD = "ucloud" + HOST_TYPE_ZSTACK = "zstack" + HOST_TYPE_GOOGLE = "google" + HOST_TYPE_CTYUN = "ctyun" + HOST_TYPE_ECLOUD = "ecloud" + HOST_TYPE_JDCLOUD = "jdcloud" + HOST_TYPE_CLOUDPODS = "cloudpods" HOST_TYPE_DEFAULT = HOST_TYPE_HYPERVISOR @@ -111,6 +112,7 @@ var HOST_TYPES = []string{ HOST_TYPE_AWS, HOST_TYPE_QCLOUD, HOST_TYPE_HUAWEI, + HOST_TYPE_HUAWEI_CLOUD_STACK, HOST_TYPE_OPENSTACK, HOST_TYPE_UCLOUD, HOST_TYPE_ZSTACK, diff --git a/pkg/apis/compute/network_const.go b/pkg/apis/compute/network_const.go index be39e5cfdb..130ee06a61 100644 --- a/pkg/apis/compute/network_const.go +++ b/pkg/apis/compute/network_const.go @@ -59,6 +59,7 @@ var ( REGIONAL_NETWORK_PROVIDERS = []string{ CLOUD_PROVIDER_HUAWEI, + CLOUD_PROVIDER_HUAWEI_CLOUD_STACK, CLOUD_PROVIDER_CTYUN, CLOUD_PROVIDER_UCLOUD, CLOUD_PROVIDER_GOOGLE, diff --git a/pkg/cloudprovider/cloudprovider.go b/pkg/cloudprovider/cloudprovider.go index c3918e95fd..7bfe35c860 100644 --- a/pkg/cloudprovider/cloudprovider.go +++ b/pkg/cloudprovider/cloudprovider.go @@ -96,6 +96,9 @@ type SCloudaccountCredential struct { // 阿里云专有云Endpoints *SApsaraEndpoints + + // Huawei Cloud Stack Online + *SHuaweiCloudStackEndpoints } type SCloudaccount struct { @@ -158,6 +161,7 @@ type ProviderConfig struct { AccountId string SApsaraEndpoints + SHuaweiCloudStackEndpoints ProxyFunc httputils.TransportProxyFunc } diff --git a/pkg/cloudprovider/endpoints.go b/pkg/cloudprovider/endpoints.go index 5f51a52717..5e786959f8 100644 --- a/pkg/cloudprovider/endpoints.go +++ b/pkg/cloudprovider/endpoints.go @@ -14,6 +14,13 @@ package cloudprovider +import ( + "reflect" + "strings" + + "yunion.io/x/pkg/utils" +) + type SApsaraEndpoints struct { EcsEndpoint string `default:"$APSARA_ECS_ENDPOINT" metavar:"APSARA_ECS_ENDPOINT"` RdsEndpoint string `default:"$APSARA_RDS_ENDPOINT"` @@ -27,3 +34,102 @@ type SApsaraEndpoints struct { MetricsEndpoint string `default:"$APSRRA_METRICS_ENDPOINT"` ResourcemanagerEndpoint string `default:"$APSARA_RESOURCEMANAGER_ENDPOINT"` } + +// SHuaweiCloudStackEndpoints 华为私有云endpoints配置 +/* +endpoint获取方式优先级: +通过参数明确指定使用指定endpoint。否则,程序根据华为云endpoint命名规则自动拼接endpoint +*/ +type SHuaweiCloudStackEndpoints struct { + caches map[string]string + + // 华为私有云Endpoint域名 + // example: hcso.com.cn + // required:true + EndpointDomain string `default:"$HUAWEI_ENDPOINT_DOMAIN" metavar:"HUAWEI_ENDPOINT_DOMAIN"` + + // 可用区ID + // example: cn-north-2 + // required: true + DefaultRegion string `default:"$HUAWEI_DEFAULT_REGION" metavar:"$HUAWEI_DEFAULT_REGION"` + + // 弹性云服务 + Ecs string `default:"$HUAWEI_ECS_ENDPOINT"` + // 云容器服务 + Cce string `default:"$HUAWEI_CCE_ENDPOINT"` + // 弹性伸缩服务 + As string `default:"$HUAWEI_AS_ENDPOINT"` + // 统一身份认证服务 + Iam string `default:"$HUAWEI_IAM_ENDPOINT"` + // 镜像服务 + Ims string `default:"$HUAWEI_IMS_ENDPOINT"` + // 云服务器备份服务 + Csbs string `default:"$HUAWEI_CSBS_ENDPOINT"` + // 云容器实例 CCI + Cci string `default:"$HUAWEI_CCI_ENDPOINT"` + // 裸金属服务器 + Bms string `default:"$HUAWEI_BMS_ENDPOINT"` + // 云硬盘 EVS + Evs string `default:"$HUAWEI_EVS_ENDPOINT"` + // 云硬盘备份 VBS + Vbs string `default:"$HUAWEI_VBS_ENDPOINT"` + // 对象存储服务 OBS + Obs string `default:"$HUAWEI_OBS_ENDPOINT"` + // 虚拟私有云 VPC + Vpc string `default:"$HUAWEI_VPC_ENDPOINT"` + // 弹性负载均衡 ELB + Elb string `default:"$HUAWEI_ELB_ENDPOINT"` + // 合作伙伴运营能力 + Bss string `default:"$HUAWEI_BSS_ENDPOINT"` + // Nat网关 NAT + Nat string `default:"$HUAWEI_NAT_ENDPOINT"` + // 分布式缓存服务 + Dcs string `default:"$HUAWEI_DCS_ENDPOINT"` + // 关系型数据库 RDS + Rds string `default:"$HUAWEI_RDS_ENDPOINT"` + // 云审计服务 + Cts string `default:"$HUAWEI_CTS_ENDPOINT"` + // 监控服务 CloudEye + Ces string `default:"$HUAWEI_CES_ENDPOINT"` + // 企业项目 + Eps string `default:"$HUAWEI_EPS_ENDPOINT"` + // 文件系统 + SfsTurbo string `default:"$HUAWEI_SFS_TURBO_ENDPOINT"` +} + +func (self *SHuaweiCloudStackEndpoints) GetEndpoint(serviceName string, region string) string { + sn := utils.Kebab2Camel(serviceName, "-") + if self.caches == nil { + self.caches = make(map[string]string, 0) + } + + key := self.DefaultRegion + "." + sn + if len(region) > 0 { + key = region + "." + sn + } + + if endpoint, ok := self.caches[key]; ok && len(endpoint) > 0 { + return endpoint + } + + var endpoint string + fileds := reflect.Indirect(reflect.ValueOf(self)) + f := fileds.FieldByNameFunc(func(c string) bool { + return strings.ToLower(c) == sn + }) + + if f.Kind() == reflect.String { + endpoint = f.String() + } + + if len(endpoint) == 0 { + endpoint = strings.Join([]string{serviceName, self.DefaultRegion, self.EndpointDomain}, ".") + } + + if len(region) > 0 { + endpoint = strings.Replace(endpoint, self.DefaultRegion, region, 1) + } + + self.caches[key] = endpoint + return endpoint +} diff --git a/pkg/compute/guestdrivers/huaweistack.go b/pkg/compute/guestdrivers/huaweistack.go new file mode 100644 index 0000000000..c3aff11151 --- /dev/null +++ b/pkg/compute/guestdrivers/huaweistack.go @@ -0,0 +1,157 @@ +// 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 guestdrivers + +import ( + "fmt" + + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db/quotas" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/billing" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +type SHuaweiCloudStackGuestDriver struct { + SManagedVirtualizedGuestDriver +} + +func init() { + driver := SHuaweiCloudStackGuestDriver{} + models.RegisterGuestDriver(&driver) +} + +func (self *SHuaweiCloudStackGuestDriver) GetHypervisor() string { + return api.HYPERVISOR_HUAWEI_CLOUD_STACK +} + +func (self *SHuaweiCloudStackGuestDriver) GetProvider() string { + return api.CLOUD_PROVIDER_HUAWEI_CLOUD_STACK +} + +func (self *SHuaweiCloudStackGuestDriver) GetComputeQuotaKeys(scope rbacutils.TRbacScope, ownerId mcclient.IIdentityProvider, brand string) models.SComputeResourceKeys { + keys := models.SComputeResourceKeys{} + keys.SBaseProjectQuotaKeys = quotas.OwnerIdProjectQuotaKeys(scope, ownerId) + keys.CloudEnv = api.CLOUD_ENV_PRIVATE_CLOUD + keys.Provider = api.CLOUD_PROVIDER_HUAWEI_CLOUD_STACK + keys.Brand = api.CLOUD_PROVIDER_HUAWEI_CLOUD_STACK + keys.Hypervisor = api.HYPERVISOR_HUAWEI_CLOUD_STACK + return keys +} + +func (self *SHuaweiCloudStackGuestDriver) GetDefaultSysDiskBackend() string { + return api.STORAGE_HUAWEI_SAS +} + +func (self *SHuaweiCloudStackGuestDriver) GetMinimalSysDiskSizeGb() int { + return 40 +} + +func (self *SHuaweiCloudStackGuestDriver) GetStorageTypes() []string { + return []string{api.STORAGE_HUAWEI_SATA, api.STORAGE_HUAWEI_SAS, api.STORAGE_HUAWEI_SSD} +} + +func (self *SHuaweiCloudStackGuestDriver) ChooseHostStorage(host *models.SHost, guest *models.SGuest, diskConfig *api.DiskConfig, storageIds []string) (*models.SStorage, error) { + return self.chooseHostStorage(self, host, diskConfig.Backend, storageIds), nil +} + +func (self *SHuaweiCloudStackGuestDriver) GetDetachDiskStatus() ([]string, error) { + return []string{api.VM_READY, api.VM_RUNNING}, nil +} + +func (self *SHuaweiCloudStackGuestDriver) GetAttachDiskStatus() ([]string, error) { + return []string{api.VM_READY, api.VM_RUNNING}, nil +} + +func (self *SHuaweiCloudStackGuestDriver) GetRebuildRootStatus() ([]string, error) { + return []string{api.VM_READY, api.VM_RUNNING}, nil +} + +func (self *SHuaweiCloudStackGuestDriver) GetChangeConfigStatus(guest *models.SGuest) ([]string, error) { + return []string{api.VM_READY}, nil +} + +func (self *SHuaweiCloudStackGuestDriver) GetDeployStatus() ([]string, error) { + return []string{api.VM_READY, api.VM_RUNNING}, nil +} + +func (self *SHuaweiCloudStackGuestDriver) ValidateResizeDisk(guest *models.SGuest, disk *models.SDisk, storage *models.SStorage) error { + if !utils.IsInStringArray(guest.Status, []string{api.VM_RUNNING, api.VM_READY}) { + return fmt.Errorf("Cannot resize disk when guest in status %s", guest.Status) + } + if !utils.IsInStringArray(storage.StorageType, []string{api.STORAGE_HUAWEI_SATA, api.STORAGE_HUAWEI_SAS, api.STORAGE_HUAWEI_SSD}) { + return fmt.Errorf("Cannot resize disk with unsupported volumes type %s", storage.StorageType) + } + + return nil +} + +func (self *SHuaweiCloudStackGuestDriver) GetGuestInitialStateAfterCreate() string { + return api.VM_RUNNING +} + +func (self *SHuaweiCloudStackGuestDriver) GetGuestInitialStateAfterRebuild() string { + return api.VM_RUNNING +} + +func (self *SHuaweiCloudStackGuestDriver) GetInstanceCapability() cloudprovider.SInstanceCapability { + return cloudprovider.SInstanceCapability{ + Hypervisor: self.GetHypervisor(), + Provider: self.GetProvider(), + DefaultAccount: cloudprovider.SDefaultAccount{ + Linux: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_DEFAULT_LINUX_LOGIN_USER, + }, + Windows: cloudprovider.SOsDefaultAccount{ + DefaultAccount: api.VM_DEFAULT_WINDOWS_LOGIN_USER, + }, + }, + Storages: cloudprovider.Storage{ + DataDisk: []cloudprovider.StorageInfo{ + cloudprovider.StorageInfo{StorageType: api.STORAGE_HUAWEI_SSD, MaxSizeGb: 32768, MinSizeGb: 10, StepSizeGb: 1, Resizable: true}, + cloudprovider.StorageInfo{StorageType: api.STORAGE_HUAWEI_SATA, MaxSizeGb: 32768, MinSizeGb: 10, StepSizeGb: 1, Resizable: true}, + cloudprovider.StorageInfo{StorageType: api.STORAGE_HUAWEI_SAS, MaxSizeGb: 32768, MinSizeGb: 10, StepSizeGb: 1, Resizable: true}, + cloudprovider.StorageInfo{StorageType: api.STORAGE_HUAWEI_GPSSD, MaxSizeGb: 32768, MinSizeGb: 10, StepSizeGb: 1, Resizable: true}, + }, + SysDisk: []cloudprovider.StorageInfo{ + cloudprovider.StorageInfo{StorageType: api.STORAGE_HUAWEI_SSD, MaxSizeGb: 1024, MinSizeGb: 40, StepSizeGb: 1, Resizable: false}, + cloudprovider.StorageInfo{StorageType: api.STORAGE_HUAWEI_SATA, MaxSizeGb: 1024, MinSizeGb: 40, StepSizeGb: 1, Resizable: false}, + cloudprovider.StorageInfo{StorageType: api.STORAGE_HUAWEI_SAS, MaxSizeGb: 1024, MinSizeGb: 40, StepSizeGb: 1, Resizable: false}, + cloudprovider.StorageInfo{StorageType: api.STORAGE_HUAWEI_GPSSD, MaxSizeGb: 1024, MinSizeGb: 40, StepSizeGb: 1, Resizable: false}, + }, + }, + } +} + +func (self *SHuaweiCloudStackGuestDriver) IsSupportedBillingCycle(bc billing.SBillingCycle) bool { + months := bc.GetMonths() + if (months >= 1 && months <= 9) || (months == 12) || (months == 24) || (months == 36) { + return true + } + + return false +} + +func (self *SHuaweiCloudStackGuestDriver) IsNeedInjectPasswordByCloudInit(desc *cloudprovider.SManagedVMCreateConfig) bool { + return true +} + +func (self *SHuaweiCloudStackGuestDriver) IsSupportSetAutoRenew() bool { + return true +} diff --git a/pkg/compute/hostdrivers/huawei_stack.go b/pkg/compute/hostdrivers/huawei_stack.go new file mode 100644 index 0000000000..271c231ea6 --- /dev/null +++ b/pkg/compute/hostdrivers/huawei_stack.go @@ -0,0 +1,73 @@ +// 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 hostdrivers + +import ( + "context" + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/compute/models" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" +) + +type SHuaweiCloudStackHostDriver struct { + SManagedVirtualizationHostDriver +} + +func init() { + driver := SHuaweiCloudStackHostDriver{} + models.RegisterHostDriver(&driver) +} + +func (self *SHuaweiCloudStackHostDriver) GetHostType() string { + return api.HOST_TYPE_HUAWEI_CLOUD_STACK +} + +func (self *SHuaweiCloudStackHostDriver) GetHypervisor() string { + return api.HYPERVISOR_HUAWEI_CLOUD_STACK +} + +// 系统盘必须至少40G +func (self *SHuaweiCloudStackHostDriver) ValidateDiskSize(storage *models.SStorage, sizeGb int) error { + switch storage.StorageType { + case api.STORAGE_HUAWEI_SSD, api.STORAGE_HUAWEI_SATA, api.STORAGE_HUAWEI_SAS: + if sizeGb < 10 || sizeGb > 32768 { + return fmt.Errorf("The %s disk size must be in the range of 10G ~ 32768GB", storage.StorageType) + } + default: + return fmt.Errorf("Not support create %s disk", storage.StorageType) + } + + return nil +} + +func (self *SHuaweiCloudStackHostDriver) ValidateResetDisk(ctx context.Context, userCred mcclient.TokenCredential, disk *models.SDisk, snapshot *models.SSnapshot, guests []models.SGuest, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + if len(guests) >= 1 { + if disk.DiskType == api.DISK_TYPE_SYS { + for _, g := range guests { + if g.Status != api.VM_READY { + return nil, httperrors.NewBadRequestError("Server %s must in status ready", g.GetName()) + } + } + } else { + return nil, httperrors.NewBadRequestError("Disk must be detached") + } + } + return data, nil +} diff --git a/pkg/compute/hostdrivers/managedvirtual.go b/pkg/compute/hostdrivers/managedvirtual.go index 72e552a963..f7b929a6d1 100644 --- a/pkg/compute/hostdrivers/managedvirtual.go +++ b/pkg/compute/hostdrivers/managedvirtual.go @@ -54,7 +54,7 @@ func (self *SManagedVirtualizationHostDriver) CheckAndSetCacheImage(ctx context. } providerName := storageCache.GetProviderName() - if utils.IsInStringArray(providerName, []string{api.CLOUD_PROVIDER_HUAWEI, api.CLOUD_PROVIDER_UCLOUD}) { + if utils.IsInStringArray(providerName, []string{api.CLOUD_PROVIDER_HUAWEI, api.CLOUD_PROVIDER_HUAWEI_CLOUD_STACK, api.CLOUD_PROVIDER_UCLOUD}) { image.OsVersion, _ = params.GetString("os_full_version") } diff --git a/pkg/compute/models/cloudaccounts.go b/pkg/compute/models/cloudaccounts.go index 26688064d3..aa0aaf81ee 100644 --- a/pkg/compute/models/cloudaccounts.go +++ b/pkg/compute/models/cloudaccounts.go @@ -436,13 +436,25 @@ func (manager *SCloudaccountManager) validateCreateData( input.Zone = obj.GetId() } + var endpointOptions jsonutils.JSONObject endpoints := cloudprovider.SApsaraEndpoints{} if input.SCloudaccountCredential.SApsaraEndpoints != nil { + endpoints = *input.SCloudaccountCredential.SApsaraEndpoints + endpointOptions = jsonutils.Marshal(input.SCloudaccountCredential.SApsaraEndpoints) + } + + hcsoEndpoints := cloudprovider.SHuaweiCloudStackEndpoints{} + if input.SCloudaccountCredential.SHuaweiCloudStackEndpoints != nil { + hcsoEndpoints = *input.SCloudaccountCredential.SHuaweiCloudStackEndpoints + endpointOptions = jsonutils.Marshal(input.SCloudaccountCredential.SHuaweiCloudStackEndpoints) + } + + if endpointOptions != nil { if input.Options == nil { input.Options = jsonutils.NewDict() } - endpoints = *input.SCloudaccountCredential.SApsaraEndpoints - input.Options.Update(jsonutils.Marshal(input.SCloudaccountCredential.SApsaraEndpoints)) + + input.Options.Update(endpointOptions) } input.SCloudaccount, err = providerDriver.ValidateCreateCloudaccountData(ctx, userCred, input.SCloudaccountCredential) @@ -499,7 +511,8 @@ func (manager *SCloudaccountManager) validateCreateData( Secret: input.Secret, ProxyFunc: proxyFunc, - SApsaraEndpoints: endpoints, + SApsaraEndpoints: endpoints, + SHuaweiCloudStackEndpoints: hcsoEndpoints, }) if err != nil { if err == cloudprovider.ErrNoSuchProvder { @@ -903,8 +916,13 @@ func (self *SCloudaccount) getProviderInternal() (cloudprovider.ICloudProvider, return nil, fmt.Errorf("Invalid password %s", err) } endpoints := cloudprovider.SApsaraEndpoints{} - if self.Provider == api.CLOUD_PROVIDER_APSARA && self.Options != nil { - self.Options.Unmarshal(&endpoints) + hcsoEndpoints := cloudprovider.SHuaweiCloudStackEndpoints{} + if self.Options != nil { + if self.Provider == api.CLOUD_PROVIDER_HUAWEI_CLOUD_STACK { + self.Options.Unmarshal(&hcsoEndpoints) + } else if self.Provider == api.CLOUD_PROVIDER_APSARA { + self.Options.Unmarshal(&endpoints) + } } return cloudprovider.GetProvider(cloudprovider.ProviderConfig{ Id: self.Id, @@ -914,9 +932,9 @@ func (self *SCloudaccount) getProviderInternal() (cloudprovider.ICloudProvider, Account: self.Account, Secret: secret, - SApsaraEndpoints: endpoints, - - ProxyFunc: self.proxyFunc(), + SApsaraEndpoints: endpoints, + SHuaweiCloudStackEndpoints: hcsoEndpoints, + ProxyFunc: self.proxyFunc(), }) } diff --git a/pkg/compute/models/cloudproviders.go b/pkg/compute/models/cloudproviders.go index 23bd5754d4..cd00671852 100644 --- a/pkg/compute/models/cloudproviders.go +++ b/pkg/compute/models/cloudproviders.go @@ -875,8 +875,13 @@ func (self *SCloudprovider) GetProvider() (cloudprovider.ICloudProvider, error) account := self.GetCloudaccount() endpoints := cloudprovider.SApsaraEndpoints{} + hscsoEndpoints := cloudprovider.SHuaweiCloudStackEndpoints{} if account.Options != nil { - account.Options.Unmarshal(&endpoints) + if self.Provider == api.CLOUD_PROVIDER_HUAWEI_CLOUD_STACK { + account.Options.Unmarshal(&hscsoEndpoints) + } else if self.Provider == api.CLOUD_PROVIDER_APSARA { + account.Options.Unmarshal(&endpoints) + } } return cloudprovider.GetProvider(cloudprovider.ProviderConfig{ @@ -888,7 +893,8 @@ func (self *SCloudprovider) GetProvider() (cloudprovider.ICloudProvider, error) Secret: passwd, ProxyFunc: account.proxyFunc(), - SApsaraEndpoints: endpoints, + SApsaraEndpoints: endpoints, + SHuaweiCloudStackEndpoints: hscsoEndpoints, }) } diff --git a/pkg/compute/models/secgroupcache.go b/pkg/compute/models/secgroupcache.go index 3df8617561..8232118700 100644 --- a/pkg/compute/models/secgroupcache.go +++ b/pkg/compute/models/secgroupcache.go @@ -606,7 +606,7 @@ func (self *SSecurityGroupCache) StartSecurityGroupCacheDeleteTask(ctx context.C } func (manager *SSecurityGroupCacheManager) InitializeData() error { - providerIds := CloudproviderManager.Query("id").In("provider", []string{api.CLOUD_PROVIDER_HUAWEI, api.CLOUD_PROVIDER_CTYUN, api.CLOUD_PROVIDER_QCLOUD}).SubQuery() + providerIds := CloudproviderManager.Query("id").In("provider", []string{api.CLOUD_PROVIDER_HUAWEI, api.CLOUD_PROVIDER_HUAWEI_CLOUD_STACK, api.CLOUD_PROVIDER_CTYUN, api.CLOUD_PROVIDER_QCLOUD}).SubQuery() deprecatedSecgroups := []SSecurityGroupCache{} q := manager.Query().In("manager_id", providerIds).NotEquals("vpc_id", api.NORMAL_VPC_ID) diff --git a/pkg/compute/regiondrivers/huaweistack.go b/pkg/compute/regiondrivers/huaweistack.go new file mode 100644 index 0000000000..49a237f8df --- /dev/null +++ b/pkg/compute/regiondrivers/huaweistack.go @@ -0,0 +1,19 @@ +package regiondrivers + +import ( + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/compute/models" +) + +type SHuaweiCloudStackRegionDriver struct { + SHuaWeiRegionDriver +} + +func init() { + driver := SHuaweiCloudStackRegionDriver{} + models.RegisterRegionDriver(&driver) +} + +func (self *SHuaweiCloudStackRegionDriver) GetProvider() string { + return api.CLOUD_PROVIDER_HUAWEI_CLOUD_STACK +} diff --git a/pkg/mcclient/options/cloudaccounts.go b/pkg/mcclient/options/cloudaccounts.go index 0f668811aa..8663313f41 100644 --- a/pkg/mcclient/options/cloudaccounts.go +++ b/pkg/mcclient/options/cloudaccounts.go @@ -271,6 +271,18 @@ func (opts *SHuaweiCloudAccountCreateOptions) Params() (jsonutils.JSONObject, er return params, nil } +type SHuaweiCloudStackAccountCreateOptions struct { + SCloudAccountCreateBaseOptions + cloudprovider.SHuaweiCloudStackEndpoints + SAccessKeyCredential +} + +func (opts *SHuaweiCloudStackAccountCreateOptions) Params() (jsonutils.JSONObject, error) { + params := jsonutils.Marshal(opts) + params.(*jsonutils.JSONDict).Add(jsonutils.NewString("HuaweiCloudStack"), "provider") + return params, nil +} + type SUcloudCloudAccountCreateOptions struct { SCloudAccountCreateBaseOptions SAccessKeyCredential @@ -448,6 +460,16 @@ func (opts *SHuaweiCloudAccountUpdateCredentialOptions) Params() (jsonutils.JSON return jsonutils.Marshal(opts), nil } +type SHuaweiCloudStackAccountUpdateCredentialOptions struct { + SCloudAccountIdOptions + cloudprovider.SHuaweiCloudStackEndpoints + SAccessKeyCredential +} + +func (opts *SHuaweiCloudStackAccountUpdateCredentialOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts), nil +} + type SUcloudCloudAccountUpdateCredentialOptions struct { SCloudAccountIdOptions SAccessKeyCredential @@ -769,6 +791,14 @@ func (opts *SHuaweiCloudAccountUpdateOptions) Params() (jsonutils.JSONObject, er return params, nil } +type SHuaweiCloudStackAccountUpdateOptions struct { + SCloudAccountUpdateBaseOptions +} + +func (opts *SHuaweiCloudStackAccountUpdateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(opts), nil +} + type SUcloudCloudAccountUpdateOptions struct { SCloudAccountUpdateBaseOptions } diff --git a/pkg/multicloud/huawei/client/client.go b/pkg/multicloud/huawei/client/client.go index b3286d7fa8..d313f116ec 100644 --- a/pkg/multicloud/huawei/client/client.go +++ b/pkg/multicloud/huawei/client/client.go @@ -23,13 +23,7 @@ import ( ) type Client struct { - signer auth.Signer - regionId string - domainId string - projectId string - - debug bool - + cfg *SClientConfig // 标记初始化状态 init bool @@ -92,6 +86,40 @@ type Client struct { SfsTurbos *modules.SfsTurboManager } +type SClientConfig struct { + signer auth.Signer + endpoint string // myhuaweicloud.com + regionId string + domainId string + projectId string + + debug bool +} + +func (self *SClientConfig) GetSigner() auth.Signer { + return self.signer +} + +func (self *SClientConfig) GetEndpoint() string { + return self.endpoint +} + +func (self *SClientConfig) GetRegionId() string { + return self.regionId +} + +func (self *SClientConfig) GetDomainId() string { + return self.domainId +} + +func (self *SClientConfig) GetProjectId() string { + return self.projectId +} + +func (self *SClientConfig) GetDebug() bool { + return self.debug +} + func (self *Client) SetHttpClient(httpClient *http.Client) { self.Credentials.SetHttpClient(httpClient) self.Servers.SetHttpClient(httpClient) @@ -150,103 +178,102 @@ func (self *Client) SetHttpClient(httpClient *http.Client) { self.SfsTurbos.SetHttpClient(httpClient) } -func (self *Client) InitWithOptions(regionId, domainId, projectId string, credential auth.Credential) error { - // 从signer中初始化 - signer, err := auth.NewSignerWithCredential(credential) - if err != nil { - return err - } - self.signer = signer - self.regionId = regionId - self.projectId = projectId - self.domainId = domainId - // 初始化 resource manager - self.initManagers() - return err -} - -func (self *Client) InitWithAccessKey(regionId, domainId, projectId, accessKey, secretKey string) error { +func (self *Client) InitWithAccessKey(endpoint, regionId, domainId, projectId, accessKey, secretKey string, debug bool) error { // accessKey signer credential := &credentials.AccessKeyCredential{ AccessKeyId: accessKey, AccessKeySecret: secretKey, } - return self.InitWithOptions(regionId, domainId, projectId, credential) + // 从signer中初始化 + signer, err := auth.NewSignerWithCredential(credential) + if err != nil { + return err + } + self.cfg = &SClientConfig{ + signer: signer, + endpoint: endpoint, + regionId: regionId, + domainId: domainId, + projectId: projectId, + debug: debug, + } + + // 初始化 resource manager + self.initManagers() + return err } func (self *Client) initManagers() { if !self.init { - self.Servers = modules.NewServerManager(self.regionId, self.projectId, self.signer, self.debug) - self.ServersV2 = modules.NewServerV2Manager(self.regionId, self.projectId, self.signer, self.debug) - self.NovaServers = modules.NewNovaServerManager(self.regionId, self.projectId, self.signer, self.debug) - self.Snapshots = modules.NewSnapshotManager(self.regionId, self.projectId, self.signer, self.debug) - self.OsSnapshots = modules.NewOsSnapshotManager(self.regionId, self.projectId, self.signer, self.debug) - self.Images = modules.NewImageManager(self.regionId, self.projectId, self.signer, self.debug) - self.OpenStackImages = modules.NewOpenstackImageManager(self.regionId, self.projectId, self.signer, self.debug) - self.Projects = modules.NewProjectManager(self.signer, self.debug) - self.Regions = modules.NewRegionManager(self.signer, self.debug) - self.Zones = modules.NewZoneManager(self.regionId, self.projectId, self.signer, self.debug) - self.Vpcs = modules.NewVpcManager(self.regionId, self.projectId, self.signer, self.debug) - self.Eips = modules.NewEipManager(self.regionId, self.projectId, self.signer, self.debug) - self.Elasticcache = modules.NewElasticcacheManager(self.regionId, self.projectId, self.signer, self.debug) - self.DcsAvailableZone = modules.NewDcsAvailableZoneManager(self.regionId, self.signer, self.debug) - self.Disks = modules.NewDiskManager(self.regionId, self.projectId, self.signer, self.debug) - self.Domains = modules.NewDomainManager(self.signer, self.debug) - self.Keypairs = modules.NewKeypairManager(self.regionId, self.projectId, self.signer, self.debug) - self.Elb = modules.NewLoadbalancerManager(self.regionId, self.projectId, self.signer, self.debug) - self.ElbBackend = modules.NewElbBackendManager(self.regionId, self.projectId, self.signer, self.debug) - self.ElbBackendGroup = modules.NewElbBackendGroupManager(self.regionId, self.projectId, self.signer, self.debug) - self.ElbListeners = modules.NewElbListenersManager(self.regionId, self.projectId, self.signer, self.debug) - self.ElbCertificates = modules.NewElbCertificatesManager(self.regionId, self.projectId, self.signer, self.debug) - self.ElbHealthCheck = modules.NewElbHealthCheckManager(self.regionId, self.projectId, self.signer, self.debug) - self.ElbL7policies = modules.NewElbL7policiesManager(self.regionId, self.projectId, self.signer, self.debug) - self.ElbPolicies = modules.NewElbPoliciesManager(self.regionId, self.projectId, self.signer, self.debug) - self.ElbWhitelist = modules.NewElbWhitelistManager(self.regionId, self.projectId, self.signer, self.debug) - self.Orders = modules.NewOrderManager(self.signer, self.debug) - self.SecurityGroupRules = modules.NewSecgroupRuleManager(self.regionId, self.projectId, self.signer, self.debug) - self.SecurityGroups = modules.NewSecurityGroupManager(self.regionId, self.projectId, self.signer, self.debug) - self.NovaSecurityGroups = modules.NewNovaSecurityGroupManager(self.regionId, self.projectId, self.signer, self.debug) - self.Subnets = modules.NewSubnetManager(self.regionId, self.projectId, self.signer, self.debug) - self.Users = modules.NewUserManager(self.signer, self.debug) - self.Users.SetDomainId(self.domainId) - self.Interface = modules.NewInterfaceManager(self.regionId, self.projectId, self.signer, self.debug) - self.Jobs = modules.NewJobManager(self.regionId, self.projectId, self.signer, self.debug) - self.Balances = modules.NewBalanceManager(self.signer, self.debug) - self.Bandwidths = modules.NewBandwidthManager(self.regionId, self.projectId, self.signer, self.debug) - self.Credentials = modules.NewCredentialManager(self.signer, self.debug) - self.Port = modules.NewPortManager(self.regionId, self.projectId, self.signer, self.debug) - self.Flavors = modules.NewFlavorManager(self.regionId, self.projectId, self.signer, self.debug) - self.VpcRoutes = modules.NewVpcRouteManager(self.regionId, self.projectId, self.signer, self.debug) - self.SNatRules = modules.NewNatSManager(self.regionId, self.projectId, self.signer, self.debug) - self.DNatRules = modules.NewNatDManager(self.regionId, self.projectId, self.signer, self.debug) - self.NatGateways = modules.NewNatGatewayManager(self.regionId, self.projectId, self.signer, self.debug) - self.VpcPeerings = modules.NewVpcPeeringManager(self.regionId, self.projectId, self.signer, self.debug) - self.DBInstance = modules.NewDBInstanceManager(self.regionId, self.projectId, self.signer, self.debug) - self.DBInstanceBackup = modules.NewDBInstanceBackupManager(self.regionId, self.projectId, self.signer, self.debug) - self.DBInstanceFlavor = modules.NewDBInstanceFlavorManager(self.regionId, self.projectId, self.signer, self.debug) - self.DBInstanceJob = modules.NewDBInstanceJobManager(self.regionId, self.projectId, self.signer, self.debug) - self.Traces = modules.NewTraceManager(self.regionId, self.projectId, self.signer, self.debug) - self.CloudEye = modules.NewCloudEyeManager(self.regionId, self.projectId, self.signer, self.debug) - self.Quotas = modules.NewQuotaManager(self.regionId, self.projectId, self.signer, self.debug) - self.EnterpriseProjects = modules.NewEnterpriseProjectManager(self.regionId, self.projectId, self.signer, self.debug) - self.EnterpriseProjects.SetDomainId(self.domainId) - self.Roles = modules.NewRoleManager(self.signer, self.debug) - self.Roles.SetDomainId(self.domainId) - self.Groups = modules.NewGroupManager(self.signer, self.debug) - self.Groups.SetDomainId(self.domainId) - self.SAMLProviders = modules.NewSAMLProviderManager(self.signer, self.debug) - self.SAMLProviders.SetDomainId(self.domainId) - self.SAMLProviderMappings = modules.NewSAMLProviderMappingManager(self.signer, self.debug) - self.SAMLProviderMappings.SetDomainId(self.domainId) - self.SfsTurbos = modules.NewSfsTurboManager(self.regionId, self.projectId, self.signer, self.debug) + self.Servers = modules.NewServerManager(self.cfg) + self.ServersV2 = modules.NewServerV2Manager(self.cfg) + self.NovaServers = modules.NewNovaServerManager(self.cfg) + self.Snapshots = modules.NewSnapshotManager(self.cfg) + self.OsSnapshots = modules.NewOsSnapshotManager(self.cfg) + self.Images = modules.NewImageManager(self.cfg) + self.OpenStackImages = modules.NewOpenstackImageManager(self.cfg) + self.Projects = modules.NewProjectManager(self.cfg) + self.Regions = modules.NewRegionManager(self.cfg) + self.Zones = modules.NewZoneManager(self.cfg) + self.Vpcs = modules.NewVpcManager(self.cfg) + self.Eips = modules.NewEipManager(self.cfg) + self.Elasticcache = modules.NewElasticcacheManager(self.cfg) + self.DcsAvailableZone = modules.NewDcsAvailableZoneManager(self.cfg) + self.Disks = modules.NewDiskManager(self.cfg) + self.Domains = modules.NewDomainManager(self.cfg) + self.Keypairs = modules.NewKeypairManager(self.cfg) + self.Elb = modules.NewLoadbalancerManager(self.cfg) + self.ElbBackend = modules.NewElbBackendManager(self.cfg) + self.ElbBackendGroup = modules.NewElbBackendGroupManager(self.cfg) + self.ElbListeners = modules.NewElbListenersManager(self.cfg) + self.ElbCertificates = modules.NewElbCertificatesManager(self.cfg) + self.ElbHealthCheck = modules.NewElbHealthCheckManager(self.cfg) + self.ElbL7policies = modules.NewElbL7policiesManager(self.cfg) + self.ElbPolicies = modules.NewElbPoliciesManager(self.cfg) + self.ElbWhitelist = modules.NewElbWhitelistManager(self.cfg) + self.Orders = modules.NewOrderManager(self.cfg) + self.SecurityGroupRules = modules.NewSecgroupRuleManager(self.cfg) + self.SecurityGroups = modules.NewSecurityGroupManager(self.cfg) + self.NovaSecurityGroups = modules.NewNovaSecurityGroupManager(self.cfg) + self.Subnets = modules.NewSubnetManager(self.cfg) + self.Users = modules.NewUserManager(self.cfg) + self.Interface = modules.NewInterfaceManager(self.cfg) + self.Jobs = modules.NewJobManager(self.cfg) + self.Balances = modules.NewBalanceManager(self.cfg) + self.Bandwidths = modules.NewBandwidthManager(self.cfg) + self.Credentials = modules.NewCredentialManager(self.cfg) + self.Port = modules.NewPortManager(self.cfg) + self.Flavors = modules.NewFlavorManager(self.cfg) + self.VpcRoutes = modules.NewVpcRouteManager(self.cfg) + self.SNatRules = modules.NewNatSManager(self.cfg) + self.DNatRules = modules.NewNatDManager(self.cfg) + self.NatGateways = modules.NewNatGatewayManager(self.cfg) + self.VpcPeerings = modules.NewVpcPeeringManager(self.cfg) + self.DBInstance = modules.NewDBInstanceManager(self.cfg) + self.DBInstanceBackup = modules.NewDBInstanceBackupManager(self.cfg) + self.DBInstanceFlavor = modules.NewDBInstanceFlavorManager(self.cfg) + self.DBInstanceJob = modules.NewDBInstanceJobManager(self.cfg) + self.Traces = modules.NewTraceManager(self.cfg) + self.CloudEye = modules.NewCloudEyeManager(self.cfg) + self.Quotas = modules.NewQuotaManager(self.cfg) + self.EnterpriseProjects = modules.NewEnterpriseProjectManager(self.cfg) + self.Roles = modules.NewRoleManager(self.cfg) + self.Groups = modules.NewGroupManager(self.cfg) + self.SAMLProviders = modules.NewSAMLProviderManager(self.cfg) + self.SAMLProviderMappings = modules.NewSAMLProviderMappingManager(self.cfg) + self.SfsTurbos = modules.NewSfsTurboManager(self.cfg) } self.init = true } -func NewClientWithAccessKey(regionId, domainId, projectId, accessKey, secretKey string, debug bool) (*Client, error) { - c := &Client{debug: debug} - err := c.InitWithAccessKey(regionId, domainId, projectId, accessKey, secretKey) +func NewClientWithAccessKey(endpoint, regionId, domainId, projectId, accessKey, secretKey string, debug bool) (*Client, error) { + c := &Client{} + err := c.InitWithAccessKey(endpoint, regionId, domainId, projectId, accessKey, secretKey, debug) return c, err } + +func NewPublicCloudClientWithAccessKey(regionId, domainId, projectId, accessKey, secretKey string, debug bool) (*Client, error) { + return NewClientWithAccessKey("myhuaweicloud.com", regionId, domainId, projectId, accessKey, secretKey, debug) +} diff --git a/pkg/multicloud/huawei/client/manager/manager.go b/pkg/multicloud/huawei/client/manager/manager.go index d73ade4b86..01f1fbddca 100644 --- a/pkg/multicloud/huawei/client/manager/manager.go +++ b/pkg/multicloud/huawei/client/manager/manager.go @@ -17,6 +17,7 @@ package manager import ( "yunion.io/x/jsonutils" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -69,3 +70,12 @@ type IManager interface { // 执行操作 POST /cloudservers// PerformAction(action string, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) } + +type IManagerConfig interface { + GetSigner() auth.Signer + GetEndpoint() string + GetRegionId() string + GetDomainId() string + GetProjectId() string + GetDebug() bool +} diff --git a/pkg/multicloud/huawei/client/modules/manager_base.go b/pkg/multicloud/huawei/client/modules/manager_base.go index 3ea6cb525e..fb3ac600d0 100644 --- a/pkg/multicloud/huawei/client/modules/manager_base.go +++ b/pkg/multicloud/huawei/client/modules/manager_base.go @@ -29,6 +29,7 @@ import ( "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/requests" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" "yunion.io/x/onecloud/pkg/util/httputils" @@ -39,7 +40,7 @@ type IRequestHook interface { } type SBaseManager struct { - signer auth.Signer + cfg manager.IManagerConfig httpClient *http.Client requestHook IRequestHook // 用于对request做特殊处理。非必要请不要使用!!!。目前只有port接口用到。 @@ -74,17 +75,21 @@ func (t *sThrottlingThreshold) Lock() { var ThrottlingLock = sThrottlingThreshold{locked: false, lockTime: time.Time{}} -func NewBaseManager2(signer auth.Signer, debug bool, requesthk IRequestHook) SBaseManager { +func NewBaseManager2(cfg manager.IManagerConfig, requesthk IRequestHook) SBaseManager { return SBaseManager{ - signer: signer, + cfg: cfg, httpClient: httputils.GetDefaultClient(), - debug: debug, + debug: cfg.GetDebug(), requestHook: requesthk, } } -func NewBaseManager(signer auth.Signer, debug bool) SBaseManager { - return NewBaseManager2(signer, debug, nil) +func NewBaseManager(cfg manager.IManagerConfig) SBaseManager { + return NewBaseManager2(cfg, nil) +} + +func (self *SBaseManager) GetEndpoint() string { + return self.cfg.GetEndpoint() } func (self *SBaseManager) GetColumns() []string { @@ -195,7 +200,7 @@ func (self *SBaseManager) jsonRequest(request requests.IRequest) (http.Header, j self.requestHook.Process(request) } // 拼接、编译、签名 requests here。 - err := self.buildRequestWithSigner(request, self.signer) + err := self.buildRequestWithSigner(request, self.cfg.GetSigner()) if err != nil { return nil, nil, err } diff --git a/pkg/multicloud/huawei/client/modules/manager_resource.go b/pkg/multicloud/huawei/client/modules/manager_resource.go index 1b47d1f4f2..a109ad29de 100644 --- a/pkg/multicloud/huawei/client/modules/manager_resource.go +++ b/pkg/multicloud/huawei/client/modules/manager_resource.go @@ -135,7 +135,7 @@ func (self *SResourceManager) getReourcePath(ctx manager.IManagerContext, rid st func (self *SResourceManager) newRequest(method, rid, spec string, ctx manager.IManagerContext) *requests.SRequest { resourcePath := self.getReourcePath(ctx, rid, spec) - return requests.NewResourceRequest(method, string(self.ServiceName), self.version, self.Region, self.ProjectId, resourcePath) + return requests.NewResourceRequest(self.GetEndpoint(), method, string(self.ServiceName), self.version, self.Region, self.ProjectId, resourcePath) } func (self *SResourceManager) List(queries map[string]string) (*responses.ListResult, error) { diff --git a/pkg/multicloud/huawei/client/modules/mod_balances.go b/pkg/multicloud/huawei/client/modules/mod_balances.go index 2075950a7b..d3b1290cb8 100644 --- a/pkg/multicloud/huawei/client/modules/mod_balances.go +++ b/pkg/multicloud/huawei/client/modules/mod_balances.go @@ -17,7 +17,7 @@ package modules import ( "fmt" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -44,9 +44,9 @@ func (self *balanceCtx) GetPath() string { } // 这个manager非常特殊。只有List 和 SetDomainId方法可用。其他方法未验证 -func NewBalanceManager(signer auth.Signer, debug bool) *SBalanceManager { +func NewBalanceManager(cfg manager.IManagerConfig) *SBalanceManager { return &SBalanceManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameBSS, Region: "cn-north-1", ProjectId: "", diff --git a/pkg/multicloud/huawei/client/modules/mod_bandwidths.go b/pkg/multicloud/huawei/client/modules/mod_bandwidths.go index dfd4a334e2..63a7f24a9e 100644 --- a/pkg/multicloud/huawei/client/modules/mod_bandwidths.go +++ b/pkg/multicloud/huawei/client/modules/mod_bandwidths.go @@ -15,19 +15,19 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SBandwidthManager struct { SResourceManager } -func NewBandwidthManager(regionId string, projectId string, signer auth.Signer, debug bool) *SBandwidthManager { +func NewBandwidthManager(cfg manager.IManagerConfig) *SBandwidthManager { return &SBandwidthManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameVPC, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1", Keyword: "bandwidth", KeywordPlural: "bandwidths", diff --git a/pkg/multicloud/huawei/client/modules/mod_ces.go b/pkg/multicloud/huawei/client/modules/mod_ces.go index b79dfc2a3f..56cc8a1021 100644 --- a/pkg/multicloud/huawei/client/modules/mod_ces.go +++ b/pkg/multicloud/huawei/client/modules/mod_ces.go @@ -21,7 +21,7 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/httperrors" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/requests" ) @@ -29,12 +29,12 @@ type SCloudEyeManager struct { SResourceManager } -func NewCloudEyeManager(regionId string, projectId string, signer auth.Signer, debug bool) *SCloudEyeManager { +func NewCloudEyeManager(cfg manager.IManagerConfig) *SCloudEyeManager { return &SCloudEyeManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameCES, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "V1.0", Keyword: "", KeywordPlural: "metrics", @@ -93,7 +93,7 @@ func (ces *SCloudEyeManager) ListMetrics() ([]SMetricMeta, error) { } func (ces *SCloudEyeManager) listMetricsInternal(start string) (string, []SMetricMeta, error) { - request := requests.NewResourceRequest("GET", string(ces.ServiceName), ces.version, ces.Region, ces.ProjectId, ces.ResourceKeyword) + request := requests.NewResourceRequest(ces.GetEndpoint(), "GET", string(ces.ServiceName), ces.version, ces.Region, ces.ProjectId, ces.ResourceKeyword) request.AddQueryParam("limit", "1000") if len(start) > 0 { request.AddQueryParam("start", start) @@ -128,7 +128,7 @@ func (ces *SCloudEyeManager) GetMetricsData(metrics []SMetricMeta, since time.Ti for i := range metrics { metricReq[i] = metrics[i].SMetric } - request := requests.NewResourceRequest("POST", string(ces.ServiceName), ces.version, ces.Region, ces.ProjectId, "batch-query-metric-data") + request := requests.NewResourceRequest(ces.GetEndpoint(), "POST", string(ces.ServiceName), ces.version, ces.Region, ces.ProjectId, "batch-query-metric-data") input := SBatchQueryMetricDataInput{ Metrics: metricReq, From: since.Unix() * 1000, diff --git a/pkg/multicloud/huawei/client/modules/mod_credential.go b/pkg/multicloud/huawei/client/modules/mod_credential.go index 94a9f43021..183818c91c 100644 --- a/pkg/multicloud/huawei/client/modules/mod_credential.go +++ b/pkg/multicloud/huawei/client/modules/mod_credential.go @@ -15,16 +15,16 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SCredentialManager struct { SResourceManager } -func NewCredentialManager(signer auth.Signer, debug bool) *SCredentialManager { +func NewCredentialManager(cfg manager.IManagerConfig) *SCredentialManager { return &SCredentialManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameIAM, Region: "", ProjectId: "", diff --git a/pkg/multicloud/huawei/client/modules/mod_dbinstance.go b/pkg/multicloud/huawei/client/modules/mod_dbinstance.go index f6b41a2c31..0645007166 100644 --- a/pkg/multicloud/huawei/client/modules/mod_dbinstance.go +++ b/pkg/multicloud/huawei/client/modules/mod_dbinstance.go @@ -20,7 +20,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/cloudprovider" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -28,12 +28,12 @@ type SDBInstanceManager struct { SResourceManager } -func NewDBInstanceManager(regionId string, projectId string, signer auth.Signer, debug bool) *SDBInstanceManager { +func NewDBInstanceManager(cfg manager.IManagerConfig) *SDBInstanceManager { return &SDBInstanceManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameRDS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v3", Keyword: "", KeywordPlural: "instances", diff --git a/pkg/multicloud/huawei/client/modules/mod_dbinstance_backup.go b/pkg/multicloud/huawei/client/modules/mod_dbinstance_backup.go index c22090d9a8..21efbe8218 100644 --- a/pkg/multicloud/huawei/client/modules/mod_dbinstance_backup.go +++ b/pkg/multicloud/huawei/client/modules/mod_dbinstance_backup.go @@ -15,19 +15,19 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SDBInstanceBackupManager struct { SResourceManager } -func NewDBInstanceBackupManager(regionId string, projectId string, signer auth.Signer, debug bool) *SDBInstanceBackupManager { +func NewDBInstanceBackupManager(cfg manager.IManagerConfig) *SDBInstanceBackupManager { return &SDBInstanceBackupManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameRDS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v3", Keyword: "backup", KeywordPlural: "backups", diff --git a/pkg/multicloud/huawei/client/modules/mod_dbinstance_flavor.go b/pkg/multicloud/huawei/client/modules/mod_dbinstance_flavor.go index 192e596afb..db0621980d 100644 --- a/pkg/multicloud/huawei/client/modules/mod_dbinstance_flavor.go +++ b/pkg/multicloud/huawei/client/modules/mod_dbinstance_flavor.go @@ -15,19 +15,19 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SDBInstanceFlavorManager struct { SResourceManager } -func NewDBInstanceFlavorManager(regionId string, projectId string, signer auth.Signer, debug bool) *SDBInstanceFlavorManager { +func NewDBInstanceFlavorManager(cfg manager.IManagerConfig) *SDBInstanceFlavorManager { return &SDBInstanceFlavorManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameRDS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v3", Keyword: "flavor", KeywordPlural: "flavor", diff --git a/pkg/multicloud/huawei/client/modules/mod_dbinstance_job.go b/pkg/multicloud/huawei/client/modules/mod_dbinstance_job.go index 3597d2e77f..080508f448 100644 --- a/pkg/multicloud/huawei/client/modules/mod_dbinstance_job.go +++ b/pkg/multicloud/huawei/client/modules/mod_dbinstance_job.go @@ -17,19 +17,19 @@ package modules import ( "yunion.io/x/jsonutils" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SDBInstanceJobManager struct { SResourceManager } -func NewDBInstanceJobManager(regionId string, projectId string, signer auth.Signer, debug bool) *SDBInstanceJobManager { +func NewDBInstanceJobManager(cfg manager.IManagerConfig) *SDBInstanceJobManager { return &SDBInstanceJobManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameRDS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v3", Keyword: "", KeywordPlural: "", diff --git a/pkg/multicloud/huawei/client/modules/mod_disks.go b/pkg/multicloud/huawei/client/modules/mod_disks.go index be68003211..ffd67ab01c 100644 --- a/pkg/multicloud/huawei/client/modules/mod_disks.go +++ b/pkg/multicloud/huawei/client/modules/mod_disks.go @@ -18,7 +18,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -26,12 +26,12 @@ type SDiskManager struct { SResourceManager } -func NewDiskManager(regionId string, projectId string, signer auth.Signer, debug bool) *SDiskManager { +func NewDiskManager(cfg manager.IManagerConfig) *SDiskManager { return &SDiskManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameEVS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v2", Keyword: "volume", KeywordPlural: "volumes", diff --git a/pkg/multicloud/huawei/client/modules/mod_dnat_rules.go b/pkg/multicloud/huawei/client/modules/mod_dnat_rules.go index 4b64531403..8f7d807813 100644 --- a/pkg/multicloud/huawei/client/modules/mod_dnat_rules.go +++ b/pkg/multicloud/huawei/client/modules/mod_dnat_rules.go @@ -15,18 +15,18 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SNatDRuleManager struct { SResourceManager } -func NewNatDManager(regionId string, projectId string, signer auth.Signer, debug bool) *SNatDRuleManager { +func NewNatDManager(cfg manager.IManagerConfig) *SNatDRuleManager { man := &SNatDRuleManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameNAT, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0", Keyword: "dnat_rule", @@ -34,8 +34,8 @@ func NewNatDManager(regionId string, projectId string, signer auth.Signer, debug ResourceKeyword: "dnat_rules", }} - if len(projectId) > 0 { - man.requestHook = &sProjectHook{projectId} + if len(cfg.GetProjectId()) > 0 { + man.requestHook = &sProjectHook{cfg.GetProjectId()} } return man } diff --git a/pkg/multicloud/huawei/client/modules/mod_domains.go b/pkg/multicloud/huawei/client/modules/mod_domains.go index 18042b6174..fd5f23bc53 100644 --- a/pkg/multicloud/huawei/client/modules/mod_domains.go +++ b/pkg/multicloud/huawei/client/modules/mod_domains.go @@ -15,16 +15,16 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SDomainManager struct { SResourceManager } -func NewDomainManager(signer auth.Signer, debug bool) *SDomainManager { +func NewDomainManager(cfg manager.IManagerConfig) *SDomainManager { return &SDomainManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameIAM, Region: "", ProjectId: "", diff --git a/pkg/multicloud/huawei/client/modules/mod_eips.go b/pkg/multicloud/huawei/client/modules/mod_eips.go index 9408eea14d..36bcf56387 100644 --- a/pkg/multicloud/huawei/client/modules/mod_eips.go +++ b/pkg/multicloud/huawei/client/modules/mod_eips.go @@ -17,19 +17,19 @@ package modules import ( "yunion.io/x/jsonutils" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SEipManager struct { SResourceManager } -func NewEipManager(regionId string, projectId string, signer auth.Signer, debug bool) *SEipManager { +func NewEipManager(cfg manager.IManagerConfig) *SEipManager { return &SEipManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameVPC, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1", Keyword: "publicip", KeywordPlural: "publicips", diff --git a/pkg/multicloud/huawei/client/modules/mod_elasticcache.go b/pkg/multicloud/huawei/client/modules/mod_elasticcache.go index 038740a3c4..39ea576497 100644 --- a/pkg/multicloud/huawei/client/modules/mod_elasticcache.go +++ b/pkg/multicloud/huawei/client/modules/mod_elasticcache.go @@ -19,7 +19,7 @@ import ( "yunion.io/x/jsonutils" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -31,12 +31,12 @@ type SDcsAvailableZoneManager struct { SResourceManager } -func NewElasticcacheManager(regionId string, projectId string, signer auth.Signer, debug bool) *SElasticcacheManager { +func NewElasticcacheManager(cfg manager.IManagerConfig) *SElasticcacheManager { return &SElasticcacheManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameDCS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1.0", Keyword: "", KeywordPlural: "instances", @@ -103,11 +103,11 @@ func (self *SElasticcacheManager) ChangeInstanceSpec(instanceId string, specCode return self.CreateInContextWithSpec(nil, fmt.Sprintf("%s/extend", instanceId), params, "") } -func NewDcsAvailableZoneManager(regionId string, signer auth.Signer, debug bool) *SDcsAvailableZoneManager { +func NewDcsAvailableZoneManager(cfg manager.IManagerConfig) *SDcsAvailableZoneManager { return &SDcsAvailableZoneManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameDCS, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v1.0", Keyword: "available_zone", diff --git a/pkg/multicloud/huawei/client/modules/mod_enterpriceprojects.go b/pkg/multicloud/huawei/client/modules/mod_enterpriceprojects.go index 89005bc348..c8a95e2372 100644 --- a/pkg/multicloud/huawei/client/modules/mod_enterpriceprojects.go +++ b/pkg/multicloud/huawei/client/modules/mod_enterpriceprojects.go @@ -15,16 +15,16 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SEnterpriseProjectManager struct { SResourceManager } -func NewEnterpriseProjectManager(regionId string, projectId string, signer auth.Signer, debug bool) *SEnterpriseProjectManager { - return &SEnterpriseProjectManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), +func NewEnterpriseProjectManager(cfg manager.IManagerConfig) *SEnterpriseProjectManager { + m := &SEnterpriseProjectManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameEPS, Region: "", ProjectId: "", @@ -34,4 +34,6 @@ func NewEnterpriseProjectManager(regionId string, projectId string, signer auth. ResourceKeyword: "enterprise-projects", }} + m.SetDomainId(cfg.GetDomainId()) + return m } diff --git a/pkg/multicloud/huawei/client/modules/mod_flavors.go b/pkg/multicloud/huawei/client/modules/mod_flavors.go index ea16904e30..16ab9d0fb6 100644 --- a/pkg/multicloud/huawei/client/modules/mod_flavors.go +++ b/pkg/multicloud/huawei/client/modules/mod_flavors.go @@ -15,19 +15,19 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SFlavorManager struct { SResourceManager } -func NewFlavorManager(regionId string, projectId string, signer auth.Signer, debug bool) *SFlavorManager { +func NewFlavorManager(cfg manager.IManagerConfig) *SFlavorManager { return &SFlavorManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameECS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1", Keyword: "flavor", KeywordPlural: "flavors", diff --git a/pkg/multicloud/huawei/client/modules/mod_groups.go b/pkg/multicloud/huawei/client/modules/mod_groups.go index 90424708d0..1bcab23db3 100644 --- a/pkg/multicloud/huawei/client/modules/mod_groups.go +++ b/pkg/multicloud/huawei/client/modules/mod_groups.go @@ -20,7 +20,7 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/cloudprovider" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -28,9 +28,9 @@ type SGroupManager struct { SResourceManager } -func NewGroupManager(signer auth.Signer, debug bool) *SGroupManager { - return &SGroupManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), +func NewGroupManager(cfg manager.IManagerConfig) *SGroupManager { + m := &SGroupManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameIAM, Region: "", ProjectId: "", @@ -40,6 +40,8 @@ func NewGroupManager(signer auth.Signer, debug bool) *SGroupManager { ResourceKeyword: "groups", }} + m.SetDomainId(cfg.GetDomainId()) + return m } func (manager *SGroupManager) ListRoles(domainId string, groupId string) (*responses.ListResult, error) { diff --git a/pkg/multicloud/huawei/client/modules/mod_images.go b/pkg/multicloud/huawei/client/modules/mod_images.go index dbf23edb06..6b432a5e0d 100644 --- a/pkg/multicloud/huawei/client/modules/mod_images.go +++ b/pkg/multicloud/huawei/client/modules/mod_images.go @@ -18,7 +18,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/onecloud/pkg/httperrors" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/requests" ) @@ -36,16 +36,16 @@ func (self *imageProject) Process(request requests.IRequest) { request.AddHeaderParam("X-Project-Id", self.projectId) } -func NewImageManager(regionId string, projectId string, signer auth.Signer, debug bool) *SImageManager { +func NewImageManager(cfg manager.IManagerConfig) *SImageManager { var requestHook imageProject - if len(projectId) > 0 { - requestHook = imageProject{projectId: projectId} + if len(cfg.GetProjectId()) > 0 { + requestHook = imageProject{projectId: cfg.GetProjectId()} } return &SImageManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager2(signer, debug, &requestHook), + SBaseManager: NewBaseManager2(cfg, &requestHook), ServiceName: ServiceNameIMS, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2", Keyword: "image", @@ -81,16 +81,16 @@ func (self *SImageManager) Get(id string, querys map[string]string) (jsonutils.J // https://support.huaweicloud.com/api-ims/zh-cn_topic_0020092108.html // 删除image只能用这个manager -func NewOpenstackImageManager(regionId string, projectId string, signer auth.Signer, debug bool) *SImageManager { +func NewOpenstackImageManager(cfg manager.IManagerConfig) *SImageManager { var requestHook imageProject - if len(projectId) > 0 { - requestHook = imageProject{projectId: projectId} + if len(cfg.GetProjectId()) > 0 { + requestHook = imageProject{projectId: cfg.GetProjectId()} } return &SImageManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager2(signer, debug, &requestHook), + SBaseManager: NewBaseManager2(cfg, &requestHook), ServiceName: ServiceNameIMS, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2", Keyword: "image", diff --git a/pkg/multicloud/huawei/client/modules/mod_interface.go b/pkg/multicloud/huawei/client/modules/mod_interface.go index 568146bcbd..f1f953e407 100644 --- a/pkg/multicloud/huawei/client/modules/mod_interface.go +++ b/pkg/multicloud/huawei/client/modules/mod_interface.go @@ -15,7 +15,7 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SInterfaceManager struct { @@ -23,12 +23,12 @@ type SInterfaceManager struct { } // 不建议使用 -func NewInterfaceManager(regionId, projectId string, signer auth.Signer, debug bool) *SInterfaceManager { +func NewInterfaceManager(cfg manager.IManagerConfig) *SInterfaceManager { return &SInterfaceManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameECS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v2", Keyword: "interfaceAttachment", KeywordPlural: "interfaceAttachments", diff --git a/pkg/multicloud/huawei/client/modules/mod_jobs.go b/pkg/multicloud/huawei/client/modules/mod_jobs.go index 3015497a25..610c48685f 100644 --- a/pkg/multicloud/huawei/client/modules/mod_jobs.go +++ b/pkg/multicloud/huawei/client/modules/mod_jobs.go @@ -19,7 +19,7 @@ import ( "yunion.io/x/jsonutils" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -27,12 +27,12 @@ type SJobManager struct { SResourceManager } -func NewJobManager(regionId string, projectId string, signer auth.Signer, debug bool) *SJobManager { +func NewJobManager(cfg manager.IManagerConfig) *SJobManager { return &SJobManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: "", - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1", Keyword: "", KeywordPlural: "", diff --git a/pkg/multicloud/huawei/client/modules/mod_keypairs.go b/pkg/multicloud/huawei/client/modules/mod_keypairs.go index 4fb5e145d4..278f1c684e 100644 --- a/pkg/multicloud/huawei/client/modules/mod_keypairs.go +++ b/pkg/multicloud/huawei/client/modules/mod_keypairs.go @@ -15,19 +15,19 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SKeypairManager struct { SResourceManager } -func NewKeypairManager(regionId string, projectId string, signer auth.Signer, debug bool) *SKeypairManager { +func NewKeypairManager(cfg manager.IManagerConfig) *SKeypairManager { return &SKeypairManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameECS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v2", Keyword: "keypair", KeywordPlural: "keypairs", diff --git a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_backend.go b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_backend.go index d4ac5ff2da..8e4363da91 100644 --- a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_backend.go +++ b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_backend.go @@ -17,7 +17,7 @@ package modules import ( "fmt" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SElbBackendManager struct { @@ -33,16 +33,16 @@ func (self *backendCtx) GetPath() string { return fmt.Sprintf("pools/%s", self.backendGroupId) } -func NewElbBackendManager(regionId string, projectId string, signer auth.Signer, debug bool) *SElbBackendManager { +func NewElbBackendManager(cfg manager.IManagerConfig) *SElbBackendManager { var requestHook portProject - if len(projectId) > 0 { - requestHook = portProject{projectId: projectId} + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} } return &SElbBackendManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager2(signer, debug, &requestHook), + SBaseManager: NewBaseManager2(cfg, &requestHook), ServiceName: ServiceNameELB, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0/lbaas", Keyword: "member", diff --git a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_backend_group.go b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_backend_group.go index 373fc533ab..c6e3fa370a 100644 --- a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_backend_group.go +++ b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_backend_group.go @@ -15,23 +15,23 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SElbBackendGroupManager struct { SResourceManager } -func NewElbBackendGroupManager(regionId string, projectId string, signer auth.Signer, debug bool) *SElbBackendGroupManager { +func NewElbBackendGroupManager(cfg manager.IManagerConfig) *SElbBackendGroupManager { var requestHook portProject - if len(projectId) > 0 { - requestHook = portProject{projectId: projectId} + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} } return &SElbBackendGroupManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager2(signer, debug, &requestHook), + SBaseManager: NewBaseManager2(cfg, &requestHook), ServiceName: ServiceNameELB, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0", Keyword: "pool", diff --git a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_certificates.go b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_certificates.go index c34b731c5d..1a9ca066b1 100644 --- a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_certificates.go +++ b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_certificates.go @@ -15,23 +15,23 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SElbCertificatesManager struct { SResourceManager } -func NewElbCertificatesManager(regionId string, projectId string, signer auth.Signer, debug bool) *SElbCertificatesManager { +func NewElbCertificatesManager(cfg manager.IManagerConfig) *SElbCertificatesManager { var requestHook portProject - if len(projectId) > 0 { - requestHook = portProject{projectId: projectId} + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} } return &SElbCertificatesManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager2(signer, debug, &requestHook), + SBaseManager: NewBaseManager2(cfg, &requestHook), ServiceName: ServiceNameELB, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0", Keyword: "", diff --git a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_healthcheck.go b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_healthcheck.go index 8518dd04fe..9ffec99d2c 100644 --- a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_healthcheck.go +++ b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_healthcheck.go @@ -17,23 +17,23 @@ package modules import ( "yunion.io/x/jsonutils" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SElbHealthCheckManager struct { SResourceManager } -func NewElbHealthCheckManager(regionId string, projectId string, signer auth.Signer, debug bool) *SElbHealthCheckManager { +func NewElbHealthCheckManager(cfg manager.IManagerConfig) *SElbHealthCheckManager { var requestHook portProject - if len(projectId) > 0 { - requestHook = portProject{projectId: projectId} + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} } return &SElbHealthCheckManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager2(signer, debug, &requestHook), + SBaseManager: NewBaseManager2(cfg, &requestHook), ServiceName: ServiceNameELB, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0", Keyword: "healthmonitor", diff --git a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_listeners.go b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_listeners.go index 36c2d2dcf3..c7d4c5679e 100644 --- a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_listeners.go +++ b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_listeners.go @@ -15,23 +15,23 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SElbListenersManager struct { SResourceManager } -func NewElbListenersManager(regionId string, projectId string, signer auth.Signer, debug bool) *SElbListenersManager { +func NewElbListenersManager(cfg manager.IManagerConfig) *SElbListenersManager { var requestHook portProject - if len(projectId) > 0 { - requestHook = portProject{projectId: projectId} + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} } return &SElbListenersManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager2(signer, debug, &requestHook), + SBaseManager: NewBaseManager2(cfg, &requestHook), ServiceName: ServiceNameELB, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0", Keyword: "listener", diff --git a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_policies.go b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_policies.go index 259257da03..f83d73993b 100644 --- a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_policies.go +++ b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_policies.go @@ -15,23 +15,23 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SElbL7policiesManager struct { SResourceManager } -func NewElbL7policiesManager(regionId string, projectId string, signer auth.Signer, debug bool) *SElbL7policiesManager { +func NewElbL7policiesManager(cfg manager.IManagerConfig) *SElbL7policiesManager { var requestHook portProject - if len(projectId) > 0 { - requestHook = portProject{projectId: projectId} + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} } return &SElbL7policiesManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager2(signer, debug, &requestHook), + SBaseManager: NewBaseManager2(cfg, &requestHook), ServiceName: ServiceNameELB, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0", Keyword: "l7policy", diff --git a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_rules.go b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_rules.go index 555aaafbd5..1b7dbef2cd 100644 --- a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_rules.go +++ b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_rules.go @@ -17,7 +17,7 @@ package modules import ( "fmt" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SElbPoliciesManager struct { @@ -33,16 +33,16 @@ func (self *policyCtx) GetPath() string { return fmt.Sprintf("l7policies/%s", self.l7policyId) } -func NewElbPoliciesManager(regionId string, projectId string, signer auth.Signer, debug bool) *SElbPoliciesManager { +func NewElbPoliciesManager(cfg manager.IManagerConfig) *SElbPoliciesManager { var requestHook portProject - if len(projectId) > 0 { - requestHook = portProject{projectId: projectId} + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} } return &SElbPoliciesManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager2(signer, debug, &requestHook), + SBaseManager: NewBaseManager2(cfg, &requestHook), ServiceName: ServiceNameELB, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0/lbaas", Keyword: "rule", diff --git a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_whitelists.go b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_whitelists.go index fc44e21ef9..178be46405 100644 --- a/pkg/multicloud/huawei/client/modules/mod_loadbalancer_whitelists.go +++ b/pkg/multicloud/huawei/client/modules/mod_loadbalancer_whitelists.go @@ -15,23 +15,23 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SElbWhitelistManager struct { SResourceManager } -func NewElbWhitelistManager(regionId string, projectId string, signer auth.Signer, debug bool) *SElbWhitelistManager { +func NewElbWhitelistManager(cfg manager.IManagerConfig) *SElbWhitelistManager { var requestHook portProject - if len(projectId) > 0 { - requestHook = portProject{projectId: projectId} + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} } return &SElbWhitelistManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager2(signer, debug, &requestHook), + SBaseManager: NewBaseManager2(cfg, &requestHook), ServiceName: ServiceNameELB, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0", Keyword: "whitelist", diff --git a/pkg/multicloud/huawei/client/modules/mod_loadbalancers.go b/pkg/multicloud/huawei/client/modules/mod_loadbalancers.go index da9c1faaea..258eb40d37 100644 --- a/pkg/multicloud/huawei/client/modules/mod_loadbalancers.go +++ b/pkg/multicloud/huawei/client/modules/mod_loadbalancers.go @@ -15,23 +15,23 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SLoadbalancerManager struct { SResourceManager } -func NewLoadbalancerManager(regionId string, projectId string, signer auth.Signer, debug bool) *SLoadbalancerManager { +func NewLoadbalancerManager(cfg manager.IManagerConfig) *SLoadbalancerManager { var requestHook portProject - if len(projectId) > 0 { - requestHook = portProject{projectId: projectId} + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} } return &SLoadbalancerManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager2(signer, debug, &requestHook), + SBaseManager: NewBaseManager2(cfg, &requestHook), ServiceName: ServiceNameELB, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0", Keyword: "loadbalancer", diff --git a/pkg/multicloud/huawei/client/modules/mod_mapping.go b/pkg/multicloud/huawei/client/modules/mod_mapping.go index 9aefbab469..b668674272 100644 --- a/pkg/multicloud/huawei/client/modules/mod_mapping.go +++ b/pkg/multicloud/huawei/client/modules/mod_mapping.go @@ -15,16 +15,16 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SAMLProviderMappingManager struct { SResourceManager } -func NewSAMLProviderMappingManager(signer auth.Signer, debug bool) *SAMLProviderMappingManager { - return &SAMLProviderMappingManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), +func NewSAMLProviderMappingManager(cfg manager.IManagerConfig) *SAMLProviderMappingManager { + m := &SAMLProviderMappingManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameIAM, Region: "", ProjectId: "", @@ -34,4 +34,6 @@ func NewSAMLProviderMappingManager(signer auth.Signer, debug bool) *SAMLProvider ResourceKeyword: "mappings", }} + m.SetDomainId(cfg.GetDomainId()) + return m } diff --git a/pkg/multicloud/huawei/client/modules/mod_natgateway.go b/pkg/multicloud/huawei/client/modules/mod_natgateway.go index 17b2898998..b73e0652fb 100644 --- a/pkg/multicloud/huawei/client/modules/mod_natgateway.go +++ b/pkg/multicloud/huawei/client/modules/mod_natgateway.go @@ -15,7 +15,7 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/requests" ) @@ -31,11 +31,11 @@ func (self *sProjectHook) Process(request requests.IRequest) { request.AddHeaderParam("X-Project-Id", self.projectId) } -func NewNatGatewayManager(regionId string, projectId string, signer auth.Signer, debug bool) *SNatGatewayManager { +func NewNatGatewayManager(cfg manager.IManagerConfig) *SNatGatewayManager { man := &SNatGatewayManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameNAT, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0", Keyword: "nat_gateway", @@ -43,8 +43,8 @@ func NewNatGatewayManager(regionId string, projectId string, signer auth.Signer, ResourceKeyword: "nat_gateways", }} - if len(projectId) > 0 { - man.requestHook = &sProjectHook{projectId} + if len(cfg.GetProjectId()) > 0 { + man.requestHook = &sProjectHook{cfg.GetProjectId()} } return man } diff --git a/pkg/multicloud/huawei/client/modules/mod_orders.go b/pkg/multicloud/huawei/client/modules/mod_orders.go index f0b29bb895..ef83fcb8cf 100644 --- a/pkg/multicloud/huawei/client/modules/mod_orders.go +++ b/pkg/multicloud/huawei/client/modules/mod_orders.go @@ -19,7 +19,6 @@ import ( "yunion.io/x/jsonutils" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -42,9 +41,9 @@ func (self *orderCtx) GetPath() string { // 客户运营能力API的Endpoint为“bss.cn-north-1.myhuaweicloud.com”。该Endpoint为全局Endpoint,中国站所有区域均可使用。 // https://support.huaweicloud.com/api-oce/zh-cn_topic_0084961226.html -func NewOrderManager(signer auth.Signer, debug bool) *SOrderManager { +func NewOrderManager(cfg manager.IManagerConfig) *SOrderManager { return &SOrderManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameBSS, Region: "cn-north-1", ProjectId: "", diff --git a/pkg/multicloud/huawei/client/modules/mod_port.go b/pkg/multicloud/huawei/client/modules/mod_port.go index c7e3adca70..5a736bcc71 100644 --- a/pkg/multicloud/huawei/client/modules/mod_port.go +++ b/pkg/multicloud/huawei/client/modules/mod_port.go @@ -15,7 +15,7 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/requests" ) @@ -33,17 +33,17 @@ func (self *portProject) Process(request requests.IRequest) { request.AddHeaderParam("X-Project-Id", self.projectId) } -func NewPortManager(regionId string, projectId string, signer auth.Signer, debug bool) *SPortManager { +func NewPortManager(cfg manager.IManagerConfig) *SPortManager { var requestHook portProject - if len(projectId) > 0 { - requestHook = portProject{projectId: projectId} + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} } return &SPortManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager2(signer, debug, &requestHook), + SBaseManager: NewBaseManager2(cfg, &requestHook), ServiceName: ServiceNameVPC, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1", Keyword: "port", KeywordPlural: "ports", diff --git a/pkg/multicloud/huawei/client/modules/mod_projects.go b/pkg/multicloud/huawei/client/modules/mod_projects.go index bef2039d52..df0febf186 100644 --- a/pkg/multicloud/huawei/client/modules/mod_projects.go +++ b/pkg/multicloud/huawei/client/modules/mod_projects.go @@ -15,16 +15,16 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SProjectManager struct { SResourceManager } -func NewProjectManager(signer auth.Signer, debug bool) *SProjectManager { +func NewProjectManager(cfg manager.IManagerConfig) *SProjectManager { return &SProjectManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameIAM, Region: "", ProjectId: "", diff --git a/pkg/multicloud/huawei/client/modules/mod_quotas.go b/pkg/multicloud/huawei/client/modules/mod_quotas.go index 69ae72fdfc..f8c3506048 100644 --- a/pkg/multicloud/huawei/client/modules/mod_quotas.go +++ b/pkg/multicloud/huawei/client/modules/mod_quotas.go @@ -15,19 +15,19 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SQuotaManager struct { SResourceManager } -func NewQuotaManager(regionId string, projectId string, signer auth.Signer, debug bool) *SQuotaManager { +func NewQuotaManager(cfg manager.IManagerConfig) *SQuotaManager { return &SQuotaManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameEVS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1", Keyword: "quotas", KeywordPlural: "quotas", diff --git a/pkg/multicloud/huawei/client/modules/mod_regions.go b/pkg/multicloud/huawei/client/modules/mod_regions.go index 65c659ae95..76ebce1ce9 100644 --- a/pkg/multicloud/huawei/client/modules/mod_regions.go +++ b/pkg/multicloud/huawei/client/modules/mod_regions.go @@ -15,16 +15,16 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SRegionManager struct { SResourceManager } -func NewRegionManager(signer auth.Signer, debug bool) *SRegionManager { +func NewRegionManager(cfg manager.IManagerConfig) *SRegionManager { return &SRegionManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameIAM, Region: "", ProjectId: "", diff --git a/pkg/multicloud/huawei/client/modules/mod_roles.go b/pkg/multicloud/huawei/client/modules/mod_roles.go index 6defe96e18..1f4751541b 100644 --- a/pkg/multicloud/huawei/client/modules/mod_roles.go +++ b/pkg/multicloud/huawei/client/modules/mod_roles.go @@ -15,16 +15,16 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SRoleManager struct { SResourceManager } -func NewRoleManager(signer auth.Signer, debug bool) *SRoleManager { - return &SRoleManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), +func NewRoleManager(cfg manager.IManagerConfig) *SRoleManager { + m := &SRoleManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameIAM, Region: "", ProjectId: "", @@ -34,4 +34,6 @@ func NewRoleManager(signer auth.Signer, debug bool) *SRoleManager { ResourceKeyword: "roles", }} + m.SetDomainId(cfg.GetDomainId()) + return m } diff --git a/pkg/multicloud/huawei/client/modules/mod_saml_provider.go b/pkg/multicloud/huawei/client/modules/mod_saml_provider.go index 435a4cd4cb..3542010ec8 100644 --- a/pkg/multicloud/huawei/client/modules/mod_saml_provider.go +++ b/pkg/multicloud/huawei/client/modules/mod_saml_provider.go @@ -15,16 +15,16 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SAMLProviderManager struct { SResourceManager } -func NewSAMLProviderManager(signer auth.Signer, debug bool) *SAMLProviderManager { - return &SAMLProviderManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), +func NewSAMLProviderManager(cfg manager.IManagerConfig) *SAMLProviderManager { + m := &SAMLProviderManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameIAM, Region: "", ProjectId: "", @@ -34,4 +34,6 @@ func NewSAMLProviderManager(signer auth.Signer, debug bool) *SAMLProviderManager ResourceKeyword: "identity_providers", }} + m.SetDomainId(cfg.GetDomainId()) + return m } diff --git a/pkg/multicloud/huawei/client/modules/mod_secgroup_rules.go b/pkg/multicloud/huawei/client/modules/mod_secgroup_rules.go index 29261415c4..200d46f186 100644 --- a/pkg/multicloud/huawei/client/modules/mod_secgroup_rules.go +++ b/pkg/multicloud/huawei/client/modules/mod_secgroup_rules.go @@ -15,19 +15,19 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SSecgroupRuleManager struct { SResourceManager } -func NewSecgroupRuleManager(regionId string, projectId string, signer auth.Signer, debug bool) *SSecgroupRuleManager { +func NewSecgroupRuleManager(cfg manager.IManagerConfig) *SSecgroupRuleManager { return &SSecgroupRuleManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameVPC, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1", Keyword: "security_group_rule", KeywordPlural: "security_group_rules", diff --git a/pkg/multicloud/huawei/client/modules/mod_secgroups.go b/pkg/multicloud/huawei/client/modules/mod_secgroups.go index 8165f4850b..fe1bfe97b8 100644 --- a/pkg/multicloud/huawei/client/modules/mod_secgroups.go +++ b/pkg/multicloud/huawei/client/modules/mod_secgroups.go @@ -15,19 +15,19 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SSecurityGroupManager struct { SResourceManager } -func NewSecurityGroupManager(regionId string, projectId string, signer auth.Signer, debug bool) *SSecurityGroupManager { +func NewSecurityGroupManager(cfg manager.IManagerConfig) *SSecurityGroupManager { return &SSecurityGroupManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameVPC, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1", Keyword: "security_group", KeywordPlural: "security_groups", @@ -36,12 +36,12 @@ func NewSecurityGroupManager(regionId string, projectId string, signer auth.Sign }} } -func NewNovaSecurityGroupManager(regionId string, projectId string, signer auth.Signer, debug bool) *SSecurityGroupManager { +func NewNovaSecurityGroupManager(cfg manager.IManagerConfig) *SSecurityGroupManager { return &SSecurityGroupManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameECS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v2.1", Keyword: "security_group", KeywordPlural: "security_groups", diff --git a/pkg/multicloud/huawei/client/modules/mod_servers.go b/pkg/multicloud/huawei/client/modules/mod_servers.go index bc92fda602..301db7aa60 100644 --- a/pkg/multicloud/huawei/client/modules/mod_servers.go +++ b/pkg/multicloud/huawei/client/modules/mod_servers.go @@ -21,7 +21,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -31,12 +31,12 @@ type SServerManager struct { // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212668.html // v.1.1 新增支持创建包年/包月的弹性云服务器。!!但是不支持查询等调用 https://support.huaweicloud.com/api-ecs/zh-cn_topic_0093055772.html -func NewServerManager(regionId, projectId string, signer auth.Signer, debug bool) *SServerManager { +func NewServerManager(cfg manager.IManagerConfig) *SServerManager { return &SServerManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameECS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1", Keyword: "server", KeywordPlural: "servers", @@ -94,12 +94,12 @@ func (self *SServerManager) Create(params jsonutils.JSONObject) (jsonutils.JSONO } // 不推荐使用这个manager -func NewNovaServerManager(regionId, projectId string, signer auth.Signer, debug bool) *SServerManager { +func NewNovaServerManager(cfg manager.IManagerConfig) *SServerManager { return &SServerManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameECS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v2.1", Keyword: "server", KeywordPlural: "servers", @@ -109,12 +109,12 @@ func NewNovaServerManager(regionId, projectId string, signer auth.Signer, debug } // 重装弹性云服务器操作系统(安装Cloud-init),请用这个manager -func NewServerV2Manager(regionId, projectId string, signer auth.Signer, debug bool) *SServerManager { +func NewServerV2Manager(cfg manager.IManagerConfig) *SServerManager { return &SServerManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameECS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v2", Keyword: "server", KeywordPlural: "servers", diff --git a/pkg/multicloud/huawei/client/modules/mod_sfs.go b/pkg/multicloud/huawei/client/modules/mod_sfs.go index de29920478..b11353e914 100644 --- a/pkg/multicloud/huawei/client/modules/mod_sfs.go +++ b/pkg/multicloud/huawei/client/modules/mod_sfs.go @@ -17,7 +17,7 @@ package modules import ( "yunion.io/x/jsonutils" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -25,12 +25,12 @@ type SfsTurboManager struct { SResourceManager } -func NewSfsTurboManager(regionId, projectId string, signer auth.Signer, debug bool) *SfsTurboManager { +func NewSfsTurboManager(cfg manager.IManagerConfig) *SfsTurboManager { return &SfsTurboManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameSFSTurbo, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1", Keyword: "", KeywordPlural: "shares", diff --git a/pkg/multicloud/huawei/client/modules/mod_snapshots.go b/pkg/multicloud/huawei/client/modules/mod_snapshots.go index b55d5985b7..cd5ee350a1 100644 --- a/pkg/multicloud/huawei/client/modules/mod_snapshots.go +++ b/pkg/multicloud/huawei/client/modules/mod_snapshots.go @@ -15,7 +15,7 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -23,12 +23,12 @@ type SSnapshotManager struct { SResourceManager } -func NewSnapshotManager(regionId, projectId string, signer auth.Signer, debug bool) *SSnapshotManager { +func NewSnapshotManager(cfg manager.IManagerConfig) *SSnapshotManager { return &SSnapshotManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameEVS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v2", Keyword: "snapshot", KeywordPlural: "snapshots", @@ -44,12 +44,12 @@ func (self *SSnapshotManager) List(querys map[string]string) (*responses.ListRes // https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408629.html // 回滚快照只能用这个manger。其他情况请不要使用 // 另外,香港-亚太还支持另外一个接口。https://support.huaweicloud.com/api-evs/zh-cn_topic_0142374138.html -func NewOsSnapshotManager(regionId string, projectId string, signer auth.Signer, debug bool) *SSnapshotManager { +func NewOsSnapshotManager(cfg manager.IManagerConfig) *SSnapshotManager { return &SSnapshotManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameEVS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v2", Keyword: "snapshot", KeywordPlural: "snapshots", diff --git a/pkg/multicloud/huawei/client/modules/mod_snat_rules.go b/pkg/multicloud/huawei/client/modules/mod_snat_rules.go index 701e7e56ab..57f9eaf5aa 100644 --- a/pkg/multicloud/huawei/client/modules/mod_snat_rules.go +++ b/pkg/multicloud/huawei/client/modules/mod_snat_rules.go @@ -15,18 +15,18 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SNatSRuleManager struct { SResourceManager } -func NewNatSManager(regionId string, projectId string, signer auth.Signer, debug bool) *SNatSRuleManager { +func NewNatSManager(cfg manager.IManagerConfig) *SNatSRuleManager { man := &SNatSRuleManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameNAT, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0", Keyword: "snat_rule", @@ -34,8 +34,8 @@ func NewNatSManager(regionId string, projectId string, signer auth.Signer, debug ResourceKeyword: "snat_rules", }} - if len(projectId) > 0 { - man.requestHook = &sProjectHook{projectId} + if len(cfg.GetProjectId()) > 0 { + man.requestHook = &sProjectHook{cfg.GetProjectId()} } return man } diff --git a/pkg/multicloud/huawei/client/modules/mod_subnets.go b/pkg/multicloud/huawei/client/modules/mod_subnets.go index cdab33359e..a69c6e309a 100644 --- a/pkg/multicloud/huawei/client/modules/mod_subnets.go +++ b/pkg/multicloud/huawei/client/modules/mod_subnets.go @@ -15,19 +15,19 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SSubnetManager struct { SResourceManager } -func NewSubnetManager(regionId string, projectId string, signer auth.Signer, debug bool) *SSubnetManager { +func NewSubnetManager(cfg manager.IManagerConfig) *SSubnetManager { return &SSubnetManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameVPC, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1", Keyword: "subnet", KeywordPlural: "subnets", diff --git a/pkg/multicloud/huawei/client/modules/mod_traces.go b/pkg/multicloud/huawei/client/modules/mod_traces.go index 0f6f3f70ed..b2a9cb288b 100644 --- a/pkg/multicloud/huawei/client/modules/mod_traces.go +++ b/pkg/multicloud/huawei/client/modules/mod_traces.go @@ -15,19 +15,19 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type STraceManager struct { SResourceManager } -func NewTraceManager(regionId string, projectId string, signer auth.Signer, debug bool) *STraceManager { +func NewTraceManager(cfg manager.IManagerConfig) *STraceManager { return &STraceManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameCTS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v2.0", Keyword: "trace", KeywordPlural: "traces", diff --git a/pkg/multicloud/huawei/client/modules/mod_users.go b/pkg/multicloud/huawei/client/modules/mod_users.go index d2757c4902..c1ff4467d5 100644 --- a/pkg/multicloud/huawei/client/modules/mod_users.go +++ b/pkg/multicloud/huawei/client/modules/mod_users.go @@ -19,7 +19,7 @@ import ( "yunion.io/x/jsonutils" - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/responses" ) @@ -27,9 +27,9 @@ type SUserManager struct { SResourceManager } -func NewUserManager(signer auth.Signer, debug bool) *SUserManager { - return &SUserManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), +func NewUserManager(cfg manager.IManagerConfig) *SUserManager { + user := &SUserManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameIAM, Region: "", ProjectId: "", @@ -39,6 +39,8 @@ func NewUserManager(signer auth.Signer, debug bool) *SUserManager { ResourceKeyword: "users", }} + user.SetDomainId(cfg.GetDomainId()) + return user } func (self *SUserManager) List(querys map[string]string) (*responses.ListResult, error) { diff --git a/pkg/multicloud/huawei/client/modules/mod_vpc_peerings.go b/pkg/multicloud/huawei/client/modules/mod_vpc_peerings.go index 393f23ac08..0e1d83bd38 100644 --- a/pkg/multicloud/huawei/client/modules/mod_vpc_peerings.go +++ b/pkg/multicloud/huawei/client/modules/mod_vpc_peerings.go @@ -15,18 +15,18 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SVpcPeeringManager struct { SResourceManager } -func NewVpcPeeringManager(regionId string, projectId string, signer auth.Signer, debug bool) *SVpcPeeringManager { +func NewVpcPeeringManager(cfg manager.IManagerConfig) *SVpcPeeringManager { return &SVpcPeeringManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameVPC, - Region: regionId, + Region: cfg.GetRegionId(), ProjectId: "", version: "v2.0", Keyword: "peering", diff --git a/pkg/multicloud/huawei/client/modules/mod_vpc_routes.go b/pkg/multicloud/huawei/client/modules/mod_vpc_routes.go index c67c168e90..1650a8e512 100644 --- a/pkg/multicloud/huawei/client/modules/mod_vpc_routes.go +++ b/pkg/multicloud/huawei/client/modules/mod_vpc_routes.go @@ -15,18 +15,18 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SVpcRouteManager struct { SResourceManager } -func NewVpcRouteManager(regionId string, projectId string, signer auth.Signer, debug bool) *SVpcRouteManager { +func NewVpcRouteManager(cfg manager.IManagerConfig) *SVpcRouteManager { return &SVpcRouteManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameVPC, - Region: regionId, + Region: cfg.GetRegionId(), // the url should not include the field "projectid" in huawei cloud api v2.0 ProjectId: "", version: "v2.0", diff --git a/pkg/multicloud/huawei/client/modules/mod_vpcs.go b/pkg/multicloud/huawei/client/modules/mod_vpcs.go index 90e69e881c..4f2dde4ee0 100644 --- a/pkg/multicloud/huawei/client/modules/mod_vpcs.go +++ b/pkg/multicloud/huawei/client/modules/mod_vpcs.go @@ -15,19 +15,19 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SVpcManager struct { SResourceManager } -func NewVpcManager(regionId string, projectId string, signer auth.Signer, debug bool) *SVpcManager { +func NewVpcManager(cfg manager.IManagerConfig) *SVpcManager { return &SVpcManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameVPC, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v1", Keyword: "vpc", KeywordPlural: "vpcs", diff --git a/pkg/multicloud/huawei/client/modules/mod_zones.go b/pkg/multicloud/huawei/client/modules/mod_zones.go index 1b32740d1e..656ccbd545 100644 --- a/pkg/multicloud/huawei/client/modules/mod_zones.go +++ b/pkg/multicloud/huawei/client/modules/mod_zones.go @@ -15,19 +15,19 @@ package modules import ( - "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/manager" ) type SZoneManager struct { SResourceManager } -func NewZoneManager(regionId string, projectId string, signer auth.Signer, debug bool) *SZoneManager { +func NewZoneManager(cfg manager.IManagerConfig) *SZoneManager { return &SZoneManager{SResourceManager: SResourceManager{ - SBaseManager: NewBaseManager(signer, debug), + SBaseManager: NewBaseManager(cfg), ServiceName: ServiceNameECS, - Region: regionId, - ProjectId: projectId, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), version: "v2", Keyword: "availabilityZoneInfo", KeywordPlural: "availabilityZoneInfo", diff --git a/pkg/multicloud/huawei/client/requests/requests.go b/pkg/multicloud/huawei/client/requests/requests.go index a02f143468..6d8c812f57 100644 --- a/pkg/multicloud/huawei/client/requests/requests.go +++ b/pkg/multicloud/huawei/client/requests/requests.go @@ -259,11 +259,11 @@ func defaultRequest() (request *SRequest) { return } -func NewResourceRequest(method, product, version, region, project, resourcePath string) *SRequest { +func NewResourceRequest(domain, method, product, version, region, project, resourcePath string) *SRequest { return &SRequest{ Scheme: "HTTPS", Method: method, - Domain: "myhuaweicloud.com", + Domain: domain, product: product, RegionId: region, version: version, diff --git a/pkg/multicloud/huawei/huawei.go b/pkg/multicloud/huawei/huawei.go index c037b7ba62..38f656f9fc 100644 --- a/pkg/multicloud/huawei/huawei.go +++ b/pkg/multicloud/huawei/huawei.go @@ -153,7 +153,7 @@ func (self *SHuaweiClient) initSigner() error { } func (self *SHuaweiClient) newRegionAPIClient(regionId string) (*client.Client, error) { - cli, err := client.NewClientWithAccessKey(regionId, self.ownerId, self.projectId, self.accessKey, self.accessSecret, self.debug) + cli, err := client.NewPublicCloudClientWithAccessKey(regionId, self.ownerId, self.projectId, self.accessKey, self.accessSecret, self.debug) if err != nil { return nil, err } @@ -165,7 +165,7 @@ func (self *SHuaweiClient) newRegionAPIClient(regionId string) (*client.Client, } func (self *SHuaweiClient) newGeneralAPIClient() (*client.Client, error) { - cli, err := client.NewClientWithAccessKey("", self.ownerId, "", self.accessKey, self.accessSecret, self.debug) + cli, err := client.NewPublicCloudClientWithAccessKey("", self.ownerId, "", self.accessKey, self.accessSecret, self.debug) if err != nil { return nil, err } diff --git a/pkg/multicloud/huaweistack/bucket.go b/pkg/multicloud/huaweistack/bucket.go new file mode 100644 index 0000000000..0158963ed3 --- /dev/null +++ b/pkg/multicloud/huaweistack/bucket.go @@ -0,0 +1,765 @@ +// 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 huaweistack + +import ( + "context" + "fmt" + "io" + "net/http" + "strconv" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + "yunion.io/x/s3cli" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/multicloud/huawei/obs" +) + +type SBucket struct { + multicloud.SBaseBucket + multicloud.HuaweiTags + + region *SRegion + + Name string + Location string + CreationDate time.Time +} + +func (b *SBucket) GetProjectId() string { + resp, err := b.region.HeadBucket(b.Name) + if err != nil { + return "" + } + epid, _ := resp.ResponseHeaders["epid"] + if len(epid) > 0 { + return epid[0] + } + return "" +} + +func (b *SBucket) GetGlobalId() string { + return b.Name +} + +func (b *SBucket) GetName() string { + return b.Name +} + +func (b *SBucket) GetLocation() string { + return b.Location +} + +func (b *SBucket) GetIRegion() cloudprovider.ICloudRegion { + return b.region +} + +func (b *SBucket) GetCreateAt() time.Time { + return b.CreationDate +} + +func (b *SBucket) GetStorageClass() string { + obscli, err := b.region.getOBSClient() + if err != nil { + log.Errorf("b.region.getOBSClient error %s", err) + return "" + } + output, err := obscli.GetBucketStoragePolicy(b.Name) + if err != nil { + log.Errorf("obscli.GetBucketStoragePolicy error %s", err) + } + return output.StorageClass +} + +func obsAcl2CannedAcl(acls []obs.Grant) cloudprovider.TBucketACLType { + switch { + case len(acls) == 1: + if acls[0].Grantee.URI == "" && acls[0].Permission == s3cli.PERMISSION_FULL_CONTROL { + return cloudprovider.ACLPrivate + } + case len(acls) == 2: + for _, g := range acls { + if g.Grantee.URI == s3cli.GRANTEE_GROUP_URI_AUTH_USERS && g.Permission == s3cli.PERMISSION_READ { + return cloudprovider.ACLAuthRead + } + if g.Grantee.URI == s3cli.GRANTEE_GROUP_URI_ALL_USERS && g.Permission == s3cli.PERMISSION_READ { + return cloudprovider.ACLPublicRead + } + } + case len(acls) == 3: + for _, g := range acls { + if g.Grantee.URI == s3cli.GRANTEE_GROUP_URI_ALL_USERS && g.Permission == s3cli.PERMISSION_WRITE { + return cloudprovider.ACLPublicReadWrite + } + } + } + return cloudprovider.ACLUnknown +} + +func (b *SBucket) GetAcl() cloudprovider.TBucketACLType { + acl := cloudprovider.ACLPrivate + obscli, err := b.region.getOBSClient() + if err != nil { + log.Errorf("b.region.getOBSClient error %s", err) + return acl + } + output, err := obscli.GetBucketAcl(b.Name) + if err != nil { + log.Errorf("obscli.GetBucketAcl error %s", err) + return acl + } + acl = obsAcl2CannedAcl(output.Grants) + return acl +} + +func (b *SBucket) SetAcl(acl cloudprovider.TBucketACLType) error { + obscli, err := b.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "b.region.getOBSClient") + } + input := &obs.SetBucketAclInput{} + input.Bucket = b.Name + input.ACL = obs.AclType(string(acl)) + _, err = obscli.SetBucketAcl(input) + if err != nil { + return errors.Wrap(err, "obscli.SetBucketAcl") + } + return nil +} + +func (b *SBucket) GetAccessUrls() []cloudprovider.SBucketAccessUrl { + return []cloudprovider.SBucketAccessUrl{ + { + Url: fmt.Sprintf("https://%s.%s", b.Name, b.region.getOBSEndpoint()), + Description: "bucket url", + Primary: true, + }, + { + Url: fmt.Sprintf("https://%s/%s", b.region.getOBSEndpoint(), b.Name), + Description: "obs url", + }, + } +} + +func (b *SBucket) GetStats() cloudprovider.SBucketStats { + stats := cloudprovider.SBucketStats{} + obscli, err := b.region.getOBSClient() + if err != nil { + log.Errorf("b.region.getOBSClient error %s", err) + stats.SizeBytes = -1 + stats.ObjectCount = -1 + return stats + } + output, err := obscli.GetBucketStorageInfo(b.Name) + if err != nil { + log.Errorf("obscli.GetBucketStorageInfo error %s", err) + stats.SizeBytes = -1 + stats.ObjectCount = -1 + return stats + } + stats.SizeBytes = output.Size + stats.ObjectCount = output.ObjectNumber + return stats +} + +func (b *SBucket) ListObjects(prefix string, marker string, delimiter string, maxCount int) (cloudprovider.SListObjectResult, error) { + result := cloudprovider.SListObjectResult{} + obscli, err := b.region.getOBSClient() + if err != nil { + return result, errors.Wrap(err, "GetOBSClient") + } + input := &obs.ListObjectsInput{} + input.Bucket = b.Name + if len(prefix) > 0 { + input.Prefix = prefix + } + if len(marker) > 0 { + input.Marker = marker + } + if len(delimiter) > 0 { + input.Delimiter = delimiter + } + if maxCount > 0 { + input.MaxKeys = maxCount + } + oResult, err := obscli.ListObjects(input) + if err != nil { + return result, errors.Wrap(err, "ListObjects") + } + result.Objects = make([]cloudprovider.ICloudObject, 0) + for _, object := range oResult.Contents { + obj := &SObject{ + bucket: b, + SBaseCloudObject: cloudprovider.SBaseCloudObject{ + StorageClass: string(object.StorageClass), + Key: object.Key, + SizeBytes: object.Size, + ETag: object.ETag, + LastModified: object.LastModified, + }, + } + result.Objects = append(result.Objects, obj) + } + if oResult.CommonPrefixes != nil { + result.CommonPrefixes = make([]cloudprovider.ICloudObject, 0) + for _, commonPrefix := range oResult.CommonPrefixes { + obj := &SObject{ + bucket: b, + SBaseCloudObject: cloudprovider.SBaseCloudObject{ + Key: commonPrefix, + }, + } + result.CommonPrefixes = append(result.CommonPrefixes, obj) + } + } + result.IsTruncated = oResult.IsTruncated + result.NextMarker = oResult.NextMarker + return result, nil +} + +func (b *SBucket) PutObject(ctx context.Context, key string, reader io.Reader, sizeBytes int64, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) error { + obscli, err := b.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "GetOBSClient") + } + input := &obs.PutObjectInput{} + input.Bucket = b.Name + input.Key = key + input.Body = reader + + if sizeBytes > 0 { + input.ContentLength = sizeBytes + } + if len(storageClassStr) > 0 { + input.StorageClass, err = str2StorageClass(storageClassStr) + if err != nil { + return err + } + } + if len(cannedAcl) == 0 { + cannedAcl = b.GetAcl() + } + input.ACL = obs.AclType(string(cannedAcl)) + if meta != nil { + val := meta.Get(cloudprovider.META_HEADER_CONTENT_TYPE) + if len(val) > 0 { + input.ContentType = val + } + val = meta.Get(cloudprovider.META_HEADER_CONTENT_MD5) + if len(val) > 0 { + input.ContentMD5 = val + } + extraMeta := make(map[string]string) + for k, v := range meta { + if utils.IsInStringArray(k, []string{ + cloudprovider.META_HEADER_CONTENT_TYPE, + cloudprovider.META_HEADER_CONTENT_MD5, + }) { + continue + } + if len(v[0]) > 0 { + extraMeta[k] = v[0] + } + } + input.Metadata = extraMeta + } + _, err = obscli.PutObject(input) + if err != nil { + return errors.Wrap(err, "PutObject") + } + return nil +} + +func (b *SBucket) NewMultipartUpload(ctx context.Context, key string, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) (string, error) { + obscli, err := b.region.getOBSClient() + if err != nil { + return "", errors.Wrap(err, "GetOBSClient") + } + + input := &obs.InitiateMultipartUploadInput{} + input.Bucket = b.Name + input.Key = key + if meta != nil { + val := meta.Get(cloudprovider.META_HEADER_CONTENT_TYPE) + if len(val) > 0 { + input.ContentType = val + } + extraMeta := make(map[string]string) + for k, v := range meta { + if utils.IsInStringArray(k, []string{ + cloudprovider.META_HEADER_CONTENT_TYPE, + }) { + continue + } + if len(v[0]) > 0 { + extraMeta[k] = v[0] + } + } + input.Metadata = extraMeta + } + if len(cannedAcl) == 0 { + cannedAcl = b.GetAcl() + } + input.ACL = obs.AclType(string(cannedAcl)) + if len(storageClassStr) > 0 { + input.StorageClass, err = str2StorageClass(storageClassStr) + if err != nil { + return "", errors.Wrap(err, "str2StorageClass") + } + } + output, err := obscli.InitiateMultipartUpload(input) + if err != nil { + return "", errors.Wrap(err, "InitiateMultipartUpload") + } + + return output.UploadId, nil +} + +func (b *SBucket) UploadPart(ctx context.Context, key string, uploadId string, partIndex int, part io.Reader, partSize int64, offset, totalSize int64) (string, error) { + obscli, err := b.region.getOBSClient() + if err != nil { + return "", errors.Wrap(err, "GetOBSClient") + } + + input := &obs.UploadPartInput{} + input.Bucket = b.Name + input.Key = key + input.UploadId = uploadId + input.PartNumber = partIndex + input.PartSize = partSize + input.Body = part + output, err := obscli.UploadPart(input) + if err != nil { + return "", errors.Wrap(err, "UploadPart") + } + + return output.ETag, nil +} + +func (b *SBucket) CompleteMultipartUpload(ctx context.Context, key string, uploadId string, partEtags []string) error { + obscli, err := b.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "GetOBSClient") + } + input := &obs.CompleteMultipartUploadInput{} + input.Bucket = b.Name + input.Key = key + input.UploadId = uploadId + parts := make([]obs.Part, len(partEtags)) + for i := range partEtags { + parts[i] = obs.Part{ + PartNumber: i + 1, + ETag: partEtags[i], + } + } + input.Parts = parts + _, err = obscli.CompleteMultipartUpload(input) + if err != nil { + return errors.Wrap(err, "CompleteMultipartUpload") + } + + return nil +} + +func (b *SBucket) AbortMultipartUpload(ctx context.Context, key string, uploadId string) error { + obscli, err := b.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "GetOBSClient") + } + + input := &obs.AbortMultipartUploadInput{} + input.Bucket = b.Name + input.Key = key + input.UploadId = uploadId + + _, err = obscli.AbortMultipartUpload(input) + if err != nil { + return errors.Wrap(err, "AbortMultipartUpload") + } + + return nil +} + +func (b *SBucket) DeleteObject(ctx context.Context, key string) error { + obscli, err := b.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "GetOBSClient") + } + input := &obs.DeleteObjectInput{} + input.Bucket = b.Name + input.Key = key + _, err = obscli.DeleteObject(input) + if err != nil { + return errors.Wrap(err, "DeleteObject") + } + return nil +} + +func (b *SBucket) GetTempUrl(method string, key string, expire time.Duration) (string, error) { + obscli, err := b.region.getOBSClient() + if err != nil { + return "", errors.Wrap(err, "GetOBSClient") + } + input := obs.CreateSignedUrlInput{} + input.Bucket = b.Name + input.Key = key + input.Expires = int(expire / time.Second) + switch method { + case "GET": + input.Method = obs.HttpMethodGet + case "PUT": + input.Method = obs.HttpMethodPut + case "DELETE": + input.Method = obs.HttpMethodDelete + default: + return "", errors.Error("unsupported method") + } + output, err := obscli.CreateSignedUrl(&input) + return output.SignedUrl, nil +} + +func (b *SBucket) LimitSupport() cloudprovider.SBucketStats { + return cloudprovider.SBucketStats{ + SizeBytes: 1, + ObjectCount: -1, + } +} + +func (b *SBucket) GetLimit() cloudprovider.SBucketStats { + stats := cloudprovider.SBucketStats{} + obscli, err := b.region.getOBSClient() + if err != nil { + log.Errorf("getOBSClient error %s", err) + return stats + } + output, err := obscli.GetBucketQuota(b.Name) + if err != nil { + return stats + } + stats.SizeBytes = output.Quota + return stats +} + +func (b *SBucket) SetLimit(limit cloudprovider.SBucketStats) error { + obscli, err := b.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "getOBSClient") + } + input := &obs.SetBucketQuotaInput{} + input.Bucket = b.Name + input.Quota = limit.SizeBytes + _, err = obscli.SetBucketQuota(input) + if err != nil { + return errors.Wrap(err, "SetBucketQuota") + } + return nil +} + +func (b *SBucket) CopyObject(ctx context.Context, destKey string, srcBucket, srcKey string, cannedAcl cloudprovider.TBucketACLType, storageClassStr string, meta http.Header) error { + obscli, err := b.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "GetOBSClient") + } + input := &obs.CopyObjectInput{} + input.Bucket = b.Name + input.Key = destKey + input.CopySourceBucket = srcBucket + input.CopySourceKey = srcKey + if len(storageClassStr) > 0 { + input.StorageClass, err = str2StorageClass(storageClassStr) + if err != nil { + return err + } + } + if len(cannedAcl) == 0 { + cannedAcl = b.GetAcl() + } + input.ACL = obs.AclType(string(cannedAcl)) + if meta != nil { + val := meta.Get(cloudprovider.META_HEADER_CONTENT_TYPE) + if len(val) > 0 { + input.ContentType = val + } + extraMeta := make(map[string]string) + for k, v := range meta { + if utils.IsInStringArray(k, []string{ + cloudprovider.META_HEADER_CONTENT_TYPE, + }) { + continue + } + if len(v[0]) > 0 { + extraMeta[k] = v[0] + } + } + input.Metadata = extraMeta + input.MetadataDirective = obs.ReplaceMetadata + } else { + input.MetadataDirective = obs.CopyMetadata + } + _, err = obscli.CopyObject(input) + if err != nil { + return errors.Wrap(err, "obscli.CopyObject") + } + return nil +} + +func (b *SBucket) GetObject(ctx context.Context, key string, rangeOpt *cloudprovider.SGetObjectRange) (io.ReadCloser, error) { + obscli, err := b.region.getOBSClient() + if err != nil { + return nil, errors.Wrap(err, "GetOBSClient") + } + input := &obs.GetObjectInput{} + input.Bucket = b.Name + input.Key = key + if rangeOpt != nil { + input.RangeStart = rangeOpt.Start + input.RangeEnd = rangeOpt.End + } + output, err := obscli.GetObject(input) + if err != nil { + return nil, errors.Wrap(err, "obscli.GetObject") + } + return output.Body, nil +} + +func (b *SBucket) CopyPart(ctx context.Context, key string, uploadId string, partIndex int, srcBucket string, srcKey string, srcOffset int64, srcLength int64) (string, error) { + obscli, err := b.region.getOBSClient() + if err != nil { + return "", errors.Wrap(err, "GetOBSClient") + } + input := &obs.CopyPartInput{} + input.Bucket = b.Name + input.Key = key + input.UploadId = uploadId + input.PartNumber = partIndex + input.CopySourceBucket = srcBucket + input.CopySourceKey = srcKey + input.CopySourceRangeStart = srcOffset + input.CopySourceRangeEnd = srcOffset + srcLength - 1 + output, err := obscli.CopyPart(input) + if err != nil { + return "", errors.Wrap(err, "CopyPart") + } + return output.ETag, nil +} + +func (b *SBucket) SetWebsite(websitConf cloudprovider.SBucketWebsiteConf) error { + obscli, err := b.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "GetOBSClient") + } + + obsWebConf := obs.SetBucketWebsiteConfigurationInput{} + obsWebConf.Bucket = b.Name + obsWebConf.BucketWebsiteConfiguration = obs.BucketWebsiteConfiguration{ + IndexDocument: obs.IndexDocument{Suffix: websitConf.Index}, + ErrorDocument: obs.ErrorDocument{Key: websitConf.ErrorDocument}, + } + _, err = obscli.SetBucketWebsiteConfiguration(&obsWebConf) + if err != nil { + return errors.Wrap(err, "obscli.SetBucketWebsiteConfiguration(&obsWebConf)") + } + return nil +} + +func (b *SBucket) GetWebsiteConf() (cloudprovider.SBucketWebsiteConf, error) { + result := cloudprovider.SBucketWebsiteConf{} + obscli, err := b.region.getOBSClient() + if err != nil { + return result, errors.Wrap(err, "GetOBSClient") + } + out, err := obscli.GetBucketWebsiteConfiguration(b.Name) + if out == nil { + return result, nil + } + result.Index = out.IndexDocument.Suffix + result.ErrorDocument = out.ErrorDocument.Key + result.Url = fmt.Sprintf("https://%s.obs-website.%s.myhuaweicloud.com", b.Name, b.region.GetId()) + return result, nil +} + +func (b *SBucket) DeleteWebSiteConf() error { + obscli, err := b.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "GetOBSClient") + } + _, err = obscli.DeleteBucketWebsiteConfiguration(b.Name) + if err != nil { + return errors.Wrapf(err, "obscli.DeleteBucketWebsiteConfiguration(%s)", b.Name) + } + return nil +} + +func (b *SBucket) SetCORS(rules []cloudprovider.SBucketCORSRule) error { + obscli, err := b.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "GetOBSClient") + } + opts := []obs.CorsRule{} + for i := range rules { + opts = append(opts, obs.CorsRule{ + AllowedOrigin: rules[i].AllowedOrigins, + AllowedMethod: rules[i].AllowedMethods, + AllowedHeader: rules[i].AllowedHeaders, + MaxAgeSeconds: rules[i].MaxAgeSeconds, + ExposeHeader: rules[i].ExposeHeaders, + }) + } + + input := obs.SetBucketCorsInput{} + input.Bucket = b.Name + input.BucketCors.CorsRules = opts + _, err = obscli.SetBucketCors(&input) + if err != nil { + return errors.Wrapf(err, "obscli.SetBucketCors(%s)", jsonutils.Marshal(input).String()) + } + return nil +} + +func (b *SBucket) GetCORSRules() ([]cloudprovider.SBucketCORSRule, error) { + obscli, err := b.region.getOBSClient() + if err != nil { + return nil, errors.Wrap(err, "GetOBSClient") + } + conf, err := obscli.GetBucketCors(b.Name) + if err != nil { + if !strings.Contains(err.Error(), "NoSuchCORSConfiguration") { + return nil, errors.Wrapf(err, "obscli.GetBucketCors(%s)", b.Name) + } + } + if conf == nil { + return nil, nil + } + result := []cloudprovider.SBucketCORSRule{} + for i := range conf.CorsRules { + result = append(result, cloudprovider.SBucketCORSRule{ + AllowedOrigins: conf.CorsRules[i].AllowedOrigin, + AllowedMethods: conf.CorsRules[i].AllowedMethod, + AllowedHeaders: conf.CorsRules[i].AllowedHeader, + MaxAgeSeconds: conf.CorsRules[i].MaxAgeSeconds, + ExposeHeaders: conf.CorsRules[i].ExposeHeader, + Id: strconv.Itoa(i), + }) + } + return result, nil +} + +func (b *SBucket) DeleteCORS() error { + obscli, err := b.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "GetOBSClient") + } + + _, err = obscli.DeleteBucketCors(b.Name) + if err != nil { + return errors.Wrapf(err, "obscli.DeleteBucketCors(%s)", b.Name) + } + return nil +} + +func (b *SBucket) GetTags() (map[string]string, error) { + obscli, err := b.region.getOBSClient() + if err != nil { + return nil, errors.Wrap(err, "GetOBSClient") + } + tagresult, err := obscli.GetBucketTagging(b.Name) + if err != nil { + if strings.Contains(err.Error(), "404") { + return nil, nil + } + return nil, errors.Wrapf(err, "osscli.GetBucketTagging(%s)", b.Name) + } + result := map[string]string{} + for i := range tagresult.Tags { + result[tagresult.Tags[i].Key] = tagresult.Tags[i].Value + } + return result, nil +} + +func (b *SBucket) SetTags(tags map[string]string, replace bool) error { + obscli, err := b.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "GetOBSClient") + } + + _, err = obscli.DeleteBucketTagging(b.Name) + if err != nil { + return errors.Wrapf(err, "DeleteBucketTagging") + } + + if len(tags) == 0 { + return nil + } + + input := obs.SetBucketTaggingInput{BucketTagging: obs.BucketTagging{}} + input.Bucket = b.Name + for k, v := range tags { + input.BucketTagging.Tags = append(input.BucketTagging.Tags, obs.Tag{Key: k, Value: v}) + } + + _, err = obscli.SetBucketTagging(&input) + if err != nil { + return errors.Wrapf(err, "obscli.SetBucketTagging(%s)", jsonutils.Marshal(input).String()) + } + return nil +} + +func (b *SBucket) ListMultipartUploads() ([]cloudprovider.SBucketMultipartUploads, error) { + obscli, err := b.region.getOBSClient() + if err != nil { + return nil, errors.Wrap(err, "GetOBSClient") + } + result := []cloudprovider.SBucketMultipartUploads{} + + input := obs.ListMultipartUploadsInput{Bucket: b.Name} + keyMarker := "" + uploadIDMarker := "" + for { + if len(keyMarker) > 0 { + input.KeyMarker = keyMarker + } + if len(uploadIDMarker) > 0 { + input.UploadIdMarker = uploadIDMarker + } + + output, err := obscli.ListMultipartUploads(&input) + if err != nil { + return nil, errors.Wrap(err, " coscli.Bucket.ListMultipartUploads(context.Background(), &input)") + } + for i := range output.Uploads { + temp := cloudprovider.SBucketMultipartUploads{ + ObjectName: output.Uploads[i].Key, + UploadID: output.Uploads[i].UploadId, + Initiator: output.Uploads[i].Initiator.DisplayName, + Initiated: output.Uploads[i].Initiated, + } + result = append(result, temp) + } + keyMarker = output.NextKeyMarker + uploadIDMarker = output.NextUploadIdMarker + if !output.IsTruncated { + break + } + } + + return result, nil +} diff --git a/pkg/multicloud/huaweistack/client/auth/credential.go b/pkg/multicloud/huaweistack/client/auth/credential.go new file mode 100644 index 0000000000..8dc818fb15 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/auth/credential.go @@ -0,0 +1,18 @@ +// 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 auth + +type Credential interface { +} diff --git a/pkg/multicloud/huaweistack/client/auth/credentials/access_key_credential.go b/pkg/multicloud/huaweistack/client/auth/credentials/access_key_credential.go new file mode 100644 index 0000000000..b156c01d3e --- /dev/null +++ b/pkg/multicloud/huaweistack/client/auth/credentials/access_key_credential.go @@ -0,0 +1,27 @@ +// 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 credentials + +type AccessKeyCredential struct { + AccessKeyId string + AccessKeySecret string +} + +func NewAccessKeyCredential(accessKeyId, accessKeySecret string) *AccessKeyCredential { + return &AccessKeyCredential{ + AccessKeyId: accessKeyId, + AccessKeySecret: accessKeySecret, + } +} diff --git a/pkg/multicloud/huaweistack/client/auth/credentials/doc.go b/pkg/multicloud/huaweistack/client/auth/credentials/doc.go new file mode 100644 index 0000000000..07ceaa786b --- /dev/null +++ b/pkg/multicloud/huaweistack/client/auth/credentials/doc.go @@ -0,0 +1,15 @@ +// 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 credentials // import "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/auth/credentials" diff --git a/pkg/multicloud/huaweistack/client/auth/doc.go b/pkg/multicloud/huaweistack/client/auth/doc.go new file mode 100644 index 0000000000..758342c1e1 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/auth/doc.go @@ -0,0 +1,15 @@ +// 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 auth // import "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/auth" diff --git a/pkg/multicloud/huaweistack/client/auth/signer.go b/pkg/multicloud/huaweistack/client/auth/signer.go new file mode 100644 index 0000000000..496fdefa45 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/auth/signer.go @@ -0,0 +1,214 @@ +// 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 auth + +import ( + "crypto/md5" + "crypto/sha256" + "encoding/hex" + "fmt" + "io/ioutil" + "sort" + "strings" + "time" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/auth/credentials" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/auth/signers" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/requests" +) + +type Signer interface { + GetName() string // 签名算法名称 + GetAccessKeyId() (accessKeyId string, err error) // 签名access key + GetSecretKey() (secretKey string, err error) // access secret + Sign(stringToSign, secretSuffix string) string // 生成签名结果 +} + +func NewSignerWithCredential(credential Credential) (signer Signer, err error) { + switch instance := credential.(type) { + case *credentials.AccessKeyCredential: + return signers.NewAccessKeySigner(instance), nil + default: + return nil, fmt.Errorf("unsupported credential error") + } +} + +// 对request进行签名 +func Sign(request requests.IRequest, signer Signer) (err error) { + return signRequest(request, signer) +} + +func signRequest(request requests.IRequest, signer Signer) error { + // https://support.huaweicloud.com/api-dis/dis_02_0508.html + // requestTime + reqTime := time.Now() + // 添加 必须的Headers + fillRequiredHeaders(request, reqTime) + // 计算CanonicalRequest + canonicalRequest := canonicalRequest(request) + // stringToSign + credentialScope := strings.Join([]string{ + formattedSignTime(reqTime, "Date"), + request.GetRegionId(), + request.GetProduct(), + "sdk_request", + }, "/") + stringToSign := strings.Join([]string{"SDK-HMAC-SHA256", + formattedSignTime(reqTime, "DateTime"), + credentialScope, + hashSha256([]byte(canonicalRequest)), + }, "\n") + // 计算SigningKey + secret, _ := signer.GetSecretKey() + signKey := getSigningKey(secret, formattedSignTime(reqTime, "Date"), + request.GetRegionId(), request.GetProduct()) + // 计算Signature + signature := signer.Sign(stringToSign, signKey) + accesskey, _ := signer.GetAccessKeyId() + addAuthorizationHeader(request, accesskey, credentialScope, signature) + return nil +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0132456728.html +// X-Project-Id 如果是专属云场景采用AK/SK 认证方式的接口请求或者多project场景采用AK/SK认证的接口请求则该字段必选。 +func fillRequiredHeaders(request requests.IRequest, t time.Time) { + request.AddHeaderParam("HOST", request.GetHost()) + request.AddHeaderParam("X-Sdk-Date", formattedSignTime(t, "DateTime")) + if len(request.GetProjectId()) > 0 { + request.AddHeaderParam("X-Project-Id", request.GetProjectId()) + } + return +} + +func buildRequestStringToSign(request requests.IRequest) string { + return "" +} + +func formattedSignTime(t time.Time, format string) string { + switch format { + case "Date": + return t.UTC().Format("20060102") + case "DateTime": + return t.UTC().Format("20060102T150405Z") + default: + return t.UTC().Format("20060102T150405Z") + } +} + +func contentSha256(request requests.IRequest) string { + method := strings.ToUpper(request.GetMethod()) + content := []byte{} + body := request.GetBodyReader() + content, _ = ioutil.ReadAll(body) + if method == "POST" { + if len(content) == 0 { + // other http method use query as content + content = []byte(request.BuildQueries()) + } + } + + return hashSha256(content) +} + +func hashSha256(msg []byte) string { + sh256 := sha256.New() + sh256.Write(msg) + + return hex.EncodeToString(sh256.Sum(nil)) +} + +func canonicalRequest(request requests.IRequest) string { + sha256 := contentSha256(request) + uri := request.GetURI() + if !strings.HasSuffix(uri, "/") { + uri = uri + "/" + } + + return strings.Join([]string{ + request.GetMethod(), + uri, + canonicalQueryString(request), + canonicalHeaders(request), + canonicalHeaderNames(request), + sha256, + }, "\n") +} + +func sortedHeaderNames(request requests.IRequest) []string { + headers := request.GetHeaders() + keys := make([]string, 0) + for k := range headers { + keys = append(keys, k) + } + + sort.Slice(keys, func(i, j int) bool { + return strings.ToLower(keys[i]) < strings.ToLower(keys[j]) + }) + + return keys +} + +func canonicalQueryString(request requests.IRequest) string { + if strings.ToUpper(request.GetMethod()) != "POST" { + return request.BuildQueries() + } + + return "" +} + +func canonicalHeaders(request requests.IRequest) string { + keys := sortedHeaderNames(request) + headers := request.GetHeaders() + ret := []string{} + for _, k := range keys { + ret = append(ret, strings.ToLower(k)+":"+strings.TrimSpace(headers[k])) + } + + return strings.Join(ret, "\n") + "\n" +} + +func canonicalHeaderNames(request requests.IRequest) string { + keys := sortedHeaderNames(request) + ret := strings.Join(keys, ";") + return strings.ToLower(ret) +} + +var SigningKeyCache = map[string][]byte{} + +func getSigningKey(secretKey, date, regionId, service string) string { + joinedKey := strings.Join([]string{secretKey, date, regionId, service}, "") + cacheKey := fmt.Sprintf("%x", md5.Sum([]byte(joinedKey))) + if v, ok := SigningKeyCache[cacheKey]; ok { + return string(v) + } + + ret := []byte("SDK" + secretKey) + for _, k := range []string{date, regionId, service, "sdk_request"} { + ret = signers.HmacSha256(k, ret) + } + + SigningKeyCache[cacheKey] = ret + return string(ret) +} + +func addAuthorizationHeader(request requests.IRequest, accessKey, credentialScope, signature string) { + auth := "SDK-HMAC-SHA256" + " " + strings.Join([]string{ + "Credential=" + accessKey + "/" + credentialScope, + "SignedHeaders=" + canonicalHeaderNames(request), + "Signature=" + signature, + }, ", ") + + request.AddHeaderParam("Authorization", auth) +} diff --git a/pkg/multicloud/huaweistack/client/auth/signers/doc.go b/pkg/multicloud/huaweistack/client/auth/signers/doc.go new file mode 100644 index 0000000000..de32305c3f --- /dev/null +++ b/pkg/multicloud/huaweistack/client/auth/signers/doc.go @@ -0,0 +1,15 @@ +// 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 signers // import "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/auth/signers" diff --git a/pkg/multicloud/huaweistack/client/auth/signers/signer_access_key.go b/pkg/multicloud/huaweistack/client/auth/signers/signer_access_key.go new file mode 100644 index 0000000000..ec0372491a --- /dev/null +++ b/pkg/multicloud/huaweistack/client/auth/signers/signer_access_key.go @@ -0,0 +1,55 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package signers + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/auth/credentials" +) + +type AccessKeySigner struct { + credential *credentials.AccessKeyCredential +} + +func (signer *AccessKeySigner) GetName() string { + return "HmacSha256" +} + +func (signer *AccessKeySigner) GetAccessKeyId() (accessKeyId string, err error) { + return signer.credential.AccessKeyId, nil +} + +func (signer *AccessKeySigner) GetSecretKey() (secretKey string, err error) { + return signer.credential.AccessKeySecret, nil +} + +func (signer *AccessKeySigner) Sign(stringToSign, secretSuffix string) string { + return hex.EncodeToString(HmacSha256(stringToSign, []byte(secretSuffix))) +} + +func HmacSha256(data string, key []byte) []byte { + mac := hmac.New(sha256.New, key) + mac.Write([]byte(data)) + return mac.Sum(nil) +} + +func NewAccessKeySigner(credential *credentials.AccessKeyCredential) *AccessKeySigner { + return &AccessKeySigner{ + credential: credential, + } +} diff --git a/pkg/multicloud/huaweistack/client/client.go b/pkg/multicloud/huaweistack/client/client.go new file mode 100644 index 0000000000..41444e49fe --- /dev/null +++ b/pkg/multicloud/huaweistack/client/client.go @@ -0,0 +1,282 @@ +// 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 client + +import ( + "net/http" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/auth/credentials" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/modules" +) + +type Client struct { + cfg *SClientConfig + // 标记初始化状态 + init bool + + Balances *modules.SBalanceManager + Bandwidths *modules.SBandwidthManager + Credentials *modules.SCredentialManager + Disks *modules.SDiskManager + Domains *modules.SDomainManager + Eips *modules.SEipManager + Endpoints *modules.SEndpointManager + Services *modules.SServiceManager + Elasticcache *modules.SElasticcacheManager + DcsAvailableZone *modules.SDcsAvailableZoneManager + Flavors *modules.SFlavorManager + Images *modules.SImageManager + OpenStackImages *modules.SImageManager + Interface *modules.SInterfaceManager + Jobs *modules.SJobManager + Keypairs *modules.SKeypairManager + Elb *modules.SLoadbalancerManager + ElbBackend *modules.SElbBackendManager + ElbBackendGroup *modules.SElbBackendGroupManager + ElbListeners *modules.SElbListenersManager + ElbCertificates *modules.SElbCertificatesManager + ElbHealthCheck *modules.SElbHealthCheckManager + ElbL7policies *modules.SElbL7policiesManager + ElbPolicies *modules.SElbPoliciesManager + ElbWhitelist *modules.SElbWhitelistManager + Orders *modules.SOrderManager + Port *modules.SPortManager + Projects *modules.SProjectManager + Regions *modules.SRegionManager + SecurityGroupRules *modules.SSecgroupRuleManager + SecurityGroups *modules.SSecurityGroupManager + NovaSecurityGroups *modules.SSecurityGroupManager + Servers *modules.SServerManager + ServersV2 *modules.SServerManager + NovaServers *modules.SServerManager + Snapshots *modules.SSnapshotManager + OsSnapshots *modules.SSnapshotManager + Subnets *modules.SSubnetManager + Users *modules.SUserManager + Vpcs *modules.SVpcManager + Zones *modules.SZoneManager + VpcRoutes *modules.SVpcRouteManager + SNatRules *modules.SNatSRuleManager + DNatRules *modules.SNatDRuleManager + NatGateways *modules.SNatGatewayManager + VpcPeerings *modules.SVpcPeeringManager + DBInstance *modules.SDBInstanceManager + DBInstanceBackup *modules.SDBInstanceBackupManager + DBInstanceFlavor *modules.SDBInstanceFlavorManager + DBInstanceJob *modules.SDBInstanceJobManager + Traces *modules.STraceManager + CloudEye *modules.SCloudEyeManager + Quotas *modules.SQuotaManager + EnterpriseProjects *modules.SEnterpriseProjectManager + Roles *modules.SRoleManager + Groups *modules.SGroupManager + SAMLProviders *modules.SAMLProviderManager + SAMLProviderMappings *modules.SAMLProviderMappingManager + SfsTurbos *modules.SfsTurboManager +} + +type SClientConfig struct { + signer auth.Signer + endpoints *cloudprovider.SHuaweiCloudStackEndpoints + regionId string + domainId string + projectId string + + debug bool +} + +func (self *SClientConfig) GetSigner() auth.Signer { + return self.signer +} + +func (self *SClientConfig) GetEndpoints() *cloudprovider.SHuaweiCloudStackEndpoints { + return self.endpoints +} + +func (self *SClientConfig) GetRegionId() string { + return self.regionId +} + +func (self *SClientConfig) GetDomainId() string { + return self.domainId +} + +func (self *SClientConfig) GetProjectId() string { + return self.projectId +} + +func (self *SClientConfig) GetDebug() bool { + return self.debug +} + +func (self *Client) SetHttpClient(httpClient *http.Client) { + self.Credentials.SetHttpClient(httpClient) + self.Servers.SetHttpClient(httpClient) + self.ServersV2.SetHttpClient(httpClient) + self.NovaServers.SetHttpClient(httpClient) + self.Snapshots.SetHttpClient(httpClient) + self.OsSnapshots.SetHttpClient(httpClient) + self.Images.SetHttpClient(httpClient) + self.OpenStackImages.SetHttpClient(httpClient) + self.Projects.SetHttpClient(httpClient) + self.Regions.SetHttpClient(httpClient) + self.Zones.SetHttpClient(httpClient) + self.Vpcs.SetHttpClient(httpClient) + self.Eips.SetHttpClient(httpClient) + self.Elasticcache.SetHttpClient(httpClient) + self.DcsAvailableZone.SetHttpClient(httpClient) + self.Disks.SetHttpClient(httpClient) + self.Domains.SetHttpClient(httpClient) + self.Keypairs.SetHttpClient(httpClient) + self.Elb.SetHttpClient(httpClient) + self.ElbBackend.SetHttpClient(httpClient) + self.ElbBackendGroup.SetHttpClient(httpClient) + self.ElbListeners.SetHttpClient(httpClient) + self.ElbCertificates.SetHttpClient(httpClient) + self.ElbHealthCheck.SetHttpClient(httpClient) + self.ElbL7policies.SetHttpClient(httpClient) + self.ElbPolicies.SetHttpClient(httpClient) + self.ElbWhitelist.SetHttpClient(httpClient) + self.Orders.SetHttpClient(httpClient) + self.SecurityGroupRules.SetHttpClient(httpClient) + self.SecurityGroups.SetHttpClient(httpClient) + self.NovaSecurityGroups.SetHttpClient(httpClient) + self.Subnets.SetHttpClient(httpClient) + self.Users.SetHttpClient(httpClient) + self.Interface.SetHttpClient(httpClient) + self.Jobs.SetHttpClient(httpClient) + self.Balances.SetHttpClient(httpClient) + self.Bandwidths.SetHttpClient(httpClient) + self.Port.SetHttpClient(httpClient) + self.Flavors.SetHttpClient(httpClient) + self.VpcRoutes.SetHttpClient(httpClient) + self.SNatRules.SetHttpClient(httpClient) + self.DNatRules.SetHttpClient(httpClient) + self.NatGateways.SetHttpClient(httpClient) + self.DBInstance.SetHttpClient(httpClient) + self.DBInstanceBackup.SetHttpClient(httpClient) + self.DBInstanceFlavor.SetHttpClient(httpClient) + self.DBInstanceJob.SetHttpClient(httpClient) + self.Traces.SetHttpClient(httpClient) + self.CloudEye.SetHttpClient(httpClient) + self.EnterpriseProjects.SetHttpClient(httpClient) + self.Roles.SetHttpClient(httpClient) + self.Groups.SetHttpClient(httpClient) + self.SAMLProviders.SetHttpClient(httpClient) + self.SAMLProviderMappings.SetHttpClient(httpClient) + self.SfsTurbos.SetHttpClient(httpClient) + self.Endpoints.SetHttpClient(httpClient) + self.Services.SetHttpClient(httpClient) +} + +func (self *Client) InitWithAccessKey(regionId, domainId, projectId, accessKey, secretKey string, debug bool, endpoints *cloudprovider.SHuaweiCloudStackEndpoints) error { + // accessKey signer + credential := &credentials.AccessKeyCredential{ + AccessKeyId: accessKey, + AccessKeySecret: secretKey, + } + + // 从signer中初始化 + signer, err := auth.NewSignerWithCredential(credential) + if err != nil { + return err + } + self.cfg = &SClientConfig{ + signer: signer, + endpoints: endpoints, + regionId: regionId, + domainId: domainId, + projectId: projectId, + debug: debug, + } + + // 初始化 resource manager + self.initManagers() + return err +} + +func (self *Client) initManagers() { + if !self.init { + self.Servers = modules.NewServerManager(self.cfg) + self.ServersV2 = modules.NewServerV2Manager(self.cfg) + self.NovaServers = modules.NewNovaServerManager(self.cfg) + self.Snapshots = modules.NewSnapshotManager(self.cfg) + self.OsSnapshots = modules.NewOsSnapshotManager(self.cfg) + self.Images = modules.NewImageManager(self.cfg) + self.OpenStackImages = modules.NewOpenstackImageManager(self.cfg) + self.Projects = modules.NewProjectManager(self.cfg) + self.Regions = modules.NewRegionManager(self.cfg) + self.Zones = modules.NewZoneManager(self.cfg) + self.Vpcs = modules.NewVpcManager(self.cfg) + self.Eips = modules.NewEipManager(self.cfg) + self.Elasticcache = modules.NewElasticcacheManager(self.cfg) + self.DcsAvailableZone = modules.NewDcsAvailableZoneManager(self.cfg) + self.Disks = modules.NewDiskManager(self.cfg) + self.Domains = modules.NewDomainManager(self.cfg) + self.Keypairs = modules.NewKeypairManager(self.cfg) + self.Elb = modules.NewLoadbalancerManager(self.cfg) + self.ElbBackend = modules.NewElbBackendManager(self.cfg) + self.ElbBackendGroup = modules.NewElbBackendGroupManager(self.cfg) + self.ElbListeners = modules.NewElbListenersManager(self.cfg) + self.ElbCertificates = modules.NewElbCertificatesManager(self.cfg) + self.ElbHealthCheck = modules.NewElbHealthCheckManager(self.cfg) + self.ElbL7policies = modules.NewElbL7policiesManager(self.cfg) + self.ElbPolicies = modules.NewElbPoliciesManager(self.cfg) + self.ElbWhitelist = modules.NewElbWhitelistManager(self.cfg) + self.Orders = modules.NewOrderManager(self.cfg) + self.SecurityGroupRules = modules.NewSecgroupRuleManager(self.cfg) + self.SecurityGroups = modules.NewSecurityGroupManager(self.cfg) + self.NovaSecurityGroups = modules.NewNovaSecurityGroupManager(self.cfg) + self.Subnets = modules.NewSubnetManager(self.cfg) + self.Users = modules.NewUserManager(self.cfg) + self.Interface = modules.NewInterfaceManager(self.cfg) + self.Jobs = modules.NewJobManager(self.cfg) + self.Balances = modules.NewBalanceManager(self.cfg) + self.Bandwidths = modules.NewBandwidthManager(self.cfg) + self.Credentials = modules.NewCredentialManager(self.cfg) + self.Port = modules.NewPortManager(self.cfg) + self.Flavors = modules.NewFlavorManager(self.cfg) + self.VpcRoutes = modules.NewVpcRouteManager(self.cfg) + self.SNatRules = modules.NewNatSManager(self.cfg) + self.DNatRules = modules.NewNatDManager(self.cfg) + self.NatGateways = modules.NewNatGatewayManager(self.cfg) + self.VpcPeerings = modules.NewVpcPeeringManager(self.cfg) + self.DBInstance = modules.NewDBInstanceManager(self.cfg) + self.DBInstanceBackup = modules.NewDBInstanceBackupManager(self.cfg) + self.DBInstanceFlavor = modules.NewDBInstanceFlavorManager(self.cfg) + self.DBInstanceJob = modules.NewDBInstanceJobManager(self.cfg) + self.Traces = modules.NewTraceManager(self.cfg) + self.CloudEye = modules.NewCloudEyeManager(self.cfg) + self.Quotas = modules.NewQuotaManager(self.cfg) + self.EnterpriseProjects = modules.NewEnterpriseProjectManager(self.cfg) + self.Roles = modules.NewRoleManager(self.cfg) + self.Groups = modules.NewGroupManager(self.cfg) + self.SAMLProviders = modules.NewSAMLProviderManager(self.cfg) + self.SAMLProviderMappings = modules.NewSAMLProviderMappingManager(self.cfg) + self.SfsTurbos = modules.NewSfsTurboManager(self.cfg) + self.Endpoints = modules.NewEndpointManager(self.cfg) + self.Services = modules.NewServiceManager(self.cfg) + } + + self.init = true +} + +func NewClientWithAccessKey(regionId, domainId, projectId, accessKey, secretKey string, debug bool, endpoints *cloudprovider.SHuaweiCloudStackEndpoints) (*Client, error) { + c := &Client{} + err := c.InitWithAccessKey(regionId, domainId, projectId, accessKey, secretKey, debug, endpoints) + return c, err +} diff --git a/pkg/multicloud/huaweistack/client/doc.go b/pkg/multicloud/huaweistack/client/doc.go new file mode 100644 index 0000000000..5c1eec10d3 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/doc.go @@ -0,0 +1,15 @@ +// 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 client // import "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client" diff --git a/pkg/multicloud/huaweistack/client/manager/doc.go b/pkg/multicloud/huaweistack/client/manager/doc.go new file mode 100644 index 0000000000..ffbd763d3a --- /dev/null +++ b/pkg/multicloud/huaweistack/client/manager/doc.go @@ -0,0 +1,15 @@ +// 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 manager // import "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" diff --git a/pkg/multicloud/huaweistack/client/manager/manager.go b/pkg/multicloud/huaweistack/client/manager/manager.go new file mode 100644 index 0000000000..97a3e222f2 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/manager/manager.go @@ -0,0 +1,82 @@ +// 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 manager + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +type IManagerContext interface { + GetPath() string +} + +type IBaseManager interface { + Version() string + KeyString() string + ServiceType() string + GetColumns() []string +} + +type IManager interface { + IBaseManager + // 获取资源列表 GET /cloudservers/? + List(queries map[string]string) (*responses.ListResult, error) + // 根据上文获取资源列表 GET /cloudservers//nics? + ListInContext(ctx IManagerContext, queries map[string]string) (*responses.ListResult, error) + ListInContextWithSpec(ctx IManagerContext, spec string, queries map[string]string, responseKey string) (*responses.ListResult, error) + + // 查询单个资源 GET /cloudservers/? + Get(id string, queries map[string]string) (jsonutils.JSONObject, error) + // 根据上文获取资源查询单个资源 GET /cloudservers//nics/? + GetInContext(ctx IManagerContext, id string, queries map[string]string) (jsonutils.JSONObject, error) + + // 创建单个资源 POST /cloudservers + Create(params jsonutils.JSONObject) (jsonutils.JSONObject, error) + // 根据上文创建单个资源 POST /cloudservers//nics/ + CreateInContext(ctx IManagerContext, params jsonutils.JSONObject) (jsonutils.JSONObject, error) + // 异步任务创建 POST /cloudservers. 返回异步任务 job_id。 todo:// 后续考虑返回一个task对象 + AsyncCreate(params jsonutils.JSONObject) (string, error) + + // 更新单个资源 PUT /cloudservers/ + Update(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) + // 根据上文更新单个资源 PUT /cloudservers//nics/ + UpdateInContext(ctx IManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) + // 根据上文和spec更新单个资源 PUT /v2.1/{project_id}/servers/{server_id}/os-reset-password + UpdateInContextWithSpec(ctx IManagerContext, id string, spec string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) + + // 删除单个资源 DELETE /cloudservers/ + Delete(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) + // 根据上文删除单个资源 DELETE /cloudservers//nics/ + DeleteInContext(ctx IManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) + // 根据上文和spec删除单个资源 + DeleteInContextWithSpec(ctx IManagerContext, id string, spec string, queries map[string]string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) + // 批量执行操作 POST /cloudservers/ + // BatchPerformAction(action string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) + // 执行操作 POST /cloudservers// + PerformAction(action string, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) +} + +type IManagerConfig interface { + GetSigner() auth.Signer + GetEndpoints() *cloudprovider.SHuaweiCloudStackEndpoints + GetRegionId() string + GetDomainId() string + GetProjectId() string + GetDebug() bool +} diff --git a/pkg/multicloud/huaweistack/client/modules/doc.go b/pkg/multicloud/huaweistack/client/modules/doc.go new file mode 100644 index 0000000000..1cdbd7921d --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/doc.go @@ -0,0 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules // import "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/modules" diff --git a/pkg/multicloud/huaweistack/client/modules/manager_base.go b/pkg/multicloud/huaweistack/client/modules/manager_base.go new file mode 100644 index 0000000000..57622b279e --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/manager_base.go @@ -0,0 +1,266 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "context" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/requests" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" + "yunion.io/x/onecloud/pkg/util/httputils" +) + +type IRequestHook interface { + Process(r requests.IRequest) +} + +type SBaseManager struct { + cfg manager.IManagerConfig + httpClient *http.Client + requestHook IRequestHook // 用于对request做特殊处理。非必要请不要使用!!!。目前只有port接口用到。 + + columns []string + debug bool +} + +type sThrottlingThreshold struct { + locked bool + lockTime time.Time +} + +func (t *sThrottlingThreshold) CheckingLock() { + if !t.locked { + return + } + + for { + if t.lockTime.Sub(time.Now()).Seconds() < 0 { + return + } + log.Debugf("throttling threshold has been reached. release at %s", t.lockTime) + time.Sleep(5 * time.Second) + } +} + +func (t *sThrottlingThreshold) Lock() { + // 锁定至少15秒 + t.locked = true + t.lockTime = time.Now().Add(15 * time.Second) +} + +var ThrottlingLock = sThrottlingThreshold{locked: false, lockTime: time.Time{}} + +func NewBaseManager2(cfg manager.IManagerConfig, requesthk IRequestHook) SBaseManager { + return SBaseManager{ + cfg: cfg, + httpClient: httputils.GetDefaultClient(), + debug: cfg.GetDebug(), + requestHook: requesthk, + } +} + +func NewBaseManager(cfg manager.IManagerConfig) SBaseManager { + return NewBaseManager2(cfg, nil) +} + +func (self *SBaseManager) GetColumns() []string { + return self.columns +} + +func (self *SBaseManager) SetHttpClient(httpClient *http.Client) { + self.httpClient = httpClient +} + +func (self *SBaseManager) _list(request requests.IRequest, responseKey string) (*responses.ListResult, error) { + _, body, err := self.jsonRequest(request) + if err != nil { + return nil, err + } + if body == nil { + log.Warningf("empty response") + return &responses.ListResult{}, nil + } + + rets, err := body.GetArray(responseKey) + if err != nil { + return nil, err + } + total, _ := body.Int("count") + // if err != nil { + // total = int64(len(rets)) + //} + + //if total == 0 { + // total = int64(len(rets)) + //} + + limit := 0 + if v, exists := request.GetQueryParams()["limit"]; exists { + limit, _ = strconv.Atoi(v) + } + + offset := 0 + if v, exists := request.GetQueryParams()["offset"]; exists { + offset, _ = strconv.Atoi(v) + } + + return &responses.ListResult{ + Data: rets, + Total: int(total), + Limit: limit, + Offset: offset, + }, nil +} + +func (self *SBaseManager) _do(request requests.IRequest, responseKey string) (jsonutils.JSONObject, error) { + _, resp, e := self.jsonRequest(request) + if e != nil { + return nil, e + } + + if resp == nil { // no reslt + return jsonutils.NewDict(), nil + } + + if len(responseKey) == 0 { + return resp, nil + } + + ret, e := resp.Get(responseKey) + if e != nil { + return nil, e + } + + return ret, nil +} + +func (self *SBaseManager) _get(request requests.IRequest, responseKey string) (jsonutils.JSONObject, error) { + return self._do(request, responseKey) +} + +type HuaweiClientError struct { + Code int + Errorcode []string + err error + Details string + ErrorCode string +} + +func (ce *HuaweiClientError) Error() string { + return jsonutils.Marshal(ce).String() +} + +func (ce *HuaweiClientError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error { + if body != nil { + body.Unmarshal(ce) + } + if ce.Code == 0 { + ce.Code = statusCode + } + if len(ce.Details) == 0 && body != nil { + ce.Details = body.String() + } + return ce +} + +func (self *SBaseManager) jsonRequest(request requests.IRequest) (http.Header, jsonutils.JSONObject, error) { + ThrottlingLock.CheckingLock() + ctx := context.Background() + // hook request + if self.requestHook != nil { + self.requestHook.Process(request) + } + // 拼接、编译、签名 requests here。 + err := self.buildRequestWithSigner(request, self.cfg.GetSigner()) + if err != nil { + return nil, nil, err + } + header := http.Header{} + for k, v := range request.GetHeaders() { + header.Set(k, v) + } + + var jsonBody jsonutils.JSONObject + content := request.GetContent() + if len(content) > 0 { + jsonBody, err = jsonutils.Parse(content) + if err != nil { + return nil, nil, fmt.Errorf("not a json body") + } + } + + client := httputils.NewJsonClient(self.httpClient) + req := httputils.NewJsonRequest(httputils.THttpMethod(request.GetMethod()), request.BuildUrl(), jsonBody) + req.SetHeader(header) + resp := &HuaweiClientError{} + const MAX_RETRY = 3 + retry := MAX_RETRY + for { + h, b, e := client.Send(ctx, req, resp, self.debug) + if e == nil { + return h, b, nil + } + + log.Errorf("[%s] %s body: %v error: %v", req.GetHttpMethod(), req.GetUrl(), jsonBody, e) + + switch err := e.(type) { + case *HuaweiClientError: + if err.ErrorCode == "APIGW.0301" { + return h, b, errors.Wrapf(httperrors.ErrInvalidAccessKey, e.Error()) + } else if err.Code == 499 && retry > 0 && request.GetMethod() == "GET" { + retry -= 1 + time.Sleep(3 * time.Second * time.Duration(MAX_RETRY-retry)) + } else if (err.Code == 404 || strings.Contains(err.Details, "could not be found") || strings.Contains(err.Details, "does not exist")) && request.GetMethod() != "POST" { + return h, b, errors.Wrap(cloudprovider.ErrNotFound, err.Error()) + } else if err.Code == 429 && retry > 0 { + // 当前请求过多。 + ThrottlingLock.Lock() + retry -= 1 + time.Sleep(15 * time.Second) + } else { + return h, b, e + } + default: + return h, b, e + } + } +} + +func (self *SBaseManager) rawRequest(request requests.IRequest) (*http.Response, error) { + ctx := context.Background() + // 拼接、编译requests here。 + header := http.Header{} + for k, v := range request.GetHeaders() { + header.Set(k, v) + } + return httputils.Request(self.httpClient, ctx, httputils.THttpMethod(request.GetMethod()), request.BuildUrl(), header, request.GetBodyReader(), self.debug) +} + +func (self *SBaseManager) buildRequestWithSigner(request requests.IRequest, signer auth.Signer) error { + return auth.Sign(request, signer) +} diff --git a/pkg/multicloud/huaweistack/client/modules/manager_resource.go b/pkg/multicloud/huaweistack/client/modules/manager_resource.go new file mode 100644 index 0000000000..fd2d990947 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/manager_resource.go @@ -0,0 +1,300 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "fmt" + "net/url" + "strings" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/requests" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +type ServiceNameType string + +const HuaWeiDefaultDomain = "myhuaweicloud.com" + +const ( + ServiceNameECS ServiceNameType = "ecs" // 弹性云服务 + ServiceNameCCE ServiceNameType = "cce" // 云容器服务 + ServiceNameAS ServiceNameType = "as" // 弹性伸缩服务 + ServiceNameIAM ServiceNameType = "iam-pub" // 统一身份认证服务 + ServiceNameIMS ServiceNameType = "ims" // 镜像服务 + ServiceNameCSBS ServiceNameType = "csbs" // 云服务器备份服务 + ServiceNameCCI ServiceNameType = "cci" // 云容器实例 CCI + ServiceNameBMS ServiceNameType = "bms" // 裸金属服务器 + ServiceNameEVS ServiceNameType = "evs" // 云硬盘 EVS + ServiceNameVBS ServiceNameType = "vbs" // 云硬盘备份 VBS + ServiceNameOBS ServiceNameType = "obs" // 对象存储服务 OBS + ServiceNameVPC ServiceNameType = "vpc" // 虚拟私有云 VPC + ServiceNameELB ServiceNameType = "elb" // 弹性负载均衡 ELB + ServiceNameBSS ServiceNameType = "bss" // 合作伙伴运营能力 + ServiceNameNAT ServiceNameType = "nat" // Nat网关 NAT + ServiceNameDCS ServiceNameType = "dcs" // 分布式缓存服务 + ServiceNameRDS ServiceNameType = "rds" // 关系型数据库 RDS + ServiceNameCTS ServiceNameType = "cts" // 云审计服务 + ServiceNameCES ServiceNameType = "ces" // 监控服务 CloudEye + ServiceNameEPS ServiceNameType = "eps" // 企业项目 + + ServiceNameSFSTurbo ServiceNameType = "sfs-turbo" // 文件系统 +) + +type SManagerContext struct { + InstanceManager manager.IManager + InstanceId string +} + +func (self *SManagerContext) GetPath() string { + path := self.InstanceManager.KeyString() + if len(self.InstanceId) > 0 { + path += fmt.Sprintf("/%s", url.PathEscape(self.InstanceId)) + } + + return path +} + +type SResourceManager struct { + SBaseManager + ctx manager.IManagerContext + ServiceName ServiceNameType // 服务名称: ecs + Region string // 区域: cn-north-1 + DomainId string + ProjectId string // 项目ID: uuid + version string // api 版本号 + Keyword string // 资源名称单数。构建URL时使用 + KeywordPlural string // 资源名称复数形式。构建URL时使用 + + ResourceKeyword string // 资源名称。url中使用 +} + +func getContent(params jsonutils.JSONObject) string { + if params == nil { + return "" + } + + return params.String() +} + +func (self *SResourceManager) Version() string { + return self.version +} + +func (self *SResourceManager) KeyString() string { + return self.ResourceKeyword +} + +func (self *SResourceManager) ServiceType() string { + return string(self.ServiceName) +} + +func (self *SResourceManager) GetEndpoint() string { + return self.cfg.GetEndpoints().GetEndpoint(self.ServiceType(), self.Region) +} + +func (self *SResourceManager) GetColumns() []string { + return []string{} +} + +func (self *SResourceManager) SetDomainId(domainId string) { + self.DomainId = domainId +} + +func (self *SResourceManager) getReourcePath(ctx manager.IManagerContext, rid string, spec string) string { + segs := []string{} + if ctx != nil { + segs = append(segs, ctx.GetPath()) + } + + segs = append(segs, self.KeyString()) + + if len(rid) > 0 { + segs = append(segs, url.PathEscape(rid)) + } + + if len(spec) > 0 { + specSegs := strings.Split(spec, "/") + for _, specSeg := range specSegs { + segs = append(segs, url.PathEscape(specSeg)) + } + } + + return strings.Join(segs, "/") +} + +func (self *SResourceManager) newRequest(method, rid, spec string, ctx manager.IManagerContext) *requests.SRequest { + resourcePath := self.getReourcePath(ctx, rid, spec) + return requests.NewResourceRequest(self.GetEndpoint(), method, string(self.ServiceName), self.version, self.Region, self.ProjectId, resourcePath) +} + +func (self *SResourceManager) List(queries map[string]string) (*responses.ListResult, error) { + return self.ListInContext(self.ctx, queries) +} + +func (self *SResourceManager) ListInContext(ctx manager.IManagerContext, queries map[string]string) (*responses.ListResult, error) { + return self.ListInContextWithSpec(ctx, "", queries, self.KeywordPlural) +} + +func (self *SResourceManager) ListInContextWithSpec(ctx manager.IManagerContext, spec string, queries map[string]string, responseKey string) (*responses.ListResult, error) { + request := self.newRequest("GET", "", spec, ctx) + for k, v := range queries { + request.AddQueryParam(k, v) + } + if len(self.DomainId) > 0 { + request.AddHeaderParam("X-Domain-Id", self.DomainId) + } + + return self._list(request, responseKey) +} + +func (self *SResourceManager) Get(id string, queries map[string]string) (jsonutils.JSONObject, error) { + return self.GetInContext(self.ctx, id, queries) +} + +func (self *SResourceManager) GetInContext(ctx manager.IManagerContext, id string, queries map[string]string) (jsonutils.JSONObject, error) { + return self.GetInContextWithSpec(ctx, id, "", queries, self.Keyword) +} + +func (self *SResourceManager) GetInContextWithSpec(ctx manager.IManagerContext, id string, spec string, queries map[string]string, responseKey string) (jsonutils.JSONObject, error) { + request := self.newRequest("GET", id, spec, ctx) + for k, v := range queries { + request.AddQueryParam(k, v) + } + + if len(self.DomainId) > 0 { + request.AddHeaderParam("X-Domain-Id", self.DomainId) + } + + return self._get(request, responseKey) +} + +func (self *SResourceManager) Create(params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.CreateInContext(self.ctx, params) +} + +func (self *SResourceManager) CreateInContext(ctx manager.IManagerContext, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.CreateInContextWithSpec(ctx, "", params, self.Keyword) +} + +func (self *SResourceManager) CreateInContextWithSpec(ctx manager.IManagerContext, spec string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) { + request := self.newRequest("POST", "", spec, ctx) + request.SetContent([]byte(params.String())) + if len(self.DomainId) > 0 { + request.AddHeaderParam("X-Domain-Id", self.DomainId) + } + + return self._do(request, responseKey) +} + +func (self *SResourceManager) AsyncCreate(params jsonutils.JSONObject) (string, error) { + return "", fmt.Errorf("not supported") +} + +func (self *SResourceManager) Update(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.UpdateInContext(self.ctx, id, params) +} + +func (self *SResourceManager) UpdateInContext(ctx manager.IManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.UpdateInContextWithSpec(ctx, id, "", params, self.Keyword) +} + +func (self *SResourceManager) UpdateInContextWithSpec(ctx manager.IManagerContext, id string, spec string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) { + request := self.newRequest("PUT", id, spec, ctx) + content := getContent(params) + if len(content) > 0 { + request.SetContent([]byte(content)) + } + if len(self.DomainId) > 0 { + request.AddHeaderParam("X-Domain-Id", self.DomainId) + } + + return self._do(request, responseKey) +} + +func (self *SResourceManager) Patch(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.PatchInContext(self.ctx, id, params) +} + +func (self *SResourceManager) PatchInContext(ctx manager.IManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.PatchInContextWithSpec(ctx, id, "", params, self.Keyword) +} + +func (self *SResourceManager) PatchInContextWithSpec(ctx manager.IManagerContext, id string, spec string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) { + request := self.newRequest("PATCH", id, spec, ctx) + content := getContent(params) + if len(content) > 0 { + request.SetContent([]byte(content)) + } + if len(self.DomainId) > 0 { + request.AddHeaderParam("X-Domain-Id", self.DomainId) + } + + return self._do(request, responseKey) +} + +func (self *SResourceManager) Delete(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.DeleteInContext(self.ctx, id, params) +} + +func (self *SResourceManager) DeleteInContext(ctx manager.IManagerContext, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.DeleteInContextWithSpec(ctx, id, "", nil, params, "") +} + +func (self *SResourceManager) DeleteInContextWithSpec(ctx manager.IManagerContext, id string, spec string, queries map[string]string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) { + request := self.newRequest("DELETE", id, spec, ctx) + for k, v := range queries { + request.AddQueryParam(k, v) + } + + content := getContent(params) + if len(content) > 0 { + request.SetContent([]byte(content)) + } + if len(self.DomainId) > 0 { + request.AddHeaderParam("X-Domain-Id", self.DomainId) + } + + return self._do(request, responseKey) +} + +func (self *SResourceManager) PerformAction(action string, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.PerformAction2(action, id, params, self.Keyword) +} + +func (self *SResourceManager) PerformAction2(action string, id string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) { + request := self.newRequest("POST", id, action, nil) + request.SetContent([]byte(getContent(params))) + if len(self.DomainId) > 0 { + request.AddHeaderParam("X-Domain-Id", self.DomainId) + } + + return self._do(request, responseKey) +} + +func (self *SResourceManager) SetVersion(v string) { + self.version = v +} + +func (self *SResourceManager) versionedURL(path string) string { + return "" +} + +// todo: Init a manager with environment variables +func (self *SResourceManager) Init() error { + return nil +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_balances.go b/pkg/multicloud/huaweistack/client/modules/mod_balances.go new file mode 100644 index 0000000000..23dbce872e --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_balances.go @@ -0,0 +1,72 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "fmt" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +/* +https://support.huaweicloud.com/api-oce/zh-cn_topic_0075195195.html +客户运营能力API的Endpoint为“bss.cn-north-1.myhuaweicloud.com”。该Endpoint为全局Endpoint,中国站所有区域均可使用。 +如何获取合作伙伴ID https://support.huaweicloud.com/bpconsole_faq/zh-cn_topic_0081005893.html +注意事项: +客户查询自身的账户余额的时候,只允许使用客户自身的AK/SK或者Token调用。 +*/ +type SBalanceManager struct { + domainId string // 租户ID + SResourceManager +} + +type balanceCtx struct { + domainId string +} + +// https://support.huaweicloud.com/api-bpconsole/zh-cn_topic_0075213309.html +// 这个manager非常特殊。url hardcode +func (self *balanceCtx) GetPath() string { + return fmt.Sprintf("%s/customer/account-mgr", self.domainId) +} + +// 这个manager非常特殊。只有List 和 SetDomainId方法可用。其他方法未验证 +func NewBalanceManager(cfg manager.IManagerConfig) *SBalanceManager { + return &SBalanceManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameBSS, + Region: "cn-north-1", + ProjectId: "", + version: "v1.0", + Keyword: "account_balance", + KeywordPlural: "account_balances", + + ResourceKeyword: "balances", + }} +} + +func (self *SBalanceManager) List(querys map[string]string) (*responses.ListResult, error) { + if len(self.domainId) == 0 { + return nil, fmt.Errorf("domainId is emtpy.Use SetDomainId method to set.") + } + + ctx := &balanceCtx{domainId: self.domainId} + return self.ListInContext(ctx, querys) +} + +func (self *SBalanceManager) SetDomainId(domainId string) { + self.domainId = domainId +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_bandwidths.go b/pkg/multicloud/huaweistack/client/modules/mod_bandwidths.go new file mode 100644 index 0000000000..4a9f8f2940 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_bandwidths.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SBandwidthManager struct { + SResourceManager +} + +func NewBandwidthManager(cfg manager.IManagerConfig) *SBandwidthManager { + return &SBandwidthManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameVPC, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1", + Keyword: "bandwidth", + KeywordPlural: "bandwidths", + + ResourceKeyword: "bandwidths", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_ces.go b/pkg/multicloud/huaweistack/client/modules/mod_ces.go new file mode 100644 index 0000000000..58a0795a25 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_ces.go @@ -0,0 +1,152 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/requests" +) + +type SCloudEyeManager struct { + SResourceManager +} + +func NewCloudEyeManager(cfg manager.IManagerConfig) *SCloudEyeManager { + return &SCloudEyeManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameCES, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "V1.0", + Keyword: "", + KeywordPlural: "metrics", + ResourceKeyword: "metrics", + }} +} + +type SMetricDimension struct { + Name string `json:"name"` + Value string `json:"value"` +} + +type SDatapoint struct { + Timestamp int64 `json:"timestamp"` + Max float64 `json:"max,omitzero"` + Min float64 `json:"min,omitzero"` + Average float64 `json:"average,omitzero"` + Sum float64 `json:"sum,omitzero"` + Variance float64 `json:"variance,omitzero"` +} + +type SMetricData struct { + SMetricMeta + + Datapoints []SDatapoint +} + +type SMetricMeta struct { + SMetric + + Unit string `json:"unit"` +} + +type SMetric struct { + MetricName string `json:"metric_name"` + Namespace string `json:"namespace"` + + Dimensions []SMetricDimension `json:"dimensions"` +} + +func (ces *SCloudEyeManager) ListMetrics() ([]SMetricMeta, error) { + metrics := make([]SMetricMeta, 0) + next := "" + for { + marker, data, err := ces.listMetricsInternal(next) + if err != nil { + return nil, errors.Wrap(err, "ces.listMetricsInternal") + } + if len(data) == 0 { + break + } + metrics = append(metrics, data...) + next = marker + } + return metrics, nil +} + +func (ces *SCloudEyeManager) listMetricsInternal(start string) (string, []SMetricMeta, error) { + request := requests.NewResourceRequest(ces.GetEndpoint(), "GET", string(ces.ServiceName), ces.version, ces.Region, ces.ProjectId, ces.ResourceKeyword) + request.AddQueryParam("limit", "1000") + if len(start) > 0 { + request.AddQueryParam("start", start) + } + _, resp, err := ces.jsonRequest(request) + if err != nil { + return "", nil, errors.Wrap(err, "ces.jsonRequest") + } + marker, _ := resp.GetString("meta_data", "marker") + metrics := make([]SMetricMeta, 0) + err = resp.Unmarshal(&metrics, "metrics") + if err != nil { + return "", nil, errors.Wrap(err, "resp.Unmarshal metrics") + } + return marker, metrics, nil +} + +type SBatchQueryMetricDataInput struct { + Metrics []SMetric `json:"metrics"` + + From int64 `json:"from"` + To int64 `json:"to"` + Period string `json:"period"` + Filter string `json:"filter"` +} + +func (ces *SCloudEyeManager) GetMetricsData(metrics []SMetricMeta, since time.Time, until time.Time) ([]SMetricData, error) { + if len(metrics) > 10 { + return nil, errors.Wrap(httperrors.ErrTooLarge, "request more than 10 metrics") + } + metricReq := make([]SMetric, len(metrics)) + for i := range metrics { + metricReq[i] = metrics[i].SMetric + } + request := requests.NewResourceRequest(ces.GetEndpoint(), "POST", string(ces.ServiceName), ces.version, ces.Region, ces.ProjectId, "batch-query-metric-data") + input := SBatchQueryMetricDataInput{ + Metrics: metricReq, + From: since.Unix() * 1000, + To: until.Unix() * 1000, + Period: "1", + Filter: "average", + } + body := jsonutils.Marshal(&input).String() + request.SetContent([]byte(body)) + _, resp, err := ces.jsonRequest(request) + if err != nil { + return nil, errors.Wrap(err, "ces.jsonRequest") + } + //log.Debugf("%s", resp) + result := make([]SMetricData, 0) + err = resp.Unmarshal(&result, "metrics") + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return result, nil +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_credential.go b/pkg/multicloud/huaweistack/client/modules/mod_credential.go new file mode 100644 index 0000000000..cb8ce9016a --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_credential.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SCredentialManager struct { + SResourceManager +} + +func NewCredentialManager(cfg manager.IManagerConfig) *SCredentialManager { + return &SCredentialManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameIAM, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v3.0", + Keyword: "credential", + KeywordPlural: "credentials", + + ResourceKeyword: "OS-CREDENTIAL/credentials", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_dbinstance.go b/pkg/multicloud/huaweistack/client/modules/mod_dbinstance.go new file mode 100644 index 0000000000..924283fe78 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_dbinstance.go @@ -0,0 +1,103 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +type SDBInstanceManager struct { + SResourceManager +} + +func NewDBInstanceManager(cfg manager.IManagerConfig) *SDBInstanceManager { + return &SDBInstanceManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameRDS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v3", + Keyword: "", + KeywordPlural: "instances", + + ResourceKeyword: "instances", + }} +} + +func (self *SDBInstanceManager) Get(id string, querys map[string]string) (jsonutils.JSONObject, error) { + if len(id) == 0 { + return nil, cloudprovider.ErrNotFound + } + resp, err := self.GetInContextWithSpec(nil, "", "", map[string]string{"id": id}, "") + if err != nil { + return nil, err + } + instances, err := resp.GetArray("instances") + if err != nil { + return nil, err + } + if len(instances) == 0 { + return nil, cloudprovider.ErrNotFound + } else if len(instances) == 1 { + return instances[0], nil + } + return nil, cloudprovider.ErrDuplicateId +} + +func (self *SDBInstanceManager) ListParameters(queries map[string]string) (*responses.ListResult, error) { + id, _ := queries["instance_id"] + if len(id) == 0 { + return nil, fmt.Errorf("SDBInstanceManager.ListParameters missing parameter instance_id") + } + + delete(queries, "instance_id") + return self.ListInContextWithSpec(nil, fmt.Sprintf("%s/configurations", id), queries, "configuration_parameters") +} + +func (self *SDBInstanceManager) ListDatabases(queries map[string]string) (*responses.ListResult, error) { + id, _ := queries["instance_id"] + if len(id) == 0 { + return nil, fmt.Errorf("SDBInstanceManager.ListDatabases missing parameter instance_id") + } + + delete(queries, "instance_id") + return self.ListInContextWithSpec(nil, fmt.Sprintf("%s/database/detail", id), queries, "databases") +} + +func (self *SDBInstanceManager) ListAccounts(queries map[string]string) (*responses.ListResult, error) { + id, _ := queries["instance_id"] + if len(id) == 0 { + return nil, fmt.Errorf("SDBInstanceManager.ListAccounts missing parameter instance_id") + } + + delete(queries, "instance_id") + return self.ListInContextWithSpec(nil, fmt.Sprintf("%s/db_user/detail", id), queries, "users") +} + +func (self *SDBInstanceManager) ListPrivileges(queries map[string]string) (*responses.ListResult, error) { + id, _ := queries["instance_id"] + if len(id) == 0 { + return nil, fmt.Errorf("SDBInstanceManager.ListPrivileges missing parameter instance_id") + } + + delete(queries, "instance_id") + return self.ListInContextWithSpec(nil, fmt.Sprintf("%s/db_user/database", id), queries, "databases") +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_dbinstance_backup.go b/pkg/multicloud/huaweistack/client/modules/mod_dbinstance_backup.go new file mode 100644 index 0000000000..5b3c50ffd3 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_dbinstance_backup.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SDBInstanceBackupManager struct { + SResourceManager +} + +func NewDBInstanceBackupManager(cfg manager.IManagerConfig) *SDBInstanceBackupManager { + return &SDBInstanceBackupManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameRDS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v3", + Keyword: "backup", + KeywordPlural: "backups", + + ResourceKeyword: "backups", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_dbinstance_flavor.go b/pkg/multicloud/huaweistack/client/modules/mod_dbinstance_flavor.go new file mode 100644 index 0000000000..e440f69a87 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_dbinstance_flavor.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SDBInstanceFlavorManager struct { + SResourceManager +} + +func NewDBInstanceFlavorManager(cfg manager.IManagerConfig) *SDBInstanceFlavorManager { + return &SDBInstanceFlavorManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameRDS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v3", + Keyword: "flavor", + KeywordPlural: "flavor", + + ResourceKeyword: "flavors", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_dbinstance_job.go b/pkg/multicloud/huaweistack/client/modules/mod_dbinstance_job.go new file mode 100644 index 0000000000..44a8705ad4 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_dbinstance_job.go @@ -0,0 +1,43 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SDBInstanceJobManager struct { + SResourceManager +} + +func NewDBInstanceJobManager(cfg manager.IManagerConfig) *SDBInstanceJobManager { + return &SDBInstanceJobManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameRDS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v3", + Keyword: "", + KeywordPlural: "", + + ResourceKeyword: "jobs", + }} +} + +func (self *SDBInstanceJobManager) Get(id string, querys map[string]string) (jsonutils.JSONObject, error) { + return self.GetInContextWithSpec(nil, "", "", map[string]string{"id": id}, "job") +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_disks.go b/pkg/multicloud/huaweistack/client/modules/mod_disks.go new file mode 100644 index 0000000000..7e739bea3a --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_disks.go @@ -0,0 +1,76 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +type SDiskManager struct { + SResourceManager +} + +func NewDiskManager(cfg manager.IManagerConfig) *SDiskManager { + return &SDiskManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameEVS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v2", + Keyword: "volume", + KeywordPlural: "volumes", + + ResourceKeyword: "cloudvolumes", + }} +} + +func (self *SDiskManager) List(querys map[string]string) (*responses.ListResult, error) { + return self.ListInContextWithSpec(nil, "detail", querys, self.KeywordPlural) +} + +// https://support.huaweicloud.com/api-evs/evs_04_2003.html +func (self *SDiskManager) AsyncCreate(params jsonutils.JSONObject) (string, error) { + origin_version := self.version + self.version = "v2.1" + defer func() { self.version = origin_version }() + + ret, err := self.CreateInContextWithSpec(nil, "", params, "") + if err != nil { + log.Debugf("AsyncCreate %s", err) + return "", err + } + + log.Debugf("AsyncCreate result %s", ret.String()) + // 按需机器 + jobId, err := ret.GetString("job_id") + if err == nil { + return jobId, nil + } + + // 包年包月机器 + return ret.GetString("order_id") +} + +// https://support.huaweicloud.com/api-evs/evs_04_2003.html +func (self *SDiskManager) GetDiskTypes() (*responses.ListResult, error) { + originKeyword := self.ResourceKeyword + self.ResourceKeyword = "" + defer func() { self.ResourceKeyword = originKeyword }() + return self.ListInContextWithSpec(self.ctx, "types", nil, "volume_types") +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_dnat_rules.go b/pkg/multicloud/huaweistack/client/modules/mod_dnat_rules.go new file mode 100644 index 0000000000..ac9a5ba5e6 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_dnat_rules.go @@ -0,0 +1,41 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SNatDRuleManager struct { + SResourceManager +} + +func NewNatDManager(cfg manager.IManagerConfig) *SNatDRuleManager { + man := &SNatDRuleManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameNAT, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0", + Keyword: "dnat_rule", + KeywordPlural: "dnat_rules", + + ResourceKeyword: "dnat_rules", + }} + if len(cfg.GetProjectId()) > 0 { + man.requestHook = &sProjectHook{cfg.GetProjectId()} + } + return man +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_domains.go b/pkg/multicloud/huaweistack/client/modules/mod_domains.go new file mode 100644 index 0000000000..e3daedfa1d --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_domains.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SDomainManager struct { + SResourceManager +} + +func NewDomainManager(cfg manager.IManagerConfig) *SDomainManager { + return &SDomainManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameIAM, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v3/auth", + Keyword: "domain", + KeywordPlural: "domains", + + ResourceKeyword: "domains", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_eips.go b/pkg/multicloud/huaweistack/client/modules/mod_eips.go new file mode 100644 index 0000000000..4f2bb59d1d --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_eips.go @@ -0,0 +1,44 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SEipManager struct { + SResourceManager +} + +func NewEipManager(cfg manager.IManagerConfig) *SEipManager { + return &SEipManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameVPC, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1", + Keyword: "publicip", + KeywordPlural: "publicips", + + ResourceKeyword: "publicips", + }} +} + +// https://support.huaweicloud.com/api-eip/eip_api_0005.html +func (self *SEipManager) Delete(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.DeleteInContextWithSpec(self.ctx, id, "", nil, params, "") +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_elasticcache.go b/pkg/multicloud/huaweistack/client/modules/mod_elasticcache.go new file mode 100644 index 0000000000..3cdfb3f86f --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_elasticcache.go @@ -0,0 +1,118 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +type SElasticcacheManager struct { + SResourceManager +} + +type SDcsAvailableZoneManager struct { + SResourceManager +} + +func NewElasticcacheManager(cfg manager.IManagerConfig) *SElasticcacheManager { + return &SElasticcacheManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameDCS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1.0", + Keyword: "", + KeywordPlural: "instances", + + ResourceKeyword: "instances", + }} +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423035.html +func (self *SElasticcacheManager) ListBackups(queries map[string]string) (*responses.ListResult, error) { + var spec string + if id, _ := queries["instance_id"]; len(id) == 0 { + return nil, fmt.Errorf("SElasticcacheManager.ListBackups missing parameter instance_id") + } else { + spec = fmt.Sprintf("%s/backups", id) + } + + delete(queries, "instance_id") + return self.ListInContextWithSpec(nil, spec, queries, "backup_record_response") +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423027.html +func (self *SElasticcacheManager) ListParameters(queries map[string]string) (*responses.ListResult, error) { + var spec string + if id, _ := queries["instance_id"]; len(id) == 0 { + return nil, fmt.Errorf("SElasticcacheManager.ListParameters missing parameter instance_id") + } else { + spec = fmt.Sprintf("%s/configs", id) + } + + delete(queries, "instance_id") + return self.ListInContextWithSpec(nil, spec, queries, "redis_config") +} + +func (self *SElasticcacheManager) Restart(instanceId string) (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + params.Add(jsonutils.NewArray(jsonutils.NewString(instanceId)), "instances") + params.Add(jsonutils.NewString("restart"), "action") + return self.UpdateInContextWithSpec(nil, "", "status", params, "") +} + +// 当前版本,只有DCS2.0实例支持清空数据功能,即flush操作。 +func (self *SElasticcacheManager) Flush(instanceId string) (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + params.Add(jsonutils.NewArray(jsonutils.NewString(instanceId)), "instances") + params.Add(jsonutils.NewString("flush"), "action") + return self.UpdateInContextWithSpec(nil, "", "status", params, "") +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423034.html +func (self *SElasticcacheManager) RestoreInstance(instanceId string, backupId string) (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + params.Add(jsonutils.NewArray(jsonutils.NewString(backupId)), "backup_id") + + return self.CreateInContextWithSpec(nil, fmt.Sprintf("%s/restores", instanceId), params, "") +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423024.html +func (self *SElasticcacheManager) ChangeInstanceSpec(instanceId string, specCode string, newCapacity int64) (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + params.Set("new_capacity", jsonutils.NewInt(newCapacity)) + params.Set("spec_code", jsonutils.NewString(specCode)) + + return self.CreateInContextWithSpec(nil, fmt.Sprintf("%s/extend", instanceId), params, "") +} + +func NewDcsAvailableZoneManager(cfg manager.IManagerConfig) *SDcsAvailableZoneManager { + return &SDcsAvailableZoneManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameDCS, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v1.0", + Keyword: "available_zone", + KeywordPlural: "available_zones", + + ResourceKeyword: "availableZones", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_endpoints.go b/pkg/multicloud/huaweistack/client/modules/mod_endpoints.go new file mode 100644 index 0000000000..5d31458b5a --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_endpoints.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SEndpointManager struct { + SResourceManager +} + +func NewEndpointManager(cfg manager.IManagerConfig) *SEndpointManager { + return &SEndpointManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameIAM, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v3", + Keyword: "endpoint", + KeywordPlural: "endpoints", + + ResourceKeyword: "endpoints", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_enterpriceprojects.go b/pkg/multicloud/huaweistack/client/modules/mod_enterpriceprojects.go new file mode 100644 index 0000000000..8a2c9b0e3b --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_enterpriceprojects.go @@ -0,0 +1,39 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SEnterpriseProjectManager struct { + SResourceManager +} + +func NewEnterpriseProjectManager(cfg manager.IManagerConfig) *SEnterpriseProjectManager { + m := &SEnterpriseProjectManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameEPS, + Region: "", + ProjectId: "", + version: "v1.0", + Keyword: "enterprise_project", + KeywordPlural: "enterprise_projects", + + ResourceKeyword: "enterprise-projects", + }} + m.SetDomainId(cfg.GetDomainId()) + return m +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_flavors.go b/pkg/multicloud/huaweistack/client/modules/mod_flavors.go new file mode 100644 index 0000000000..7d44dcfffd --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_flavors.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SFlavorManager struct { + SResourceManager +} + +func NewFlavorManager(cfg manager.IManagerConfig) *SFlavorManager { + return &SFlavorManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameECS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1", + Keyword: "flavor", + KeywordPlural: "flavors", + + ResourceKeyword: "cloudservers/flavors", // 这个接口有点特殊,实际只用到了list一个方法。为了简便直接把cloudservers附上。 + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_groups.go b/pkg/multicloud/huaweistack/client/modules/mod_groups.go new file mode 100644 index 0000000000..e56cd9ae89 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_groups.go @@ -0,0 +1,122 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "fmt" + + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +type SGroupManager struct { + SResourceManager +} + +func NewGroupManager(cfg manager.IManagerConfig) *SGroupManager { + m := &SGroupManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameIAM, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v3", + Keyword: "group", + KeywordPlural: "groups", + + ResourceKeyword: "groups", + }} + m.SetDomainId(cfg.GetDomainId()) + return m +} + +func (manager *SGroupManager) ListRoles(domainId string, groupId string) (*responses.ListResult, error) { + if len(domainId) == 0 { + return nil, fmt.Errorf("missing domainId") + } + if len(groupId) == 0 { + return nil, fmt.Errorf("missing groupId") + } + manager.SetVersion(fmt.Sprintf("v3/domains/%s", domainId)) + return manager.ListInContextWithSpec(nil, fmt.Sprintf("%s/roles", groupId), nil, "roles") +} + +func (manager *SGroupManager) DeleteProjectRole(projectId, groupId, roleId string) error { + if len(projectId) == 0 { + return fmt.Errorf("missing projectId") + } + if len(groupId) == 0 { + return fmt.Errorf("missing groupId") + } + if len(roleId) == 0 { + return fmt.Errorf("missing roleId") + } + manager.SetVersion(fmt.Sprintf("v3/projects/%s", projectId)) + _, err := manager.DeleteInContextWithSpec(nil, groupId, fmt.Sprintf("roles/%s", roleId), nil, nil, "") + if err != nil && errors.Cause(err) == cloudprovider.ErrNotFound { + return nil + } + return err +} + +func (manager *SGroupManager) DeleteRole(domainId string, groupId, roleId string) error { + if len(domainId) == 0 { + return fmt.Errorf("missing domainId") + } + if len(groupId) == 0 { + return fmt.Errorf("missing groupId") + } + if len(roleId) == 0 { + return fmt.Errorf("missing roleId") + } + manager.SetVersion(fmt.Sprintf("v3/domains/%s", domainId)) + _, err := manager.DeleteInContextWithSpec(nil, groupId, fmt.Sprintf("roles/%s", roleId), nil, nil, "") + if err != nil && errors.Cause(err) == cloudprovider.ErrNotFound { + return nil + } + return err +} + +func (manager *SGroupManager) AddProjectRole(projectId string, groupId, roleId string) error { + if len(projectId) == 0 { + return fmt.Errorf("missing projectId") + } + if len(groupId) == 0 { + return fmt.Errorf("missing groupId") + } + if len(roleId) == 0 { + return fmt.Errorf("missing roleId") + } + manager.SetVersion(fmt.Sprintf("v3/projects/%s", projectId)) + _, err := manager.UpdateInContextWithSpec(nil, groupId, fmt.Sprintf("roles/%s", roleId), nil, "") + return err +} + +func (manager *SGroupManager) AddRole(domainId string, groupId, roleId string) error { + if len(domainId) == 0 { + return fmt.Errorf("missing domainId") + } + if len(groupId) == 0 { + return fmt.Errorf("missing groupId") + } + if len(roleId) == 0 { + return fmt.Errorf("missing roleId") + } + manager.SetVersion(fmt.Sprintf("v3/domains/%s", domainId)) + _, err := manager.UpdateInContextWithSpec(nil, groupId, fmt.Sprintf("roles/%s", roleId), nil, "") + return err +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_images.go b/pkg/multicloud/huaweistack/client/modules/mod_images.go new file mode 100644 index 0000000000..12e35b7c91 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_images.go @@ -0,0 +1,101 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/requests" +) + +type SImageManager struct { + SResourceManager +} + +type imageProject struct { + projectId string +} + +// image创建接口若非默认project,需要在header中指定X-Project-ID。url中未携带project信息(与其他接口相比有一点特殊) +// 绕过了ResourceManager中的projectid。直接在发送json请求前注入X-Project-ID +func (self *imageProject) Process(request requests.IRequest) { + request.AddHeaderParam("X-Project-Id", self.projectId) +} + +func NewImageManager(cfg manager.IManagerConfig) *SImageManager { + var requestHook imageProject + if len(cfg.GetProjectId()) > 0 { + requestHook = imageProject{projectId: cfg.GetProjectId()} + } + + return &SImageManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager2(cfg, &requestHook), + ServiceName: ServiceNameIMS, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2", + Keyword: "image", + KeywordPlural: "images", + + ResourceKeyword: "cloudimages", + }} +} + +//https://support.huaweicloud.com/api-ims/zh-cn_topic_0020091566.html +func (self *SImageManager) Get(id string, querys map[string]string) (jsonutils.JSONObject, error) { + if querys == nil { + querys = make(map[string]string, 0) + } + + querys["id"] = id + // 这里默认使用private + // if t, exists := querys["__imagetype"]; !exists || len(t) == 0 { + // querys["__imagetype"] = "private" + // } + + ret, err := self.ListInContext(nil, querys) + if err != nil { + return nil, err + } + + if ret.Data == nil || len(ret.Data) == 0 { + return nil, httperrors.NewNotFoundError("image %s not found", id) + } + + return ret.Data[0], nil +} + +// https://support.huaweicloud.com/api-ims/zh-cn_topic_0020092108.html +// 删除image只能用这个manager +func NewOpenstackImageManager(cfg manager.IManagerConfig) *SImageManager { + var requestHook imageProject + if len(cfg.GetProjectId()) > 0 { + requestHook = imageProject{projectId: cfg.GetProjectId()} + } + + return &SImageManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager2(cfg, &requestHook), + ServiceName: ServiceNameIMS, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2", + Keyword: "image", + KeywordPlural: "images", + + ResourceKeyword: "images", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_interface.go b/pkg/multicloud/huaweistack/client/modules/mod_interface.go new file mode 100644 index 0000000000..8e8292546e --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_interface.go @@ -0,0 +1,38 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SInterfaceManager struct { + SResourceManager +} + +// 不建议使用 +func NewInterfaceManager(cfg manager.IManagerConfig) *SInterfaceManager { + return &SInterfaceManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameECS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v2", + Keyword: "interfaceAttachment", + KeywordPlural: "interfaceAttachments", + + ResourceKeyword: "os-interface", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_jobs.go b/pkg/multicloud/huaweistack/client/modules/mod_jobs.go new file mode 100644 index 0000000000..7e1ed69bbe --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_jobs.go @@ -0,0 +1,71 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +type SJobManager struct { + SResourceManager +} + +func NewJobManager(cfg manager.IManagerConfig) *SJobManager { + return &SJobManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: "", + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1", + Keyword: "", + KeywordPlural: "", + + ResourceKeyword: "jobs", + }} +} + +func (self *SJobManager) Get(id string, querys map[string]string) (jsonutils.JSONObject, error) { + processedQuery, err := self.processQueryParam(querys) + if err != nil { + return nil, err + } + + return self.GetInContext(nil, id, processedQuery) +} + +func (self *SJobManager) List(querys map[string]string) (*responses.ListResult, error) { + processedQuery, err := self.processQueryParam(querys) + if err != nil { + return nil, err + } + return self.ListInContext(nil, processedQuery) +} + +// 兼容查询不同ServiceName服务的Job做的特殊处理。 +func (self *SJobManager) processQueryParam(querys map[string]string) (map[string]string, error) { + service_type, exists := querys["service_type"] + if !exists { + return querys, fmt.Errorf("must specific query parameter `service_type`. e.g. ecs|ims|iam") + } + + self.ServiceName = ServiceNameType(service_type) + delete(querys, "service_type") + return querys, nil +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_keypairs.go b/pkg/multicloud/huaweistack/client/modules/mod_keypairs.go new file mode 100644 index 0000000000..6cb968d5f0 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_keypairs.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SKeypairManager struct { + SResourceManager +} + +func NewKeypairManager(cfg manager.IManagerConfig) *SKeypairManager { + return &SKeypairManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameECS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v2", + Keyword: "keypair", + KeywordPlural: "keypairs", + + ResourceKeyword: "os-keypairs", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_backend.go b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_backend.go new file mode 100644 index 0000000000..d9bdbb7797 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_backend.go @@ -0,0 +1,62 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "fmt" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SElbBackendManager struct { + SResourceManager +} + +type backendCtx struct { + backendGroupId string +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561556.html +func (self *backendCtx) GetPath() string { + return fmt.Sprintf("pools/%s", self.backendGroupId) +} + +func NewElbBackendManager(cfg manager.IManagerConfig) *SElbBackendManager { + var requestHook portProject + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} + } + + return &SElbBackendManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager2(cfg, &requestHook), + ServiceName: ServiceNameELB, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0/lbaas", + Keyword: "member", + KeywordPlural: "members", + + ResourceKeyword: "members", + }} +} + +func (self *SElbBackendManager) SetBackendGroupId(lbgId string) error { + if len(lbgId) == 0 { + return fmt.Errorf("SetBackendGroupId id should not be emtpy") + } + + self.ctx = &backendCtx{backendGroupId: lbgId} + return nil +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_backend_group.go b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_backend_group.go new file mode 100644 index 0000000000..2bf4af2261 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_backend_group.go @@ -0,0 +1,42 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SElbBackendGroupManager struct { + SResourceManager +} + +func NewElbBackendGroupManager(cfg manager.IManagerConfig) *SElbBackendGroupManager { + var requestHook portProject + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} + } + + return &SElbBackendGroupManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager2(cfg, &requestHook), + ServiceName: ServiceNameELB, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0", + Keyword: "pool", + KeywordPlural: "pools", + + ResourceKeyword: "lbaas/pools", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_certificates.go b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_certificates.go new file mode 100644 index 0000000000..c533b0ca78 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_certificates.go @@ -0,0 +1,42 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SElbCertificatesManager struct { + SResourceManager +} + +func NewElbCertificatesManager(cfg manager.IManagerConfig) *SElbCertificatesManager { + var requestHook portProject + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} + } + + return &SElbCertificatesManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager2(cfg, &requestHook), + ServiceName: ServiceNameELB, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0", + Keyword: "", + KeywordPlural: "certificates", + + ResourceKeyword: "lbaas/certificates", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_healthcheck.go b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_healthcheck.go new file mode 100644 index 0000000000..c9799337fd --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_healthcheck.go @@ -0,0 +1,48 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SElbHealthCheckManager struct { + SResourceManager +} + +func NewElbHealthCheckManager(cfg manager.IManagerConfig) *SElbHealthCheckManager { + var requestHook portProject + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} + } + + return &SElbHealthCheckManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager2(cfg, &requestHook), + ServiceName: ServiceNameELB, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0", + Keyword: "healthmonitor", + KeywordPlural: "healthmonitors", + + ResourceKeyword: "lbaas/healthmonitors", + }} +} + +func (self *SElbHealthCheckManager) Delete(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.DeleteInContextWithSpec(self.ctx, id, "", nil, params, "") +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_listeners.go b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_listeners.go new file mode 100644 index 0000000000..ba7337ead4 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_listeners.go @@ -0,0 +1,42 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SElbListenersManager struct { + SResourceManager +} + +func NewElbListenersManager(cfg manager.IManagerConfig) *SElbListenersManager { + var requestHook portProject + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} + } + + return &SElbListenersManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager2(cfg, &requestHook), + ServiceName: ServiceNameELB, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0", + Keyword: "listener", + KeywordPlural: "listeners", + + ResourceKeyword: "lbaas/listeners", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_policies.go b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_policies.go new file mode 100644 index 0000000000..ed5f16206b --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_policies.go @@ -0,0 +1,42 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SElbL7policiesManager struct { + SResourceManager +} + +func NewElbL7policiesManager(cfg manager.IManagerConfig) *SElbL7policiesManager { + var requestHook portProject + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} + } + + return &SElbL7policiesManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager2(cfg, &requestHook), + ServiceName: ServiceNameELB, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0", + Keyword: "l7policy", + KeywordPlural: "l7policies", + + ResourceKeyword: "lbaas/l7policies", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_rules.go b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_rules.go new file mode 100644 index 0000000000..564033d130 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_rules.go @@ -0,0 +1,62 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "fmt" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SElbPoliciesManager struct { + SResourceManager +} + +type policyCtx struct { + l7policyId string +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561556.html +func (self *policyCtx) GetPath() string { + return fmt.Sprintf("l7policies/%s", self.l7policyId) +} + +func NewElbPoliciesManager(cfg manager.IManagerConfig) *SElbPoliciesManager { + var requestHook portProject + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} + } + + return &SElbPoliciesManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager2(cfg, &requestHook), + ServiceName: ServiceNameELB, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0/lbaas", + Keyword: "rule", + KeywordPlural: "rules", + + ResourceKeyword: "rules", + }} +} + +func (self *SElbPoliciesManager) SetL7policyId(lbpId string) error { + if len(lbpId) == 0 { + return fmt.Errorf("SetL7policyId id should not be emtpy") + } + + self.ctx = &policyCtx{l7policyId: lbpId} + return nil +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_whitelists.go b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_whitelists.go new file mode 100644 index 0000000000..6fa888ddda --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancer_whitelists.go @@ -0,0 +1,42 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SElbWhitelistManager struct { + SResourceManager +} + +func NewElbWhitelistManager(cfg manager.IManagerConfig) *SElbWhitelistManager { + var requestHook portProject + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} + } + + return &SElbWhitelistManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager2(cfg, &requestHook), + ServiceName: ServiceNameELB, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0", + Keyword: "whitelist", + KeywordPlural: "whitelists", + + ResourceKeyword: "lbaas/whitelists", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_loadbalancers.go b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancers.go new file mode 100644 index 0000000000..2ded3b962b --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_loadbalancers.go @@ -0,0 +1,42 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SLoadbalancerManager struct { + SResourceManager +} + +func NewLoadbalancerManager(cfg manager.IManagerConfig) *SLoadbalancerManager { + var requestHook portProject + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} + } + + return &SLoadbalancerManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager2(cfg, &requestHook), + ServiceName: ServiceNameELB, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0", + Keyword: "loadbalancer", + KeywordPlural: "loadbalancers", + + ResourceKeyword: "lbaas/loadbalancers", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_mapping.go b/pkg/multicloud/huaweistack/client/modules/mod_mapping.go new file mode 100644 index 0000000000..5fb2610f9c --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_mapping.go @@ -0,0 +1,39 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SAMLProviderMappingManager struct { + SResourceManager +} + +func NewSAMLProviderMappingManager(cfg manager.IManagerConfig) *SAMLProviderMappingManager { + m := &SAMLProviderMappingManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameIAM, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v3/OS-FEDERATION", + Keyword: "mapping", + KeywordPlural: "mappings", + + ResourceKeyword: "mappings", + }} + m.SetDomainId(cfg.GetDomainId()) + return m +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_natgateway.go b/pkg/multicloud/huaweistack/client/modules/mod_natgateway.go new file mode 100644 index 0000000000..d3d83924d7 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_natgateway.go @@ -0,0 +1,50 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/requests" +) + +type SNatGatewayManager struct { + SResourceManager +} + +type sProjectHook struct { + projectId string +} + +func (self *sProjectHook) Process(request requests.IRequest) { + request.AddHeaderParam("X-Project-Id", self.projectId) +} + +func NewNatGatewayManager(cfg manager.IManagerConfig) *SNatGatewayManager { + man := &SNatGatewayManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameNAT, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0", + Keyword: "nat_gateway", + KeywordPlural: "nat_gateways", + + ResourceKeyword: "nat_gateways", + }} + if len(cfg.GetProjectId()) > 0 { + man.requestHook = &sProjectHook{cfg.GetProjectId()} + } + return man +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_orders.go b/pkg/multicloud/huaweistack/client/modules/mod_orders.go new file mode 100644 index 0000000000..ebcfea08f3 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_orders.go @@ -0,0 +1,104 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +// domian 客户账号ID https://support.huaweicloud.com/oce_faq/zh-cn_topic_0113714840.html +type SOrderManager struct { + orderCtx manager.IManagerContext + SResourceManager +} + +type orderCtx struct { + domainId string +} + +// {domain_id}/common/ +// 这个manager非常特殊。url hardcode +func (self *orderCtx) GetPath() string { + return fmt.Sprintf("%s/common", self.domainId) +} + +// 客户运营能力API的Endpoint为“bss.cn-north-1.myhuaweicloud.com”。该Endpoint为全局Endpoint,中国站所有区域均可使用。 +// https://support.huaweicloud.com/api-oce/zh-cn_topic_0084961226.html +func NewOrderManager(cfg manager.IManagerConfig) *SOrderManager { + return &SOrderManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameBSS, + Region: "cn-north-1", + ProjectId: "", + version: "v1.0", + Keyword: "", + KeywordPlural: "", + + ResourceKeyword: "order-mgr", + }} +} + +func (self *SOrderManager) SetDomainId(domainId string) error { + if len(domainId) == 0 { + return fmt.Errorf("SetDomainId domain id should not be emtpy") + } + + self.orderCtx = &orderCtx{domainId: domainId} + return nil +} + +// 查询客户包周期资源列表 https://support.huaweicloud.com/api-oce/zh-cn_topic_0084961226.html +func (self *SOrderManager) List(querys map[string]string) (*responses.ListResult, error) { + return nil, fmt.Errorf("Not Suppport List Order") +} + +// 查询订单的资源开通详情 https://support.huaweicloud.com/api-oce/api_order_00001.html +func (self *SOrderManager) Get(id string, querys map[string]string) (jsonutils.JSONObject, error) { + if self.orderCtx == nil { + return nil, fmt.Errorf("domainId is emtpy.Use SetDomainId method to set.") + } + + // !!!特殊调用 + return self.GetInContextWithSpec(self.orderCtx, "orders-resource", id, querys, "") +} + +func (self *SOrderManager) PerformAction(action string, id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + request := self.newRequest("POST", id, action, self.orderCtx) + request.SetContent([]byte(getContent(params))) + + return self._do(request, "") +} + +func (self *SOrderManager) GetPeriodResourceList(querys map[string]string) (*responses.ListResult, error) { + if self.orderCtx == nil { + return nil, fmt.Errorf("domainId is emtpy.Use SetDomainId method to set.") + } + + return self.ListInContextWithSpec(self.orderCtx, "resources/detail", querys, "data") +} + +// https://support.huaweicloud.com/api-bpconsole/zh-cn_topic_0082522029.html +func (self *SOrderManager) RenewPeriodResource(params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + if self.orderCtx == nil { + return nil, fmt.Errorf("domainId is emtpy.Use SetDomainId method to set.") + } + + return self.CreateInContextWithSpec(self.orderCtx, "resources/renew", params, "order_ids") +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_port.go b/pkg/multicloud/huaweistack/client/modules/mod_port.go new file mode 100644 index 0000000000..d32e5b5b53 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_port.go @@ -0,0 +1,53 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/requests" +) + +type SPortManager struct { + SResourceManager +} + +type portProject struct { + projectId string +} + +// port接口查询时若非默认project,需要在header中指定X-Project-ID。url中未携带project信息(与其他接口相比有一点特殊) +// 绕过了ResourceManager中的projectid。直接在发送json请求前注入X-Project-ID +func (self *portProject) Process(request requests.IRequest) { + request.AddHeaderParam("X-Project-Id", self.projectId) +} + +func NewPortManager(cfg manager.IManagerConfig) *SPortManager { + var requestHook portProject + if len(cfg.GetProjectId()) > 0 { + requestHook = portProject{projectId: cfg.GetProjectId()} + } + + return &SPortManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager2(cfg, &requestHook), + ServiceName: ServiceNameVPC, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1", + Keyword: "port", + KeywordPlural: "ports", + + ResourceKeyword: "ports", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_projects.go b/pkg/multicloud/huaweistack/client/modules/mod_projects.go new file mode 100644 index 0000000000..6a7b2a7b9b --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_projects.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SProjectManager struct { + SResourceManager +} + +func NewProjectManager(cfg manager.IManagerConfig) *SProjectManager { + return &SProjectManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameIAM, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v3", + Keyword: "project", + KeywordPlural: "projects", + + ResourceKeyword: "projects", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_quotas.go b/pkg/multicloud/huaweistack/client/modules/mod_quotas.go new file mode 100644 index 0000000000..73451837bc --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_quotas.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SQuotaManager struct { + SResourceManager +} + +func NewQuotaManager(cfg manager.IManagerConfig) *SQuotaManager { + return &SQuotaManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameEVS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1", + Keyword: "quotas", + KeywordPlural: "quotas", + + ResourceKeyword: "quotas", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_regions.go b/pkg/multicloud/huaweistack/client/modules/mod_regions.go new file mode 100644 index 0000000000..d3162b922d --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_regions.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SRegionManager struct { + SResourceManager +} + +func NewRegionManager(cfg manager.IManagerConfig) *SRegionManager { + return &SRegionManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameIAM, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v3", + Keyword: "region", + KeywordPlural: "regions", + + ResourceKeyword: "regions", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_roles.go b/pkg/multicloud/huaweistack/client/modules/mod_roles.go new file mode 100644 index 0000000000..ba6772d8dd --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_roles.go @@ -0,0 +1,39 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SRoleManager struct { + SResourceManager +} + +func NewRoleManager(cfg manager.IManagerConfig) *SRoleManager { + m := &SRoleManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameIAM, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v3", + Keyword: "role", + KeywordPlural: "roles", + + ResourceKeyword: "roles", + }} + m.SetDomainId(cfg.GetDomainId()) + return m +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_saml_provider.go b/pkg/multicloud/huaweistack/client/modules/mod_saml_provider.go new file mode 100644 index 0000000000..3149c4e0e1 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_saml_provider.go @@ -0,0 +1,39 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SAMLProviderManager struct { + SResourceManager +} + +func NewSAMLProviderManager(cfg manager.IManagerConfig) *SAMLProviderManager { + m := &SAMLProviderManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameIAM, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v3/OS-FEDERATION", + Keyword: "identity_provider", + KeywordPlural: "identity_providers", + + ResourceKeyword: "identity_providers", + }} + m.SetDomainId(cfg.GetDomainId()) + return m +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_secgroup_rules.go b/pkg/multicloud/huaweistack/client/modules/mod_secgroup_rules.go new file mode 100644 index 0000000000..4b641666d1 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_secgroup_rules.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SSecgroupRuleManager struct { + SResourceManager +} + +func NewSecgroupRuleManager(cfg manager.IManagerConfig) *SSecgroupRuleManager { + return &SSecgroupRuleManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameVPC, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1", + Keyword: "security_group_rule", + KeywordPlural: "security_group_rules", + + ResourceKeyword: "security-group-rules", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_secgroups.go b/pkg/multicloud/huaweistack/client/modules/mod_secgroups.go new file mode 100644 index 0000000000..6a6bfc1217 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_secgroups.go @@ -0,0 +1,51 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SSecurityGroupManager struct { + SResourceManager +} + +func NewSecurityGroupManager(cfg manager.IManagerConfig) *SSecurityGroupManager { + return &SSecurityGroupManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameVPC, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1", + Keyword: "security_group", + KeywordPlural: "security_groups", + + ResourceKeyword: "security-groups", + }} +} + +func NewNovaSecurityGroupManager(cfg manager.IManagerConfig) *SSecurityGroupManager { + return &SSecurityGroupManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameECS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v2.1", + Keyword: "security_group", + KeywordPlural: "security_groups", + + ResourceKeyword: "os-security-groups", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_servers.go b/pkg/multicloud/huaweistack/client/modules/mod_servers.go new file mode 100644 index 0000000000..7942753447 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_servers.go @@ -0,0 +1,124 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "fmt" + "strconv" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +type SServerManager struct { + SResourceManager +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212668.html +// v.1.1 新增支持创建包年/包月的弹性云服务器。!!但是不支持查询等调用 https://support.huaweicloud.com/api-ecs/zh-cn_topic_0093055772.html +func NewServerManager(cfg manager.IManagerConfig) *SServerManager { + return &SServerManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameECS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1", + Keyword: "server", + KeywordPlural: "servers", + + ResourceKeyword: "cloudservers", + }} +} + +func (self *SServerManager) List(querys map[string]string) (*responses.ListResult, error) { + if offset, exists := querys["offset"]; !exists { + // 华为云分页参数各式各样。cloudserver offset从1开始。部分其他接口从0开始。 + // 另外部分接口使用pager分页 或者 maker分页 + querys["offset"] = "1" + } else { + n, err := strconv.Atoi(offset) + if err != nil { + return nil, fmt.Errorf("offset is invalid: %s", offset) + } + querys["offset"] = strconv.Itoa(n + 1) + } + return self.ListInContextWithSpec(nil, "detail", querys, self.KeywordPlural) +} + +/* +返回job id 或者 order id + +https://support.huaweicloud.com/api-ecs/zh-cn_topic_0093055772.html +创建按需的弹性云服务 ——> job_id 任务ID (返回数据uuid举例:"70a599e0-31e7-49b7-b260-868f441e862b") +包年包月机器 --> order_id (返回数据举例: "CS1711152257C60TL") +*/ +func (self *SServerManager) AsyncCreate(params jsonutils.JSONObject) (string, error) { + origin_version := self.version + self.version = "v1.1" + defer func() { self.version = origin_version }() + + ret, err := self.CreateInContextWithSpec(nil, "", params, "") + if err != nil { + log.Debugf("AsyncCreate %s", err) + return "", err + } + + log.Debugf("AsyncCreate result %s", ret.String()) + // 按需机器 + jobId, err := ret.GetString("job_id") + if err == nil { + return jobId, nil + } + + // 包年包月机器 + return ret.GetString("order_id") +} + +func (self *SServerManager) Create(params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return nil, fmt.Errorf("not supported.please use AsyncCreate") +} + +// 不推荐使用这个manager +func NewNovaServerManager(cfg manager.IManagerConfig) *SServerManager { + return &SServerManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameECS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v2.1", + Keyword: "server", + KeywordPlural: "servers", + + ResourceKeyword: "servers", + }} +} + +// 重装弹性云服务器操作系统(安装Cloud-init),请用这个manager +func NewServerV2Manager(cfg manager.IManagerConfig) *SServerManager { + return &SServerManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameECS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v2", + Keyword: "server", + KeywordPlural: "servers", + + ResourceKeyword: "cloudservers", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_services.go b/pkg/multicloud/huaweistack/client/modules/mod_services.go new file mode 100644 index 0000000000..7a874bf5dd --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_services.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SServiceManager struct { + SResourceManager +} + +func NewServiceManager(cfg manager.IManagerConfig) *SServiceManager { + return &SServiceManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameIAM, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v3", + Keyword: "service", + KeywordPlural: "services", + + ResourceKeyword: "services", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_sfs.go b/pkg/multicloud/huaweistack/client/modules/mod_sfs.go new file mode 100644 index 0000000000..731a63e9a4 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_sfs.go @@ -0,0 +1,48 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +type SfsTurboManager struct { + SResourceManager +} + +func NewSfsTurboManager(cfg manager.IManagerConfig) *SfsTurboManager { + return &SfsTurboManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameSFSTurbo, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1", + Keyword: "", + KeywordPlural: "shares", + + ResourceKeyword: "sfs-turbo/shares", + }} +} + +func (self *SfsTurboManager) List(querys map[string]string) (*responses.ListResult, error) { + return self.ListInContextWithSpec(nil, "detail", querys, self.KeywordPlural) +} + +func (self *SfsTurboManager) Create(params jsonutils.JSONObject) (jsonutils.JSONObject, error) { + return self.CreateInContextWithSpec(self.ctx, "", params, "") +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_snapshots.go b/pkg/multicloud/huaweistack/client/modules/mod_snapshots.go new file mode 100644 index 0000000000..82b0e7cf53 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_snapshots.go @@ -0,0 +1,59 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +type SSnapshotManager struct { + SResourceManager +} + +func NewSnapshotManager(cfg manager.IManagerConfig) *SSnapshotManager { + return &SSnapshotManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameEVS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v2", + Keyword: "snapshot", + KeywordPlural: "snapshots", + + ResourceKeyword: "snapshots", + }} +} + +func (self *SSnapshotManager) List(querys map[string]string) (*responses.ListResult, error) { + return self.ListInContextWithSpec(nil, "detail", querys, self.KeywordPlural) +} + +// https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408629.html +// 回滚快照只能用这个manger。其他情况请不要使用 +// 另外,香港-亚太还支持另外一个接口。https://support.huaweicloud.com/api-evs/zh-cn_topic_0142374138.html +func NewOsSnapshotManager(cfg manager.IManagerConfig) *SSnapshotManager { + return &SSnapshotManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameEVS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v2", + Keyword: "snapshot", + KeywordPlural: "snapshots", + + ResourceKeyword: "os-vendor-snapshots", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_snat_rules.go b/pkg/multicloud/huaweistack/client/modules/mod_snat_rules.go new file mode 100644 index 0000000000..71d350e3e6 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_snat_rules.go @@ -0,0 +1,41 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SNatSRuleManager struct { + SResourceManager +} + +func NewNatSManager(cfg manager.IManagerConfig) *SNatSRuleManager { + man := &SNatSRuleManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameNAT, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0", + Keyword: "snat_rule", + KeywordPlural: "snat_rules", + + ResourceKeyword: "snat_rules", + }} + if len(cfg.GetProjectId()) > 0 { + man.requestHook = &sProjectHook{cfg.GetProjectId()} + } + return man +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_subnets.go b/pkg/multicloud/huaweistack/client/modules/mod_subnets.go new file mode 100644 index 0000000000..c57a8c65a3 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_subnets.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SSubnetManager struct { + SResourceManager +} + +func NewSubnetManager(cfg manager.IManagerConfig) *SSubnetManager { + return &SSubnetManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameVPC, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1", + Keyword: "subnet", + KeywordPlural: "subnets", + + ResourceKeyword: "subnets", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_traces.go b/pkg/multicloud/huaweistack/client/modules/mod_traces.go new file mode 100644 index 0000000000..83af088aaf --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_traces.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type STraceManager struct { + SResourceManager +} + +func NewTraceManager(cfg manager.IManagerConfig) *STraceManager { + return &STraceManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameCTS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v2.0", + Keyword: "trace", + KeywordPlural: "traces", + + ResourceKeyword: "system/trace", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_users.go b/pkg/multicloud/huaweistack/client/modules/mod_users.go new file mode 100644 index 0000000000..adc1f60ab8 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_users.go @@ -0,0 +1,69 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "fmt" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" +) + +type SUserManager struct { + SResourceManager +} + +func NewUserManager(cfg manager.IManagerConfig) *SUserManager { + user := &SUserManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameIAM, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v3.0/OS-USER", + Keyword: "user", + KeywordPlural: "users", + + ResourceKeyword: "users", + }} + user.SetDomainId(cfg.GetDomainId()) + return user +} + +func (self *SUserManager) List(querys map[string]string) (*responses.ListResult, error) { + self.SetVersion("v3") + return self.SResourceManager.List(querys) +} + +func (self *SUserManager) Delete(id string) (jsonutils.JSONObject, error) { + self.SetVersion("v3") + return self.SResourceManager.Delete(id, nil) +} + +func (self *SUserManager) ResetPassword(id, password string) error { + params := map[string]interface{}{ + "user": map[string]string{ + "password": password, + }, + } + _, err := self.SResourceManager.Update(id, jsonutils.Marshal(params)) + return err +} + +func (self *SUserManager) ListGroups(userId string) (*responses.ListResult, error) { + self.SetVersion("v3") + return self.SResourceManager.ListInContextWithSpec(nil, fmt.Sprintf("%s/groups", userId), nil, "groups") +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_vpc_peerings.go b/pkg/multicloud/huaweistack/client/modules/mod_vpc_peerings.go new file mode 100644 index 0000000000..8c96ed1fd9 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_vpc_peerings.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SVpcPeeringManager struct { + SResourceManager +} + +func NewVpcPeeringManager(cfg manager.IManagerConfig) *SVpcPeeringManager { + return &SVpcPeeringManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameVPC, + Region: cfg.GetRegionId(), + ProjectId: "", + version: "v2.0", + Keyword: "peering", + KeywordPlural: "peerings", + + ResourceKeyword: "vpc/peerings", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_vpc_routes.go b/pkg/multicloud/huaweistack/client/modules/mod_vpc_routes.go new file mode 100644 index 0000000000..62615fd5c4 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_vpc_routes.go @@ -0,0 +1,38 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SVpcRouteManager struct { + SResourceManager +} + +func NewVpcRouteManager(cfg manager.IManagerConfig) *SVpcRouteManager { + return &SVpcRouteManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameVPC, + Region: cfg.GetRegionId(), + // the url should not include the field "projectid" in huawei cloud api v2.0 + ProjectId: "", + version: "v2.0", + Keyword: "route", + KeywordPlural: "routes", + + ResourceKeyword: "vpc/routes", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_vpcs.go b/pkg/multicloud/huaweistack/client/modules/mod_vpcs.go new file mode 100644 index 0000000000..b89585eaea --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_vpcs.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SVpcManager struct { + SResourceManager +} + +func NewVpcManager(cfg manager.IManagerConfig) *SVpcManager { + return &SVpcManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameVPC, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v1", + Keyword: "vpc", + KeywordPlural: "vpcs", + + ResourceKeyword: "vpcs", + }} +} diff --git a/pkg/multicloud/huaweistack/client/modules/mod_zones.go b/pkg/multicloud/huaweistack/client/modules/mod_zones.go new file mode 100644 index 0000000000..00ca67d5fb --- /dev/null +++ b/pkg/multicloud/huaweistack/client/modules/mod_zones.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package modules + +import ( + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" +) + +type SZoneManager struct { + SResourceManager +} + +func NewZoneManager(cfg manager.IManagerConfig) *SZoneManager { + return &SZoneManager{SResourceManager: SResourceManager{ + SBaseManager: NewBaseManager(cfg), + ServiceName: ServiceNameECS, + Region: cfg.GetRegionId(), + ProjectId: cfg.GetProjectId(), + version: "v2", + Keyword: "availabilityZoneInfo", + KeywordPlural: "availabilityZoneInfo", + + ResourceKeyword: "os-availability-zone", + }} +} diff --git a/pkg/multicloud/huaweistack/client/requests/doc.go b/pkg/multicloud/huaweistack/client/requests/doc.go new file mode 100644 index 0000000000..ba3824d718 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/requests/doc.go @@ -0,0 +1,15 @@ +// 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 requests // import "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/requests" diff --git a/pkg/multicloud/huaweistack/client/requests/requests.go b/pkg/multicloud/huaweistack/client/requests/requests.go new file mode 100644 index 0000000000..177dcd250c --- /dev/null +++ b/pkg/multicloud/huaweistack/client/requests/requests.go @@ -0,0 +1,259 @@ +// 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 requests + +import ( + "fmt" + "io" + "net/url" + "sort" + "strings" +) + +type IRequest interface { + GetScheme() string + GetMethod() string + GetEndpoint() string + GetPort() string + GetRegionId() string + GetProjectId() string + GetHost() string + GetURI() string + GetHeaders() map[string]string + GetQueryParams() map[string]string + GetFormParams() map[string]string + GetContent() []byte + GetBodyReader() io.Reader + GetProduct() string + GetVersion() string + + SetStringToSign(stringToSign string) + GetStringToSign() string + + SetContent(content []byte) + SetScheme(scheme string) + BuildUrl() string + BuildQueries() string + + AddHeaderParam(key, value string) + AddQueryParam(key, value string) + AddFormParam(key, value string) +} + +type SRequest struct { + Scheme string // HTTP、HTTPS + Method string // GET、PUT、DELETE、POST、PATCH + Endpoint string // ecs.cn-north-1.myhuaweicloud.com + Port string // 80 + RegionId string // cn-north-1 + + product string // 弹性云服务 ECS : ecs + version string // API版本: v2 + projectId string // 项目ID: 43cbe5e77aaf4665bbb962062dc1fc9d.可以为空。 + + resourcePath string // /users/{user_id}/groups + QueryParams map[string]string + Headers map[string]string + FormParams map[string]string + Content []byte + + queries string + + stringToSign string +} + +func (self *SRequest) GetProjectId() string { + return self.projectId +} + +func (self *SRequest) GetScheme() string { + return self.Scheme +} + +func (self *SRequest) GetMethod() string { + return self.Method +} + +func (self *SRequest) GetEndpoint() string { + return self.Endpoint +} + +func (self *SRequest) GetRegionId() string { + return self.RegionId +} + +func (self *SRequest) GetPort() string { + return self.Port +} + +func (self *SRequest) GetHeaders() map[string]string { + return self.Headers +} + +func (self *SRequest) GetQueryParams() map[string]string { + return self.QueryParams +} + +func (self *SRequest) GetFormParams() map[string]string { + return self.FormParams +} + +func (self *SRequest) GetContent() []byte { + return self.Content +} + +func (self *SRequest) GetBodyReader() io.Reader { + if self.FormParams != nil && len(self.FormParams) > 0 { + formData := GetUrlFormedMap(self.FormParams) + return strings.NewReader(formData) + } else { + return strings.NewReader(string(self.Content)) + } +} + +func (self *SRequest) GetProduct() string { + return self.product +} + +func (self *SRequest) GetVersion() string { + return self.version +} + +func (self *SRequest) SetStringToSign(stringToSign string) { + self.stringToSign = stringToSign +} + +func (self *SRequest) GetStringToSign() string { + return self.stringToSign +} + +func (self *SRequest) SetContent(content []byte) { + self.Content = content +} + +func (self *SRequest) SetScheme(scheme string) { + self.Scheme = scheme +} + +func (self *SRequest) BuildUrl() string { + scheme := strings.ToLower(self.Scheme) + baseUrl := fmt.Sprintf("%s://%s", scheme, self.GetHost()) + queries := self.BuildQueries() + if len(queries) > 0 { + return baseUrl + self.GetURI() + "?" + queries + } else { + return baseUrl + self.GetURI() + } +} + +func (self *SRequest) GetHost() string { + scheme := strings.ToLower(self.Scheme) + host := self.GetEndpoint() + if len(self.Port) > 0 { + if (scheme == "http" && self.Port == "80") || (scheme == "https" && self.Port == "443") { + host = fmt.Sprintf("%s:%s", host, self.Port) + } + } + + return host +} + +func (self *SRequest) GetURI() string { + // URI + uri := "" + for _, m := range []string{self.version, self.projectId} { + if len(m) > 0 { + uri += fmt.Sprintf("/%s", m) + } + } + + if len(self.resourcePath) > 0 { + s := "" + if !strings.HasPrefix(self.resourcePath, "/") { + s = "/" + } + + if strings.HasSuffix(self.resourcePath, "/") { + strings.TrimSuffix(s, "/") + } + + uri = uri + s + self.resourcePath + } + + return uri +} + +func (self *SRequest) BuildQueries() string { + self.queries = GetUrlFormedMap(self.QueryParams) + return self.queries +} + +func (self *SRequest) AddHeaderParam(key, value string) { + self.Headers[key] = value +} + +func (self *SRequest) AddQueryParam(key, value string) { + self.QueryParams[key] = value +} + +func (self *SRequest) AddFormParam(key, value string) { + self.FormParams[key] = value +} + +func GetUrlFormedMap(source map[string]string) string { + // 按key排序后编译 + keys := make([]string, 0) + for k := range source { + keys = append(keys, k) + } + + sort.Slice(keys, func(i, j int) bool { + return strings.ToLower(keys[i]) < strings.ToLower(keys[j]) + }) + + urlEncoder := url.Values{} + for _, k := range keys { + urlEncoder.Add(k, source[k]) + } + + return urlEncoder.Encode() +} + +func defaultRequest() (request *SRequest) { + request = &SRequest{ + Scheme: "HTTPS", + Method: "GET", + QueryParams: make(map[string]string), + Headers: map[string]string{}, + FormParams: make(map[string]string), + } + return +} + +func NewResourceRequest(endpoint, method, product, version, region, project, resourcePath string) *SRequest { + return &SRequest{ + Scheme: "HTTPS", + Method: method, + Endpoint: endpoint, + product: product, + RegionId: region, + version: version, + projectId: project, + resourcePath: resourcePath, + QueryParams: make(map[string]string), + Headers: map[string]string{}, + FormParams: make(map[string]string), + } +} diff --git a/pkg/multicloud/huaweistack/client/responses/doc.go b/pkg/multicloud/huaweistack/client/responses/doc.go new file mode 100644 index 0000000000..00cfe7867e --- /dev/null +++ b/pkg/multicloud/huaweistack/client/responses/doc.go @@ -0,0 +1,15 @@ +// 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 responses // import "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" diff --git a/pkg/multicloud/huaweistack/client/responses/responses.go b/pkg/multicloud/huaweistack/client/responses/responses.go new file mode 100644 index 0000000000..7bdae4aa35 --- /dev/null +++ b/pkg/multicloud/huaweistack/client/responses/responses.go @@ -0,0 +1,77 @@ +// 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 responses + +import ( + "regexp" + "strings" + + "yunion.io/x/jsonutils" +) + +type ListResult struct { + Data []jsonutils.JSONObject + Total int + Limit int + Offset int + NextLink string +} + +func ListResult2JSONWithKey(result *ListResult, key string) jsonutils.JSONObject { + obj := jsonutils.NewDict() + if result.Total > 0 { + obj.Add(jsonutils.NewInt(int64(result.Total)), "total") + } + if result.Limit > 0 { + obj.Add(jsonutils.NewInt(int64(result.Limit)), "limit") + } + if result.Offset > 0 { + obj.Add(jsonutils.NewInt(int64(result.Offset)), "offset") + } + arr := jsonutils.NewArray(result.Data...) + obj.Add(arr, key) + return obj +} + +func ListResult2JSON(result *ListResult) jsonutils.JSONObject { + return ListResult2JSONWithKey(result, "data") +} + +func JSON2ListResult(result jsonutils.JSONObject) *ListResult { + total, _ := result.Int("total") + limit, _ := result.Int("limit") + offset, _ := result.Int("offset") + data, _ := result.GetArray("data") + return &ListResult{Data: data, Total: int(total), Limit: int(limit), Offset: int(offset)} +} + +// 将key中的冒号替换成 +func TransColonToDot(obj jsonutils.JSONObject) (jsonutils.JSONObject, error) { + re, _ := regexp.Compile("[a-zA-Z0-9](:+)[^\"]+\"\\s*:\\s*") + + if obj == nil { + return obj, nil + } + + newStr := re.ReplaceAllStringFunc(obj.String(), func(s string) string { + count := strings.Count(s, ":") + if count > 1 { + return strings.Replace(s, ":", ".", count-1) + } + return s + }) + + return jsonutils.ParseString(newStr) +} diff --git a/pkg/multicloud/huaweistack/client/responses/responses_test.go b/pkg/multicloud/huaweistack/client/responses/responses_test.go new file mode 100644 index 0000000000..8cd532ddba --- /dev/null +++ b/pkg/multicloud/huaweistack/client/responses/responses_test.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package responses + +import ( + "testing" + + "yunion.io/x/jsonutils" +) + +func TestTransColonToDot(t *testing.T) { + raw := `{"A:b::C": "1:2:3", "A": true, "B": ["1:2", ":", "c"], "D:E": true}` + obj, err := jsonutils.ParseString(raw) + if err != nil { + t.Fatalf("json parse: %v", err) + } + gotj, err := TransColonToDot(obj) + if err != nil { + t.Fatalf("trans: %v", err) + } + wantj, _ := jsonutils.ParseString(`{"A":true,"A.b..C":"1:2:3","B":["1:2",":","c"],"D.E":true}`) + if !wantj.Equals(gotj) { + t.Errorf("trans failed, want:\n%s\ngot:\n%s", wantj, gotj) + } +} diff --git a/pkg/multicloud/huaweistack/cloudgroup.go b/pkg/multicloud/huaweistack/cloudgroup.go new file mode 100644 index 0000000000..bb3aabc060 --- /dev/null +++ b/pkg/multicloud/huaweistack/cloudgroup.go @@ -0,0 +1,297 @@ +// 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 huaweistack + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SCloudgroup struct { + client *SHuaweiClient + Name string + Description string + Id string + CreateTime string +} + +func (group *SCloudgroup) GetName() string { + return group.Name +} + +func (group *SCloudgroup) GetDescription() string { + return group.Description +} + +func (group *SCloudgroup) GetGlobalId() string { + return group.Id +} + +func (group *SCloudgroup) Delete() error { + return group.client.DeleteGroup(group.Id) +} + +func (group *SCloudgroup) AddUser(name string) error { + user, err := group.client.GetIClouduserByName(name) + if err != nil { + return errors.Wrap(err, "GetIClouduserByName") + } + return group.client.AddUserToGroup(group.Id, user.GetGlobalId()) +} + +func (group *SCloudgroup) RemoveUser(name string) error { + user, err := group.client.GetIClouduserByName(name) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil + } + return errors.Wrapf(err, "GetIClouduserByName(%s)", name) + } + return group.client.RemoveUserFromGroup(group.Id, user.GetGlobalId()) +} + +func (group *SCloudgroup) DetachSystemPolicy(roleId string) error { + return group.client.DetachGroupRole(group.Id, roleId) +} + +func (group *SCloudgroup) DetachCustomPolicy(roleId string) error { + return group.client.DetachGroupRole(group.Id, roleId) +} + +func (group *SCloudgroup) AttachSystemPolicy(roleId string) error { + return group.client.AttachGroupRole(group.Id, roleId) +} + +func (group *SCloudgroup) AttachCustomPolicy(roleId string) error { + return group.client.AttachGroupRole(group.Id, roleId) +} + +func (group *SCloudgroup) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + roles, err := group.client.GetGroupRoles(group.Id) + if err != nil { + return nil, errors.Wrap(err, "GetGroupRoles") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range roles { + ret = append(ret, &roles[i]) + } + return ret, nil +} + +func (group *SCloudgroup) GetICustomCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return []cloudprovider.ICloudpolicy{}, nil +} + +func (group *SCloudgroup) GetICloudusers() ([]cloudprovider.IClouduser, error) { + users, err := group.client.GetGroupUsers(group.Id) + if err != nil { + return nil, err + } + ret := []cloudprovider.IClouduser{} + for i := range users { + users[i].client = group.client + ret = append(ret, &users[i]) + } + return ret, nil +} + +func (self *SHuaweiClient) GetGroups(domainId, name string) ([]SCloudgroup, error) { + params := map[string]string{} + if len(domainId) > 0 { + params["domain_id"] = self.ownerId + } + if len(name) > 0 { + params["name"] = name + } + + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + + groups := []SCloudgroup{} + err = doListAllWithNextLink(client.Groups.List, params, &groups) + if err != nil { + return nil, errors.Wrap(err, "doListAllWithOffset") + } + return groups, nil +} + +func (self *SHuaweiClient) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups, err := self.GetGroups("", "") + if err != nil { + return nil, errors.Wrap(err, "GetGroup") + } + ret := []cloudprovider.ICloudgroup{} + for i := range groups { + if groups[i].Name != "admin" { + groups[i].client = self + ret = append(ret, &groups[i]) + } + } + return ret, nil +} + +func (self *SHuaweiClient) GetGroupUsers(groupId string) ([]SClouduser, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + + resp, err := client.Groups.ListInContextWithSpec(nil, fmt.Sprintf("%s/users", groupId), nil, "users") + if err != nil { + return nil, errors.Wrap(err, "") + } + users := []SClouduser{} + err = jsonutils.Update(&users, resp.Data) + if err != nil { + return nil, errors.Wrap(err, "jsonutils.Update") + } + return users, nil +} + +func (self *SHuaweiClient) GetGroupRoles(groupId string) ([]SRole, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + resp, err := client.Groups.ListRoles(self.ownerId, groupId) + if err != nil { + return nil, errors.Wrap(err, "ListRoles") + } + roles := []SRole{} + err = jsonutils.Update(&roles, resp.Data) + if err != nil { + return nil, errors.Wrap(err, "jsonutils.Update") + } + return roles, nil +} + +func (self *SHuaweiClient) CreateGroup(name, desc string) (*SCloudgroup, error) { + params := map[string]string{ + "name": name, + } + if len(desc) > 0 { + params["description"] = desc + } + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + + group := SCloudgroup{client: self} + err = DoCreate(client.Groups.Create, jsonutils.Marshal(map[string]interface{}{"group": params}), &group) + if err != nil { + return nil, errors.Wrap(err, "DoCreate") + } + return &group, nil +} + +func (self *SHuaweiClient) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { + group, err := self.CreateGroup(name, desc) + if err != nil { + return nil, errors.Wrap(err, "CreateGroup") + } + return group, nil +} + +func (self *SHuaweiClient) DeleteGroup(id string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + return DoDeleteWithSpec(client.Groups.DeleteInContextWithSpec, nil, id, "", nil, nil) +} + +func (self *SHuaweiClient) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + groups, err := self.GetGroups(self.ownerId, name) + if err != nil { + return nil, errors.Wrap(err, "GetGroups") + } + if len(groups) == 0 { + return nil, cloudprovider.ErrNotFound + } + if len(groups) > 1 { + return nil, cloudprovider.ErrDuplicateId + } + groups[0].client = self + return &groups[0], nil +} + +func (self *SHuaweiClient) AddUserToGroup(groupId, userId string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + _, err = client.Groups.UpdateInContextWithSpec(nil, groupId, fmt.Sprintf("users/%s", userId), nil, "") + return err +} + +func (self *SHuaweiClient) RemoveUserFromGroup(groupId, userId string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + _, err = client.Groups.DeleteInContextWithSpec(nil, groupId, fmt.Sprintf("users/%s", userId), nil, nil, "") + return err +} + +func (self *SHuaweiClient) DetachGroupRole(groupId, roleId string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + err = client.Groups.DeleteRole(self.ownerId, groupId, roleId) + if err != nil { + return errors.Wrapf(err, "DeleteRole") + } + projects, err := self.GetProjects() + if err != nil { + return errors.Wrapf(err, "GetProjects") + } + for _, project := range projects { + err = client.Groups.DeleteProjectRole(project.ID, groupId, roleId) + if err != nil { + return errors.Wrapf(err, "DeleteProjectRole") + } + } + return nil +} + +func (self *SHuaweiClient) AttachGroupRole(groupId, roleId string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + err = client.Groups.AddRole(self.ownerId, groupId, roleId) + if err != nil { + return errors.Wrapf(err, "AddRole") + } + projects, err := self.GetProjects() + if err != nil { + return errors.Wrapf(err, "GetProjects") + } + for _, project := range projects { + err = client.Groups.AddProjectRole(project.ID, groupId, roleId) + if err != nil { + return errors.Wrapf(err, "AddProjectRole") + } + } + return nil +} diff --git a/pkg/multicloud/huaweistack/clouduser.go b/pkg/multicloud/huaweistack/clouduser.go new file mode 100644 index 0000000000..3a545c60e5 --- /dev/null +++ b/pkg/multicloud/huaweistack/clouduser.go @@ -0,0 +1,221 @@ +// 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 huaweistack + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/modules" +) + +type SLink struct { + Next string + Previous string + Self string +} + +type SClouduser struct { + client *SHuaweiClient + + Description string + DomainId string + Enabled bool + ForceResetPwd bool + Id string + LastProjectId string + Links SLink + Name string + PasswordExpiresAt string + PwdStatus bool +} + +func (user *SClouduser) GetGlobalId() string { + return user.Id +} + +func (user *SClouduser) GetName() string { + return user.Name +} + +func (user *SClouduser) GetEmailAddr() string { + return "" +} + +func (user *SClouduser) GetInviteUrl() string { + return "" +} + +func (user *SClouduser) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return []cloudprovider.ICloudpolicy{}, nil +} + +func (user *SClouduser) GetICustomCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return []cloudprovider.ICloudpolicy{}, nil +} + +func (user *SClouduser) AttachSystemPolicy(policyType string) error { + return cloudprovider.ErrNotSupported +} + +func (user *SClouduser) AttachCustomPolicy(policyType string) error { + return cloudprovider.ErrNotSupported +} + +func (user *SClouduser) DetachSystemPolicy(policyId string) error { + return cloudprovider.ErrNotSupported +} + +func (user *SClouduser) DetachCustomPolicy(policyId string) error { + return cloudprovider.ErrNotSupported +} + +func (user *SClouduser) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + groups, err := user.client.ListUserGroups(user.Id) + if err != nil { + return nil, errors.Wrap(err, "Users.ListGroups") + } + ret := []cloudprovider.ICloudgroup{} + for i := range groups { + groups[i].client = user.client + ret = append(ret, &groups[i]) + } + return ret, nil +} + +func (user *SClouduser) Delete() error { + return user.client.DeleteClouduser(user.Id) +} + +func (user *SClouduser) IsConsoleLogin() bool { + return user.Enabled == true +} + +func (user *SClouduser) ResetPassword(password string) error { + return user.client.ResetClouduserPassword(user.Id, password) +} + +func (self *SHuaweiClient) DeleteClouduser(id string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + _, err = client.Users.Delete(id) + return err +} + +func (self *SHuaweiClient) ListUserGroups(userId string) ([]SCloudgroup, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + result, err := client.Users.ListGroups(userId) + if err != nil { + return nil, errors.Wrap(err, "Users.ListGroups") + } + groups := []SCloudgroup{} + err = jsonutils.Update(&groups, result.Data) + if err != nil { + return nil, errors.Wrap(err, "jsonutils.Update") + } + return groups, nil +} + +func (self *SHuaweiClient) GetCloudusers(name string) ([]SClouduser, error) { + params := map[string]string{} + if len(name) > 0 { + params["name"] = name + } + users := []SClouduser{} + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + err = doListAllWithOffset(client.Users.List, params, &users) + if err != nil { + return nil, errors.Wrap(err, "doListAllWithOffset") + } + return users, nil +} + +func (self *SHuaweiClient) GetICloudusers() ([]cloudprovider.IClouduser, error) { + users, err := self.GetCloudusers("") + if err != nil { + return nil, errors.Wrap(err, "GetCloudusers") + } + iUsers := []cloudprovider.IClouduser{} + for i := range users { + if users[i].Name != self.ownerName { + users[i].client = self + iUsers = append(iUsers, &users[i]) + } + } + return iUsers, nil +} + +func (self *SHuaweiClient) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + users, err := self.GetCloudusers(name) + if err != nil { + return nil, errors.Wrapf(err, "GetCloudusers(%s)", name) + } + if len(users) == 0 { + return nil, cloudprovider.ErrNotFound + } + if len(users) > 1 { + return nil, cloudprovider.ErrDuplicateId + } + users[0].client = self + return &users[0], nil +} + +func (self *SHuaweiClient) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + return self.CreateClouduser(conf.Name, conf.Password, conf.Desc) +} + +func (self *SHuaweiClient) CreateClouduser(name, password, desc string) (*SClouduser, error) { + params := map[string]string{ + "name": name, + "domain_id": self.ownerId, + } + if len(password) > 0 { + params["password"] = password + } + if len(desc) > 0 { + params["description"] = desc + } + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + user := SClouduser{client: self} + err = DoCreate(client.Users.Create, jsonutils.Marshal(map[string]interface{}{"user": params}), &user) + if err != nil { + ce, ok := err.(*modules.HuaweiClientError) + if ok && len(ce.Errorcode) > 0 && ce.Errorcode[0] == "1101" { + return nil, errors.Wrap(err, `IAM user name. The length is between 5 and 32. The first digit is not a number. Special characters can only contain the '_' '-' or ' '`) //https://support.huaweicloud.com/api-iam/iam_08_0015.html + } + return nil, errors.Wrap(err, "DoCreate") + } + return &user, nil +} + +func (self *SHuaweiClient) ResetClouduserPassword(id, password string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + return client.Users.ResetPassword(id, password) +} diff --git a/pkg/multicloud/huaweistack/consts.go b/pkg/multicloud/huaweistack/consts.go new file mode 100644 index 0000000000..3a8db71130 --- /dev/null +++ b/pkg/multicloud/huaweistack/consts.go @@ -0,0 +1,90 @@ +// 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 huaweistack + +// 华为云返回的时间格式 +const DATETIME_FORMAT = "2006-01-02T15:04:05.999999999" + +// Task status +const ( + TASK_SUCCESS = "SUCCESS" + TASK_FAIL = "FAIL" +) + +// Charging Type +const ( + POST_PAID = "postPaid" // 按需付费 + PRE_PAID = "prePaid" // 包年包月 +) + +// 资源类型 https://support.huaweicloud.com/api-oce/zh-cn_topic_0079291752.html +const ( + RESOURCE_TYPE_VM = "hws.resource.type.vm" // ECS虚拟机 + RESOURCE_TYPE_VOLUME = "hws.resource.type.volume" // EVS卷 + RESOURCE_TYPE_BANDWIDTH = "hws.resource.type.bandwidth" // VPC带宽 + RESOURCE_TYPE_IP = "hws.resource.type.ip" // VPC公网IP + RESOURCE_TYPE_IMAGE = "hws.resource.type.marketplace" // 市场镜像 +) + +// Not Found Error code +// 网络等资源直接通过http code 404即可判断资源不存在。另外有些资源可能不是返回404这里单独列出来 +const ( + VM_NOT_FOUND = "Ecs.0114" // 云服务器不存在 + ECS_NOT_FOUND = "Ecs.0614" // 弹性云服务器不存在 + IMG_ID_NOT_FOUND = "IMG.0027" // 请求的镜像ID不存在 + IMG_NOT_FOUND = "IMG.0027" // 镜像不存在 + IMG_ERR_NOT_FOUND = "IMG.0057" // 镜像文件不存在或者为空或者不是允许格式的文件 + IMG_BACKUP_NOT_FOUND = "IMG.0020" // 备份不存在 + IMG_VM_BACKUP_NOT_FOUND = "IMG.0127" // 云服务器备份不存在 + IMG_VM_NOT_FOUND = "IMG.0005" // 云主机不存在 + JOB_NOT_FOUND = "Common.0011" // jobId为空 + EVS_NOT_FOUND = "EVS.5404" // 磁盘、快照和备份等资源未找到。 + FIP_NOT_FOUND = "VPC.0504" // 未找到弹性公网IP。 + VPC_NOT_FOUND = "VPC.0012" // 未找到弹性公网VPC。 +) + +var NOT_FOUND_CODES = []string{ + VM_NOT_FOUND, + ECS_NOT_FOUND, + IMG_ID_NOT_FOUND, + IMG_NOT_FOUND, + IMG_ERR_NOT_FOUND, + IMG_BACKUP_NOT_FOUND, + IMG_VM_BACKUP_NOT_FOUND, + IMG_VM_NOT_FOUND, + JOB_NOT_FOUND, + EVS_NOT_FOUND, + FIP_NOT_FOUND, + VPC_NOT_FOUND, +} + +// 包周期资源相关常量 +// https://support.huaweicloud.com/api-bpconsole/zh-cn_topic_0082522029.html +// expire_mode 0:进入宽限期 1:转按需 2:自动退订 3:自动续订(当前只支持ECS、EVS和VPC) +const ( + EXPIRE_MODE_TO_POSTPAID = 1 + EXPIRE_MODE_AUTO_UNSUBSCRIBE = 2 + EXPIRE_MODE_AUTO_RENEW = 3 +) + +const ( + PERIOD_TYPE_MONTH = 2 + PERIOD_TYPE_YEAR = 3 +) + +const ( + AUTO_PAY_TRUE = 1 + AUTO_PAY_FALSE = 0 +) diff --git a/pkg/multicloud/huaweistack/dbinstance.go b/pkg/multicloud/huaweistack/dbinstance.go new file mode 100644 index 0000000000..29b0980058 --- /dev/null +++ b/pkg/multicloud/huaweistack/dbinstance.go @@ -0,0 +1,717 @@ +// 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 huaweistack + +import ( + "context" + "fmt" + "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" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/util/billing" +) + +type SBackupStrategy struct { + KeepDays int + StartTime string +} + +type SDatastore struct { + Type string + Version string +} + +type SHa struct { + ReplicationMode string +} + +type SNonde struct { + AvailabilityZone string + Id string + Name string + Role string + Staus string +} + +type SVolume struct { + Size int + Type string +} + +type SRelatedInstance struct { + Id string + Type string +} + +type SDBInstance struct { + multicloud.SDBInstanceBase + multicloud.HuaweiTags + region *SRegion + + flavorCache []SDBInstanceFlavor + + BackupStrategy SBackupStrategy + Created string //time.Time + Datastore SDatastore + DbUserName string + DIskEncryptionId string + FlavorRef string + Ha SHa + Id string + MaintenanceWindow string + Name string + Nodes []SNonde + Port int + PrivateIps []string + PublicIps []string + Region string + RelatedInstance []SRelatedInstance + SecurityGroupId string + Status string + SubnetId string + SwitchStrategy string + TimeZone string + Type string + Updated string //time.Time + Volume SVolume + VpcId string + EnterpriseProjectId string +} + +func (region *SRegion) GetDBInstances() ([]SDBInstance, error) { + params := map[string]string{} + dbinstances := []SDBInstance{} + err := doListAllWithOffset(region.ecsClient.DBInstance.List, params, &dbinstances) + return dbinstances, err +} + +func (region *SRegion) GetDBInstance(instanceId string) (*SDBInstance, error) { + if len(instanceId) == 0 { + return nil, cloudprovider.ErrNotFound + } + instance := SDBInstance{region: region} + err := DoGet(region.ecsClient.DBInstance.Get, instanceId, nil, &instance) + return &instance, err +} + +func (rds *SDBInstance) GetName() string { + return rds.Name +} + +func (rds *SDBInstance) GetId() string { + return rds.Id +} + +func (rds *SDBInstance) GetGlobalId() string { + return rds.GetId() +} + +// 值为“BUILD”,表示实例正在创建。 +// 值为“ACTIVE”,表示实例正常。 +// 值为“FAILED”,表示实例异常。 +// 值为“FROZEN”,表示实例冻结。 +// 值为“MODIFYING”,表示实例正在扩容。 +// 值为“REBOOTING”,表示实例正在重启。 +// 值为“RESTORING”,表示实例正在恢复。 +// 值为“MODIFYING INSTANCE TYPE”,表示实例正在转主备。 +// 值为“SWITCHOVER”,表示实例正在主备切换。 +// 值为“MIGRATING”,表示实例正在迁移。 +// 值为“BACKING UP”,表示实例正在进行备份。 +// 值为“MODIFYING DATABASE PORT”,表示实例正在修改数据库端口。 +// 值为“STORAGE FULL”,表示实例磁盘空间满。 + +func (rds *SDBInstance) GetStatus() string { + switch rds.Status { + case "BUILD", "MODIFYING", "MODIFYING INSTANCE TYPE", "SWITCHOVER", "MODIFYING DATABASE PORT": + return api.DBINSTANCE_DEPLOYING + case "ACTIVE": + return api.DBINSTANCE_RUNNING + case "FAILED", "FROZEN", "STORAGE FULL": + return api.DBINSTANCE_UNKNOWN + case "REBOOTING": + return api.DBINSTANCE_REBOOTING + case "RESTORING": + return api.DBINSTANCE_RESTORING + case "MIGRATING": + return api.DBINSTANCE_MIGRATING + case "BACKING UP": + return api.DBINSTANCE_BACKING_UP + } + return rds.Status +} + +func (rds *SDBInstance) GetBillingType() string { + _, err := rds.region.GetOrderResourceDetail(fmt.Sprintf("%s.vm", rds.Id)) + if err != nil { + return billing_api.BILLING_TYPE_POSTPAID + } + return billing_api.BILLING_TYPE_PREPAID +} + +func (rds *SDBInstance) GetSecurityGroupIds() ([]string, error) { + return []string{rds.SecurityGroupId}, nil +} + +func (rds *SDBInstance) fetchFlavor() error { + if len(rds.flavorCache) > 0 { + return nil + } + flavors, err := rds.region.GetDBInstanceFlavors(rds.Datastore.Type, rds.Datastore.Version) + if err != nil { + return err + } + rds.flavorCache = flavors + return nil +} + +func (rds *SDBInstance) GetExpiredAt() time.Time { + order, err := rds.region.GetOrderResourceDetail(fmt.Sprintf("%s.vm", rds.Id)) + if err != nil { + return time.Time{} + } + return order.ExpireTime +} + +func (rds *SDBInstance) GetStorageType() string { + return rds.Volume.Type +} + +func (rds *SDBInstance) GetCreatedAt() time.Time { + t, err := time.Parse("2006-01-02T15:04:05Z0700", rds.Created) + if err != nil { + return time.Time{} + } + return t +} + +func (rds *SDBInstance) GetEngine() string { + return rds.Datastore.Type +} + +func (rds *SDBInstance) GetEngineVersion() string { + return rds.Datastore.Version +} + +func (rds *SDBInstance) GetInstanceType() string { + return rds.FlavorRef +} + +func (rds *SDBInstance) GetCategory() string { + switch rds.Type { + case "Single": + return api.HUAWEI_DBINSTANCE_CATEGORY_SINGLE + case "Ha": + return api.HUAWEI_DBINSTANCE_CATEGORY_HA + case "Replica": + return api.HUAWEI_DBINSTANCE_CATEGORY_REPLICA + } + return rds.Type +} + +func (rds *SDBInstance) GetVcpuCount() int { + err := rds.fetchFlavor() + if err != nil { + log.Errorf("failed to fetch flavors: %v", err) + return 0 + } + for _, flavor := range rds.flavorCache { + if flavor.SpecCode == rds.FlavorRef { + return flavor.Vcpus + } + } + return 0 +} + +func (rds *SDBInstance) GetVmemSizeMB() int { + err := rds.fetchFlavor() + if err != nil { + log.Errorf("failed to fetch flavors: %v", err) + return 0 + } + for _, flavor := range rds.flavorCache { + if flavor.SpecCode == rds.FlavorRef { + return flavor.Ram * 1024 + } + } + return 0 +} + +func (rds *SDBInstance) GetDiskSizeGB() int { + return rds.Volume.Size +} + +func (rds *SDBInstance) GetPort() int { + return rds.Port +} + +func (rds *SDBInstance) GetMaintainTime() string { + return rds.MaintenanceWindow +} + +func (rds *SDBInstance) GetIVpcId() string { + return rds.VpcId +} + +func (rds *SDBInstance) GetProjectId() string { + return rds.EnterpriseProjectId +} + +func (rds *SDBInstance) Refresh() error { + instance, err := rds.region.GetDBInstance(rds.Id) + if err != nil { + return err + } + return jsonutils.Update(rds, instance) +} + +func (rds *SDBInstance) GetZone1Id() string { + return rds.GetZoneIdByRole("master") +} + +func (rds *SDBInstance) GetZoneIdByRole(role string) string { + for _, node := range rds.Nodes { + if node.Role == role { + zone, err := rds.region.getZoneById(node.AvailabilityZone) + if err != nil { + log.Errorf("failed to found zone %s for rds %s error: %v", node.AvailabilityZone, rds.Name, err) + return "" + } + return zone.GetGlobalId() + } + } + return "" +} + +func (rds *SDBInstance) GetZone2Id() string { + return rds.GetZoneIdByRole("slave") +} + +func (rds *SDBInstance) GetZone3Id() string { + return "" +} + +type SRdsNetwork struct { + SubnetId string + IP string +} + +func (rds *SDBInstance) GetDBNetworks() ([]cloudprovider.SDBInstanceNetwork, error) { + ret := []cloudprovider.SDBInstanceNetwork{} + for _, ip := range rds.PrivateIps { + network := cloudprovider.SDBInstanceNetwork{ + IP: ip, + NetworkId: rds.SubnetId, + } + ret = append(ret, network) + } + return ret, nil +} + +func (rds *SDBInstance) GetInternalConnectionStr() string { + for _, ip := range rds.PrivateIps { + return ip + } + return "" +} + +func (rds *SDBInstance) GetConnectionStr() string { + for _, ip := range rds.PublicIps { + return ip + } + return "" +} + +func (region *SRegion) GetIDBInstanceById(instanceId string) (cloudprovider.ICloudDBInstance, error) { + dbinstance, err := region.GetDBInstance(instanceId) + if err != nil { + log.Errorf("failed to get dbinstance by id %s error: %v", instanceId, err) + } + return dbinstance, err +} + +func (region *SRegion) GetIDBInstances() ([]cloudprovider.ICloudDBInstance, error) { + instances, err := region.GetDBInstances() + if err != nil { + return nil, errors.Wrapf(err, "region.GetDBInstances()") + } + idbinstances := []cloudprovider.ICloudDBInstance{} + for i := 0; i < len(instances); i++ { + instances[i].region = region + idbinstances = append(idbinstances, &instances[i]) + } + return idbinstances, nil +} + +func (rds *SDBInstance) GetIDBInstanceParameters() ([]cloudprovider.ICloudDBInstanceParameter, error) { + parameters, err := rds.region.GetDBInstanceParameters(rds.Id) + if err != nil { + return nil, err + } + iparameters := []cloudprovider.ICloudDBInstanceParameter{} + for i := 0; i < len(parameters); i++ { + iparameters = append(iparameters, ¶meters[i]) + } + return iparameters, nil +} + +func (rds *SDBInstance) GetIDBInstanceDatabases() ([]cloudprovider.ICloudDBInstanceDatabase, error) { + databases, err := rds.region.GetDBInstanceDatabases(rds.Id) + if err != nil { + return nil, errors.Wrap(err, "rds.region.GetDBInstanceDatabases(rds.Id)") + } + + idatabase := []cloudprovider.ICloudDBInstanceDatabase{} + for i := 0; i < len(databases); i++ { + databases[i].instance = rds + idatabase = append(idatabase, &databases[i]) + } + return idatabase, nil +} + +func (rds *SDBInstance) GetIDBInstanceAccounts() ([]cloudprovider.ICloudDBInstanceAccount, error) { + accounts, err := rds.region.GetDBInstanceAccounts(rds.Id) + if err != nil { + return nil, errors.Wrap(err, "rds.region.GetDBInstanceAccounts(rds.Id)") + } + + user := "root" + if rds.GetEngine() == api.DBINSTANCE_TYPE_SQLSERVER { + user = "rduser" + } + + accounts = append(accounts, SDBInstanceAccount{ + Name: user, + instance: rds, + }) + + iaccounts := []cloudprovider.ICloudDBInstanceAccount{} + for i := 0; i < len(accounts); i++ { + accounts[i].instance = rds + iaccounts = append(iaccounts, &accounts[i]) + } + return iaccounts, nil +} + +func (rds *SDBInstance) Delete() error { + return rds.region.DeleteDBInstance(rds.Id) +} + +func (region *SRegion) DeleteDBInstance(instanceId string) error { + _, err := region.ecsClient.DBInstance.Delete(instanceId, nil) + return err +} + +func (region *SRegion) CreateIDBInstance(desc *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { + zoneIds := []string{} + zones, err := region.GetIZones() + if err != nil { + return nil, err + } + for _, zone := range zones { + zoneIds = append(zoneIds, zone.GetId()) + } + + if len(desc.SecgroupIds) == 0 { + return nil, fmt.Errorf("Missing secgroupId") + } + + params := map[string]interface{}{ + "region": region.ID, + "name": desc.Name, + "datastore": map[string]string{ + "type": desc.Engine, + "version": desc.EngineVersion, + }, + "password": desc.Password, + "volume": map[string]interface{}{ + "type": desc.StorageType, + "size": desc.DiskSizeGB, + }, + "vpc_id": desc.VpcId, + "subnet_id": desc.NetworkId, + "security_group_id": desc.SecgroupIds[0], + } + + if len(desc.ProjectId) > 0 { + params["enterprise_project_id"] = desc.ProjectId + } + + if len(desc.MasterInstanceId) > 0 { + params["replica_of_id"] = desc.MasterInstanceId + delete(params, "security_group_id") + } + + if len(desc.RdsId) > 0 && len(desc.BackupId) > 0 { + params["restore_point"] = map[string]interface{}{ + "backup_id": desc.BackupId, + "instance_id": desc.RdsId, + "type": "backup", + } + } + + switch desc.Category { + case api.HUAWEI_DBINSTANCE_CATEGORY_HA: + switch desc.Engine { + case api.DBINSTANCE_TYPE_MYSQL, api.DBINSTANCE_TYPE_POSTGRESQL: + params["ha"] = map[string]string{ + "mode": "Ha", + "replication_mode": "async", + } + case api.DBINSTANCE_TYPE_SQLSERVER: + params["ha"] = map[string]string{ + "mode": "Ha", + "replication_mode": "sync", + } + } + case api.HUAWEI_DBINSTANCE_CATEGORY_SINGLE: + case api.HUAWEI_DBINSTANCE_CATEGORY_REPLICA: + } + + if desc.BillingCycle != nil { + periodType := "month" + periodNum := desc.BillingCycle.GetMonths() + if desc.BillingCycle.GetYears() > 0 { + periodType = "year" + periodNum = desc.BillingCycle.GetYears() + } + params["charge_info"] = map[string]interface{}{ + "charge_mode": "prePaid", + "period_type": periodType, + "period_num": periodNum, + "is_auto_renew": false, + } + } + params["flavor_ref"] = desc.InstanceType + params["availability_zone"] = desc.ZoneId + resp, err := region.ecsClient.DBInstance.Create(jsonutils.Marshal(params)) + if err != nil { + return nil, errors.Wrapf(err, "Create") + } + + instance := &SDBInstance{region: region} + err = resp.Unmarshal(instance, "instance") + if err != nil { + return nil, errors.Wrap(err, `resp.Unmarshal(&instance, "instance")`) + } + if jobId, _ := resp.GetString("job_id"); len(jobId) > 0 { + err = cloudprovider.Wait(10*time.Second, 20*time.Minute, func() (bool, error) { + job, err := region.ecsClient.DBInstanceJob.Get(jobId, map[string]string{"id": jobId}) + if err != nil { + return false, nil + } + status, _ := job.GetString("status") + process, _ := job.GetString("process") + log.Debugf("create dbinstance job %s status: %s process: %s", jobId, status, process) + if status == "Completed" { + return true, nil + } + if status == "Failed" { + return false, fmt.Errorf("create failed") + } + return false, nil + }) + } + return instance, err +} + +func (rds *SDBInstance) Reboot() error { + return rds.region.RebootDBInstance(rds.Id) +} + +func (rds *SDBInstance) OpenPublicConnection() error { + return fmt.Errorf("Huawei current not support this operation") + //return rds.region.PublicConnectionAction(rds.Id, "openRC") +} + +func (rds *SDBInstance) ClosePublicConnection() error { + return fmt.Errorf("Huawei current not support this operation") + //return rds.region.PublicConnectionAction(rds.Id, "closeRC") +} + +func (region *SRegion) PublicConnectionAction(instanceId string, action string) error { + resp, err := region.ecsClient.DBInstance.PerformAction2(action, instanceId, nil, "") + if err != nil { + return errors.Wrapf(err, "rds.%s", action) + } + + if jobId, _ := resp.GetString("job_id"); len(jobId) > 0 { + err = cloudprovider.WaitCreated(10*time.Second, 20*time.Minute, func() bool { + job, err := region.ecsClient.DBInstanceJob.Get(jobId, map[string]string{"id": jobId}) + if err != nil { + log.Errorf("failed to get job %s info error: %v", jobId, err) + return false + } + status, _ := job.GetString("status") + process, _ := job.GetString("process") + if status == "Completed" { + return true + } + log.Debugf("%s dbinstance job %s status: %s process: %s", action, jobId, status, process) + return false + }) + } + + return nil + +} + +func (region *SRegion) RebootDBInstance(instanceId string) error { + params := jsonutils.Marshal(map[string]interface{}{ + "restart": map[string]string{}, + }) + resp, err := region.ecsClient.DBInstance.PerformAction2("action", instanceId, params, "") + if err != nil { + return err + } + if jobId, _ := resp.GetString("job_id"); len(jobId) > 0 { + err = cloudprovider.WaitCreated(10*time.Second, 20*time.Minute, func() bool { + job, err := region.ecsClient.DBInstanceJob.Get(jobId, map[string]string{"id": jobId}) + if err != nil { + log.Errorf("failed to get job %s info error: %v", jobId, err) + return false + } + status, _ := job.GetString("status") + process, _ := job.GetString("process") + if status == "Completed" { + return true + } + log.Debugf("reboot dbinstance job %s status: %s process: %s", jobId, status, process) + return false + }) + } + return err +} + +type SDBInstanceFlavor struct { + Vcpus int + Ram int //单位GB + SpecCode string + InstanceMode string //实例模型 +} + +func (region *SRegion) GetDBInstanceFlavors(engine string, version string) ([]SDBInstanceFlavor, error) { + flavors := []SDBInstanceFlavor{} + resp, err := region.ecsClient.DBInstanceFlavor.ListInContextWithSpec(nil, engine, map[string]string{"version_name": version}, "flavors") + if err != nil { + return nil, err + } + return flavors, jsonutils.Update(&flavors, resp.Data) +} + +func (rds *SDBInstance) CreateAccount(conf *cloudprovider.SDBInstanceAccountCreateConfig) error { + return rds.region.CreateDBInstanceAccount(rds.Id, conf.Name, conf.Password) +} + +func (region *SRegion) CreateDBInstanceAccount(instanceId, account, password string) error { + params := map[string]string{ + "name": account, + "password": password, + } + _, err := region.ecsClient.DBInstance.CreateInContextWithSpec(nil, fmt.Sprintf("%s/db_user", instanceId), jsonutils.Marshal(params), "") + return err +} + +func (rds *SDBInstance) CreateDatabase(conf *cloudprovider.SDBInstanceDatabaseCreateConfig) error { + return rds.region.CreateDBInstanceDatabase(rds.Id, conf.Name, conf.CharacterSet) +} + +func (region *SRegion) CreateDBInstanceDatabase(instanceId, database, characterSet string) error { + params := map[string]string{ + "name": database, + "character_set": characterSet, + } + _, err := region.ecsClient.DBInstance.CreateInContextWithSpec(nil, fmt.Sprintf("%s/database", instanceId), jsonutils.Marshal(params), "") + return err +} + +func (rds *SDBInstance) ChangeConfig(cxt context.Context, desc *cloudprovider.SManagedDBInstanceChangeConfig) error { + return rds.region.ChangeDBInstanceConfig(rds.Id, desc.InstanceType, desc.DiskSizeGB) +} + +func (region *SRegion) ChangeDBInstanceConfig(instanceId string, instanceType string, diskSizeGb int) error { + instance, err := region.GetIDBInstanceById(instanceId) + if err != nil { + return errors.Wrapf(err, "region.GetIDBInstanceById(%s)", instanceId) + } + + if len(instanceType) > 0 { + params := map[string]map[string]string{ + "resize_flavor": map[string]string{ + "spec_code": instanceType, + }, + } + _, err := region.ecsClient.DBInstance.PerformAction("action", instanceId, jsonutils.Marshal(params)) + if err != nil { + return errors.Wrap(err, "resize_flavor") + } + cloudprovider.WaitStatus(instance, api.DBINSTANCE_RUNNING, time.Second*5, time.Minute*30) + } + if diskSizeGb > 0 { + params := map[string]map[string]int{ + "enlarge_volume": map[string]int{ + "size": diskSizeGb, + }, + } + _, err := region.ecsClient.DBInstance.PerformAction("action", instanceId, jsonutils.Marshal(params)) + if err != nil { + return errors.Wrap(err, "enlarge_volume") + } + cloudprovider.WaitStatus(instance, api.DBINSTANCE_RUNNING, time.Second*5, time.Minute*30) + } + return nil +} + +func (rds *SDBInstance) RecoveryFromBackup(conf *cloudprovider.SDBInstanceRecoveryConfig) error { + if len(conf.OriginDBInstanceExternalId) == 0 { + conf.OriginDBInstanceExternalId = rds.Id + } + return rds.region.RecoveryDBInstanceFromBackup(rds.Id, conf.OriginDBInstanceExternalId, conf.BackupId, conf.Databases) +} + +func (region *SRegion) RecoveryDBInstanceFromBackup(target, origin string, backupId string, databases map[string]string) error { + source := map[string]interface{}{ + "type": "backup", + "backup_id": backupId, + } + if len(origin) > 0 { + source["instance_id"] = origin + } + if len(databases) > 0 { + source["database_name"] = databases + } + params := map[string]interface{}{ + "source": source, + "target": map[string]string{ + "instance_id": target, + }, + } + _, err := region.ecsClient.DBInstance.PerformAction("", "recovery", jsonutils.Marshal(params)) + if err != nil { + return errors.Wrap(err, "dbinstance.recovery") + } + return nil +} + +func (rds *SDBInstance) Renew(bc billing.SBillingCycle) error { + return rds.region.RenewInstance(rds.Id, bc) +} diff --git a/pkg/multicloud/huaweistack/dbinstance_account.go b/pkg/multicloud/huaweistack/dbinstance_account.go new file mode 100644 index 0000000000..56a0a919bb --- /dev/null +++ b/pkg/multicloud/huaweistack/dbinstance_account.go @@ -0,0 +1,132 @@ +// 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 huaweistack + +import ( + "fmt" + + "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 SDBInstanceAccount struct { + multicloud.SDBInstanceAccountBase + multicloud.HuaweiTags + instance *SDBInstance + Name string +} + +func (account *SDBInstanceAccount) GetName() string { + return account.Name +} + +func (account *SDBInstanceAccount) Delete() error { + return account.instance.region.DeleteDBInstanceAccount(account.instance.Id, account.Name) +} + +func (region *SRegion) DeleteDBInstanceAccount(instanceId string, account string) error { + return DoDeleteWithSpec(region.ecsClient.DBInstance.DeleteInContextWithSpec, nil, instanceId, fmt.Sprintf("db_user/%s", account), nil, nil) +} + +func (account *SDBInstanceAccount) GetIDBInstanceAccountPrivileges() ([]cloudprovider.ICloudDBInstanceAccountPrivilege, error) { + privileges, err := account.instance.region.GetDBInstancePrivvileges(account.instance.Id, account.Name) + if err != nil { + return nil, err + } + iprivileves := []cloudprovider.ICloudDBInstanceAccountPrivilege{} + for i := 0; i < len(privileges); i++ { + privileges[i].account = account + iprivileves = append(iprivileves, &privileges[i]) + } + return iprivileves, nil +} + +func (region *SRegion) GetDBInstanceAccounts(instanceId string) ([]SDBInstanceAccount, error) { + params := map[string]string{ + "instance_id": instanceId, + } + accounts := []SDBInstanceAccount{} + err := doListAllWithPage(region.ecsClient.DBInstance.ListAccounts, params, &accounts) + if err != nil { + return nil, err + } + return accounts, nil +} + +func (region *SRegion) GetDBInstancePrivvileges(instanceId string, username string) ([]SDatabasePrivilege, error) { + params := map[string]string{ + "instance_id": instanceId, + "user-name": username, + } + privileges := []SDatabasePrivilege{} + err := doListAllWithPage(region.ecsClient.DBInstance.ListPrivileges, params, &privileges) + if err != nil { + return nil, err + } + return privileges, nil +} + +func (account *SDBInstanceAccount) RevokePrivilege(database string) error { + return account.instance.region.RevokeDBInstancePrivilege(account.instance.Id, account.Name, database) +} + +func (region *SRegion) RevokeDBInstancePrivilege(instanceId string, account, database string) error { + params := map[string]interface{}{ + "db_name": database, + "users": []map[string]interface{}{ + map[string]interface{}{ + "name": account, + }, + }, + } + return DoDeleteWithSpec(region.ecsClient.DBInstance.DeleteInContextWithSpec, nil, instanceId, "db_privilege", nil, jsonutils.Marshal(params)) +} + +func (account *SDBInstanceAccount) GrantPrivilege(database, privilege string) error { + return account.instance.region.GrantDBInstancePrivilege(account.instance.Id, account.Name, database, privilege) +} + +func (region *SRegion) GrantDBInstancePrivilege(instanceId string, account, database string, privilege string) error { + readonly := false + switch privilege { + case api.DATABASE_PRIVILEGE_R: + readonly = true + case api.DATABASE_PRIVILEGE_RW: + default: + return fmt.Errorf("Unknown privilege %s", privilege) + } + params := map[string]interface{}{ + "db_name": database, + "users": []map[string]interface{}{ + map[string]interface{}{ + "name": account, + "readonly": readonly, + }, + }, + } + _, err := region.ecsClient.DBInstance.PerformAction("db_privilege", instanceId, jsonutils.Marshal(params)) + return err +} + +func (account *SDBInstanceAccount) ResetPassword(password string) error { + return account.instance.region.ResetDBInstanceAccountPassword(account.instance.Id, account.Name, password) +} + +func (region *SRegion) ResetDBInstanceAccountPassword(instanceId, account, password string) error { + return fmt.Errorf("The API does not exist or has not been published in the environment") +} diff --git a/pkg/multicloud/huaweistack/dbinstance_backup.go b/pkg/multicloud/huaweistack/dbinstance_backup.go new file mode 100644 index 0000000000..808708dbc5 --- /dev/null +++ b/pkg/multicloud/huaweistack/dbinstance_backup.go @@ -0,0 +1,236 @@ +// 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 huaweistack + +import ( + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SDBInstanceBackup struct { + multicloud.SDBInstanceBackupBase + multicloud.HuaweiTags + region *SRegion + + BeginTime string + Datastore SDatastore + EndTime string + Id string + InstanceId string + Name string + Size int + Status string + Type string +} + +func (backup *SDBInstanceBackup) GetId() string { + return backup.Id +} + +func (backup *SDBInstanceBackup) GetGlobalId() string { + return backup.Id +} + +func (backup *SDBInstanceBackup) GetName() string { + return backup.Name +} + +func (backup *SDBInstanceBackup) GetEngine() string { + return backup.Datastore.Type +} + +func (backup *SDBInstanceBackup) GetEngineVersion() string { + return backup.Datastore.Version +} + +func (backup *SDBInstanceBackup) GetStartTime() time.Time { + //2019-08-05T08:00:02+0000 + t, err := time.Parse("2006-01-02T15:04:05Z0700", backup.BeginTime) + if err != nil { + return time.Time{} + } + return t +} + +func (backup *SDBInstanceBackup) GetEndTime() time.Time { + t, err := time.Parse("2006-01-02T15:04:05Z0700", backup.EndTime) + if err != nil { + return time.Time{} + } + return t +} + +func (backup *SDBInstanceBackup) GetBackupMode() string { + switch backup.Type { + case "manual": + return api.BACKUP_MODE_MANUAL + default: + return api.BACKUP_MODE_AUTOMATED + } +} + +func (backup *SDBInstanceBackup) GetStatus() string { + switch backup.Status { + case "COMPLETED": + return api.DBINSTANCE_BACKUP_READY + case "FAILED": + return api.DBINSTANCE_BACKUP_FAILED + case "BUILDING": + return api.DBINSTANCE_BACKUP_CREATING + case "DELETING": + return api.DBINSTANCE_BACKUP_DELETING + default: + return api.DBINSTANCE_BACKUP_UNKNOWN + } +} + +func (backup *SDBInstanceBackup) GetBackupSizeMb() int { + return backup.Size / 1024 +} + +func (backup *SDBInstanceBackup) GetDBNames() string { + return "" +} + +func (backup *SDBInstanceBackup) Delete() error { + return backup.region.DeleteDBInstanceBackup(backup.Id) +} + +func (region *SRegion) DeleteDBInstanceBackup(backupId string) error { + _, err := region.ecsClient.DBInstanceBackup.Delete(backupId, nil) + return err +} + +func (backup *SDBInstanceBackup) GetDBInstanceId() string { + return backup.InstanceId +} + +func (region *SRegion) GetDBInstanceBackups(instanceId, backupId string) ([]SDBInstanceBackup, error) { + params := map[string]string{ + "instance_id": instanceId, + } + if len(backupId) > 0 { + params["backup_id"] = backupId + } + backups := []SDBInstanceBackup{} + err := doListAllWithOffset(region.ecsClient.DBInstanceBackup.List, params, &backups) + if err != nil { + return nil, err + } + return backups, nil +} + +func (region *SRegion) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstanceBackup, error) { + dbinstnaces, err := region.GetIDBInstances() + if err != nil { + return nil, err + } + ibackups := []cloudprovider.ICloudDBInstanceBackup{} + for i := 0; i < len(dbinstnaces); i++ { + _dbinstance := dbinstnaces[i].(*SDBInstance) + _ibackup, err := _dbinstance.GetIDBInstanceBackups() + if err != nil { + return nil, errors.Wrapf(err, "_dbinstance(%v).GetIDBInstanceBackups", _dbinstance) + } + ibackups = append(ibackups, _ibackup...) + } + return ibackups, nil +} + +func (region *SRegion) GetIDBInstanceBackupById(backupId string) (cloudprovider.ICloudDBInstanceBackup, error) { + backups, err := region.GetIDBInstanceBackups() + if err != nil { + return nil, errors.Wrap(err, "region.GetIDBInstanceBackups") + } + for _, backup := range backups { + if backup.GetGlobalId() == backupId { + return backup, nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (rds *SDBInstance) GetIDBInstanceBackups() ([]cloudprovider.ICloudDBInstanceBackup, error) { + backups, err := rds.region.GetDBInstanceBackups(rds.Id, "") + if err != nil { + return nil, err + } + + ibackups := []cloudprovider.ICloudDBInstanceBackup{} + for i := 0; i < len(backups); i++ { + backups[i].region = rds.region + ibackups = append(ibackups, &backups[i]) + } + return ibackups, nil +} + +func (backup *SDBInstanceBackup) Refresh() error { + backups, err := backup.region.GetDBInstanceBackups(backup.InstanceId, backup.Id) + if err != nil { + return err + } + if len(backups) == 0 { + return cloudprovider.ErrNotFound + } + return jsonutils.Update(backup, backups[0]) +} + +func (rds *SDBInstance) CreateIBackup(conf *cloudprovider.SDBInstanceBackupCreateConfig) (string, error) { + backupId, err := rds.region.CreateDBInstanceBackup(rds.Id, conf.Name, conf.Description, conf.Databases) + if err != nil { + return "", err + } + backup, err := rds.region.GetIDBInstanceBackupById(backupId) + if err != nil { + return "", errors.Wrap(err, "region.GetIDBInstanceBackupById") + } + cloudprovider.WaitStatus(backup, api.DBINSTANCE_BACKUP_READY, time.Second*3, time.Minute*30) + return backupId, nil +} + +func (region *SRegion) CreateDBInstanceBackup(instanceId string, name string, descrition string, databases []string) (string, error) { + params := map[string]interface{}{ + "instance_id": instanceId, + "name": name, + "description": descrition, + } + if len(databases) > 0 { + dbs := []map[string]string{} + for _, database := range databases { + dbs = append(dbs, map[string]string{"name": database}) + } + params["databases"] = dbs + } + resp, err := region.ecsClient.DBInstanceBackup.Create(jsonutils.Marshal(params)) + if err != nil { + return "", errors.Wrap(err, "DBInstanceBackup.Create") + } + backupId, err := resp.GetString("id") + if err != nil { + return "", errors.Wrap(err, "resp.GetBackupId") + } + return backupId, nil +} + +func (self *SDBInstanceBackup) CreateICloudDBInstance(opts *cloudprovider.SManagedDBInstanceCreateConfig) (cloudprovider.ICloudDBInstance, error) { + opts.BackupId = self.Id + return self.region.CreateIDBInstance(opts) +} diff --git a/pkg/multicloud/huaweistack/dbinstance_database.go b/pkg/multicloud/huaweistack/dbinstance_database.go new file mode 100644 index 0000000000..87d95e5a80 --- /dev/null +++ b/pkg/multicloud/huaweistack/dbinstance_database.go @@ -0,0 +1,72 @@ +// 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 huaweistack + +import ( + "fmt" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SDBInstanceDatabase struct { + instance *SDBInstance + multicloud.SDBInstanceDatabaseBase + multicloud.HuaweiTags + + Name string + CharacterSet string +} + +func (database *SDBInstanceDatabase) GetId() string { + return database.Name +} + +func (database *SDBInstanceDatabase) GetGlobalId() string { + return database.Name +} + +func (database *SDBInstanceDatabase) GetName() string { + return database.Name +} + +func (database *SDBInstanceDatabase) GetStatus() string { + return api.DBINSTANCE_DATABASE_RUNNING +} + +func (database *SDBInstanceDatabase) GetCharacterSet() string { + return database.CharacterSet +} + +func (database *SDBInstanceDatabase) Delete() error { + return database.instance.region.DeleteDBInstanceDatabase(database.instance.Id, database.Name) +} + +func (region *SRegion) DeleteDBInstanceDatabase(instanceId, database string) error { + _, err := region.ecsClient.DBInstance.DeleteInContextWithSpec(nil, instanceId, fmt.Sprintf("database/%s", database), nil, nil, "") + return err +} + +func (region *SRegion) GetDBInstanceDatabases(instanceId string) ([]SDBInstanceDatabase, error) { + params := map[string]string{ + "instance_id": instanceId, + } + databases := []SDBInstanceDatabase{} + err := doListAllWithPage(region.ecsClient.DBInstance.ListDatabases, params, &databases) + if err != nil { + return nil, err + } + return databases, nil +} diff --git a/pkg/multicloud/huaweistack/dbinstance_parameter.go b/pkg/multicloud/huaweistack/dbinstance_parameter.go new file mode 100644 index 0000000000..9ac9e912dc --- /dev/null +++ b/pkg/multicloud/huaweistack/dbinstance_parameter.go @@ -0,0 +1,55 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huaweistack + +type SDBInstanceParameter struct { + instance *SDBInstance + + Name string + Value string + RestartRequired bool + Readonly bool + ValueRange string + Type string + Description string +} + +func (region *SRegion) GetDBInstanceParameters(dbinstanceId string) ([]SDBInstanceParameter, error) { + params := map[string]string{ + "instance_id": dbinstanceId, + } + paramters := []SDBInstanceParameter{} + err := doListAll(region.ecsClient.DBInstance.ListParameters, params, ¶mters) + if err != nil { + return nil, err + } + return paramters, nil +} + +func (param *SDBInstanceParameter) GetGlobalId() string { + return param.Name +} + +func (param *SDBInstanceParameter) GetKey() string { + return param.Name +} + +func (param *SDBInstanceParameter) GetValue() string { + return param.Value +} + +func (param *SDBInstanceParameter) GetDescription() string { + return param.Description +} diff --git a/pkg/multicloud/huaweistack/dbinstance_privilege.go b/pkg/multicloud/huaweistack/dbinstance_privilege.go new file mode 100644 index 0000000000..f7dc7f1347 --- /dev/null +++ b/pkg/multicloud/huaweistack/dbinstance_privilege.go @@ -0,0 +1,43 @@ +// 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 huaweistack + +import ( + "fmt" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +type SDatabasePrivilege struct { + account *SDBInstanceAccount + + Name string + Readonly bool +} + +func (privilege *SDatabasePrivilege) GetGlobalId() string { + return fmt.Sprintf("%s/%s", privilege.account.Name, privilege.Name) +} + +func (privilege *SDatabasePrivilege) GetPrivilege() string { + if privilege.Readonly { + return api.DATABASE_PRIVILEGE_R + } + return api.DATABASE_PRIVILEGE_RW +} + +func (privilege *SDatabasePrivilege) GetDBName() string { + return privilege.Name +} diff --git a/pkg/multicloud/huaweistack/disk.go b/pkg/multicloud/huaweistack/disk.go new file mode 100644 index 0000000000..8d388c75db --- /dev/null +++ b/pkg/multicloud/huaweistack/disk.go @@ -0,0 +1,558 @@ +// 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 huaweistack + +import ( + "context" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +/* +华为云云硬盘 +======创建========== +1.磁盘只能挂载到同一可用区的云服务器内,创建后不支持更换可用区 +2.计费模式 包年包月/按需计费 +3.*支持自动备份 + + +共享盘 和 普通盘:https://support.huaweicloud.com/productdesc-evs/zh-cn_topic_0032860759.html +根据是否支持挂载至多台云服务器可以将云硬盘分为非共享云硬盘和共享云硬盘。 +一个非共享云硬盘只能挂载至一台云服务器,而一个共享云硬盘可以同时挂载至多台云服务器。 +单个共享云硬盘最多可同时挂载给16个云服务器。目前,共享云硬盘只适用于数据盘,不支持系统盘。 +*/ + +type Attachment struct { + ServerID string `json:"server_id"` + AttachmentID string `json:"attachment_id"` + AttachedAt string `json:"attached_at"` + HostName string `json:"host_name"` + VolumeID string `json:"volume_id"` + Device string `json:"device"` + ID string `json:"id"` +} + +type DiskMeta struct { + ResourceSpecCode string `json:"resourceSpecCode"` + Billing string `json:"billing"` + ResourceType string `json:"resourceType"` + AttachedMode string `json:"attached_mode"` + Readonly string `json:"readonly"` +} + +type VolumeImageMetadata struct { + QuickStart string `json:"__quick_start"` + ContainerFormat string `json:"container_format"` + MinRAM string `json:"min_ram"` + ImageName string `json:"image_name"` + ImageID string `json:"image_id"` + OSType string `json:"__os_type"` + OSFeatureList string `json:"__os_feature_list"` + MinDisk string `json:"min_disk"` + SupportKVM string `json:"__support_kvm"` + VirtualEnvType string `json:"virtual_env_type"` + SizeGB string `json:"size"` + OSVersion string `json:"__os_version"` + OSBit string `json:"__os_bit"` + SupportKVMHi1822Hiovs string `json:"__support_kvm_hi1822_hiovs"` + SupportXen string `json:"__support_xen"` + Description string `json:"__description"` + Imagetype string `json:"__imagetype"` + DiskFormat string `json:"disk_format"` + ImageSourceType string `json:"__image_source_type"` + Checksum string `json:"checksum"` + Isregistered string `json:"__isregistered"` + HwVifMultiqueueEnabled string `json:"hw_vif_multiqueue_enabled"` + Platform string `json:"__platform"` +} + +// https://support.huaweicloud.com/api-evs/zh-cn_topic_0124881427.html +type SDisk struct { + storage *SStorage + multicloud.SDisk + multicloud.HuaweiDiskTags + details *SResourceDetail + + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Attachments []Attachment `json:"attachments"` + Description string `json:"description"` + SizeGB int `json:"size"` + Metadata DiskMeta `json:"metadata"` + Encrypted bool `json:"encrypted"` + Bootable string `json:"bootable"` + Multiattach bool `json:"multiattach"` + AvailabilityZone string `json:"availability_zone"` + SourceVolid string `json:"source_volid"` + SnapshotID string `json:"snapshot_id"` + CreatedAt time.Time `json:"created_at"` + VolumeType string `json:"volume_type"` + VolumeImageMetadata VolumeImageMetadata `json:"volume_image_metadata"` + ReplicationStatus string `json:"replication_status"` + UserID string `json:"user_id"` + ConsistencygroupID string `json:"consistencygroup_id"` + UpdatedAt string `json:"updated_at"` + EnterpriseProjectId string + + ExpiredTime time.Time +} + +func (self *SDisk) GetId() string { + return self.ID +} + +func (self *SDisk) GetName() string { + if len(self.Name) == 0 { + return self.ID + } + + return self.Name +} + +func (self *SDisk) GetGlobalId() string { + return self.ID +} + +func (self *SDisk) GetStatus() string { + // https://support.huaweicloud.com/api-evs/zh-cn_topic_0051803385.html + switch self.Status { + case "creating", "downloading": + return api.DISK_ALLOCATING + case "available", "in-use": + return api.DISK_READY + case "error": + return api.DISK_ALLOC_FAILED + case "attaching": + return api.DISK_ATTACHING + case "detaching": + return api.DISK_DETACHING + case "restoring-backup": + return api.DISK_REBUILD + case "backing-up": + return api.DISK_BACKUP_STARTALLOC // ? + case "error_restoring": + return api.DISK_BACKUP_ALLOC_FAILED + case "uploading": + return api.DISK_SAVING //? + case "extending": + return api.DISK_RESIZING + case "error_extending": + return api.DISK_ALLOC_FAILED // ? + case "deleting": + return api.DISK_DEALLOC //? + case "error_deleting": + return api.DISK_DEALLOC_FAILED // ? + case "rollbacking": + return api.DISK_REBUILD + case "error_rollbacking": + return api.DISK_UNKNOWN + default: + return api.DISK_UNKNOWN + } +} + +func (self *SDisk) Refresh() error { + new, err := self.storage.zone.region.GetDisk(self.GetId()) + if err != nil { + return err + } + return jsonutils.Update(self, new) +} + +func (self *SDisk) IsEmulated() bool { + return false +} + +func (self *SDisk) getResourceDetails() *SResourceDetail { + if self.details != nil { + return self.details + } + + res, err := self.storage.zone.region.GetOrderResourceDetail(self.GetId()) + if err != nil { + log.Debugln(err) + return nil + } + + self.details = &res + return self.details +} + +func (self *SDisk) GetBillingType() string { + details := self.getResourceDetails() + if details == nil { + return billing_api.BILLING_TYPE_POSTPAID + } else { + return billing_api.BILLING_TYPE_PREPAID + } +} + +func (self *SDisk) GetCreatedAt() time.Time { + return self.CreatedAt +} + +func (self *SDisk) GetExpiredAt() time.Time { + var expiredTime time.Time + details := self.getResourceDetails() + if details != nil { + expiredTime = details.ExpireTime + } + + return expiredTime +} + +func (self *SDisk) GetIStorage() (cloudprovider.ICloudStorage, error) { + return self.storage, nil +} + +func (self *SDisk) GetDiskFormat() string { + // self.volume_type ? + return "vhd" +} + +func (self *SDisk) GetDiskSizeMB() int { + return int(self.SizeGB * 1024) +} + +func (self *SDisk) checkAutoDelete(attachments []Attachment) bool { + autodelete := false + for _, attach := range attachments { + if len(attach.ServerID) > 0 { + // todo : 忽略错误?? + vm, err := self.storage.zone.region.GetInstanceByID(attach.ServerID) + if err != nil { + volumes := vm.OSExtendedVolumesVolumesAttached + for _, vol := range volumes { + if vol.ID == self.ID && strings.ToLower(vol.DeleteOnTermination) == "true" { + autodelete = true + } + } + } + + break + } + } + + return autodelete +} + +func (self *SDisk) GetIsAutoDelete() bool { + if len(self.Attachments) > 0 { + return self.checkAutoDelete(self.Attachments) + } + + return false +} + +func (self *SDisk) GetTemplateId() string { + return self.VolumeImageMetadata.ImageID +} + +// Bootable 表示硬盘是否为启动盘。 +// 启动盘 != 系统盘(必须是启动盘且挂载在root device上) +func (self *SDisk) GetDiskType() string { + if self.Bootable == "true" { + return api.DISK_TYPE_SYS + } else { + return api.DISK_TYPE_DATA + } +} + +func (self *SDisk) GetFsFormat() string { + return "" +} + +func (self *SDisk) GetIsNonPersistent() bool { + return false +} + +func (self *SDisk) GetDriver() string { + // https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762431.html + // scsi or vbd? + // todo: implement me + return "scsi" +} + +func (self *SDisk) GetCacheMode() string { + return "none" +} + +func (self *SDisk) GetMountpoint() string { + if len(self.Attachments) > 0 { + return self.Attachments[0].Device + } + + return "" +} + +func (self *SDisk) GetMountServerId() string { + if len(self.Attachments) > 0 { + return self.Attachments[0].ServerID + } + + return "" +} + +func (self *SDisk) GetAccessPath() string { + return "" +} + +func (self *SDisk) Delete(ctx context.Context) error { + disk, err := self.storage.zone.region.GetDisk(self.GetId()) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil + } + return err + } + if disk.Status != "deleting" { + // 等待硬盘ready + cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 60*time.Second) + err := self.storage.zone.region.DeleteDisk(self.GetId()) + if err != nil { + return err + } + } + + return cloudprovider.WaitDeleted(self, 10*time.Second, 120*time.Second) +} + +func (self *SDisk) CreateISnapshot(ctx context.Context, name string, desc string) (cloudprovider.ICloudSnapshot, error) { + if snapshotId, err := self.storage.zone.region.CreateSnapshot(self.GetId(), name, desc); err != nil { + log.Errorf("createSnapshot fail %s", err) + return nil, err + } else if snapshot, err := self.getSnapshot(snapshotId); err != nil { + return nil, err + } else { + snapshot.region = self.storage.zone.region + if err := cloudprovider.WaitStatus(snapshot, api.SNAPSHOT_READY, 15*time.Second, 3600*time.Second); err != nil { + return nil, err + } + return snapshot, nil + } +} + +func (self *SDisk) getSnapshot(snapshotId string) (*SSnapshot, error) { + snapshot, err := self.storage.zone.region.GetSnapshotById(snapshotId) + return &snapshot, err +} + +func (self *SDisk) GetISnapshot(snapshotId string) (cloudprovider.ICloudSnapshot, error) { + snapshot, err := self.getSnapshot(snapshotId) + return snapshot, err +} + +func (self *SDisk) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { + snapshots, err := self.storage.zone.region.GetSnapshots(self.ID, "") + if err != nil { + return nil, err + } + + isnapshots := make([]cloudprovider.ICloudSnapshot, len(snapshots)) + for i := 0; i < len(snapshots); i++ { + isnapshots[i] = &snapshots[i] + } + return isnapshots, nil +} + +func (self *SDisk) Resize(ctx context.Context, newSizeMB int64) error { + err := cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 60*time.Second) + if err != nil { + return err + } + + sizeGb := newSizeMB / 1024 + err = self.storage.zone.region.resizeDisk(self.GetId(), sizeGb) + if err != nil { + return err + } + + return cloudprovider.WaitStatusWithDelay(self, api.DISK_READY, 15*time.Second, 5*time.Second, 60*time.Second) +} + +func (self *SDisk) Detach() error { + err := self.storage.zone.region.DetachDisk(self.GetMountServerId(), self.GetId()) + if err != nil { + log.Debugf("detach server %s disk %s failed: %s", self.GetMountServerId(), self.GetId(), err) + return err + } + + return cloudprovider.WaitCreated(5*time.Second, 60*time.Second, func() bool { + err := self.Refresh() + if err != nil { + log.Debugln(err) + return false + } + + if self.Status == "available" { + return true + } + + return false + }) +} + +func (self *SDisk) Attach(device string) error { + err := self.storage.zone.region.AttachDisk(self.GetMountServerId(), self.GetId(), device) + if err != nil { + log.Debugf("attach server %s disk %s failed: %s", self.GetMountServerId(), self.GetId(), err) + return err + } + + return cloudprovider.WaitStatusWithDelay(self, api.DISK_READY, 10*time.Second, 5*time.Second, 60*time.Second) +} + +// 在线卸载磁盘 https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0036046828.html +// 对于挂载在系统盘盘位(也就是“/dev/sda”或“/dev/vda”挂载点)上的磁盘,当前仅支持离线卸载 +func (self *SDisk) Reset(ctx context.Context, snapshotId string) (string, error) { + mountpoint := self.GetMountpoint() + if len(mountpoint) > 0 { + err := self.Detach() + if err != nil { + return "", err + } + } + + diskId, err := self.storage.zone.region.resetDisk(self.GetId(), snapshotId) + if err != nil { + return diskId, err + } + + err = cloudprovider.WaitStatus(self, api.DISK_READY, 5*time.Second, 300*time.Second) + if err != nil { + return "", err + } + + if len(mountpoint) > 0 { + err := self.Attach(mountpoint) + if err != nil { + return "", err + } + } + + return diskId, nil +} + +// 华为云不支持重置 +func (self *SDisk) Rebuild(ctx context.Context) error { + return cloudprovider.ErrNotSupported +} + +func (self *SRegion) GetDisk(diskId string) (*SDisk, error) { + if len(diskId) == 0 { + return nil, cloudprovider.ErrNotFound + } + var disk SDisk + err := DoGet(self.ecsClient.Disks.Get, diskId, nil, &disk) + return &disk, err +} + +// https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762430.html +func (self *SRegion) GetDisks(zoneId string) ([]SDisk, error) { + queries := map[string]string{} + if len(zoneId) > 0 { + queries["availability_zone"] = zoneId + } + + disks := make([]SDisk, 0) + err := doListAllWithOffset(self.ecsClient.Disks.List, queries, &disks) + return disks, err +} + +// https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762427.html +func (self *SRegion) CreateDisk(zoneId string, category string, name string, sizeGb int, snapshotId string, desc string, projectId string) (string, error) { + params := jsonutils.NewDict() + volumeObj := jsonutils.NewDict() + volumeObj.Add(jsonutils.NewString(name), "name") + volumeObj.Add(jsonutils.NewString(zoneId), "availability_zone") + volumeObj.Add(jsonutils.NewString(desc), "description") + volumeObj.Add(jsonutils.NewString(category), "volume_type") + volumeObj.Add(jsonutils.NewInt(int64(sizeGb)), "size") + if len(snapshotId) > 0 { + volumeObj.Add(jsonutils.NewString(snapshotId), "snapshot_id") + } + if len(projectId) > 0 { + volumeObj.Add(jsonutils.NewString(projectId), "enterprise_project_id") + } + + params.Add(volumeObj, "volume") + // 目前只支持创建按需资源,返回job id。 如果创建包年包月资源则返回order id + _id, err := self.ecsClient.Disks.AsyncCreate(params) + if err != nil { + log.Debugf("AsyncCreate with params: %s", params) + return "", errors.Wrap(err, "AsyncCreate") + } + + // 按需计费 + volumeId, err := self.GetTaskEntityID(self.ecsClient.Disks.ServiceType(), _id, "volume_id") + if err != nil { + return "", errors.Wrap(err, "GetAllSubTaskEntityIDs") + } + + if len(volumeId) == 0 { + return "", errors.Errorf("CreateInstance job %s result is emtpy", _id) + } else { + return volumeId, nil + } +} + +// https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762428.html +// 默认删除云硬盘关联的所有快照 +func (self *SRegion) DeleteDisk(diskId string) error { + return DoDeleteWithSpec(self.ecsClient.Disks.DeleteInContextWithSpec, nil, diskId, "", nil, nil) +} + +/* +扩容状态为available的云硬盘时,没有约束限制。 +扩容状态为in-use的云硬盘时,有以下约束: +不支持共享云硬盘,即multiattach参数值必须为false。 +云硬盘所挂载的云服务器状态必须为ACTIVE、PAUSED、SUSPENDED、SHUTOFF才支持扩容 +*/ +func (self *SRegion) resizeDisk(diskId string, sizeGB int64) error { + params := jsonutils.NewDict() + osExtendObj := jsonutils.NewDict() + osExtendObj.Add(jsonutils.NewInt(sizeGB), "new_size") // GB + params.Add(osExtendObj, "os-extend") + _, err := self.ecsClient.Disks.PerformAction2("action", diskId, params, "") + return err +} + +/* +https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408629.html +只支持快照回滚到源云硬盘,不支持快照回滚到其它指定云硬盘。 +只有云硬盘状态处于“available”或“error_rollbacking”状态才允许快照回滚到源云硬盘。 +*/ +func (self *SRegion) resetDisk(diskId, snapshotId string) (string, error) { + params := jsonutils.NewDict() + rollbackObj := jsonutils.NewDict() + rollbackObj.Add(jsonutils.NewString(diskId), "volume_id") + params.Add(rollbackObj, "rollback") + _, err := self.ecsClient.OsSnapshots.PerformAction2("rollback", snapshotId, params, "") + return diskId, err +} + +func (self *SDisk) GetProjectId() string { + return self.EnterpriseProjectId +} diff --git a/pkg/multicloud/huaweistack/disktype.go b/pkg/multicloud/huaweistack/disktype.go new file mode 100644 index 0000000000..f4ac9c0dc0 --- /dev/null +++ b/pkg/multicloud/huaweistack/disktype.go @@ -0,0 +1,40 @@ +// 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 huaweistack + +import "strings" + +type SDiskType struct { + ExtraSpecs ExtraSpecs `json:"extra_specs"` + Name string `json:"name"` + QosSpecsID string `json:"qos_specs_id"` + ID string `json:"id"` + IsPublic bool `json:"is_public"` +} + +type ExtraSpecs struct { + VolumeBackendName string `json:"volume_backend_name"` + AvailabilityZone string `json:"availability-zone"` + RESKEYAvailabilityZones string `json:"RESKEY:availability_zones"` + OSVendorExtendedSoldOutAvailabilityZones string `json:"os-vendor-extended:sold_out_availability_zones"` +} + +func (self *SDiskType) IsAvaliableInZone(zoneId string) bool { + if len(self.QosSpecsID) > 0 && strings.Contains(self.ExtraSpecs.RESKEYAvailabilityZones, zoneId) && !strings.Contains(self.ExtraSpecs.OSVendorExtendedSoldOutAvailabilityZones, zoneId) { + return true + } + + return false +} diff --git a/pkg/multicloud/huaweistack/doc.go b/pkg/multicloud/huaweistack/doc.go new file mode 100644 index 0000000000..05b9ca551d --- /dev/null +++ b/pkg/multicloud/huaweistack/doc.go @@ -0,0 +1,15 @@ +// 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 huaweistack // import huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" diff --git a/pkg/multicloud/huaweistack/domain.go b/pkg/multicloud/huaweistack/domain.go new file mode 100644 index 0000000000..134e565745 --- /dev/null +++ b/pkg/multicloud/huaweistack/domain.go @@ -0,0 +1,47 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huaweistack + +// https://support.huaweicloud.com/api-iam/zh-cn_topic_0057845574.html +// 租户列表 +type SDomain struct { + Contacts string `json:"contacts"` + Description string `json:"description"` + Enabled bool `json:"enabled"` + EnterpriseName string `json:"enterpriseName"` + ID string `json:"id"` + Name string `json:"name"` + Tagflag int `json:"tagflag"` +} + +func (self *SHuaweiClient) GetDomains() ([]SDomain, error) { + huawei, _ := self.newGeneralAPIClient() + domains := make([]SDomain, 0) + err := doListAll(huawei.Domains.List, nil, &domains) + return domains, err +} + +func (self *SHuaweiClient) getEnabledDomains() ([]SDomain, error) { + domains, err := self.GetDomains() + + enabledDomains := make([]SDomain, 0) + for i := range domains { + if domains[i].Enabled { + enabledDomains = append(enabledDomains, domains[i]) + } + } + + return enabledDomains, err +} diff --git a/pkg/multicloud/huaweistack/eip.go b/pkg/multicloud/huaweistack/eip.go new file mode 100644 index 0000000000..657f954163 --- /dev/null +++ b/pkg/multicloud/huaweistack/eip.go @@ -0,0 +1,402 @@ +// 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 huaweistack + +import ( + "fmt" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type TInternetChargeType string + +const ( + InternetChargeByTraffic = TInternetChargeType("traffic") + InternetChargeByBandwidth = TInternetChargeType("bandwidth") +) + +type Bandwidth struct { + ID string `json:"id"` + Name string `json:"name"` + Size int64 `json:"size"` + ShareType string `json:"share_type"` + PublicipInfo []PublicipInfo `json:"publicip_info"` + TenantID string `json:"tenant_id"` + BandwidthType string `json:"bandwidth_type"` + ChargeMode string `json:"charge_mode"` + BillingInfo string `json:"billing_info"` + EnterpriseProjectID string `json:"enterprise_project_id"` +} + +type PublicipInfo struct { + PublicipID string `json:"publicip_id"` + PublicipAddress string `json:"publicip_address"` + PublicipType string `json:"publicip_type"` + IPVersion int64 `json:"ip_version"` +} + +type SProfile struct { + UserID string `json:"user_id"` + ProductID string `json:"product_id"` + RegionID string `json:"region_id"` + OrderID string `json:"order_id"` +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090598.html +type SEipAddress struct { + region *SRegion + port *Port + multicloud.SEipBase + multicloud.HuaweiTags + + ID string `json:"id"` + Status string `json:"status"` + Profile *SProfile `json:"profile,omitempty"` + Type string `json:"type"` + PublicIPAddress string `json:"public_ip_address"` + PrivateIPAddress string `json:"private_ip_address"` + TenantID string `json:"tenant_id"` + CreateTime time.Time `json:"create_time"` + BandwidthID string `json:"bandwidth_id"` + BandwidthShareType string `json:"bandwidth_share_type"` + BandwidthSize int64 `json:"bandwidth_size"` + BandwidthName string `json:"bandwidth_name"` + EnterpriseProjectID string `json:"enterprise_project_id"` + IPVersion int64 `json:"ip_version"` + PortId string `json:"port_id"` + EnterpriseProjectId string +} + +func (self *SEipAddress) GetId() string { + return self.ID +} + +func (self *SEipAddress) GetName() string { + if len(self.BandwidthName) == 0 { + return self.BandwidthName + } + + return self.PublicIPAddress +} + +func (self *SEipAddress) GetGlobalId() string { + return self.ID +} + +func (self *SEipAddress) GetStatus() string { + // https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090598.html + switch self.Status { + case "ACTIVE", "DOWN", "ELB": + return api.EIP_STATUS_READY + case "PENDING_CREATE", "NOTIFYING": + return api.EIP_STATUS_ALLOCATE + case "BINDING": + return api.EIP_STATUS_ALLOCATE + case "BIND_ERROR": + return api.EIP_STATUS_ALLOCATE_FAIL + case "PENDING_DELETE", "NOTIFY_DELETE": + return api.EIP_STATUS_DEALLOCATE + default: + return api.EIP_STATUS_UNKNOWN + } +} + +func (self *SEipAddress) Refresh() error { + if self.IsEmulated() { + return nil + } + new, err := self.region.GetEip(self.ID) + if err != nil { + return err + } + return jsonutils.Update(self, new) +} + +func (self *SEipAddress) IsEmulated() bool { + return false +} + +func (self *SEipAddress) GetIpAddr() string { + return self.PublicIPAddress +} + +func (self *SEipAddress) GetMode() string { + return api.EIP_MODE_STANDALONE_EIP +} + +func (self *SEipAddress) GetPort() *Port { + if len(self.PortId) == 0 { + return nil + } + + if self.port != nil { + return self.port + } + + port, err := self.region.GetPort(self.PortId) + if err != nil { + return nil + } else { + self.port = &port + } + + return self.port +} + +func (self *SEipAddress) GetAssociationType() string { + if len(self.PortId) == 0 { + return "" + } + port, err := self.region.GetPort(self.PortId) + if err != nil { + log.Errorf("Get eip %s port %s error: %v", self.ID, self.PortId, err) + return "" + } + + switch port.DeviceOwner { + case "neutron:LOADBALANCER", "neutron:LOADBALANCERV2": + return api.EIP_ASSOCIATE_TYPE_LOADBALANCER + case "network:nat_gateway": + return api.EIP_ASSOCIATE_TYPE_NAT_GATEWAY + default: + log.Infof("eip %s associate type: %s", self.ID, port.DeviceOwner) + return api.EIP_ASSOCIATE_TYPE_SERVER + } +} + +func (self *SEipAddress) GetAssociationExternalId() string { + // network/0273a359d61847fc83405926c958c746/ext-floatingips?tenantId=0273a359d61847fc83405926c958c746&limit=2000 + // 只能通过 port id 反查device id. + if len(self.PortId) > 0 { + port, _ := self.region.GetPort(self.PortId) + return port.DeviceID + } + return "" +} + +func (self *SEipAddress) GetBandwidth() int { + return int(self.BandwidthSize) // Mb +} + +func (self *SEipAddress) GetINetworkId() string { + return "" +} + +func (self *SEipAddress) GetInternetChargeType() string { + // https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090603.html + bandwidth, err := self.region.GetEipBandwidth(self.BandwidthID) + if err != nil { + return api.EIP_CHARGE_TYPE_BY_TRAFFIC + } + + if bandwidth.ChargeMode != "traffic" { + return api.EIP_CHARGE_TYPE_BY_BANDWIDTH + } else { + return api.EIP_CHARGE_TYPE_BY_TRAFFIC + } +} + +func (self *SEipAddress) GetBillingType() string { + if self.Profile == nil { + return billing_api.BILLING_TYPE_POSTPAID + } else { + return billing_api.BILLING_TYPE_PREPAID + } +} + +func (self *SEipAddress) GetCreatedAt() time.Time { + return self.CreateTime +} + +func (self *SEipAddress) GetExpiredAt() time.Time { + return time.Time{} +} + +func (self *SEipAddress) Delete() error { + return self.region.DeallocateEIP(self.ID) +} + +func (self *SEipAddress) Associate(conf *cloudprovider.AssociateConfig) error { + portId, err := self.region.GetInstancePortId(conf.InstanceId) + if err != nil { + return err + } + + if len(self.PortId) > 0 { + if self.PortId == portId { + return nil + } + + return fmt.Errorf("eip %s aready associate with port %s", self.GetId(), self.PortId) + } + + err = self.region.AssociateEipWithPortId(self.ID, portId) + if err != nil { + return err + } + + err = cloudprovider.WaitStatusWithDelay(self, api.EIP_STATUS_READY, 10*time.Second, 10*time.Second, 180*time.Second) + return err +} + +func (self *SEipAddress) Dissociate() error { + if len(self.PortId) == 0 { + return nil + } + port, err := self.region.GetPort(self.PortId) + if err != nil { + return errors.Wrapf(err, "GetPort(%s)", self.PortId) + } + + err = self.region.DissociateEip(self.ID, port.DeviceID) + if err != nil { + return errors.Wrapf(err, "DissociateEip") + } + err = cloudprovider.WaitStatus(self, api.EIP_STATUS_READY, 10*time.Second, 180*time.Second) + return err +} + +func (self *SEipAddress) ChangeBandwidth(bw int) error { + return self.region.UpdateEipBandwidth(self.BandwidthID, bw) +} + +func (self *SRegion) GetInstancePortId(instanceId string) (string, error) { + // 目前只绑定一个网卡 + // todo: 还需要按照ports状态进行过滤 + ports, err := self.GetPorts(instanceId) + if err != nil { + return "", err + } + + if len(ports) == 0 { + return "", fmt.Errorf("AssociateEip instance %s port is empty", instanceId) + } + + return ports[0].ID, nil +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090596.html +func (self *SRegion) AllocateEIP(name string, bwMbps int, chargeType TInternetChargeType, bgpType string, projectId string) (*SEipAddress, error) { + paramsStr := ` +{ + "publicip": { + "type": "%s", + "ip_version": 4 + }, + "bandwidth": { + "name": "%s", + "size": %d, + "share_type": "PER", + "charge_mode": "%s" + } +} +` + if len(bgpType) == 0 { + return nil, fmt.Errorf("AllocateEIP bgp type should not be empty") + } + paramsStr = fmt.Sprintf(paramsStr, bgpType, name, bwMbps, chargeType) + _params, _ := jsonutils.ParseString(paramsStr) + params := _params.(*jsonutils.JSONDict) + if len(projectId) > 0 { + params.Set("enterprise_project_id", jsonutils.NewString(projectId)) + } + eip := SEipAddress{} + err := DoCreate(self.ecsClient.Eips.Create, params, &eip) + return &eip, err +} + +func (self *SRegion) GetEip(eipId string) (*SEipAddress, error) { + var eip SEipAddress + err := DoGet(self.ecsClient.Eips.Get, eipId, nil, &eip) + eip.region = self + return &eip, err +} + +func (self *SRegion) DeallocateEIP(eipId string) error { + _, err := self.ecsClient.Eips.Delete(eipId, nil) + return err +} + +func (self *SRegion) AssociateEip(eipId string, instanceId string) error { + portId, err := self.GetInstancePortId(instanceId) + if err != nil { + return err + } + return self.AssociateEipWithPortId(eipId, portId) +} + +func (self *SRegion) AssociateEipWithPortId(eipId string, portId string) error { + params := jsonutils.NewDict() + publicIPObj := jsonutils.NewDict() + publicIPObj.Add(jsonutils.NewString(portId), "port_id") + params.Add(publicIPObj, "publicip") + + _, err := self.ecsClient.Eips.Update(eipId, params) + return err +} + +func (self *SRegion) DissociateEip(eipId string, instanceId string) error { + eip, err := self.GetEip(eipId) + if err != nil { + return err + } + + // 已经是解绑状态 + if eip.Status == "DOWN" { + return nil + } + + remoteInstanceId := eip.GetAssociationExternalId() + if remoteInstanceId != instanceId { + return fmt.Errorf("eip %s associate with another instance %s", eipId, remoteInstanceId) + } + + paramsStr := `{"publicip":{"port_id":null}}` + params, _ := jsonutils.ParseString(paramsStr) + _, err = self.ecsClient.Eips.Update(eipId, params) + return err +} + +func (self *SRegion) UpdateEipBandwidth(bandwidthId string, bw int) error { + paramStr := `{ + "bandwidth": + { + "size": %d + } + }` + + paramStr = fmt.Sprintf(paramStr, bw) + params, _ := jsonutils.ParseString(paramStr) + _, err := self.ecsClient.Bandwidths.Update(bandwidthId, params) + return err +} + +func (self *SRegion) GetEipBandwidth(bandwidthId string) (Bandwidth, error) { + bandwidth := Bandwidth{} + err := DoGet(self.ecsClient.Bandwidths.Get, bandwidthId, nil, &bandwidth) + return bandwidth, err +} + +func (self *SEipAddress) GetProjectId() string { + return self.EnterpriseProjectId +} diff --git a/pkg/multicloud/huaweistack/elasticcache_account.go b/pkg/multicloud/huaweistack/elasticcache_account.go new file mode 100644 index 0000000000..b68fa5c01a --- /dev/null +++ b/pkg/multicloud/huaweistack/elasticcache_account.go @@ -0,0 +1,108 @@ +// 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 huaweistack + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + 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 + multicloud.HuaweiTags + + cacheDB *SElasticcache +} + +func (self *SElasticcacheAccount) GetId() string { + return fmt.Sprintf("%s/root", self.cacheDB.InstanceID) +} + +func (self *SElasticcacheAccount) GetName() string { + if len(self.cacheDB.AccessUser) > 0 { + return self.cacheDB.AccessUser + } + + return "root" +} + +func (self *SElasticcacheAccount) GetGlobalId() string { + return self.GetId() +} + +func (self *SElasticcacheAccount) GetStatus() string { + return api.ELASTIC_CACHE_ACCOUNT_STATUS_AVAILABLE +} + +func (self *SElasticcacheAccount) GetAccountType() string { + return api.ELASTIC_CACHE_ACCOUNT_TYPE_ADMIN +} + +func (self *SElasticcacheAccount) GetAccountPrivilege() string { + return api.ELASTIC_CACHE_ACCOUNT_PRIVILEGE_WRITE +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423031.html +// 未找到关闭密码的开放api, 不支持开启/关闭密码访问 +// https://console.huaweicloud.com/dcs/rest/v2/41f6bfe48d7f4455b7754f7c1b11ae34/instances/26db46e2-c7d8-4b5e-bd36-b5278d2fe17c/password/reset +// new_password: "26db46e2!" +// no_password_access: false +func (self *SElasticcacheAccount) ResetPassword(input cloudprovider.SCloudElasticCacheAccountResetPasswordInput) error { + if input.OldPassword == nil { + return fmt.Errorf("elasticcacheAccount.ResetPassword.input OldPassword should not be empty") + } + + type ResetPasswordResult struct { + Result string `json:"result"` + Message string `json:"message"` + } + + result := ResetPasswordResult{} + params := jsonutils.NewDict() + params.Set("old_password", jsonutils.NewString(*input.OldPassword)) + params.Set("new_password", jsonutils.NewString(input.NewPassword)) + err := DoUpdateWithSpec2(self.cacheDB.region.ecsClient.Elasticcache.UpdateInContextWithSpec, self.cacheDB.GetId(), "password", params, &result) + if err != nil { + return errors.Wrap(err, "elasticcacheAccount.ResetPassword") + } + + if result.Result != "success" { + return errors.Wrap(fmt.Errorf(result.Message), "elasticcacheAccount.ResetPassword") + } + + return nil +} + +func (self *SElasticcacheAccount) UpdateAccount(input cloudprovider.SCloudElasticCacheAccountUpdateInput) error { + if input.Password != nil { + inputPassword := cloudprovider.SCloudElasticCacheAccountResetPasswordInput{} + inputPassword.NewPassword = *input.Password + inputPassword.OldPassword = input.OldPassword + inputPassword.NoPasswordAccess = input.NoPasswordAccess + return self.ResetPassword(inputPassword) + } + + return nil +} + +func (self *SElasticcacheAccount) Delete() error { + return cloudprovider.ErrNotSupported +} diff --git a/pkg/multicloud/huaweistack/elasticcache_acl.go b/pkg/multicloud/huaweistack/elasticcache_acl.go new file mode 100644 index 0000000000..b81898ecb5 --- /dev/null +++ b/pkg/multicloud/huaweistack/elasticcache_acl.go @@ -0,0 +1,15 @@ +// 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 huaweistack diff --git a/pkg/multicloud/huaweistack/elasticcache_backup.go b/pkg/multicloud/huaweistack/elasticcache_backup.go new file mode 100644 index 0000000000..084ba43e9e --- /dev/null +++ b/pkg/multicloud/huaweistack/elasticcache_backup.go @@ -0,0 +1,138 @@ +// 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 huaweistack + +import ( + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423035.html +type SElasticcacheBackup struct { + multicloud.SElasticcacheBackupBase + multicloud.HuaweiTags + + cacheDB *SElasticcache + + Status string `json:"status"` + Remark string `json:"remark"` + Period string `json:"period"` + Progress string `json:"progress"` + SizeByte int64 `json:"size"` + InstanceID string `json:"instance_id"` + BackupID string `json:"backup_id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ExecutionAt time.Time `json:"execution_at"` + BackupType string `json:"backup_type"` + BackupName string `json:"backup_name"` + ErrorCode string `json:"error_code"` + IsSupportRestore string `json:"is_support_restore"` +} + +func (self *SElasticcacheBackup) GetId() string { + return self.BackupID +} + +func (self *SElasticcacheBackup) GetName() string { + return self.BackupName +} + +func (self *SElasticcacheBackup) GetGlobalId() string { + return self.GetId() +} + +func (self *SElasticcacheBackup) Refresh() error { + cache, err := self.cacheDB.GetICloudElasticcacheBackup(self.GetId()) + if err != nil { + return errors.Wrap(err, "ElasticcacheBackup.Refresh.GetICloudElasticcacheBackup") + } + + err = jsonutils.Update(self, cache) + if err != nil { + return errors.Wrap(err, "ElasticcacheBackup.Refresh.Update") + } + + return nil +} + +func (self *SElasticcacheBackup) GetStatus() string { + switch self.Status { + case "waiting", "backuping": + return api.ELASTIC_CACHE_BACKUP_STATUS_CREATING + case "succeed": + return api.ELASTIC_CACHE_BACKUP_STATUS_SUCCESS + case "failed": + return api.ELASTIC_CACHE_BACKUP_STATUS_FAILED + case "expired": + return api.ELASTIC_CACHE_BACKUP_STATUS_CREATE_EXPIRED + case "deleted": + return api.ELASTIC_CACHE_BACKUP_STATUS_CREATE_DELETED + default: + return self.Status + } +} + +func (self *SElasticcacheBackup) GetBackupSizeMb() int { + return int(self.SizeByte / 1024 / 1024) +} + +func (self *SElasticcacheBackup) GetBackupType() string { + switch self.BackupType { + case "manual": + return api.ELASTIC_CACHE_BACKUP_MODE_MANUAL + case "auto": + return api.ELASTIC_CACHE_BACKUP_MODE_AUTOMATED + default: + return self.BackupType + } + +} + +func (self *SElasticcacheBackup) GetBackupMode() string { + return "" +} + +func (self *SElasticcacheBackup) GetDownloadURL() string { + return "" +} + +func (self *SElasticcacheBackup) GetStartTime() time.Time { + return self.CreatedAt +} + +func (self *SElasticcacheBackup) GetEndTime() time.Time { + return self.UpdatedAt +} + +func (self *SElasticcacheBackup) Delete() error { + return cloudprovider.ErrNotSupported +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423034.html +func (self *SElasticcacheBackup) RestoreInstance(instanceId string) error { + _, err := self.cacheDB.region.ecsClient.Elasticcache.RestoreInstance(instanceId, self.GetId()) + if err != nil { + return nil + } + + return nil +} diff --git a/pkg/multicloud/huaweistack/elasticcache_instance.go b/pkg/multicloud/huaweistack/elasticcache_instance.go new file mode 100644 index 0000000000..24feb34a6e --- /dev/null +++ b/pkg/multicloud/huaweistack/elasticcache_instance.go @@ -0,0 +1,760 @@ +// 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 huaweistack + +import ( + "fmt" + "strconv" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/util/billing" +) + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423020.html +type SElasticcache struct { + multicloud.SElasticcacheBase + multicloud.HuaweiTags + + region *SRegion + + Name string `json:"name"` + Engine string `json:"engine"` + CapacityGB int `json:"capacity"` + IP string `json:"ip"` + DomainName string `json:"domainName"` + Port int `json:"port"` + Status string `json:"status"` + Libos bool `json:"libos"` + Description string `json:"description"` + Task string `json:"task"` + MaxMemoryMB int `json:"max_memory"` + UsedMemoryMB int `json:"used_memory"` + InstanceID string `json:"instance_id"` + ResourceSpecCode string `json:"resource_spec_code"` + EngineVersion string `json:"engine_version"` + InternalVersion string `json:"internal_version"` + ChargingMode int `json:"charging_mode"` + CapacityMinor string `json:"capacity_minor"` + VpcID string `json:"vpc_id"` + VpcName string `json:"vpc_name"` + TaskStatus string `json:"task_status"` + CreatedAt string `json:"created_at"` + ErrorCode string `json:"error_code"` + UserID string `json:"user_id"` + UserName string `json:"user_name"` + MaintainBegin string `json:"maintain_begin"` + MaintainEnd string `json:"maintain_end"` + NoPasswordAccess string `json:"no_password_access"` + AccessUser string `json:"access_user"` + EnablePublicip bool `json:"enable_publicip"` + PublicipID string `json:"publicip_id"` + PublicipAddress string `json:"publicip_address"` + EnableSSL bool `json:"enable_ssl"` + ServiceUpgrade bool `json:"service_upgrade"` + ServiceTaskID string `json:"service_task_id"` + IsFree string `json:"is_free"` + EnterpriseProjectID string `json:"enterprise_project_id"` + AvailableZones []string `json:"available_zones"` + SubnetID string `json:"subnet_id"` + SecurityGroupID string `json:"security_group_id"` + BackendAddrs []string `json:"backend_addrs"` + ProductID string `json:"product_id"` + SecurityGroupName string `json:"security_group_name"` + SubnetName string `json:"subnet_name"` + OrderID string `json:"order_id"` + SubnetCIDR string `json:"subnet_cidr"` + InstanceBackupPolicy string `json:"instance_backup_policy"` + EnterpriseProjectName string `json:"enterprise_project_name"` +} + +func (self *SElasticcache) GetId() string { + return self.InstanceID +} + +func (self *SElasticcache) GetName() string { + return self.Name +} + +func (self *SElasticcache) GetGlobalId() string { + return self.GetId() +} + +func (self *SElasticcache) GetProjectId() string { + return self.EnterpriseProjectID +} + +func (self *SElasticcache) Refresh() error { + cache, err := self.region.GetElasticCache(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) GetStatus() string { + switch self.Status { + case "RUNNING": + return api.ELASTIC_CACHE_STATUS_RUNNING + case "CREATING": + return api.ELASTIC_CACHE_STATUS_DEPLOYING + case "CREATEFAILED": + return api.ELASTIC_CACHE_STATUS_CREATE_FAILED + case "ERROR": + return api.ELASTIC_CACHE_STATUS_ERROR + case "RESTARTING": + return api.ELASTIC_CACHE_STATUS_RESTARTING + case "FROZEN": + return api.ELASTIC_CACHE_STATUS_UNAVAILABLE + case "EXTENDING": + return api.ELASTIC_CACHE_STATUS_CHANGING + case "RESTORING": + return api.ELASTIC_CACHE_STATUS_TRANSFORMING // ? + case "FLUSHING": + return api.ELASTIC_CACHE_STATUS_FLUSHING + } + + return "" +} + +func (self *SElasticcache) GetBillingType() string { + // charging_mode “0”:按需计费 “1”:按包年包月计费 + if self.ChargingMode == 1 { + return billing_api.BILLING_TYPE_PREPAID + } else { + return billing_api.BILLING_TYPE_POSTPAID + } +} + +func (self *SElasticcache) GetCreatedAt() time.Time { + var createtime time.Time + if len(self.CreatedAt) > 0 { + createtime, _ = time.Parse("2006-01-02T15:04:05.000Z", self.CreatedAt) + } + + return createtime +} + +func (self *SElasticcache) GetExpiredAt() time.Time { + var expiredTime time.Time + if self.ChargingMode == 1 { + res, err := self.region.GetOrderResourceDetail(self.GetId()) + if err != nil { + log.Debugln(err) + } + + expiredTime = res.ExpireTime + } + + return expiredTime +} + +func (self *SElasticcache) GetInstanceType() string { + // todo: ?? + return self.ResourceSpecCode +} + +func (self *SElasticcache) GetCapacityMB() int { + return self.CapacityGB * 1024 +} + +func (self *SElasticcache) GetArchType() string { + /* + 资源规格标识。 + + dcs.single_node:表示实例类型为单机 + dcs.master_standby:表示实例类型为主备 + dcs.cluster:表示实例类型为集群 + */ + if strings.Contains(self.ResourceSpecCode, "single") { + return api.ELASTIC_CACHE_ARCH_TYPE_SINGLE + } else if strings.Contains(self.ResourceSpecCode, "ha") { + return api.ELASTIC_CACHE_ARCH_TYPE_MASTER + } else if strings.Contains(self.ResourceSpecCode, "cluster") { + return api.ELASTIC_CACHE_ARCH_TYPE_CLUSTER + } else if strings.Contains(self.ResourceSpecCode, "proxy") { + return api.ELASTIC_CACHE_ARCH_TYPE_CLUSTER + } + + return "" +} + +func (self *SElasticcache) GetNodeType() string { + // single(单副本) | double(双副本) + if strings.Contains(self.ResourceSpecCode, "single") { + return "single" + } else { + return "double" + } +} + +func (self *SElasticcache) GetEngine() string { + return self.Engine +} + +func (self *SElasticcache) GetEngineVersion() string { + return self.EngineVersion +} + +func (self *SElasticcache) GetVpcId() string { + return self.VpcID +} + +func (self *SElasticcache) GetZoneId() string { + if len(self.AvailableZones) > 0 { + zone, err := self.region.getZoneById(self.AvailableZones[0]) + if err != nil { + log.Errorf("elasticcache.GetZoneId %s", err) + return "" + } + + return zone.GetGlobalId() + } + + return "" +} + +func (self *SElasticcache) GetNetworkType() string { + return api.LB_NETWORK_TYPE_VPC +} + +func (self *SElasticcache) GetNetworkId() string { + return self.SubnetID +} + +func (self *SElasticcache) GetPrivateDNS() string { + return self.DomainName +} + +func (self *SElasticcache) GetPrivateIpAddr() string { + return self.IP +} + +func (self *SElasticcache) GetPrivateConnectPort() int { + return self.Port +} + +func (self *SElasticcache) GetPublicDNS() string { + return self.PublicipAddress +} + +func (self *SElasticcache) GetPublicIpAddr() string { + return self.PublicipAddress +} + +func (self *SElasticcache) GetPublicConnectPort() int { + return self.Port +} + +func (self *SElasticcache) GetMaintainStartTime() string { + return self.MaintainBegin +} + +func (self *SElasticcache) GetMaintainEndTime() string { + return self.MaintainEnd +} + +func (self *SElasticcache) GetICloudElasticcacheAccounts() ([]cloudprovider.ICloudElasticcacheAccount, error) { + iaccounts := []cloudprovider.ICloudElasticcacheAccount{} + iaccount := &SElasticcacheAccount{cacheDB: self} + iaccounts = append(iaccounts, iaccount) + return iaccounts, nil +} + +func (self *SElasticcache) GetICloudElasticcacheAcls() ([]cloudprovider.ICloudElasticcacheAcl, error) { + // 华为云使用安全组做访问控制。目前未支持 + return []cloudprovider.ICloudElasticcacheAcl{}, nil +} + +func (self *SElasticcache) GetICloudElasticcacheBackups() ([]cloudprovider.ICloudElasticcacheBackup, error) { + start := self.GetCreatedAt().Format("20060102150405") + end := time.Now().Format("20060102150405") + backups, err := self.region.GetElasticCacheBackups(self.GetId(), start, end) + if err != nil { + return nil, err + } + + ibackups := make([]cloudprovider.ICloudElasticcacheBackup, len(backups)) + for i := range backups { + backups[i].cacheDB = self + ibackups[i] = &backups[i] + } + + return ibackups, nil +} + +func (self *SElasticcache) GetICloudElasticcacheParameters() ([]cloudprovider.ICloudElasticcacheParameter, error) { + parameters, err := self.region.GetElasticCacheParameters(self.GetId()) + if err != nil { + return nil, err + } + + iparameters := make([]cloudprovider.ICloudElasticcacheParameter, len(parameters)) + for i := range parameters { + parameters[i].cacheDB = self + iparameters[i] = ¶meters[i] + } + + return iparameters, nil +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423035.html +func (self *SRegion) GetElasticCacheBackups(instanceId, startTime, endTime string) ([]SElasticcacheBackup, error) { + params := make(map[string]string) + params["instance_id"] = instanceId + params["beginTime"] = startTime + params["endTime"] = endTime + + backups := make([]SElasticcacheBackup, 0) + err := doListAll(self.ecsClient.Elasticcache.ListBackups, params, &backups) + if err != nil { + return nil, err + } + + return backups, nil +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423027.html +func (self *SRegion) GetElasticCacheParameters(instanceId string) ([]SElasticcacheParameter, error) { + params := make(map[string]string) + params["instance_id"] = instanceId + + parameters := make([]SElasticcacheParameter, 0) + err := doListAll(self.ecsClient.Elasticcache.ListParameters, params, ¶meters) + if err != nil { + return nil, err + } + + return parameters, nil +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423044.html +func (self *SRegion) GetElasticCaches() ([]SElasticcache, error) { + params := make(map[string]string) + caches := make([]SElasticcache, 0) + err := doListAll(self.ecsClient.Elasticcache.List, params, &caches) + if err != nil { + return nil, errors.Wrap(err, "region.GetElasticCaches") + } + + for i := range caches { + cache, err := self.GetElasticCache(caches[i].GetId()) + if err != nil { + return nil, err + } else { + caches[i] = *cache + } + + caches[i].region = self + } + + return caches, nil +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423020.html +func (self *SRegion) GetElasticCache(instanceId string) (*SElasticcache, error) { + cache := SElasticcache{} + err := DoGet(self.ecsClient.Elasticcache.Get, instanceId, nil, &cache) + if err != nil { + return nil, errors.Wrapf(err, "region.GetElasticCache %s", instanceId) + } + + cache.region = self + return &cache, nil +} + +func (self *SRegion) GetIElasticcacheById(id string) (cloudprovider.ICloudElasticcache, error) { + ec, err := self.GetElasticCache(id) + if err != nil { + return nil, errors.Wrap(err, "region.GetIElasticCacheById.GetElasticCache") + } + + return ec, nil +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423047.html +func (self *SRegion) zoneNameToDcsZoneIds(zoneIds []string) ([]string, error) { + type Z struct { + ID string `json:"id"` + Code string `json:"code"` + Name string `json:"name"` + Port string `json:"port"` + ResourceAvailability string `json:"resource_availability"` + } + + rs := []Z{} + err := doListAll(self.ecsClient.DcsAvailableZone.List, nil, &rs) + if err != nil { + return nil, errors.Wrap(err, "region.zoneNameToDcsZoneIds") + } + + zoneMap := map[string]string{} + for i := range rs { + if rs[i].ResourceAvailability == "true" { + zoneMap[rs[i].Code] = rs[i].ID + } + } + + ret := []string{} + for _, zone := range zoneIds { + if id, ok := zoneMap[zone]; ok { + ret = append(ret, id) + } else { + return nil, errors.Wrap(fmt.Errorf("zone %s not found or not available", zone), "region.zoneNameToDcsZoneIds") + } + } + + return ret, nil +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423019.html +func (self *SRegion) CreateIElasticcaches(ec *cloudprovider.SCloudElasticCacheInput) (cloudprovider.ICloudElasticcache, error) { + params := jsonutils.NewDict() + params.Set("name", jsonutils.NewString(ec.InstanceName)) + params.Set("engine", jsonutils.NewString(ec.Engine)) + params.Set("engine_version", jsonutils.NewString(ec.EngineVersion)) + params.Set("capacity", jsonutils.NewInt(ec.CapacityGB)) + params.Set("vpc_id", jsonutils.NewString(ec.VpcId)) + 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) + if err != nil { + return nil, err + } + params.Set("available_zones", jsonutils.NewStringArray(zones)) + + if len(ec.ProjectId) > 0 { + params.Set("enterprise_project_id", jsonutils.NewString(ec.ProjectId)) + } + + if len(ec.Password) > 0 { + params.Set("no_password_access", jsonutils.NewString("false")) + params.Set("password", jsonutils.NewString(ec.Password)) + + // todo: 这里换成常量 + if ec.Engine == "Memcache" { + params.Set("access_user", jsonutils.NewString(ec.UserName)) + } + } else { + params.Set("no_password_access", jsonutils.NewString("true")) + } + + if len(ec.EipId) > 0 { + params.Set("enable_publicip", jsonutils.NewString("true")) + params.Set("publicip_id", jsonutils.NewString(ec.EipId)) + // enable_ssl + } else { + params.Set("enable_publicip", jsonutils.NewString("false")) + } + + if len(ec.PrivateIpAddress) > 0 { + params.Set("private_ip", jsonutils.NewString(ec.PrivateIpAddress)) + } + + if len(ec.MaintainBegin) > 0 { + params.Set("maintain_begin", jsonutils.NewString(ec.MaintainBegin)) + params.Set("maintain_end", jsonutils.NewString(ec.MaintainEnd)) + } + + if strings.ToLower(ec.ChargeType) == billing_api.BILLING_TYPE_PREPAID && ec.BC != nil { + bssParam := jsonutils.NewDict() + bssParam.Set("charging_mode", jsonutils.NewString("prePaid")) + bssParam.Set("is_auto_pay", jsonutils.NewString("true")) + bssParam.Set("is_auto_renew", jsonutils.NewString(fmt.Sprintf("%v", ec.BC.AutoRenew))) + if ec.BC.GetMonths() >= 1 && ec.BC.GetMonths() >= 9 { + bssParam.Set("period_type", jsonutils.NewString("month")) + bssParam.Set("period_num", jsonutils.NewInt(int64(ec.BC.GetMonths()))) + } else if ec.BC.GetYears() >= 1 && ec.BC.GetYears() <= 3 { + bssParam.Set("period_type", jsonutils.NewString("year")) + bssParam.Set("period_num", jsonutils.NewInt(int64(ec.BC.GetYears()))) + } else { + return nil, fmt.Errorf("region.CreateIElasticcaches invalid billing cycle.reqired month (1~9) or year(1~3)") + } + + params.Set("bss_param", bssParam) + } + + ret := &SElasticcache{} + err = DoCreate(self.ecsClient.Elasticcache.Create, params, ret) + if err != nil { + return nil, errors.Wrap(err, "region.CreateIElasticcaches") + } + + ret.region = self + return ret, nil +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423030.html +func (self *SElasticcache) Restart() error { + resp, err := self.region.ecsClient.Elasticcache.Restart(self.GetId()) + if err != nil { + return errors.Wrap(err, "elasticcache.Restart") + } + + rets, err := resp.GetArray("results") + if err != nil { + return errors.Wrap(err, "elasticcache.results") + } + + for _, r := range rets { + if ret, _ := r.GetString("result"); ret != "success" { + return fmt.Errorf("elasticcache.Restart failed") + } + } + + return nil +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423022.html +func (self *SElasticcache) Delete() error { + err := DoDelete(self.region.ecsClient.Elasticcache.Delete, self.GetId(), nil, nil) + if err != nil { + return errors.Wrap(err, "elasticcache.Delete") + } + + return nil +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423024.html +func (self *SElasticcache) ChangeInstanceSpec(spec string) error { + segs := strings.Split(spec, ":") + if len(segs) < 2 { + return fmt.Errorf("elasticcache.ChangeInstanceSpec invalid sku %s", spec) + } + + if !strings.HasPrefix(segs[1], "m") || !strings.HasSuffix(segs[1], "g") { + return fmt.Errorf("elasticcache.ChangeInstanceSpec sku %s memeory size is invalid.", spec) + } + + newCapacity := segs[1][1 : len(segs[1])-1] + capacity, err := strconv.Atoi(newCapacity) + if err != nil { + return errors.Wrap(fmt.Errorf("invalid sku capacity %s", spec), "Elasticcache.ChangeInstanceSpec") + } + + _, err = self.region.ecsClient.Elasticcache.ChangeInstanceSpec(self.GetId(), segs[0], int64(capacity)) + if err != nil { + return errors.Wrap(err, "elasticcache.ChangeInstanceSpec") + } + + return nil +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423021.html +func (self *SElasticcache) SetMaintainTime(maintainStartTime, maintainEndTime string) error { + params := jsonutils.NewDict() + params.Set("maintain_begin", jsonutils.NewString(maintainStartTime)) + params.Set("maintain_end", jsonutils.NewString(maintainEndTime)) + err := DoUpdate(self.region.ecsClient.Elasticcache.Update, self.GetId(), params, nil) + if err != nil { + return errors.Wrap(err, "elasticcache.SetMaintainTime") + } + + return nil +} + +// https://support.huaweicloud.com/usermanual-dcs/dcs-zh-ug-180314001.html +// 目前只有Redis3.0版本密码模式的实例支持通过公网访问Redis实例,其他版本暂不支持公网访问。 +// todo: 目前没找到api +func (self *SElasticcache) AllocatePublicConnection(port int) (string, error) { + return "", cloudprovider.ErrNotSupported +} + +// todo: 目前没找到api +func (self *SElasticcache) ReleasePublicConnection() error { + return cloudprovider.ErrNotSupported +} + +func (self *SElasticcache) CreateAccount(account cloudprovider.SCloudElasticCacheAccountInput) (cloudprovider.ICloudElasticcacheAccount, error) { + return nil, cloudprovider.ErrNotSupported +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423031.html + +func (self *SElasticcache) CreateAcl(aclName, securityIps string) (cloudprovider.ICloudElasticcacheAcl, error) { + return nil, cloudprovider.ErrNotSupported +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423029.html +func (self *SElasticcache) UpdateInstanceParameters(config jsonutils.JSONObject) error { + params := jsonutils.NewDict() + params.Set("redis_config", config) + err := DoUpdateWithSpec(self.region.ecsClient.Elasticcache.UpdateInContextWithSpec, self.GetId(), "configs", params) + if err != nil { + return errors.Wrap(err, "elasticcache.UpdateInstanceParameters") + } + + return nil +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423033.html +func (self *SElasticcache) CreateBackup(desc string) (cloudprovider.ICloudElasticcacheBackup, error) { + return nil, cloudprovider.ErrNotSupported +} + +func backupPeriodTrans(config cloudprovider.SCloudElasticCacheBackupPolicyUpdateInput) *jsonutils.JSONArray { + segs := strings.Split(config.PreferredBackupPeriod, ",") + ret := jsonutils.NewArray() + for _, seg := range segs { + switch seg { + case "Monday": + ret.Add(jsonutils.NewString("1")) + case "Tuesday": + ret.Add(jsonutils.NewString("2")) + case "Wednesday": + ret.Add(jsonutils.NewString("3")) + case "Thursday": + ret.Add(jsonutils.NewString("4")) + case "Friday": + ret.Add(jsonutils.NewString("5")) + case "Saturday": + ret.Add(jsonutils.NewString("6")) + case "Sunday": + ret.Add(jsonutils.NewString("7")) + } + } + + return ret +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423021.html +func (self *SElasticcache) UpdateBackupPolicy(config cloudprovider.SCloudElasticCacheBackupPolicyUpdateInput) error { + params := jsonutils.NewDict() + policy := jsonutils.NewDict() + policy.Set("save_days", jsonutils.NewInt(int64(config.BackupReservedDays))) + policy.Set("backup_type", jsonutils.NewString(config.BackupType)) + plan := jsonutils.NewDict() + backTime := strings.ReplaceAll(config.PreferredBackupTime, "Z", "") + backupPeriod := backupPeriodTrans(config) + plan.Set("begin_at", jsonutils.NewString(backTime)) + plan.Set("period_type", jsonutils.NewString("weekly")) + plan.Set("backup_at", backupPeriod) + policy.Set("periodical_backup_plan", plan) + params.Set("instance_backup_policy", policy) + err := DoUpdateWithSpec(self.region.ecsClient.Elasticcache.UpdateInContextWithSpec, self.GetId(), "configs", params) + if err != nil { + return errors.Wrap(err, "elasticcache.UpdateInstanceParameters") + } + + return nil +} + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423030.html +// 当前版本,只有DCS2.0实例支持清空数据功能,即flush操作。 +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") + } + + rets, err := resp.GetArray("results") + if err != nil { + return errors.Wrap(err, "elasticcache.FlushInstance") + } + + for _, r := range rets { + if ret, _ := r.GetString("result"); ret != "success" { + return fmt.Errorf("elasticcache.FlushInstance failed") + } + } + + return nil +} + +// SElasticcacheAccount => ResetPassword +func (self *SElasticcache) UpdateAuthMode(noPwdAccess bool, password string) error { + return cloudprovider.ErrNotSupported +} + +func (self *SElasticcache) GetAuthMode() string { + switch self.NoPasswordAccess { + case "true": + return "off" + default: + return "on" + } +} + +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 { + return nil, errors.Wrap(err, "Elasticcache.GetICloudElasticcacheAccount.Accounts") + } + + for i := range accounts { + account := accounts[i] + if account.GetGlobalId() == accountId { + 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.GetICloudElasticcacheBackups() + if err != nil { + return nil, err + } + + for _, backup := range backups { + if backup.GetId() == backupId { + return backup, nil + } + } + + return nil, cloudprovider.ErrNotFound +} + +func (instance *SElasticcache) SetTags(tags map[string]string, replace bool) error { + return cloudprovider.ErrNotImplemented +} + +func (self *SElasticcache) UpdateSecurityGroups(secgroupIds []string) error { + return errors.Wrap(cloudprovider.ErrNotSupported, "UpdateSecurityGroups") +} + +func (self *SElasticcache) Renew(bc billing.SBillingCycle) error { + return cloudprovider.ErrNotSupported +} + +func (self *SElasticcache) SetAutoRenew(autoRenew bool) error { + return cloudprovider.ErrNotSupported +} diff --git a/pkg/multicloud/huaweistack/elasticcache_parameter.go b/pkg/multicloud/huaweistack/elasticcache_parameter.go new file mode 100644 index 0000000000..39a88437ac --- /dev/null +++ b/pkg/multicloud/huaweistack/elasticcache_parameter.go @@ -0,0 +1,78 @@ +// 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 huaweistack + +import ( + "fmt" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/multicloud" +) + +// https://support.huaweicloud.com/api-dcs/dcs-zh-api-180423027.html +type SElasticcacheParameter struct { + multicloud.SElasticcacheParameterBase + multicloud.HuaweiTags + + cacheDB *SElasticcache + + Description string `json:"description"` + ParamID int64 `json:"param_id"` + ParamName string `json:"param_name"` + ParamValue string `json:"param_value"` + DefaultValue string `json:"default_value"` + ValueType string `json:"value_type"` + ValueRange string `json:"value_range"` +} + +func (self *SElasticcacheParameter) GetId() string { + return fmt.Sprintf("%d", self.ParamID) +} + +func (self *SElasticcacheParameter) GetName() string { + return self.ParamName +} + +func (self *SElasticcacheParameter) GetGlobalId() string { + return fmt.Sprintf("%s/%s", self.cacheDB.InstanceID, 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.ParamValue +} + +func (self *SElasticcacheParameter) GetParameterValueRange() string { + return self.Description +} + +func (self *SElasticcacheParameter) GetDescription() string { + return self.ValueRange +} + +func (self *SElasticcacheParameter) GetModifiable() bool { + return true +} + +func (self *SElasticcacheParameter) GetForceRestart() bool { + return false +} diff --git a/pkg/multicloud/huaweistack/enterpriceprojects.go b/pkg/multicloud/huaweistack/enterpriceprojects.go new file mode 100644 index 0000000000..4d3ac12dd9 --- /dev/null +++ b/pkg/multicloud/huaweistack/enterpriceprojects.go @@ -0,0 +1,114 @@ +// 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 huaweistack + +import ( + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +// https://support.huaweicloud.com/api-em/zh-cn_topic_0121230880.html +type SEnterpriseProject struct { + multicloud.SResourceBase + multicloud.HuaweiTags + + Id string + Name string + Description string + Status int + CreatedAt time.Time + UpdatedAt time.Time +} + +func (self *SHuaweiClient) GetEnterpriseProjects() ([]SEnterpriseProject, error) { + projects := []SEnterpriseProject{} + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + err = doListAllWithOffset(client.EnterpriseProjects.List, map[string]string{}, &projects) + if err != nil { + return nil, errors.Wrap(err, "doListAllWithOffset") + } + return projects, nil +} + +func (ep *SEnterpriseProject) GetId() string { + return ep.Id +} + +func (ep *SEnterpriseProject) GetGlobalId() string { + return ep.Id +} + +func (ep *SEnterpriseProject) GetStatus() string { + if ep.Status == 1 { + return api.EXTERNAL_PROJECT_STATUS_AVAILABLE + } + return api.EXTERNAL_PROJECT_STATUS_UNAVAILABLE +} + +func (ep *SEnterpriseProject) GetName() string { + return ep.Name +} + +func (self *SHuaweiClient) CreateExterpriseProject(name, desc string) (*SEnterpriseProject, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + params := map[string]string{ + "name": name, + } + if len(desc) > 0 { + params["description"] = desc + } + resp, err := client.EnterpriseProjects.Create(jsonutils.Marshal(params)) + if err != nil { + if strings.Contains(err.Error(), "EPS.0004") { + return nil, cloudprovider.ErrNotSupported + } + return nil, errors.Wrap(err, "EnterpriseProjects.Create") + } + project := &SEnterpriseProject{} + err = resp.Unmarshal(&project) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return project, nil +} + +func (self *SHuaweiClient) CreateIProject(name string) (cloudprovider.ICloudProject, error) { + return self.CreateExterpriseProject(name, "") +} + +func (self *SHuaweiClient) GetIProjects() ([]cloudprovider.ICloudProject, error) { + projects, err := self.GetEnterpriseProjects() + if err != nil { + return nil, errors.Wrap(err, "GetProjects") + } + ret := []cloudprovider.ICloudProject{} + for i := range projects { + ret = append(ret, &projects[i]) + } + return ret, nil +} diff --git a/pkg/multicloud/huaweistack/host.go b/pkg/multicloud/huaweistack/host.go new file mode 100644 index 0000000000..b393cfcbc7 --- /dev/null +++ b/pkg/multicloud/huaweistack/host.go @@ -0,0 +1,306 @@ +// 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 huaweistack + +import ( + "fmt" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/osprofile" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/util/billing" +) + +type SHost struct { + multicloud.SHostBase + zone *SZone + + projectId string +} + +func (self *SHost) GetId() string { + return fmt.Sprintf("%s-%s", self.zone.region.client.cpcfg.Id, self.zone.GetId()) +} + +func (self *SHost) GetName() string { + return fmt.Sprintf("%s-%s", self.zone.region.client.cpcfg.Name, self.zone.GetId()) +} + +func (self *SHost) GetGlobalId() string { + return fmt.Sprintf("%s-%s", self.zone.region.client.cpcfg.Id, self.zone.GetId()) +} + +func (self *SHost) GetStatus() string { + return api.HOST_STATUS_RUNNING +} + +func (self *SHost) Refresh() error { + return nil +} + +func (self *SHost) IsEmulated() bool { + return true +} + +func (self *SHost) GetIVMs() ([]cloudprovider.ICloudVM, error) { + vms, err := self.zone.region.GetInstances() + if err != nil { + return nil, err + } + + filtedVms := make([]SInstance, 0) + for i := range vms { + if vms[i].OSEXTAZAvailabilityZone == self.zone.GetId() { + filtedVms = append(filtedVms, vms[i]) + } + } + + ivms := make([]cloudprovider.ICloudVM, len(filtedVms)) + for i := 0; i < len(filtedVms); i += 1 { + filtedVms[i].host = self + ivms[i] = &filtedVms[i] + } + return ivms, nil +} + +func (self *SHost) GetIVMById(id string) (cloudprovider.ICloudVM, error) { + vm, err := self.zone.region.GetInstanceByID(id) + vm.host = self + return &vm, err +} + +func (self *SHost) GetIWires() ([]cloudprovider.ICloudWire, error) { + return self.zone.GetIWires() +} + +func (self *SHost) GetIStorages() ([]cloudprovider.ICloudStorage, error) { + return self.zone.GetIStorages() +} + +func (self *SHost) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + return self.zone.GetIStorageById(id) +} + +func (self *SHost) GetEnabled() bool { + return true +} + +func (self *SHost) GetHostStatus() string { + return api.HOST_ONLINE +} + +func (self *SHost) GetAccessIp() string { + return "" +} + +func (self *SHost) GetAccessMac() string { + return "" +} + +func (self *SHost) GetSysInfo() jsonutils.JSONObject { + info := jsonutils.NewDict() + info.Add(jsonutils.NewString(CLOUD_PROVIDER_HUAWEI), "manufacture") + return info +} + +func (self *SHost) GetSN() string { + return "" +} + +func (self *SHost) GetCpuCount() int { + return 0 +} + +func (self *SHost) GetNodeCount() int8 { + return 0 +} + +func (self *SHost) GetCpuDesc() string { + return "" +} + +func (self *SHost) GetCpuMhz() int { + return 0 +} + +func (self *SHost) GetMemSizeMB() int { + return 0 +} + +func (self *SHost) GetStorageSizeMB() int { + return 0 +} + +func (self *SHost) GetStorageType() string { + return api.DISK_TYPE_HYBRID +} + +func (self *SHost) GetHostType() string { + return api.HOST_TYPE_HUAWEI_CLOUD_STACK +} + +func (self *SHost) GetIsMaintenance() bool { + return false +} + +func (self *SHost) GetVersion() string { + return HUAWEI_API_VERSION +} + +func (self *SHost) GetInstanceById(instanceId string) (*SInstance, error) { + instance, err := self.zone.region.GetInstanceByID(instanceId) + if err != nil { + return nil, err + } + + instance.host = self + return &instance, nil +} + +func (self *SHost) CreateVM(desc *cloudprovider.SManagedVMCreateConfig) (cloudprovider.ICloudVM, error) { + vmId, err := self._createVM( + desc.Name, desc.ExternalImageId, desc.SysDisk, + desc.Cpu, desc.MemoryMB, desc.InstanceType, + desc.ExternalNetworkId, desc.IpAddr, + desc.Description, desc.Account, + desc.Password, desc.DataDisks, + desc.PublicKey, desc.ExternalSecgroupId, + desc.UserData, desc.BillingCycle, desc.ProjectId, desc.Tags) + if err != nil { + return nil, err + } + + vm, err := self.GetInstanceById(vmId) + if err != nil { + return nil, err + } + + return vm, err +} + +func (self *SHost) GetIHostNics() ([]cloudprovider.ICloudHostNetInterface, error) { + return nil, cloudprovider.ErrNotSupported +} + +func (self *SHost) _createVM(name string, imgId string, sysDisk cloudprovider.SDiskInfo, cpu int, memMB int, instanceType string, + networkId string, ipAddr string, desc string, account string, passwd string, + diskSizes []cloudprovider.SDiskInfo, publicKey string, secgroupId string, + userData string, bc *billing.SBillingCycle, projectId string, tags map[string]string) (string, error) { + net := self.zone.getNetworkById(networkId) + if net == nil { + return "", fmt.Errorf("invalid network ID %s", networkId) + } + + if net.wire == nil { + log.Errorf("network's wire is empty") + return "", fmt.Errorf("network's wire is empty") + } + + if net.wire.vpc == nil { + log.Errorf("wire's vpc is empty") + return "", fmt.Errorf("wire's vpc is empty") + } + + // 同步keypair + var err error + keypair := "" + if len(publicKey) > 0 { + keypair, err = self.zone.region.syncKeypair(publicKey) + if err != nil { + return "", err + } + } + + // 镜像及硬盘配置 + img, err := self.zone.region.GetImage(imgId) + if err != nil { + log.Errorf("getiamge %s fail %s", imgId, err) + return "", err + } + if img.Status != ImageStatusActive { + log.Errorf("image %s status %s", imgId, img.Status) + return "", fmt.Errorf("image not ready") + } + // passwd, windows机型直接使用密码比较方便 + if strings.ToLower(img.Platform) == strings.ToLower(osprofile.OS_TYPE_WINDOWS) && len(passwd) > 0 { + keypair = "" + } + + if strings.ToLower(img.Platform) == strings.ToLower(osprofile.OS_TYPE_WINDOWS) { + if u, err := updateWindowsUserData(userData, img.OSVersion, account, passwd); err == nil { + userData = u + } else { + return "", errors.Wrap(err, "SHost.CreateVM.updateWindowsUserData") + } + } + + disks := make([]SDisk, len(diskSizes)+1) + disks[0].SizeGB = img.SizeGB + if sysDisk.SizeGB > 0 && sysDisk.SizeGB > img.SizeGB { + disks[0].SizeGB = sysDisk.SizeGB + } + disks[0].VolumeType = sysDisk.StorageType + + for i, dataDisk := range diskSizes { + disks[i+1].SizeGB = dataDisk.SizeGB + disks[i+1].VolumeType = dataDisk.StorageType + } + + _, err = self.zone.region.GetSecurityGroupDetails(secgroupId) + if err != nil { + return "", errors.Wrap(err, "SHost.CreateVM.GetSecurityGroupDetails") + } + + // 创建实例 + if len(instanceType) > 0 { + log.Debugf("Try instancetype : %s", instanceType) + vmId, err := self.zone.region.CreateInstance(name, imgId, instanceType, networkId, secgroupId, net.VpcID, self.zone.GetId(), desc, disks, ipAddr, keypair, publicKey, passwd, userData, bc, projectId, tags) + if err != nil { + log.Errorf("Failed for %s: %s", instanceType, err) + return "", fmt.Errorf("create %s failed:%s", instanceType, ErrMessage(err)) + } else { + return vmId, nil + } + } + + // 匹配实例类型 + instanceTypes, err := self.zone.region.GetMatchInstanceTypes(cpu, memMB, self.zone.GetId()) + if err != nil { + return "", err + } + if len(instanceTypes) == 0 { + return "", fmt.Errorf("instance type %dC%dMB not avaiable", cpu, memMB) + } + + var vmId string + for _, instType := range instanceTypes { + instanceTypeId := instType.Name + log.Debugf("Try instancetype : %s", instanceTypeId) + vmId, err = self.zone.region.CreateInstance(name, imgId, instanceTypeId, networkId, secgroupId, net.VpcID, self.zone.GetId(), desc, disks, ipAddr, keypair, publicKey, passwd, userData, bc, projectId, tags) + if err != nil { + log.Errorf("Failed for %s: %s", instanceTypeId, err) + } else { + return vmId, nil + } + } + + return "", fmt.Errorf("create failed: %s", ErrMessage(err)) +} diff --git a/pkg/multicloud/huaweistack/huawei.go b/pkg/multicloud/huaweistack/huawei.go new file mode 100644 index 0000000000..c7da124168 --- /dev/null +++ b/pkg/multicloud/huaweistack/huawei.go @@ -0,0 +1,564 @@ +// 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 huaweistack + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/timeutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/huawei/obs" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/auth" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/auth/credentials" +) + +/* +待解决问题: +1.同步的子账户中有一条空记录.需要查原因 +2.安全组同步需要进一步确认 +3.实例接口需要进一步确认 +4.BGP type 目前是hard code在代码中。需要考虑从cloudmeta服务中查询 +*/ + +const ( + CLOUD_PROVIDER_HUAWEI = api.CLOUD_PROVIDER_HUAWEI_CLOUD_STACK + CLOUD_PROVIDER_HUAWEI_CN = "华为云Stack" + CLOUD_PROVIDER_HUAWEI_EN = "HuaweiCloudStack" + + HUAWEI_API_VERSION = "" +) + +var HUAWEI_REGION_CACHES = map[string]userRegionsCache{} + +type userRegionsCache struct { + UserId string + ExpireAt time.Time + Regions []SRegion +} + +type HuaweiClientConfig struct { + cpcfg cloudprovider.ProviderConfig + endpoints *cloudprovider.SHuaweiCloudStackEndpoints + + projectId string // 华为云项目ID. + accessKey string + accessSecret string + + debug bool +} + +func NewHuaweiClientConfig(accessKey, accessSecret, projectId string, endpoints *cloudprovider.SHuaweiCloudStackEndpoints) *HuaweiClientConfig { + cfg := &HuaweiClientConfig{ + projectId: projectId, + accessKey: accessKey, + accessSecret: accessSecret, + endpoints: endpoints, + } + return cfg +} + +func (cfg *HuaweiClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *HuaweiClientConfig { + cfg.cpcfg = cpcfg + return cfg +} + +func (cfg *HuaweiClientConfig) Debug(debug bool) *HuaweiClientConfig { + cfg.debug = debug + return cfg +} + +type SHuaweiClient struct { + *HuaweiClientConfig + + signer auth.Signer + + isMainProject bool // whether the project is the main project in the region + + ownerId string + ownerName string + ownerCreateTime time.Time + + iregions []cloudprovider.ICloudRegion + iBuckets []cloudprovider.ICloudBucket + + projects []SProject + regions []SRegion +} + +// 进行资源操作时参数account 对应数据库cloudprovider表中的account字段,由accessKey和projectID两部分组成,通过"/"分割。 +// 初次导入Subaccount时,参数account对应cloudaccounts表中的account字段,即accesskey。此时projectID为空, +// 只能进行同步子账号、查询region列表等projectId无关的操作。 +func NewHuaweiClient(cfg *HuaweiClientConfig) (*SHuaweiClient, error) { + client := SHuaweiClient{ + HuaweiClientConfig: cfg, + } + err := client.init() + if err != nil { + return nil, err + } + return &client, nil +} + +func (self *SHuaweiClient) init() error { + err := self.fetchRegions() + if err != nil { + return err + } + err = self.initSigner() + if err != nil { + return errors.Wrap(err, "initSigner") + } + err = self.initOwner() + if err != nil { + return errors.Wrap(err, "fetchOwner") + } + if self.debug { + log.Debugf("OwnerId: %s name: %s", self.ownerId, self.ownerName) + } + return nil +} + +func (self *SHuaweiClient) initSigner() error { + var err error + cred := credentials.NewAccessKeyCredential(self.accessKey, self.accessKey) + self.signer, err = auth.NewSignerWithCredential(cred) + if err != nil { + return err + } + return nil +} + +func (self *SHuaweiClient) newRegionAPIClient(regionId string) (*client.Client, error) { + cli, err := client.NewClientWithAccessKey(regionId, self.ownerId, self.projectId, self.accessKey, self.accessSecret, self.debug, self.endpoints) + if err != nil { + return nil, err + } + + httpClient := self.cpcfg.AdaptiveTimeoutHttpClient() + cli.SetHttpClient(httpClient) + + return cli, nil +} + +func (self *SHuaweiClient) newGeneralAPIClient() (*client.Client, error) { + cli, err := client.NewClientWithAccessKey(self.endpoints.DefaultRegion, self.ownerId, "", self.accessKey, self.accessSecret, self.debug, self.endpoints) + if err != nil { + return nil, err + } + + httpClient := self.cpcfg.AdaptiveTimeoutHttpClient() + cli.SetHttpClient(httpClient) + + return cli, nil +} + +func (self *SHuaweiClient) fetchRegions() error { + huawei, _ := self.newGeneralAPIClient() + if self.regions == nil { + userId, err := self.GetUserId() + if err != nil { + return errors.Wrap(err, "GetUserId") + } + + if regionsCache, ok := HUAWEI_REGION_CACHES[userId]; !ok || regionsCache.ExpireAt.Sub(time.Now()).Seconds() > 0 { + regions := make([]SRegion, 0) + err := doListAll(huawei.Regions.List, nil, ®ions) + if err != nil { + return errors.Wrap(err, "Regions.List") + } + + HUAWEI_REGION_CACHES[userId] = userRegionsCache{ExpireAt: time.Now().Add(24 * time.Hour), UserId: userId, Regions: regions} + } + + self.regions = HUAWEI_REGION_CACHES[userId].Regions + } + + filtedRegions := make([]SRegion, 0) + if len(self.projectId) > 0 { + project, err := self.GetProjectById(self.projectId) + if err != nil { + return err + } + + regionId := strings.Split(project.Name, "_")[0] + for _, region := range self.regions { + if region.ID == regionId { + filtedRegions = append(filtedRegions, region) + } + } + if regionId == project.Name { + self.isMainProject = true + } + } else { + filtedRegions = self.regions + } + + self.iregions = make([]cloudprovider.ICloudRegion, len(filtedRegions)) + for i := 0; i < len(filtedRegions); i += 1 { + filtedRegions[i].client = self + _, err := filtedRegions[i].getECSClient() + if err != nil { + return err + } + self.iregions[i] = &filtedRegions[i] + } + return nil +} + +func (self *SHuaweiClient) invalidateIBuckets() { + self.iBuckets = nil +} + +func (self *SHuaweiClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) { + if self.iBuckets == nil { + err := self.fetchBuckets() + if err != nil { + return nil, errors.Wrap(err, "fetchBuckets") + } + } + return self.iBuckets, nil +} + +func getOBSEndpoint(regionId string) string { + return fmt.Sprintf("obs.%s.myhuaweicloud.com", regionId) +} + +func (client *SHuaweiClient) getOBSClient(regionId string) (*obs.ObsClient, error) { + endpoint := getOBSEndpoint(regionId) + return obs.New(client.accessKey, client.accessSecret, endpoint) +} + +func (self *SHuaweiClient) fetchBuckets() error { + obscli, err := self.getOBSClient(self.endpoints.DefaultRegion) + if err != nil { + return errors.Wrap(err, "getOBSClient") + } + input := &obs.ListBucketsInput{QueryLocation: true} + output, err := obscli.ListBuckets(input) + if err != nil { + return errors.Wrap(err, "obscli.ListBuckets") + } + self.ownerId = output.Owner.ID + + ret := make([]cloudprovider.ICloudBucket, 0) + for i := range output.Buckets { + bInfo := output.Buckets[i] + region, err := self.getIRegionByRegionId(bInfo.Location) + if err != nil { + log.Errorf("fail to find region %s", bInfo.Location) + continue + } + b := SBucket{ + region: region.(*SRegion), + + Name: bInfo.Name, + Location: bInfo.Location, + CreationDate: bInfo.CreationDate, + } + ret = append(ret, &b) + } + self.iBuckets = ret + return nil +} + +func (self *SHuaweiClient) GetCloudRegionExternalIdPrefix() string { + if len(self.projectId) > 0 { + return self.iregions[0].GetGlobalId() + } else { + return CLOUD_PROVIDER_HUAWEI + } +} + +func (self *SHuaweiClient) UpdateAccount(accessKey, secret string) error { + if self.accessKey != accessKey || self.accessSecret != secret { + self.accessKey = accessKey + self.accessSecret = secret + return self.fetchRegions() + } else { + return nil + } +} + +func (self *SHuaweiClient) GetRegions() []SRegion { + regions := make([]SRegion, len(self.iregions)) + for i := 0; i < len(regions); i += 1 { + region := self.iregions[i].(*SRegion) + regions[i] = *region + } + return regions +} + +func (self *SHuaweiClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { + projects, err := self.fetchProjects() + if err != nil { + return nil, err + } + + // https://support.huaweicloud.com/api-iam/zh-cn_topic_0074171149.html + subAccounts := make([]cloudprovider.SSubAccount, 0) + for i := range projects { + project := projects[i] + // name 为MOS的project是华为云内部的一个特殊project。不需要同步到本地 + if strings.ToLower(project.Name) == "mos" { + continue + } + // https://www.huaweicloud.com/notice/2018/20190618171312411.html + expiredAt, _ := timeutils.ParseTimeStr("2020-09-16 00:00:00") + if !self.ownerCreateTime.IsZero() && self.ownerCreateTime.After(expiredAt) && strings.ToLower(project.Name) == "cn-north-1" { + continue + } + s := cloudprovider.SSubAccount{ + Name: fmt.Sprintf("%s-%s", self.cpcfg.Name, project.Name), + Account: fmt.Sprintf("%s/%s", self.accessKey, project.ID), + HealthStatus: project.GetHealthStatus(), + } + + subAccounts = append(subAccounts, s) + } + + return subAccounts, nil +} + +func (client *SHuaweiClient) GetAccountId() string { + return client.ownerId +} + +func (client *SHuaweiClient) GetIamLoginUrl() string { + return fmt.Sprintf("https://auth.huaweicloud.com/authui/login.html?account=%s#/login", client.ownerName) +} + +func (self *SHuaweiClient) GetIRegions() []cloudprovider.ICloudRegion { + return self.iregions +} + +func (self *SHuaweiClient) getIRegionByRegionId(id string) (cloudprovider.ICloudRegion, error) { + for i := 0; i < len(self.iregions); i += 1 { + log.Debugf("%d ID: %s", i, self.iregions[i].GetId()) + if self.iregions[i].GetId() == id { + return self.iregions[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SHuaweiClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) { + for i := 0; i < len(self.iregions); i += 1 { + if self.iregions[i].GetGlobalId() == id { + return self.iregions[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SHuaweiClient) GetRegion(regionId string) *SRegion { + if len(regionId) == 0 { + regionId = self.endpoints.DefaultRegion + } + + for i := 0; i < len(self.iregions); i += 1 { + if self.iregions[i].GetId() == regionId { + return self.iregions[i].(*SRegion) + } + } + return nil +} + +func (self *SHuaweiClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) { + for i := 0; i < len(self.iregions); i += 1 { + ihost, err := self.iregions[i].GetIHostById(id) + if err == nil { + return ihost, nil + } else if errors.Cause(err) != cloudprovider.ErrNotFound { + return nil, err + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SHuaweiClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) { + for i := 0; i < len(self.iregions); i += 1 { + ivpc, err := self.iregions[i].GetIVpcById(id) + if err == nil { + return ivpc, nil + } else if errors.Cause(err) != cloudprovider.ErrNotFound { + return nil, err + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SHuaweiClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + for i := 0; i < len(self.iregions); i += 1 { + istorage, err := self.iregions[i].GetIStorageById(id) + if err == nil { + return istorage, nil + } else if errors.Cause(err) != cloudprovider.ErrNotFound { + return nil, err + } + } + return nil, cloudprovider.ErrNotFound +} + +// 总账户余额 +type SAccountBalance struct { + AvailableAmount float64 + CreditAmount float64 + DesignatedAmount float64 +} + +// 账户余额 +// https://support.huaweicloud.com/api-oce/zh-cn_topic_0109685133.html +type SBalance struct { + Amount float64 `json:"amount"` + Currency string `json:"currency"` + AccountID string `json:"account_id"` + AccountType int64 `json:"account_type"` + DesignatedAmount float64 `json:"designated_amount,omitempty"` + CreditAmount float64 `json:"credit_amount,omitempty"` + MeasureUnit int64 `json:"measure_unit"` +} + +// 这里的余额指的是所有租户的总余额 +func (self *SHuaweiClient) QueryAccountBalance() (*SAccountBalance, error) { + domains, err := self.getEnabledDomains() + if err != nil { + return nil, err + } + + result := &SAccountBalance{} + for _, domain := range domains { + balances, err := self.queryDomainBalances(domain.ID) + if err != nil { + return nil, err + } + for _, balance := range balances { + result.AvailableAmount += balance.Amount + result.CreditAmount += balance.CreditAmount + result.DesignatedAmount += balance.DesignatedAmount + } + } + + return result, nil +} + +// https://support.huaweicloud.com/api-bpconsole/zh-cn_topic_0075213309.html +func (self *SHuaweiClient) queryDomainBalances(domainId string) ([]SBalance, error) { + huawei, _ := self.newGeneralAPIClient() + huawei.Balances.SetDomainId(domainId) + balances := make([]SBalance, 0) + err := doListAll(huawei.Balances.List, nil, &balances) + if err != nil { + return nil, err + } + + return balances, nil +} + +func (self *SHuaweiClient) GetVersion() string { + return HUAWEI_API_VERSION +} + +func (self *SHuaweiClient) GetAccessEnv() string { + return "" +} + +func (self *SHuaweiClient) GetCapabilities() []string { + caps := []string{ + cloudprovider.CLOUD_CAPABILITY_PROJECT, + cloudprovider.CLOUD_CAPABILITY_COMPUTE, + cloudprovider.CLOUD_CAPABILITY_NETWORK, + cloudprovider.CLOUD_CAPABILITY_LOADBALANCER, + // cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE, + cloudprovider.CLOUD_CAPABILITY_RDS, + cloudprovider.CLOUD_CAPABILITY_CACHE, + cloudprovider.CLOUD_CAPABILITY_EVENT, + cloudprovider.CLOUD_CAPABILITY_CLOUDID, + cloudprovider.CLOUD_CAPABILITY_SAML_AUTH, + cloudprovider.CLOUD_CAPABILITY_NAT, + cloudprovider.CLOUD_CAPABILITY_NAS, + } + // huawei objectstore is shared across projects(subscriptions) + // to avoid multiple project access the same bucket + // only main project is allow to access objectstore bucket + if self.isMainProject { + caps = append(caps, cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE) + } + return caps +} + +func (self *SHuaweiClient) GetUserId() (string, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return "", errors.Wrap(err, "SHuaweiClient.GetUserId.newGeneralAPIClient") + } + + type cred struct { + UserId string `json:"user_id"` + } + + ret := &cred{} + err = DoGet(client.Credentials.Get, self.accessKey, nil, ret) + if err != nil { + return "", errors.Wrap(err, "SHuaweiClient.GetUserId.DoGet") + } + + return ret.UserId, nil +} + +// owner id == domain_id == account id +func (self *SHuaweiClient) GetOwnerId() (string, error) { + userId, err := self.GetUserId() + if err != nil { + return "", errors.Wrap(err, "SHuaweiClient.GetOwnerId.GetUserId") + } + + client, err := self.newGeneralAPIClient() + if err != nil { + return "", errors.Wrap(err, "SHuaweiClient.GetOwnerId.newGeneralAPIClient") + } + + type user struct { + DomainId string `json:"domain_id"` + Name string `json:"name"` + CreateTime string + } + + ret := &user{} + err = DoGet(client.Users.Get, userId, nil, ret) + if err != nil { + return "", errors.Wrap(err, "SHuaweiClient.GetOwnerId.DoGet") + } + self.ownerName = ret.Name + // 2021-02-02 02:43:28.0 + self.ownerCreateTime, _ = timeutils.ParseTimeStr(strings.TrimSuffix(ret.CreateTime, ".0")) + return ret.DomainId, nil +} + +func (self *SHuaweiClient) initOwner() error { + ownerId, err := self.GetOwnerId() + if err != nil { + return errors.Wrap(err, "SHuaweiClient.initOwner") + } + + self.ownerId = ownerId + return nil +} diff --git a/pkg/multicloud/huaweistack/image.go b/pkg/multicloud/huaweistack/image.go new file mode 100644 index 0000000000..d1bd0dadf5 --- /dev/null +++ b/pkg/multicloud/huaweistack/image.go @@ -0,0 +1,479 @@ +// 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 huaweistack + +import ( + "context" + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/apis" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/util/imagetools" +) + +type TImageOwnerType string + +const ( + ImageOwnerPublic TImageOwnerType = "gold" // 公共镜像:gold + ImageOwnerSelf TImageOwnerType = "private" // 私有镜像:private + ImageOwnerShared TImageOwnerType = "shared" // 共享镜像:shared + + EnvFusionCompute = "FusionCompute" + EnvIronic = "Ironic" +) + +const ( + ImageStatusQueued = "queued" // queued:表示镜像元数据已经创建成功,等待上传镜像文件。 + ImageStatusSaving = "saving" // saving:表示镜像正在上传文件到后端存储。 + ImageStatusDeleted = "deleted" // deleted:表示镜像已经删除。 + ImageStatusKilled = "killed" // killed:表示镜像上传错误。 + ImageStatusActive = "active" // active:表示镜像可以正常使用 +) + +// https://support.huaweicloud.com/api-ims/zh-cn_topic_0020091565.html +type SImage struct { + multicloud.SImageBase + multicloud.HuaweiTags + storageCache *SStoragecache + + // normalized image info + imgInfo *imagetools.ImageInfo + + Schema string `json:"schema"` + MinDiskGB int64 `json:"min_disk"` + CreatedAt time.Time `json:"created_at"` + ImageSourceType string `json:"__image_source_type"` + ContainerFormat string `json:"container_format"` + File string `json:"file"` + UpdatedAt time.Time `json:"updated_at"` + Protected bool `json:"protected"` + Checksum string `json:"checksum"` + ID string `json:"id"` + Isregistered string `json:"__isregistered"` + MinRamMB int `json:"min_ram"` + Lazyloading string `json:"__lazyloading"` + Owner string `json:"owner"` + OSType string `json:"__os_type"` + Imagetype string `json:"__imagetype"` + Visibility string `json:"visibility"` + VirtualEnvType string `json:"virtual_env_type"` + Platform string `json:"__platform"` + SizeGB int `json:"size"` + ImageSize int64 `json:"__image_size"` + OSBit string `json:"__os_bit"` + OSVersion string `json:"__os_version"` + Name string `json:"name"` + Self string `json:"self"` + DiskFormat string `json:"disk_format"` + Status string `json:"status"` + SupportKVMFPGAType string `json:"__support_kvm_fpga_type"` + SupportKVMNVMEHIGHIO string `json:"__support_nvme_highio"` + SupportLargeMemory string `json:"__support_largememory"` + SupportDiskIntensive string `json:"__support_diskintensive"` + SupportHighPerformance string `json:"__support_highperformance"` + SupportXENGPUType string `json:"__support_xen_gpu_type"` + SupportKVMGPUType string `json:"__support_kvm_gpu_type"` + SupportGPUT4 string `json:"__support_gpu_t4"` + SupportKVMAscend310 string `json:"__support_kvm_ascend_310"` + SupportArm string `json:"__support_arm"` +} + +func (self *SImage) GetMinRamSizeMb() int { + return self.MinRamMB +} + +func (self *SImage) GetId() string { + return self.ID +} + +func (self *SImage) GetName() string { + return self.Name +} + +func (self *SImage) GetGlobalId() string { + return self.ID +} + +func (self *SImage) GetStatus() string { + switch self.Status { + case ImageStatusQueued: + return api.CACHED_IMAGE_STATUS_CACHING + case ImageStatusActive: + return api.CACHED_IMAGE_STATUS_ACTIVE + case ImageStatusKilled: + return api.CACHED_IMAGE_STATUS_CACHE_FAILED + default: + return api.CACHED_IMAGE_STATUS_CACHE_FAILED + } +} + +func (self *SImage) GetImageStatus() string { + switch self.Status { + case ImageStatusQueued: + return cloudprovider.IMAGE_STATUS_QUEUED + case ImageStatusActive: + return cloudprovider.IMAGE_STATUS_ACTIVE + case ImageStatusKilled: + return cloudprovider.IMAGE_STATUS_KILLED + default: + return cloudprovider.IMAGE_STATUS_KILLED + } +} + +func (self *SImage) Refresh() error { + new, err := self.storageCache.region.GetImage(self.GetId()) + if err != nil { + return err + } + return jsonutils.Update(self, new) +} + +func (self *SImage) GetImageType() cloudprovider.TImageType { + switch self.Imagetype { + case "gold": + return cloudprovider.ImageTypeSystem + case "private": + return cloudprovider.ImageTypeCustomized + case "shared": + return cloudprovider.ImageTypeShared + default: + return cloudprovider.ImageTypeCustomized + } +} + +func (self *SImage) GetSizeByte() int64 { + return int64(self.MinDiskGB) * 1024 * 1024 * 1024 +} + +func (self *SImage) getNormalizedImageInfo() *imagetools.ImageInfo { + if self.imgInfo == nil { + imgInfo := imagetools.NormalizeImageInfo(self.ImageSourceType, self.OSType, self.OSType, self.Platform, "") + self.imgInfo = &imgInfo + } + + return self.imgInfo +} + +func (self *SImage) GetOsType() string { + return self.getNormalizedImageInfo().OsType +} + +func (self *SImage) GetOsDist() string { + return self.getNormalizedImageInfo().OsDistro +} + +func (self *SImage) GetOsVersion() string { + return self.getNormalizedImageInfo().OsVersion +} + +func (self *SImage) GetOsArch() string { + return self.getNormalizedImageInfo().OsArch +} + +func (self *SImage) GetMinOsDiskSizeGb() int { + return int(self.MinDiskGB) +} + +func (self *SImage) GetImageFormat() string { + return self.DiskFormat +} + +func (self *SImage) GetCreatedAt() time.Time { + return self.CreatedAt +} + +func (self *SImage) IsEmulated() bool { + return false +} + +func (self *SImage) GetSysTags() map[string]string { + data := map[string]string{} + if len(self.OSBit) > 0 { + data["os_arch"] = self.GetOsArch() + } + if len(self.OSType) > 0 { + data["os_name"] = self.GetOsType() + } + if len(self.Platform) > 0 { + data["os_distribution"] = self.GetOsDist() + } + if len(self.OSVersion) > 0 { + data["os_version"] = self.GetOsVersion() + } + return data +} + +func (self *SImage) Delete(ctx context.Context) error { + return self.storageCache.region.DeleteImage(self.GetId()) +} + +func (self *SImage) GetIStoragecache() cloudprovider.ICloudStoragecache { + return self.storageCache +} + +func (self *SRegion) GetImage(imageId string) (*SImage, error) { + image := &SImage{} + err := DoGet(self.ecsClient.Images.Get, imageId, nil, image) + if err != nil { + return nil, errors.Wrap(err, "DoGet") + } + return image, nil +} + +func excludeImage(image SImage) bool { + if image.VirtualEnvType == "Ironic" { + return true + } + + if len(image.SupportDiskIntensive) > 0 { + return true + } + + if len(image.SupportKVMFPGAType) > 0 || len(image.SupportKVMAscend310) > 0 { + return true + } + + if len(image.SupportKVMGPUType) > 0 { + return true + } + + if len(image.SupportKVMNVMEHIGHIO) > 0 { + return true + } + + if len(image.SupportGPUT4) > 0 { + return true + } + + if len(image.SupportXENGPUType) > 0 { + return true + } + + if len(image.SupportHighPerformance) > 0 { + return true + } + + if len(image.SupportArm) > 0 { + return true + } + + return false +} + +// https://support.huaweicloud.com/api-ims/zh-cn_topic_0060804959.html +func (self *SRegion) GetImages(status string, imagetype TImageOwnerType, name string, envType string) ([]SImage, error) { + queries := map[string]string{} + if len(status) > 0 { + queries["status"] = status + } + + if len(imagetype) > 0 { + queries["__imagetype"] = string(imagetype) + if imagetype == ImageOwnerPublic { + queries["protected"] = "True" + } + } + if len(envType) > 0 { + queries["virtual_env_type"] = envType + } + + if len(name) > 0 { + queries["name"] = name + } + + images := make([]SImage, 0) + err := doListAllWithMarker(self.ecsClient.Images.List, queries, &images) + + // 排除掉需要特定镜像才能创建的实例类型 + // https://support.huaweicloud.com/eu-west-0-api-ims/zh-cn_topic_0031617666.html#ZH-CN_TOPIC_0031617666__table48545918250 + // https://support.huaweicloud.com/productdesc-ecs/zh-cn_topic_0088142947.html + filtedImages := make([]SImage, 0) + for i := range images { + if !excludeImage(images[i]) { + filtedImages = append(filtedImages, images[i]) + } + } + + return filtedImages, err +} + +func (self *SRegion) DeleteImage(imageId string) error { + return DoDelete(self.ecsClient.OpenStackImages.Delete, imageId, nil, nil) +} + +func (self *SRegion) GetImageByName(name string) (*SImage, error) { + if len(name) == 0 { + return nil, fmt.Errorf("image name should not be empty") + } + + images, err := self.GetImages("", TImageOwnerType(""), name, "") + if err != nil { + return nil, err + } + if len(images) == 0 { + return nil, cloudprovider.ErrNotFound + } + + log.Debugf("%d image found match name %s", len(images), name) + return &images[0], nil +} + +/* https://support.huaweicloud.com/api-ims/zh-cn_topic_0020092109.html + os version 取值范围: https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html + 用于创建私有镜像的源云服务器系统盘大小大于等于40GB且不超过1024GB。 + 目前支持vhd,zvhd、raw,qcow2 + todo: 考虑使用镜像快速导入。 https://support.huaweicloud.com/api-ims/zh-cn_topic_0133188204.html + 使用OBS文件创建镜像 + + * openstack原生接口支持的格式:https://support.huaweicloud.com/api-ims/zh-cn_topic_0031615566.html +*/ +func (self *SRegion) ImportImageJob(name string, osDist string, osVersion string, osArch string, bucket string, key string, minDiskGB int64) (string, error) { + os_version, err := stdVersion(osDist, osVersion, osArch) + log.Debugf("%s %s %s: %s.min_disk %d GB", osDist, osVersion, osArch, os_version, minDiskGB) + if err != nil { + log.Debugln(err) + } + + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(name), "name") + image_url := fmt.Sprintf("%s:%s", bucket, key) + params.Add(jsonutils.NewString(image_url), "image_url") + if len(os_version) > 0 { + params.Add(jsonutils.NewString(os_version), "os_version") + } + params.Add(jsonutils.NewBool(true), "is_config_init") + params.Add(jsonutils.NewBool(true), "is_config") + params.Add(jsonutils.NewInt(minDiskGB), "min_disk") + + ret, err := self.ecsClient.Images.PerformAction2("action", "", params, "") + if err != nil { + return "", err + } + + return ret.GetString("job_id") +} + +func formatVersion(osDist string, osVersion string) (string, error) { + err := fmt.Errorf("unsupport version %s.reference: https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html", osVersion) + dist := strings.ToLower(osDist) + if dist == "ubuntu" || dist == "redhat" || dist == "centos" || dist == "oracle" || dist == "euleros" { + parts := strings.Split(osVersion, ".") + if len(parts) < 2 { + return "", err + } + + return parts[0] + "." + parts[1], nil + } + + if dist == "debian" { + parts := strings.Split(osVersion, ".") + if len(parts) < 3 { + return "", err + } + + return parts[0] + "." + parts[1] + "." + parts[2], nil + } + + if dist == "fedora" || dist == "windows" || dist == "suse" { + parts := strings.Split(osVersion, ".") + if len(parts) < 1 { + return "", err + } + + return parts[0], nil + } + + if dist == "opensuse" { + parts := strings.Split(osVersion, ".") + if len(parts) == 0 { + return "", err + } + + if len(parts) == 1 { + return parts[0], nil + } + + if len(parts) >= 2 { + return parts[0] + "." + parts[1], nil + } + } + + return "", err +} + +// todo: 如何保持同步更新 +// https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html +func stdVersion(osDist string, osVersion string, osArch string) (string, error) { + // 架构 + arch := "" + switch osArch { + case "64", apis.OS_ARCH_X86_64: + arch = "64bit" + case "32", apis.OS_ARCH_X86_32: + arch = "32bit" + default: + return "", fmt.Errorf("unsupported arch %s.reference: https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html", osArch) + } + + _dist := strings.Split(strings.TrimSpace(osDist), " ")[0] + _dist = strings.ToLower(_dist) + // 版本 + ver, err := formatVersion(_dist, osVersion) + if err != nil { + return "", err + } + + // 操作系统 + dist := "" + + switch _dist { + case "ubuntu": + return fmt.Sprintf("Ubuntu %s server %s", ver, arch), nil + case "redhat": + dist = "Redhat Linux Enterprise" + case "centos": + dist = "CentOS" + case "fedora": + dist = "Fedora" + case "debian": + dist = "Debian GNU/Linux" + case "windows": + dist = "Windows Server" + case "oracle": + dist = "Oracle Linux Server release" + case "suse": + dist = "SUSE Linux Enterprise Server" + case "opensuse": + dist = "OpenSUSE" + case "euleros": + dist = "EulerOS" + default: + return "", fmt.Errorf("unsupported os %s. reference: https://support.huaweicloud.com/api-ims/zh-cn_topic_0031617666.html", dist) + } + + return fmt.Sprintf("%s %s %s", dist, ver, arch), nil +} + +func (self *SImage) UEFI() bool { + return false +} diff --git a/pkg/multicloud/huaweistack/instance.go b/pkg/multicloud/huaweistack/instance.go new file mode 100644 index 0000000000..ebea15d913 --- /dev/null +++ b/pkg/multicloud/huaweistack/instance.go @@ -0,0 +1,1523 @@ +// 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 huaweistack + +import ( + "context" + "encoding/base64" + "fmt" + "sort" + "strconv" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/osprofile" + "yunion.io/x/pkg/utils" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/modules" + "yunion.io/x/onecloud/pkg/util/billing" + "yunion.io/x/onecloud/pkg/util/cloudinit" +) + +const ( + InstanceStatusRunning = "ACTIVE" + InstanceStatusTerminated = "DELETED" + InstanceStatusStopped = "SHUTOFF" +) + +type IpAddress struct { + Version string `json:"version"` + Addr string `json:"addr"` + OSEXTIPSMACMACAddr string `json:"OS-EXT-IPS-MAC:mac_addr"` + OSEXTIPSPortID string `json:"OS-EXT-IPS:port_id"` + OSEXTIPSType string `json:"OS-EXT-IPS:type"` +} + +type Flavor struct { + Disk string `json:"disk"` + Vcpus string `json:"vcpus"` + RAM string `json:"ram"` + ID string `json:"id"` + Name string `json:"name"` +} + +type Image struct { + ID string `json:"id"` +} + +type VMMetadata struct { + MeteringImageID string `json:"metering.image_id"` + MeteringImagetype string `json:"metering.imagetype"` + MeteringResourcespeccode string `json:"metering.resourcespeccode"` + ImageName string `json:"image_name"` + OSBit string `json:"os_bit"` + VpcID string `json:"vpc_id"` + MeteringResourcetype string `json:"metering.resourcetype"` + CascadedInstanceExtrainfo string `json:"cascaded.instance_extrainfo"` + OSType string `json:"os_type"` + ChargingMode string `json:"charging_mode"` +} + +type OSExtendedVolumesVolumesAttached struct { + Device string `json:"device"` + BootIndex string `json:"bootIndex"` + ID string `json:"id"` + DeleteOnTermination string `json:"delete_on_termination"` +} + +type OSSchedulerHints struct { +} + +type SecurityGroup struct { + Name string `json:"name"` +} + +type SysTag struct { + Key string `json:"key"` + Value string `json:"value"` +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148849.html +// https://support.huaweicloud.com/api-bpconsole/zh-cn_topic_0100166287.html v1.1 支持创建包年/包月的弹性云服务器 +type SInstance struct { + multicloud.SInstanceBase + multicloud.HuaweiTags + + host *SHost + + ID string `json:"id"` + Name string `json:"name"` + Addresses map[string][]IpAddress `json:"addresses"` + Flavor Flavor `json:"flavor"` + AccessIPv4 string `json:"accessIPv4"` + AccessIPv6 string `json:"accessIPv6"` + Status string `json:"status"` + Progress string `json:"progress"` + HostID string `json:"hostId"` + Updated string `json:"updated"` + Created time.Time `json:"created"` + Metadata VMMetadata `json:"metadata"` + Description string `json:"description"` + Locked bool `json:"locked"` + ConfigDrive string `json:"config_drive"` + TenantID string `json:"tenant_id"` + UserID string `json:"user_id"` + KeyName string `json:"key_name"` + + OSExtendedVolumesVolumesAttached []OSExtendedVolumesVolumesAttached `json:"os-extended-volumes:volumes_attached"` + OSEXTSTSTaskState string `json:"OS-EXT-STS:task_state"` + OSEXTSTSPowerState int64 `json:"OS-EXT-STS:power_state"` + OSEXTSTSVMState string `json:"OS-EXT-STS:vm_state"` + OSEXTSRVATTRHost string `json:"OS-EXT-SRV-ATTR:host"` + OSEXTSRVATTRInstanceName string `json:"OS-EXT-SRV-ATTR:instance_name"` + OSEXTSRVATTRHypervisorHostname string `json:"OS-EXT-SRV-ATTR:hypervisor_hostname"` + OSDCFDiskConfig string `json:"OS-DCF:diskConfig"` + OSEXTAZAvailabilityZone string `json:"OS-EXT-AZ:availability_zone"` + OSSchedulerHints OSSchedulerHints `json:"os:scheduler_hints"` + OSEXTSRVATTRRootDeviceName string `json:"OS-EXT-SRV-ATTR:root_device_name"` + OSEXTSRVATTRRamdiskID string `json:"OS-EXT-SRV-ATTR:ramdisk_id"` + EnterpriseProjectID string `json:"enterprise_project_id"` + OSEXTSRVATTRUserData string `json:"OS-EXT-SRV-ATTR:user_data"` + OSSRVUSGLaunchedAt time.Time `json:"OS-SRV-USG:launched_at"` + OSEXTSRVATTRKernelID string `json:"OS-EXT-SRV-ATTR:kernel_id"` + OSEXTSRVATTRLaunchIndex int64 `json:"OS-EXT-SRV-ATTR:launch_index"` + HostStatus string `json:"host_status"` + OSEXTSRVATTRReservationID string `json:"OS-EXT-SRV-ATTR:reservation_id"` + OSEXTSRVATTRHostname string `json:"OS-EXT-SRV-ATTR:hostname"` + OSSRVUSGTerminatedAt time.Time `json:"OS-SRV-USG:terminated_at"` + SysTags []SysTag `json:"sys_tags"` + SecurityGroups []SecurityGroup `json:"security_groups"` + EnterpriseProjectId string +} + +func compareSet(currentSet []string, newSet []string) (add []string, remove []string, keep []string) { + sort.Strings(currentSet) + sort.Strings(newSet) + + i, j := 0, 0 + for i < len(currentSet) || j < len(newSet) { + if i < len(currentSet) && j < len(newSet) { + if currentSet[i] == newSet[j] { + keep = append(keep, currentSet[i]) + i += 1 + j += 1 + } else if currentSet[i] < newSet[j] { + remove = append(remove, currentSet[i]) + i += 1 + } else { + add = append(add, newSet[j]) + j += 1 + } + } else if i >= len(currentSet) { + add = append(add, newSet[j]) + j += 1 + } else if j >= len(newSet) { + remove = append(remove, currentSet[i]) + i += 1 + } + } + + return add, remove, keep +} + +// 启动盘 != 系统盘(必须是启动盘且挂载在root device上) +func isBootDisk(server *SInstance, disk *SDisk) bool { + if disk.GetDiskType() != api.DISK_TYPE_SYS { + return false + } + + for _, attachment := range disk.Attachments { + if attachment.ServerID == server.GetId() && attachment.Device == server.OSEXTSRVATTRRootDeviceName { + return true + } + } + + return false +} + +func (self *SInstance) GetId() string { + return self.ID +} + +func (self *SInstance) GetName() string { + return self.Name +} + +func (self *SInstance) GetGlobalId() string { + return self.ID +} + +func (self *SInstance) GetStatus() string { + switch self.Status { + case "ACTIVE": + return api.VM_RUNNING + case "MIGRATING", "REBUILD", "BUILD", "RESIZE", "VERIFY_RESIZE": // todo: pending ? + return api.VM_STARTING + case "REBOOT", "HARD_REBOOT": + return api.VM_STOPPING + case "SHUTOFF": + return api.VM_READY + default: + return api.VM_UNKNOWN + } +} + +func (self *SInstance) Refresh() error { + new, err := self.host.zone.region.GetInstanceByID(self.GetId()) + new.host = self.host + if err != nil { + return err + } + + if new.Status == InstanceStatusTerminated { + log.Debugf("Instance already terminated.") + return cloudprovider.ErrNotFound + } + + err = jsonutils.Update(self, new) + if err != nil { + return err + } + return nil +} + +func (self *SInstance) IsEmulated() bool { + return false +} + +func (self *SInstance) GetInstanceType() string { + return self.Flavor.ID +} + +func (self *SInstance) GetSecurityGroupIds() ([]string, error) { + return self.host.zone.region.GetInstanceSecrityGroupIds(self.GetId()) +} + +func (self *SInstance) GetSysTags() map[string]string { + data := map[string]string{} + // cn-north-1::et2.2xlarge.16::win + lowerOs := self.GetOSType() + if strings.HasPrefix(lowerOs, "win") { + lowerOs = "win" + } + priceKey := fmt.Sprintf("%s::%s::%s", self.host.zone.region.GetId(), self.GetInstanceType(), lowerOs) + data["price_key"] = priceKey + data["zone_ext_id"] = self.host.zone.GetGlobalId() + if len(self.Metadata.MeteringImageID) > 0 { + if image, err := self.host.zone.region.GetImage(self.Metadata.MeteringImageID); err != nil { + log.Errorf("Failed to find image %s for instance %s zone %s", self.Metadata.MeteringImageID, self.GetId(), self.OSEXTAZAvailabilityZone) + } else { + meta := image.GetSysTags() + for k, v := range meta { + data[k] = v + } + } + } + return data +} + +// https://support.huaweicloud.com/api-ecs/ecs_02_1002.html +// key 相同时value不会替换 +func (self *SRegion) CreateServerTags(instanceId string, tags map[string]string) error { + params := map[string]interface{}{ + "action": "create", + } + + tagsObj := []map[string]string{} + for k, v := range tags { + tagsObj = append(tagsObj, map[string]string{"key": k, "value": v}) + } + params["tags"] = tagsObj + + _, err := self.ecsClient.Servers.PerformAction2("tags/action", instanceId, jsonutils.Marshal(params), "") + return err +} + +// https://support.huaweicloud.com/api-ecs/ecs_02_1003.html +func (self *SRegion) DeleteServerTags(instanceId string, tagsKey []string) error { + params := map[string]interface{}{ + "action": "delete", + } + tagsObj := []map[string]string{} + for _, k := range tagsKey { + tagsObj = append(tagsObj, map[string]string{"key": k}) + } + params["tags"] = tagsObj + + _, err := self.ecsClient.Servers.PerformAction2("tags/action", instanceId, jsonutils.Marshal(params), "") + return err +} + +func (self *SInstance) SetTags(tags map[string]string, replace bool) error { + existedTags, err := self.GetTags() + if err != nil { + return errors.Wrap(err, "self.GetTags()") + } + deleteTagsKey := []string{} + for k := range existedTags { + if replace { + deleteTagsKey = append(deleteTagsKey, k) + } else { + if _, ok := tags[k]; ok { + deleteTagsKey = append(deleteTagsKey, k) + } + } + } + if len(deleteTagsKey) > 0 { + err := self.host.zone.region.DeleteServerTags(self.GetId(), deleteTagsKey) + if err != nil { + return errors.Wrapf(err, "self.host.zone.region.DeleteServerTags(%s,%s)", self.GetId(), deleteTagsKey) + } + } + if len(tags) > 0 { + err := self.host.zone.region.CreateServerTags(self.GetId(), tags) + if err != nil { + return errors.Wrapf(err, "self.host.zone.region.CreateServerTags(%s,%s)", self.GetId(), jsonutils.Marshal(tags).String()) + } + } + return nil +} + +func (self *SInstance) GetBillingType() string { + // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148849.html + // charging_mode “0”:按需计费 “1”:按包年包月计费 + if self.Metadata.ChargingMode == "1" { + return billing_api.BILLING_TYPE_PREPAID + } else { + return billing_api.BILLING_TYPE_POSTPAID + } +} + +func (self *SInstance) GetCreatedAt() time.Time { + return self.Created +} + +// charging_mode “0”:按需计费 “1”:按包年包月计费 +func (self *SInstance) GetExpiredAt() time.Time { + var expiredTime time.Time + if self.Metadata.ChargingMode == "1" { + res, err := self.host.zone.region.GetOrderResourceDetail(self.GetId()) + if err != nil { + log.Debugln(err) + } + + expiredTime = res.ExpireTime + } + + return expiredTime +} + +func (self *SInstance) GetIHost() cloudprovider.ICloudHost { + return self.host +} + +func (self *SInstance) GetIDisks() ([]cloudprovider.ICloudDisk, error) { + err := self.Refresh() + if err != nil { + return nil, err + } + + attached := self.OSExtendedVolumesVolumesAttached + disks := make([]SDisk, 0) + for _, vol := range attached { + disk, err := self.host.zone.region.GetDisk(vol.ID) + if err != nil { + return nil, err + } + + disks = append(disks, *disk) + } + + idisks := make([]cloudprovider.ICloudDisk, len(disks)) + for i := 0; i < len(disks); i += 1 { + storage, err := self.host.zone.getStorageByCategory(disks[i].VolumeType) + if err != nil { + return nil, err + } + disks[i].storage = storage + idisks[i] = &disks[i] + // 将系统盘放到第0个位置 + if isBootDisk(self, &disks[i]) { + _temp := idisks[0] + idisks[0] = &disks[i] + idisks[i] = _temp + } + } + return idisks, nil +} + +func (self *SInstance) GetINics() ([]cloudprovider.ICloudNic, error) { + nics := make([]cloudprovider.ICloudNic, 0) + + // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148849.html + // OS-EXT-IPS.type + // todo: 这里没有区分是IPv4 还是 IPv6。统一当IPv4处理了.可能会引发错误 + for _, ipAddresses := range self.Addresses { + for _, ipAddress := range ipAddresses { + if ipAddress.OSEXTIPSType == "fixed" { + nic := SInstanceNic{ + instance: self, + ipAddr: ipAddress.Addr, + macAddr: ipAddress.OSEXTIPSMACMACAddr, + } + nics = append(nics, &nic) + } + } + } + return nics, nil +} + +func (self *SInstance) GetIEIP() (cloudprovider.ICloudEIP, error) { + ips := make([]string, 0) + for _, addresses := range self.Addresses { + for _, address := range addresses { + if address.OSEXTIPSType != "fixed" && !strings.HasPrefix(address.Addr, "100.") { + ips = append(ips, address.Addr) + } + } + } + + if len(ips) == 0 { + return nil, nil + } + + eips, err := self.host.zone.region.GetEips() + if err != nil { + return nil, err + } + + for _, eip := range eips { + if eip.PublicIPAddress == ips[0] { + return &eip, nil + } + } + + return nil, nil +} + +func (self *SInstance) GetVcpuCount() int { + cpu, _ := strconv.Atoi(self.Flavor.Vcpus) + return cpu +} + +func (self *SInstance) GetVmemSizeMB() int { + mem, _ := strconv.Atoi(self.Flavor.RAM) + return int(mem) +} + +func (self *SInstance) GetBootOrder() string { + return "dcn" +} + +func (self *SInstance) GetVga() string { + return "std" +} + +func (self *SInstance) GetVdi() string { + return "vnc" +} + +func (self *SInstance) GetOSType() string { + return osprofile.NormalizeOSType(self.Metadata.OSType) +} + +func (self *SInstance) GetOSName() string { + return self.Metadata.ImageName +} + +func (self *SInstance) GetBios() string { + return "BIOS" +} + +func (self *SInstance) GetMachine() string { + return "pc" +} + +func (self *SInstance) AssignSecurityGroup(secgroupId string) error { + return self.SetSecurityGroups([]string{secgroupId}) +} + +func (self *SInstance) SetSecurityGroups(secgroupIds []string) error { + currentSecgroups, err := self.host.zone.region.GetInstanceSecrityGroupIds(self.GetId()) + if err != nil { + return err + } + + add, remove, _ := compareSet(currentSecgroups, secgroupIds) + err = self.host.zone.region.assignSecurityGroups(add, self.GetId()) + if err != nil { + return err + } + + return self.host.zone.region.unassignSecurityGroups(remove, self.GetId()) +} + +func (self *SInstance) GetHypervisor() string { + return api.HYPERVISOR_HUAWEI_CLOUD_STACK +} + +func (self *SInstance) StartVM(ctx context.Context) error { + if self.Status == InstanceStatusRunning { + return nil + } + + timeout := 300 * time.Second + interval := 15 * time.Second + + startTime := time.Now() + for time.Now().Sub(startTime) < timeout { + err := self.Refresh() + if err != nil { + return err + } + + if self.GetStatus() == api.VM_RUNNING { + return nil + } else if self.GetStatus() == api.VM_READY { + err := self.host.zone.region.StartVM(self.GetId()) + if err != nil { + return err + } + } + time.Sleep(interval) + } + return cloudprovider.ErrTimeout +} + +func (self *SInstance) StopVM(ctx context.Context, opts *cloudprovider.ServerStopOptions) error { + if self.Status == InstanceStatusStopped { + return nil + } + + if self.Status == InstanceStatusTerminated { + log.Debugf("Instance already terminated.") + return nil + } + + err := self.host.zone.region.StopVM(self.GetId(), opts.IsForce) + if err != nil { + return err + } + return cloudprovider.WaitStatus(self, api.VM_READY, 10*time.Second, 300*time.Second) // 5mintues +} + +func (self *SInstance) DeleteVM(ctx context.Context) error { + if self.Status == InstanceStatusTerminated { + return nil + } + + for { + err := self.host.zone.region.DeleteVM(self.GetId()) + if err != nil && self.Status != InstanceStatusTerminated { + log.Errorf("DeleteVM fail: %s", err) + return err + } else { + break + } + } + + return cloudprovider.WaitDeleted(self, 10*time.Second, 300*time.Second) // 5minutes +} + +func (self *SInstance) UpdateVM(ctx context.Context, name string) error { + return self.host.zone.region.UpdateVM(self.GetId(), name) +} + +// https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0032380449.html +// 创建云服务器过程中注入用户数据。支持注入文本、文本文件或gzip文件。 +// 注入内容,需要进行base64格式编码。注入内容(编码之前的内容)最大长度32KB。 +// 对于Linux弹性云服务器,adminPass参数传入时,user_data参数不生效。 +func (self *SInstance) UpdateUserData(userData string) error { + return cloudprovider.ErrNotSupported +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876349.html 使用原镜像重装 +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876971.html 更换系统盘操作系统 +// 不支持调整系统盘大小 +func (self *SInstance) RebuildRoot(ctx context.Context, desc *cloudprovider.SManagedVMRebuildRootConfig) (string, error) { + var err error + var jobId string + + publicKeyName := "" + if len(desc.PublicKey) > 0 { + publicKeyName, err = self.host.zone.region.syncKeypair(desc.PublicKey) + if err != nil { + return "", err + } + } + + image, err := self.host.zone.region.GetImage(desc.ImageId) + if err != nil { + return "", errors.Wrap(err, "SInstance.RebuildRoot.GetImage") + } + + // Password存在的情况下,windows 系统直接使用密码 + if strings.ToLower(image.Platform) == strings.ToLower(osprofile.OS_TYPE_WINDOWS) && len(desc.Password) > 0 { + publicKeyName = "" + } + + userData, err := updateUserData(self.OSEXTSRVATTRUserData, image.OSVersion, desc.Account, desc.Password, desc.PublicKey) + if err != nil { + return "", errors.Wrap(err, "SInstance.RebuildRoot.updateUserData") + } + + if self.Metadata.MeteringImageID == desc.ImageId { + jobId, err = self.host.zone.region.RebuildRoot(ctx, self.UserID, self.GetId(), desc.Password, publicKeyName, userData) + if err != nil { + return "", err + } + } else { + jobId, err = self.host.zone.region.ChangeRoot(ctx, self.UserID, self.GetId(), desc.ImageId, desc.Password, publicKeyName, userData) + if err != nil { + return "", err + } + } + + err = self.host.zone.region.waitTaskStatus(self.host.zone.region.ecsClient.Servers.ServiceType(), jobId, TASK_SUCCESS, 15*time.Second, 900*time.Second) + if err != nil { + log.Errorf("RebuildRoot task error %s", err) + return "", err + } + + err = self.Refresh() + if err != nil { + return "", err + } + + idisks, err := self.GetIDisks() + if err != nil { + return "", err + } + + if len(idisks) == 0 { + return "", fmt.Errorf("server %s has no volume attached.", self.GetId()) + } + + return idisks[0].GetId(), nil +} + +func (self *SInstance) DeployVM(ctx context.Context, name string, username string, password string, publicKey string, deleteKeypair bool, description string) error { + return self.host.zone.region.DeployVM(self.GetId(), name, password, publicKey, deleteKeypair, description) +} + +func (self *SInstance) ChangeConfig(ctx context.Context, config *cloudprovider.SManagedVMChangeConfig) error { + instanceTypes := []string{} + if len(config.InstanceType) > 0 { + instanceTypes = []string{config.InstanceType} + } else { + flavors, err := self.host.zone.region.GetMatchInstanceTypes(config.Cpu, config.MemoryMB, self.OSEXTAZAvailabilityZone) + if err != nil { + return errors.Wrapf(err, "GetMatchInstanceTypes") + } + for _, flavor := range flavors { + instanceTypes = append(instanceTypes, flavor.ID) + } + } + var err error + for _, instanceType := range instanceTypes { + err = self.host.zone.region.ChangeVMConfig(self.GetId(), instanceType) + if err != nil { + log.Warningf("ChangeVMConfig %s for %s error: %v", self.GetId(), instanceType, err) + } else { + return cloudprovider.WaitStatusWithDelay(self, api.VM_READY, 15*time.Second, 15*time.Second, 180*time.Second) + } + } + if err != nil { + return errors.Wrapf(err, "ChangeVMConfig") + } + return fmt.Errorf("Failed to change vm config, specification not supported") +} + +// todo:// 返回jsonobject感觉很诡异。不能直接知道内部细节 +func (self *SInstance) GetVNCInfo() (jsonutils.JSONObject, error) { + return self.host.zone.region.GetInstanceVNCUrl(self.GetId()) +} + +func (self *SInstance) NextDeviceName() (string, error) { + prefix := "s" + if strings.Contains(self.OSEXTSRVATTRRootDeviceName, "/vd") { + prefix = "v" + } + + currents := []string{} + for _, item := range self.OSExtendedVolumesVolumesAttached { + currents = append(currents, strings.ToLower(item.Device)) + } + + for i := 0; i < 25; i++ { + device := fmt.Sprintf("/dev/%sd%s", prefix, string(98+i)) + if ok, _ := utils.InStringArray(device, currents); !ok { + return device, nil + } + } + + return "", fmt.Errorf("disk devicename out of index, current deivces: %s", currents) +} + +func (self *SInstance) AttachDisk(ctx context.Context, diskId string) error { + device, err := self.NextDeviceName() + if err != nil { + return errors.Wrap(err, "Instance.AttachDisk.NextDeviceName") + } + + err = self.host.zone.region.AttachDisk(self.GetId(), diskId, device) + if err != nil { + return errors.Wrap(err, "Instance.AttachDisk.AttachDisk") + } + + return cloudprovider.Wait(5*time.Second, 60*time.Second, func() (bool, error) { + disk, err := self.host.zone.region.GetDisk(diskId) + if err != nil { + log.Debugf("Instance.AttachDisk.GetDisk %s", err) + return false, nil + } + + if disk.Status == "in-use" { + return true, nil + } + + return false, nil + }) +} + +func (self *SInstance) DetachDisk(ctx context.Context, diskId string) error { + err := self.host.zone.region.DetachDisk(self.GetId(), diskId) + if err != nil { + return errors.Wrap(err, "Instance.DetachDisk") + } + + return cloudprovider.Wait(5*time.Second, 60*time.Second, func() (bool, error) { + disk, err := self.host.zone.region.GetDisk(diskId) + if err != nil { + log.Debugf("Instance.DetachDisk.GetDisk %s", err) + return false, nil + } + + if disk.Status == "available" { + return true, nil + } + + return false, nil + }) +} + +func (self *SInstance) CreateDisk(ctx context.Context, sizeMb int, uuid string, driver string) error { + return cloudprovider.ErrNotSupported +} + +func (self *SInstance) Renew(bc billing.SBillingCycle) error { + return self.host.zone.region.RenewInstance(self.GetId(), bc) +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0094148850.html +func (self *SRegion) GetInstances() ([]SInstance, error) { + queries := make(map[string]string) + + if len(self.client.projectId) > 0 { + queries["project_id"] = self.client.projectId + } + + instances := make([]SInstance, 0) + err := doListAllWithPagerOffset(self.ecsClient.Servers.List, queries, &instances) + return instances, err +} + +func (self *SRegion) GetInstanceByID(instanceId string) (SInstance, error) { + instance := SInstance{} + err := DoGet(self.ecsClient.Servers.Get, instanceId, nil, &instance) + return instance, err +} + +func (self *SRegion) GetInstanceByIds(ids []string) ([]SInstance, int, error) { + instances := make([]SInstance, 0) + for _, instanceId := range ids { + instance, err := self.GetInstanceByID(instanceId) + if err != nil { + return nil, 0, err + } + instances = append(instances, instance) + } + + return instances, len(instances), nil +} + +/* +系统盘大小取值范围:1-1024 GB,且必须不小于镜像min_disk. +*/ +type SServerCreate struct { + AvailabilityZone string `json:"availability_zone"` + Name string `json:"name"` + ImageRef string `json:"imageRef"` + RootVolume RootVolume `json:"root_volume"` + DataVolumes []DataVolume `json:"data_volumes"` + FlavorRef string `json:"flavorRef"` + UserData string `json:"user_data"` + Vpcid string `json:"vpcid"` + SecurityGroups []SecGroup `json:"security_groups"` + Nics []NIC `json:"nics"` + KeyName string `json:"key_name"` + AdminPass string `json:"adminPass"` + Count int64 `json:"count"` + Extendparam ServerExtendparam `json:"extendparam"` + ServerTags []ServerTag `json:"server_tags"` + Description string `json:"description"` +} + +type DataVolume struct { + Volumetype string `json:"volumetype"` + SizeGB int `json:"size"` + Extendparam *DataVolumeExtendparam `json:"extendparam,omitempty"` + Multiattach *bool `json:"multiattach,omitempty"` + HwPassthrough *string `json:"hw:passthrough,omitempty"` +} + +type DataVolumeExtendparam struct { + SnapshotID string `json:"snapshotId"` +} + +type ServerExtendparam struct { + ChargingMode string `json:"chargingMode"` // 计费模式 prePaid|postPaid + PeriodType string `json:"periodType"` // 周期类型:month|year + PeriodNum string `json:"periodNum"` // 订购周期数:periodType=month(周期类型为月)时,取值为[1,9]。periodType=year(周期类型为年)时,取值为1。 + IsAutoRenew string `json:"isAutoRenew"` // 是否自动续订 true|false + IsAutoPay string `json:"isAutoPay"` // 是否自动从客户的账户中支付 true|false + RegionID string `json:"regionID"` + EnterpriseProjectId string `json:"enterprise_project_id,omitempty"` +} + +type NIC struct { + SubnetID string `json:"subnet_id"` // 网络ID. 与 SNetwork里的ID对应。统一使用这个ID + IpAddress string `json:"ip_address"` +} + +type RootVolume struct { + Volumetype string `json:"volumetype"` + SizeGB int `json:"size"` +} + +type SecGroup struct { + ID string `json:"id"` +} + +type ServerTag struct { + Key string `json:"key"` + Value string `json:"value"` +} + +/* +包月机器退订规则: https://support.huaweicloud.com/usermanual-billing/zh-cn_topic_0083138805.html +5天无理由全额退订:新购资源(不包含续费资源)在开通的五天内且退订次数不超过10次(每账号每年10次)的符合5天无理由全额退订。 +非5天无理由退订:不符合5天无理由全额退订条件的退订,都属于非5天无理由退订。非5天无理由退订,不限制退订次数,但需要收取退订手续费。 + +退订资源的方法: https://support.huaweicloud.com/usermanual-billing/zh-cn_topic_0072297197.html +*/ +func (self *SRegion) CreateInstance(name string, imageId string, instanceType string, SubnetId string, + securityGroupId string, vpcId string, zoneId string, desc string, disks []SDisk, ipAddr string, + keypair string, publicKey string, passwd string, userData string, bc *billing.SBillingCycle, projectId string, tags map[string]string) (string, error) { + params := SServerCreate{} + params.AvailabilityZone = zoneId + params.Name = name + params.FlavorRef = instanceType + params.ImageRef = imageId + params.Description = desc + params.Count = 1 + params.Nics = []NIC{{SubnetID: SubnetId, IpAddress: ipAddr}} + params.SecurityGroups = []SecGroup{{ID: securityGroupId}} + params.Vpcid = vpcId + + for i, disk := range disks { + if i == 0 { + params.RootVolume.Volumetype = disk.VolumeType + params.RootVolume.SizeGB = disk.SizeGB + } else { + dataVolume := DataVolume{} + dataVolume.Volumetype = disk.VolumeType + dataVolume.SizeGB = disk.SizeGB + params.DataVolumes = append(params.DataVolumes, dataVolume) + } + } + + if len(projectId) > 0 { + params.Extendparam.EnterpriseProjectId = projectId + } + + // billing type + if bc != nil { + params.Extendparam.ChargingMode = PRE_PAID + if bc.GetMonths() <= 9 { + params.Extendparam.PeriodNum = strconv.Itoa(bc.GetMonths()) + params.Extendparam.PeriodType = "month" + } else { + params.Extendparam.PeriodNum = strconv.Itoa(bc.GetYears()) + params.Extendparam.PeriodType = "year" + } + + params.Extendparam.RegionID = self.GetId() + if bc.AutoRenew { + params.Extendparam.IsAutoRenew = "true" + } else { + params.Extendparam.IsAutoRenew = "false" + } + params.Extendparam.IsAutoPay = "true" + } else { + params.Extendparam.ChargingMode = POST_PAID + } + + // https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212668.html#ZH-CN_TOPIC_0020212668__table761103195216 + if len(keypair) > 0 { + params.KeyName = keypair + } else { + params.AdminPass = passwd + } + + if len(userData) > 0 { + params.UserData = userData + } + + if len(tags) > 0 { + serverTags := []ServerTag{} + for k, v := range tags { + serverTags = append(serverTags, ServerTag{Key: k, Value: v}) + } + params.ServerTags = serverTags + } + + serverObj := jsonutils.Marshal(params) + createParams := jsonutils.NewDict() + createParams.Add(serverObj, "server") + _id, err := self.ecsClient.Servers.AsyncCreate(createParams) + if err != nil { + return "", err + } + + var ids []string + if params.Extendparam.ChargingMode == POST_PAID { + // 按需计费 + ids, err = self.GetAllSubTaskEntityIDs(self.ecsClient.Servers.ServiceType(), _id, "server_id") + } else { + // 包年包月 + err = cloudprovider.WaitCreated(10*time.Second, 300*time.Second, func() bool { + log.Debugf("WaitCreated %s", _id) + order, e := self.GetOrder(_id) + if e != nil { + log.Debugf(e.Error()) + return false + } + + if order.TotalSize == 0 { + return false + } + + ids, err = self.getAllResIdsByType(_id, RESOURCE_TYPE_VM) + if err != nil { + log.Debugln(err) + return false + } + + if len(ids) > 0 { + return true + } + + return false + }) + } + + if err != nil { + return "", err + } else if len(ids) == 0 { + return "", fmt.Errorf("CreateInstance job %s result is emtpy", _id) + } else if len(ids) == 1 { + return ids[0], nil + } else { + return "", fmt.Errorf("CreateInstance job %s mutliple instance id returned. %s", _id, ids) + } +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067161469.html +// 添加多个安全组时,建议最多为弹性云服务器添加5个安全组。 +// todo: 确认是否需要先删除,再进行添加操作 +func (self *SRegion) assignSecurityGroups(secgroupIds []string, instanceId string) error { + _, err := self.GetInstanceByID(instanceId) + if err != nil { + return err + } + + for i := range secgroupIds { + secId := secgroupIds[i] + params := jsonutils.NewDict() + secgroupObj := jsonutils.NewDict() + secgroupObj.Add(jsonutils.NewString(secId), "name") + params.Add(secgroupObj, "addSecurityGroup") + + _, err := self.ecsClient.NovaServers.PerformAction("action", instanceId, params) + if err != nil { + return err + } + } + return nil +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067161717.html +func (self *SRegion) unassignSecurityGroups(secgroupIds []string, instanceId string) error { + for i := range secgroupIds { + secId := secgroupIds[i] + params := jsonutils.NewDict() + secgroupObj := jsonutils.NewDict() + secgroupObj.Add(jsonutils.NewString(secId), "name") + params.Add(secgroupObj, "removeSecurityGroup") + + _, err := self.ecsClient.NovaServers.PerformAction("action", instanceId, params) + if err != nil { + return err + } + } + return nil +} + +func (self *SRegion) GetInstanceStatus(instanceId string) (string, error) { + instance, err := self.GetInstanceByID(instanceId) + if err != nil { + return "", err + } + return instance.Status, nil +} + +func (self *SRegion) instanceStatusChecking(instanceId, status string) error { + remoteStatus, err := self.GetInstanceStatus(instanceId) + if err != nil { + log.Errorf("Fail to get instance status: %s", err) + return err + } + if status != remoteStatus { + log.Errorf("instanceStatusChecking: vm status is %s expect %s", remoteStatus, status) + return cloudprovider.ErrInvalidStatus + } + + return nil +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212207.html +func (self *SRegion) StartVM(instanceId string) error { + rstatus, err := self.GetInstanceStatus(instanceId) + if err != nil { + return err + } + + if rstatus == InstanceStatusRunning { + return nil + } + + if rstatus != InstanceStatusStopped { + log.Errorf("instanceStatusChecking: vm status is %s expect %s", rstatus, InstanceStatusStopped) + return cloudprovider.ErrInvalidStatus + } + + params := jsonutils.NewDict() + startObj := jsonutils.NewDict() + serversObj := jsonutils.NewArray() + serverObj := jsonutils.NewDict() + serverObj.Add(jsonutils.NewString(instanceId), "id") + serversObj.Add(serverObj) + startObj.Add(serversObj, "servers") + params.Add(startObj, "os-start") + _, err = self.ecsClient.Servers.PerformAction2("action", "", params, "") + return err +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212651.html +func (self *SRegion) StopVM(instanceId string, isForce bool) error { + rstatus, err := self.GetInstanceStatus(instanceId) + if err != nil { + return err + } + + if rstatus == InstanceStatusStopped { + return nil + } + + if rstatus != InstanceStatusRunning { + log.Errorf("instanceStatusChecking: vm status is %s expect %s", rstatus, InstanceStatusRunning) + return cloudprovider.ErrInvalidStatus + } + + params := jsonutils.NewDict() + stopObj := jsonutils.NewDict() + serversObj := jsonutils.NewArray() + serverObj := jsonutils.NewDict() + serverObj.Add(jsonutils.NewString(instanceId), "id") + serversObj.Add(serverObj) + stopObj.Add(serversObj, "servers") + if isForce { + stopObj.Add(jsonutils.NewString("HARD"), "type") + } else { + stopObj.Add(jsonutils.NewString("SOFT"), "type") + } + params.Add(stopObj, "os-stop") + _, err = self.ecsClient.Servers.PerformAction2("action", "", params, "") + return err +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212679.html +// 只删除主机。弹性IP和数据盘需要单独删除 +func (self *SRegion) DeleteVM(instanceId string) error { + remoteStatus, err := self.GetInstanceStatus(instanceId) + if err != nil { + return err + } + + if remoteStatus != InstanceStatusStopped { + log.Errorf("DeleteVM vm status is %s expect %s", remoteStatus, InstanceStatusStopped) + return cloudprovider.ErrInvalidStatus + } + + params := jsonutils.NewDict() + serversObj := jsonutils.NewArray() + serverObj := jsonutils.NewDict() + serverObj.Add(jsonutils.NewString(instanceId), "id") + serversObj.Add(serverObj) + params.Add(serversObj, "servers") + params.Add(jsonutils.NewBool(false), "delete_publicip") + params.Add(jsonutils.NewBool(false), "delete_volume") + + _, err = self.ecsClient.Servers.PerformAction2("delete", "", params, "") + return err +} + +func (self *SRegion) UpdateVM(instanceId, name string) error { + params := jsonutils.NewDict() + serverObj := jsonutils.NewDict() + serverObj.Add(jsonutils.NewString(name), "name") + params.Add(serverObj, "server") + + _, err := self.ecsClient.Servers.Update(instanceId, params) + return err +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876349.html +// 返回job id +func (self *SRegion) RebuildRoot(ctx context.Context, userId, instanceId, passwd, publicKeyName, userData string) (string, error) { + params := jsonutils.NewDict() + reinstallObj := jsonutils.NewDict() + + if len(publicKeyName) > 0 { + reinstallObj.Add(jsonutils.NewString(publicKeyName), "keyname") + } else if len(passwd) > 0 { + reinstallObj.Add(jsonutils.NewString(passwd), "adminpass") + } else { + return "", fmt.Errorf("both password and publicKey are empty.") + } + + if len(userData) > 0 { + meta := jsonutils.NewDict() + meta.Add(jsonutils.NewString(userData), "user_data") + reinstallObj.Add(meta, "metadata") + } + + if len(userId) > 0 { + reinstallObj.Add(jsonutils.NewString(userId), "userid") + } + + params.Add(reinstallObj, "os-reinstall") + ret, err := self.ecsClient.ServersV2.PerformAction2("reinstallos", instanceId, params, "") + if err != nil { + return "", err + } + + return ret.GetString("job_id") +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0067876971.html +// 返回job id +func (self *SRegion) ChangeRoot(ctx context.Context, userId, instanceId, imageId, passwd, publicKeyName, userData string) (string, error) { + params := jsonutils.NewDict() + changeOsObj := jsonutils.NewDict() + + if len(publicKeyName) > 0 { + changeOsObj.Add(jsonutils.NewString(publicKeyName), "keyname") + } else if len(passwd) > 0 { + changeOsObj.Add(jsonutils.NewString(passwd), "adminpass") + } else { + return "", fmt.Errorf("both password and publicKey are empty.") + } + + if len(userData) > 0 { + meta := jsonutils.NewDict() + meta.Add(jsonutils.NewString(userData), "user_data") + changeOsObj.Add(meta, "metadata") + } + + if len(userId) > 0 { + changeOsObj.Add(jsonutils.NewString(userId), "userid") + } + + changeOsObj.Add(jsonutils.NewString(imageId), "imageid") + params.Add(changeOsObj, "os-change") + + ret, err := self.ecsClient.ServersV2.PerformAction2("changeos", instanceId, params, "") + if err != nil { + return "", err + } + + return ret.GetString("job_id") +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212692.html +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0110109377.html +// 一键式重置密码 需要安装安装一键式重置密码插件 https://support.huaweicloud.com/usermanual-ecs/zh-cn_topic_0068095385.html +// 目前不支持直接重置密钥 +func (self *SRegion) DeployVM(instanceId string, name string, password string, keypairName string, deleteKeypair bool, description string) error { + serverObj := jsonutils.NewDict() + if len(name) > 0 { + serverObj.Add(jsonutils.NewString(name), "name") + } + + // if len(description) > 0 { + // serverObj.Add(jsonutils.NewString(description), "description") + // } + + if serverObj.Size() > 0 { + params := jsonutils.NewDict() + params.Add(serverObj, "server") + // 这里华为返回的image字段是字符串。和SInstance的定义的image是字典结构不一致。 + err := DoUpdate(self.ecsClient.NovaServers.Update, instanceId, params, nil) + if err != nil { + return err + } + } + + if len(password) > 0 { + params := jsonutils.NewDict() + passwdObj := jsonutils.NewDict() + passwdObj.Add(jsonutils.NewString(password), "new_password") + params.Add(passwdObj, "reset-password") + + err := DoUpdateWithSpec(self.ecsClient.NovaServers.UpdateInContextWithSpec, instanceId, "os-reset-password", params) + if err != nil { + return err + } + } + + return nil +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212653.html +func (self *SRegion) ChangeVMConfig(instanceId string, instanceType string) error { + self.ecsClient.Servers.SetVersion("v1.1") + defer self.ecsClient.Servers.SetVersion("v1") + + params := jsonutils.NewDict() + resizeObj := jsonutils.NewDict() + resizeObj.Add(jsonutils.NewString(instanceType), "flavorRef") + params.Add(resizeObj, "resize") + _, err := self.ecsClient.Servers.PerformAction2("resize", instanceId, params, "") + return errors.Wrapf(err, "PerformAction2(resize)") +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0142763126.html 微版本2.6及以上? +// https://support.huaweicloud.com/api-ecs/ecs_02_0208.html +func (self *SRegion) GetInstanceVNCUrl(instanceId string) (jsonutils.JSONObject, error) { + params := jsonutils.NewDict() + vncObj := jsonutils.NewDict() + vncObj.Add(jsonutils.NewString("novnc"), "type") + vncObj.Add(jsonutils.NewString("vnc"), "protocol") + params.Add(vncObj, "remote_console") + + ret, err := self.ecsClient.Servers.PerformAction2("remote_console", instanceId, params, "remote_console") + if err != nil { + return nil, err + } + + if retDict, ok := ret.(*jsonutils.JSONDict); ok { + retDict.Set("protocol", jsonutils.NewString("huawei")) + } + + return ret, nil +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0022472987.html +// XEN平台虚拟机device为必选参数。 +func (self *SRegion) AttachDisk(instanceId string, diskId string, device string) error { + params := jsonutils.NewDict() + volumeObj := jsonutils.NewDict() + volumeObj.Add(jsonutils.NewString(diskId), "volumeId") + if len(device) > 0 { + volumeObj.Add(jsonutils.NewString(device), "device") + } + + params.Add(volumeObj, "volumeAttachment") + + _, err := self.ecsClient.Servers.PerformAction2("attachvolume", instanceId, params, "") + return err +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0022472988.html +// 默认非强制卸载。delete_flag=0 +func (self *SRegion) DetachDisk(instanceId string, diskId string) error { + path := fmt.Sprintf("detachvolume/%s", diskId) + err := DoDeleteWithSpec(self.ecsClient.Servers.DeleteInContextWithSpec, nil, instanceId, path, nil, nil) + //volume a2091934-2669-4fca-8eb4-a950c1836b3c is not in server 49b053d2-f798-432f-af55-76eb6ef2c769 attach volume list => 磁盘已经被卸载了 + if err != nil && strings.Contains(err.Error(), fmt.Sprintf("is not in server")) && strings.Contains(err.Error(), fmt.Sprintf("attach volume list")) { + return nil + } + return err +} + +// // https://support.huaweicloud.com/api-bpconsole/zh-cn_topic_0082522029.html +// 只支持传入主资源ID, 根据“查询客户包周期资源列表”接口响应参数中的“is_main_resource”来标识。 +// expire_mode 0:进入宽限期 1:转按需 2:自动退订 3:自动续订(当前只支持ECS、EVS和VPC) +func (self *SRegion) RenewInstance(instanceId string, bc billing.SBillingCycle) error { + params := jsonutils.NewDict() + res := jsonutils.NewArray() + res.Add(jsonutils.NewString(instanceId)) + params.Add(res, "resource_ids") + params.Add(jsonutils.NewInt(EXPIRE_MODE_AUTO_UNSUBSCRIBE), "expire_mode") // 自动退订 + params.Add(jsonutils.NewInt(AUTO_PAY_TRUE), "isAutoPay") // 自动支付 + month := int64(bc.GetMonths()) + year := int64(bc.GetYears()) + + if month >= 1 && month <= 11 { + params.Add(jsonutils.NewInt(PERIOD_TYPE_MONTH), "period_type") + params.Add(jsonutils.NewInt(month), "period_num") + } else if year >= 1 && year <= 3 { + params.Add(jsonutils.NewInt(PERIOD_TYPE_YEAR), "period_type") + params.Add(jsonutils.NewInt(year), "period_num") + } else { + return fmt.Errorf("invalid renew period %d month,must be 1~11 month or 1~3 year", month) + } + + domainId, err := self.getDomianId() + if err != nil { + return err + } + + err = self.ecsClient.Orders.SetDomainId(domainId) + if err != nil { + return err + } + + _, err = self.ecsClient.Orders.RenewPeriodResource(params) + return err +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0065817702.html +func (self *SRegion) GetInstanceSecrityGroupIds(instanceId string) ([]string, error) { + if len(instanceId) == 0 { + return nil, fmt.Errorf("GetInstanceSecrityGroups instanceId is empty") + } + + securitygroups := make([]SSecurityGroup, 0) + ctx := &modules.SManagerContext{InstanceManager: self.ecsClient.NovaServers, InstanceId: instanceId} + err := DoListInContext(self.ecsClient.NovaSecurityGroups.ListInContext, ctx, nil, &securitygroups) + if err != nil { + return nil, err + } + + securitygroupIds := []string{} + for _, secgroup := range securitygroups { + securitygroupIds = append(securitygroupIds, secgroup.GetId()) + } + + return securitygroupIds, nil +} + +// https://support.huaweicloud.com/api-oce/zh-cn_topic_0082522030.html +func (self *SRegion) UnsubscribeInstance(instanceId string, domianId string) (jsonutils.JSONObject, error) { + unsubObj := jsonutils.NewDict() + unsubObj.Add(jsonutils.NewInt(1), "unSubType") + unsubObj.Add(jsonutils.NewInt(5), "unsubscribeReasonType") + unsubObj.Add(jsonutils.NewString("no reason"), "unsubscribeReason") + resList := jsonutils.NewArray() + resList.Add(jsonutils.NewString(instanceId)) + unsubObj.Add(resList, "resourceIds") + + self.ecsClient.Orders.SetDomainId(domianId) + return self.ecsClient.Orders.PerformAction("resources/delete", "", unsubObj) +} + +func (self *SInstance) GetProjectId() string { + return self.EnterpriseProjectId +} + +func (self *SInstance) GetError() error { + return nil +} + +func updateUserData(userData, osVersion, username, password, publicKey string) (string, error) { + winOS := strings.ToLower(osprofile.OS_TYPE_WINDOWS) + osVersion = strings.ToLower(osVersion) + config := &cloudinit.SCloudConfig{} + if strings.Contains(osVersion, winOS) { + if _config, err := cloudinit.ParseUserDataBase64(userData); err == nil { + config = _config + } else { + log.Debugf("updateWindowsUserData invalid userdata %s", userData) + } + } else { + if _config, err := cloudinit.ParseUserDataBase64(userData); err == nil { + config = _config + } else { + return "", fmt.Errorf("updateLinuxUserData invalid userdata %s", userData) + } + } + + user := cloudinit.NewUser(username) + config.RemoveUser(user) + config.DisableRoot = 0 + if len(password) > 0 { + config.SshPwauth = cloudinit.SSH_PASSWORD_AUTH_ON + user.Password(password) + config.MergeUser(user) + } + + if len(publicKey) > 0 { + user.SshKey(publicKey) + config.MergeUser(user) + } + + if strings.Contains(osVersion, winOS) { + userData, err := updateWindowsUserData(config.UserDataPowerShell(), osVersion, username, password) + if err != nil { + return "", errors.Wrap(err, "updateUserData.updateWindowsUserData") + } + return userData, nil + } else { + return config.UserDataBase64(), nil + } +} + +func updateWindowsUserData(userData string, osVersion string, username, password string) (string, error) { + // Windows Server 2003, Windows Vista, Windows Server 2008, Windows Server 2003 R2, Windows Server 2000, Windows Server 2012, Windows Server 2003 with SP1, Windows 8 + oldVersions := []string{"2000", "2003", "2008", "2012", "Vista"} + isOldVersion := false + for i := range oldVersions { + if strings.Contains(osVersion, oldVersions[i]) { + isOldVersion = true + } + } + + shells := "" + if isOldVersion { + shells += fmt.Sprintf("rem cmd\n") + if username == "Administrator" { + shells += fmt.Sprintf("net user %s %s\n", username, password) + } else { + shells += fmt.Sprintf("net user %s %s /add\n", username, password) + shells += fmt.Sprintf("net localgroup administrators %s /add\n", username) + } + + shells += fmt.Sprintf("net user %s /active:yes", username) + } else { + if !strings.HasPrefix(userData, "#ps1") { + shells = fmt.Sprintf("#ps1\n%s", userData) + } + } + + return base64.StdEncoding.EncodeToString([]byte(shells)), nil +} + +func (self *SRegion) SaveImage(instanceId string, opts *cloudprovider.SaveImageOptions) (*SImage, error) { + params := map[string]string{ + "name": opts.Name, + "instance_id": instanceId, + } + if len(opts.Notes) > 0 { + params["description"] = func() string { + opts.Notes = strings.ReplaceAll(opts.Notes, "<", "") + opts.Notes = strings.ReplaceAll(opts.Notes, ">", "") + opts.Notes = strings.ReplaceAll(opts.Notes, "\n", "") + if len(opts.Notes) > 1024 { + opts.Notes = opts.Notes[:1024] + } + return opts.Notes + }() + } + resp, err := self.ecsClient.Images.CreateInContextWithSpec(nil, "action", jsonutils.Marshal(params), "") + if err != nil { + return nil, errors.Wrapf(err, "Images.Create") + } + jobId, err := resp.GetString("job_id") + if err != nil { + return nil, errors.Wrapf(err, "resp.GetString(job_id)") + } + err = self.waitTaskStatus(self.ecsClient.Images.ServiceType(), jobId, TASK_SUCCESS, 15*time.Second, 10*time.Minute) + if err != nil { + return nil, errors.Wrapf(err, "waitTaskStatus") + } + imageId, err := self.GetTaskEntityID(self.ecsClient.Images.ServiceType(), jobId, "image_id") + if err != nil { + return nil, errors.Wrapf(err, "GetTaskEntityID") + } + image, err := self.GetImage(imageId) + if err != nil { + return nil, errors.Wrapf(err, "GetImage(%s)", imageId) + } + image.storageCache = self.getStoragecache() + return image, nil +} + +func (self *SInstance) SaveImage(opts *cloudprovider.SaveImageOptions) (cloudprovider.ICloudImage, error) { + image, err := self.host.zone.region.SaveImage(self.ID, opts) + if err != nil { + return nil, errors.Wrapf(err, "SaveImage") + } + return image, nil +} diff --git a/pkg/multicloud/huaweistack/instancenic.go b/pkg/multicloud/huaweistack/instancenic.go new file mode 100644 index 0000000000..4e54b45bd2 --- /dev/null +++ b/pkg/multicloud/huaweistack/instancenic.go @@ -0,0 +1,122 @@ +// 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 huaweistack + +import ( + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/modules" +) + +// =========================================== +type Interface struct { + PortState string `json:"port_state"` + FixedIPS []FixedIP `json:"fixed_ips"` + NetID string `json:"net_id"` // 网络ID. 与 SNetwork里的ID对应。统一使用这个ID + PortID string `json:"port_id"` + MACAddr string `json:"mac_addr"` +} + +/* +subnet: {id: "b09877fc-90d4-4fc8-b343-e6e00cb2b233", name: "subnet-149c", cidr: "192.168.0.0/24",…} +availability_zone: "cn-north-1b" +cidr: "192.168.0.0/24" +dhcp_enable: true +dnsList: ["100.125.1.250", "100.125.21.250"] +gateway_ip: "192.168.0.1" +id: "b09877fc-90d4-4fc8-b343-e6e00cb2b233" +ipv6_enable: false +name: "subnet-149c" +neutron_network_id: "b09877fc-90d4-4fc8-b343-e6e00cb2b233" +neutron_subnet_id: "81fcfaa0-8e73-4472-9eba-3b2b7736d3a7" +primary_dns: "100.125.1.250" +secondary_dns: "100.125.21.250" +status: "ACTIVE" +tags: [] +vpc_id: "877f1feb-3dc8-4c2d-92e9-0d94fd7d79dd"} +*/ +type FixedIP struct { + SubnetID string `json:"subnet_id"` // 子网ID, 与SNetwork中的 neutron_subnet_id对应. 注意!!! 并不是SNetwork ID。 + IPAddress string `json:"ip_address"` +} + +// =========================================== + +type SInstanceNic struct { + instance *SInstance + ipAddr string + macAddr string + + cloudprovider.DummyICloudNic +} + +func (self *SInstanceNic) GetId() string { + return "" +} + +func (self *SInstanceNic) GetIP() string { + return self.ipAddr +} + +func (self *SInstanceNic) GetMAC() string { + return self.macAddr +} + +func (self *SInstanceNic) GetDriver() string { + return "virtio" +} + +func (self *SInstanceNic) InClassicNetwork() bool { + return false +} + +func (self *SInstanceNic) GetINetwork() cloudprovider.ICloudNetwork { + instanceId := self.instance.GetId() + subnets, err := self.instance.host.zone.region.getSubnetIdsByInstanceId(instanceId) + if err != nil || len(subnets) == 0 { + log.Errorf("getSubnetIdsByInstanceId error: %s", err.Error()) + return nil + } + + wires, err := self.instance.host.GetIWires() + if err != nil { + return nil + } + for i := 0; i < len(wires); i += 1 { + wire := wires[i].(*SWire) + net := wire.getNetworkById(subnets[0]) + if net != nil { + return net + } + } + return nil +} + +func (self *SRegion) getSubnetIdsByInstanceId(instanceId string) ([]string, error) { + ctx := &modules.SManagerContext{InstanceManager: self.ecsClient.NovaServers, InstanceId: instanceId} + interfaces := make([]Interface, 0) + err := DoListInContext(self.ecsClient.Interface.ListInContext, ctx, nil, &interfaces) + if err != nil { + return nil, err + } + + subnets := make([]string, 0) + for _, i := range interfaces { + subnets = append(subnets, i.NetID) + } + + return subnets, nil +} diff --git a/pkg/multicloud/huaweistack/instancetype.go b/pkg/multicloud/huaweistack/instancetype.go new file mode 100644 index 0000000000..fe83a5327c --- /dev/null +++ b/pkg/multicloud/huaweistack/instancetype.go @@ -0,0 +1,299 @@ +// 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 huaweistack + +import ( + "strconv" + "strings" +) + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212656.html +type SInstanceType struct { + ID string `json:"id"` + Name string `json:"name"` + Vcpus string `json:"vcpus"` + RamMB int `json:"ram"` // 内存大小 + Disk string `json:"disk"` + Swap string `json:"swap"` + OSFLVEXTDATAEphemeral int64 `json:"OS-FLV-EXT-DATA:ephemeral"` + RxtxFactor int64 `json:"rxtx_factor"` + OSFLVDISABLEDDisabled bool `json:"OS-FLV-DISABLED:disabled"` + OSFlavorAccessIsPublic bool `json:"os-flavor-access:is_public"` + OSExtraSpecs OSExtraSpecs `json:"os_extra_specs"` // 扩展规格 +} + +type OSExtraSpecs struct { + EcsPerformancetype string `json:"ecs:performancetype"` + EcsGeneration string `json:"ecs:generation"` +} + +var FLAVOR_FAMILY_CATEGORY_MAP = map[string]string{ + "s1": "通用型I代", + "s2": "通用型II代", + "s3": "通用型S3", + "sn3": "通用型", + "s6": "通用型S6", + "p1": "GPU P1型", + "pi1": "GPU Pi1型", + "p2v": "GPU P2v型", + "t6": "通用型T6", + "m1": "内存优化型I代", + "m2": "内存优化型II代", + "m3": "内存优化型", + "m3ne": "内存优化M3ne型", + "h1": "高性能计算型I代", + "h2": "高性能计算型II代", + "h3": "高性能计算型", + "hc2": "高性能计算HC2型", + "hi3": "超高性能计算型", + "d1": "密集存储型I代", + "d2": "密集存储型II代", + "d3": "磁盘增强型", + "g1": "GPU加速型I代", + "g2": "GPU加速型II代", + "g3": "GPU加速型III代", + "f1": "FPGA高性能型", + "f2": "FPGA通用型", + "fp1": "FPGA FP1型", + "fp1c": "FPGA FP1C型", + "ai1": "人工智能Ai1型", + "c1": "通用计算增强C1型", + "c2": "通用计算增强C2型", + "c3": "通用计算增强C3型", + "c3ne": "通用计算增强C3ne型", + "c6": "通用计算增强C6型", + "e1": "大内存E1型", + "e2": "大内存E2型", + "et2": "大内存ET2型", + "e3": "大内存E3型", + "i3": "超高I/O型", + "kc1": "鲲鹏通用计算增强型", + "km1": "鲲鹏内存优化型", + "ki1": "鲲鹏超高I/O型", + "kai1s": "鲲鹏AI推理加速型", +} + +func getFlavorCategory(family string) string { + ret, ok := FLAVOR_FAMILY_CATEGORY_MAP[family] + if ok { + return ret + } + + return family +} + +func getFlavorLocalCategory(family string) string { + switch family { + case "s1", "s2", "s3", "sn3", "s6", "t6": + return "general-purpose" + case "c1", "c2", "c3", "c3ne", "c6", "h1", "h2", "h3", "hc2", "hi3", "kc1": + return "compute-optimized" + case "m1", "m2", "m3", "m3ne", "e1", "e2", "et2", "e3", "km1": + return "memory-optimized" + case "d1", "d2", "d3": + return "storage-optimized" + case "p1", "pi1", "p2v", "g1", "g2", "g3": + return "gpu-compute" + default: + return "others" + } +} + +// https://support.huaweicloud.com/productdesc-ecs/ecs_01_0066.html +// https://support.huaweicloud.com/ecs_faq/ecs_faq_0105.html +func GetCpuArch(flavorId string) string { + if strings.HasPrefix(flavorId, "k") { + return "aarch64" + } + + return "x86" +} + +func (self *SInstanceType) GetId() string { + return self.ID +} + +func (self *SInstanceType) GetName() string { + return self.ID +} + +func (self *SInstanceType) GetGlobalId() string { + return self.ID +} + +func (self *SInstanceType) GetStatus() string { + return "" +} + +func (self *SInstanceType) Refresh() error { + return nil +} + +func (self *SInstanceType) IsEmulated() bool { + return false +} + +func (self *SInstanceType) GetSysTags() map[string]string { + return nil +} + +func (self *SInstanceType) GetTags() (map[string]string, error) { + return nil, nil +} + +func (self *SInstanceType) SetTags(tags map[string]string, replace bool) error { + return nil +} + +func (self *SInstanceType) GetInstanceTypeFamily() string { + if len(self.OSExtraSpecs.EcsGeneration) > 0 { + return self.OSExtraSpecs.EcsGeneration + } else { + return strings.Split(self.ID, ".")[0] + } +} + +func (self *SInstanceType) GetInstanceTypeCategory() string { + return getFlavorCategory(self.GetInstanceTypeFamily()) +} + +func (self *SInstanceType) GetPrepaidStatus() string { + return "available" +} + +func (self *SInstanceType) GetPostpaidStatus() string { + return "available" +} + +func (self *SInstanceType) GetCpuCoreCount() int { + count, err := strconv.Atoi(self.Vcpus) + if err != nil { + return count + } + return 0 +} + +func (self *SInstanceType) GetMemorySizeMB() int { + return self.RamMB +} + +func (self *SInstanceType) GetOsName() string { + return "" +} + +func (self *SInstanceType) GetSysDiskResizable() bool { + return false +} + +func (self *SInstanceType) GetSysDiskType() string { + return "" +} + +func (self *SInstanceType) GetSysDiskMinSizeGB() int { + return 0 +} + +func (self *SInstanceType) GetSysDiskMaxSizeGB() int { + return 0 +} + +func (self *SInstanceType) GetAttachedDiskType() string { + return "" +} + +func (self *SInstanceType) GetAttachedDiskSizeGB() int { + return 0 +} + +func (self *SInstanceType) GetAttachedDiskCount() int { + return 0 +} + +func (self *SInstanceType) GetDataDiskTypes() string { + return "" +} + +func (self *SInstanceType) GetDataDiskMaxCount() int { + return 0 +} + +func (self *SInstanceType) GetNicType() string { + return "" +} + +func (self *SInstanceType) GetNicMaxCount() int { + return 0 +} + +func (self *SInstanceType) GetGpuAttachable() bool { + return self.OSExtraSpecs.EcsPerformancetype == "gpu" +} + +func (self *SInstanceType) GetGpuSpec() string { + if self.OSExtraSpecs.EcsPerformancetype == "gpu" { + return self.OSExtraSpecs.EcsGeneration + } + + return "" +} + +func (self *SInstanceType) GetGpuCount() int { + if self.OSExtraSpecs.EcsPerformancetype == "gpu" { + return 1 + } + + return 0 +} + +func (self *SInstanceType) GetGpuMaxCount() int { + if self.OSExtraSpecs.EcsPerformancetype == "gpu" { + return 1 + } + + return 0 +} + +func (self *SInstanceType) Delete() error { + return nil +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212656.html +func (self *SRegion) fetchInstanceTypes(zoneId string) ([]SInstanceType, error) { + querys := map[string]string{} + if len(zoneId) > 0 { + querys["availability_zone"] = zoneId + } + + instanceTypes := make([]SInstanceType, 0) + err := doListAll(self.ecsClient.Flavors.List, querys, &instanceTypes) + return instanceTypes, err +} + +func (self *SRegion) GetMatchInstanceTypes(cpu int, memMB int, zoneId string) ([]SInstanceType, error) { + instanceTypes, err := self.fetchInstanceTypes(zoneId) + if err != nil { + return nil, err + } + + ret := make([]SInstanceType, 0) + for _, t := range instanceTypes { + // cpu & mem & disk都匹配才行 + if t.Vcpus == strconv.Itoa(cpu) && t.RamMB == memMB { + ret = append(ret, t) + } + } + + return ret, nil +} diff --git a/pkg/multicloud/huaweistack/keypair.go b/pkg/multicloud/huaweistack/keypair.go new file mode 100644 index 0000000000..9f8a1e17d7 --- /dev/null +++ b/pkg/multicloud/huaweistack/keypair.go @@ -0,0 +1,105 @@ +// 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 huaweistack + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/aokoli/goutils" + "golang.org/x/crypto/ssh" + + "yunion.io/x/jsonutils" +) + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212676.html +type SKeypair struct { + Fingerprint string `json:"fingerprint"` + Name string `json:"name"` + PublicKey string `json:"public_key"` +} + +func (self *SRegion) getFingerprint(publicKey string) (string, error) { + pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(publicKey)) + if err != nil { + return "", fmt.Errorf("publicKey error %s", err) + } + + fingerprint := strings.Replace(ssh.FingerprintLegacyMD5(pk), ":", "", -1) + return fingerprint, nil +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212676.html +func (self *SRegion) GetKeypairs() ([]SKeypair, int, error) { + keypairs := make([]SKeypair, 0) + err := doListAll(self.ecsClient.Keypairs.List, nil, &keypairs) + return keypairs, len(keypairs), err +} + +func (self *SRegion) lookUpKeypair(publicKey string) (string, error) { + keypairs, _, err := self.GetKeypairs() + if err != nil { + return "", err + } + + fingerprint, err := self.getFingerprint(publicKey) + if err != nil { + return "", err + } + + for _, keypair := range keypairs { + if keypair.Fingerprint == fingerprint { + return keypair.Name, nil + } + } + + return "", fmt.Errorf("keypair not found %s", err) +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0020212678.html +func (self *SRegion) ImportKeypair(name, publicKey string) (*SKeypair, error) { + keypairObj := jsonutils.NewDict() + keypairObj.Add(jsonutils.NewString(name), "name") + keypairObj.Add(jsonutils.NewString(publicKey), "public_key") + params := jsonutils.NewDict() + params.Set("keypair", keypairObj) + ret := SKeypair{} + err := DoCreate(self.ecsClient.Keypairs.Create, params, &ret) + return &ret, err +} + +func (self *SRegion) importKeypair(publicKey string) (string, error) { + prefix, e := goutils.RandomAlphabetic(6) + if e != nil { + return "", fmt.Errorf("publicKey error %s", e) + } + + name := prefix + strconv.FormatInt(time.Now().Unix(), 10) + if k, e := self.ImportKeypair(name, publicKey); e != nil { + return "", fmt.Errorf("keypair import error %s", e) + } else { + return k.Name, nil + } +} + +func (self *SRegion) syncKeypair(publicKey string) (string, error) { + name, e := self.lookUpKeypair(publicKey) + if e == nil { + return name, nil + } + return self.importKeypair(publicKey) +} diff --git a/pkg/multicloud/huaweistack/latitud_and_longitude.go b/pkg/multicloud/huaweistack/latitud_and_longitude.go new file mode 100644 index 0000000000..2316170a47 --- /dev/null +++ b/pkg/multicloud/huaweistack/latitud_and_longitude.go @@ -0,0 +1,44 @@ +// 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 huaweistack + +import ( + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +// China: https://developer.huaweicloud.com/endpoint +// International: https://developer-intl.huaweicloud.com/endpoint +// ref: https://countrycode.org +var LatitudeAndLongitude = map[string]cloudprovider.SGeographicInfo{ + "cn-east-2": {Latitude: 31.210344, Longitude: 121.455364, City: api.CITY_SHANG_HAI, CountryCode: api.COUNTRY_CODE_CN}, + "cn-east-3": {Latitude: 31.210344, Longitude: 121.455364, City: api.CITY_SHANG_HAI, CountryCode: api.COUNTRY_CODE_CN}, + "cn-north-1": {Latitude: 39.997743, Longitude: 116.304542, City: api.CITY_BEI_JING, CountryCode: api.COUNTRY_CODE_CN}, + "cn-north-4": {Latitude: 39.997743, Longitude: 116.304542, City: api.CITY_BEI_JING, CountryCode: api.COUNTRY_CODE_CN}, + "cn-south-1": {Latitude: 23.12911, Longitude: 113.264385, City: api.CITY_GUANG_ZHOU, CountryCode: api.COUNTRY_CODE_CN}, + "cn-south-2": {Latitude: 23.12911, Longitude: 113.264385, City: api.CITY_GUANG_ZHOU, CountryCode: api.COUNTRY_CODE_CN}, + "ap-southeast-1": {Latitude: 22.396428, Longitude: 114.109497, City: api.CITY_HONG_KONG, CountryCode: api.COUNTRY_CODE_CN}, + "ap-southeast-2": {Latitude: 13.7563309, Longitude: 100.5017651, City: api.CITY_BANGKOK, CountryCode: api.COUNTRY_CODE_TH}, + "ap-southeast-3": {Latitude: 1.360386, Longitude: 103.821195, City: api.CITY_SINGAPORE, CountryCode: api.COUNTRY_CODE_SG}, + "eu-west-0": {Latitude: 48.856614, Longitude: 2.3522219, City: api.CITY_PARIS, CountryCode: api.COUNTRY_CODE_FR}, + "cn-northeast-1": {Latitude: 38.91400300000001, Longitude: 121.614682, City: api.CITY_SHANG_HAI, CountryCode: api.COUNTRY_CODE_CN}, + "cn-southwest-2": {Latitude: 26.6470035286, Longitude: 106.6302113880, City: api.CITY_GUI_YANG, CountryCode: api.COUNTRY_CODE_CN}, + "af-south-1": {Latitude: -26.1714537, Longitude: 27.8999389, City: api.CITY_JOHANNESBURG, CountryCode: api.COUNTRY_CODE_ZA}, + "sa-brazil-1": {Latitude: -23.5505199, Longitude: -46.6333094, City: api.CITY_SAO_PAULO, CountryCode: api.COUNTRY_CODE_BR}, + "na-mexico-1": {Latitude: 55.1182908, Longitude: 141.0377645, City: api.CITY_MEXICO, CountryCode: api.COUNTRY_CODE_MX}, + "la-south-2": {Latitude: -33.45206, Longitude: -70.676031, City: api.CITY_SANTIAGO, CountryCode: api.COUNTRY_CODE_CL}, + "cn-north-9": {Latitude: 41.0178713, Longitude: 113.094978, City: api.CITY_NEI_MENG_GU, CountryCode: api.COUNTRY_CODE_CN}, + "cn-north-219": {Latitude: 39.997743, Longitude: 116.304542, City: api.CITY_BEI_JING, CountryCode: api.COUNTRY_CODE_CN}, +} diff --git a/pkg/multicloud/huaweistack/loadbalancer.go b/pkg/multicloud/huaweistack/loadbalancer.go new file mode 100644 index 0000000000..b19434df32 --- /dev/null +++ b/pkg/multicloud/huaweistack/loadbalancer.go @@ -0,0 +1,553 @@ +// 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 huaweistack + +import ( + "context" + "fmt" + "time" + + "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" +) + +var LB_ALGORITHM_MAP = map[string]string{ + api.LB_SCHEDULER_WRR: "ROUND_ROBIN", + api.LB_SCHEDULER_WLC: "LEAST_CONNECTIONS", + api.LB_SCHEDULER_SCH: "SOURCE_IP", +} + +var LB_PROTOCOL_MAP = map[string]string{ + api.LB_LISTENER_TYPE_HTTP: "HTTP", + api.LB_LISTENER_TYPE_HTTPS: "TERMINATED_HTTPS", + api.LB_LISTENER_TYPE_UDP: "UDP", + api.LB_LISTENER_TYPE_TCP: "TCP", +} + +var LBBG_PROTOCOL_MAP = map[string]string{ + api.LB_LISTENER_TYPE_HTTP: "HTTP", + api.LB_LISTENER_TYPE_HTTPS: "HTTP", + api.LB_LISTENER_TYPE_UDP: "UDP", + api.LB_LISTENER_TYPE_TCP: "TCP", +} + +var LB_STICKY_SESSION_MAP = map[string]string{ + api.LB_STICKY_SESSION_TYPE_INSERT: "HTTP_COOKIE", + api.LB_STICKY_SESSION_TYPE_SERVER: "APP_COOKIE", +} + +var LB_HEALTHCHECK_TYPE_MAP = map[string]string{ + api.LB_HEALTH_CHECK_HTTP: "HTTP", + api.LB_HEALTH_CHECK_TCP: "TCP", + api.LB_HEALTH_CHECK_UDP: "UDP_CONNECT", +} + +type SLoadbalancer struct { + multicloud.SResourceBase + multicloud.HuaweiTags + region *SRegion + subnet *SNetwork + eip *SEipAddress + + Description string `json:"description"` + ProvisioningStatus string `json:"provisioning_status"` + TenantID string `json:"tenant_id"` + ProjectID string `json:"project_id"` + AdminStateUp bool `json:"admin_state_up"` + Provider string `json:"provider"` + Pools []Pool `json:"pools"` + Listeners []Listener `json:"listeners"` + VipPortID string `json:"vip_port_id"` + OperatingStatus string `json:"operating_status"` + VipAddress string `json:"vip_address"` + VipSubnetID string `json:"vip_subnet_id"` + ID string `json:"id"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type Listener struct { + ID string `json:"id"` +} + +type Pool struct { + ID string `json:"id"` +} + +func (self *SLoadbalancer) GetIEIP() (cloudprovider.ICloudEIP, error) { + if self.GetEip() == nil { + return nil, nil + } + + return self.eip, nil +} + +func (self *SLoadbalancer) GetId() string { + return self.ID +} + +func (self *SLoadbalancer) GetName() string { + return self.Name +} + +func (self *SLoadbalancer) GetGlobalId() string { + return self.ID +} + +func (self *SLoadbalancer) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (self *SLoadbalancer) Refresh() error { + lb, err := self.region.GetLoadbalancer(self.GetId()) + if err != nil { + return err + } + + return jsonutils.Update(self, lb) +} + +func (self *SLoadbalancer) IsEmulated() bool { + return false +} + +func (self *SLoadbalancer) GetProjectId() string { + return self.ProjectID +} + +func (self *SLoadbalancer) GetAddress() string { + return self.VipAddress +} + +// todo: api.LB_ADDR_TYPE_INTERNET? +func (self *SLoadbalancer) GetAddressType() string { + return api.LB_ADDR_TYPE_INTRANET +} + +func (self *SLoadbalancer) GetNetworkType() string { + return api.LB_NETWORK_TYPE_VPC +} + +func (self *SLoadbalancer) GetNetworkIds() []string { + net := self.GetNetwork() + if net != nil { + return []string{net.GetId()} + } + + return []string{} +} + +func (self *SLoadbalancer) GetNetwork() *SNetwork { + if self.subnet == nil { + port, err := self.region.GetPort(self.VipPortID) + if err == nil { + net, err := self.region.getNetwork(port.NetworkID) + if err == nil { + self.subnet = net + } else { + log.Debugf("huawei.SLoadbalancer.getNetwork %s", err) + } + } else { + log.Debugf("huawei.SLoadbalancer.GetPort %s", err) + } + } + + return self.subnet +} + +func (self *SLoadbalancer) GetEip() *SEipAddress { + if self.eip == nil { + eips, _ := self.region.GetEips() + for i := range eips { + eip := &eips[i] + if eip.PortId == self.VipPortID { + self.eip = eip + } + } + } + + return self.eip +} + +func (self *SLoadbalancer) GetVpcId() string { + net := self.GetNetwork() + if net != nil { + return net.VpcID + } + + return "" +} + +func (self *SLoadbalancer) GetZoneId() string { + net := self.GetNetwork() + if net != nil { + z, err := self.region.getZoneById(net.AvailabilityZone) + if err != nil { + log.Infof("getZoneById %s %s", net.AvailabilityZone, err) + return "" + } + + return z.GetGlobalId() + } + + return "" +} + +func (self *SLoadbalancer) GetZone1Id() string { + return "" +} + +func (self *SLoadbalancer) GetLoadbalancerSpec() string { + return "" +} + +func (self *SLoadbalancer) GetChargeType() string { + eip := self.GetEip() + if eip != nil { + return eip.GetInternetChargeType() + } + + return api.EIP_CHARGE_TYPE_BY_TRAFFIC +} + +func (self *SLoadbalancer) GetEgressMbps() int { + eip := self.GetEip() + if eip != nil { + return eip.GetBandwidth() + } + + return 0 +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0141008275.html +func (self *SLoadbalancer) Delete(ctx context.Context) error { + return self.region.DeleteLoadBalancer(self.GetId()) +} + +func (self *SLoadbalancer) Start() error { + return nil +} + +func (self *SLoadbalancer) Stop() error { + return cloudprovider.ErrNotSupported +} + +func (self *SLoadbalancer) GetILoadBalancerListeners() ([]cloudprovider.ICloudLoadbalancerListener, error) { + ret, err := self.region.GetLoadBalancerListeners(self.GetId()) + if err != nil { + return nil, err + } + + iret := make([]cloudprovider.ICloudLoadbalancerListener, 0) + for i := range ret { + listener := ret[i] + listener.lb = self + iret = append(iret, &listener) + } + + return iret, nil +} + +func (self *SLoadbalancer) GetILoadBalancerBackendGroups() ([]cloudprovider.ICloudLoadbalancerBackendGroup, error) { + ret, err := self.region.GetLoadBalancerBackendGroups(self.GetId()) + if err != nil { + return nil, err + } + + iret := make([]cloudprovider.ICloudLoadbalancerBackendGroup, 0) + for i := range ret { + bg := ret[i] + bg.lb = self + iret = append(iret, &bg) + } + + return iret, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561549.html +func (self *SLoadbalancer) CreateILoadBalancerBackendGroup(group *cloudprovider.SLoadbalancerBackendGroup) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { + ret, err := self.region.CreateLoadBalancerBackendGroup(group) + ret.lb = self + return &ret, err +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561563.html +func (self *SLoadbalancer) CreateHealthCheck(backendGroupId string, healthcheck *cloudprovider.SLoadbalancerHealthCheck) error { + _, err := self.region.CreateLoadBalancerHealthCheck(backendGroupId, healthcheck) + return err +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561548.html +func (self *SLoadbalancer) GetILoadBalancerBackendGroupById(groupId string) (cloudprovider.ICloudLoadbalancerBackendGroup, error) { + ret := &SElbBackendGroup{} + err := DoGet(self.region.ecsClient.ElbBackendGroup.Get, groupId, nil, ret) + if err != nil { + return nil, err + } + + ret.lb = self + ret.region = self.region + return ret, nil +} + +func (self *SLoadbalancer) CreateILoadBalancerListener(ctx context.Context, listener *cloudprovider.SLoadbalancerListener) (cloudprovider.ICloudLoadbalancerListener, error) { + ret, err := self.region.CreateLoadBalancerListener(listener) + if err != nil { + return nil, err + } + + ret.lb = self + return &ret, nil +} + +func (self *SLoadbalancer) GetILoadBalancerListenerById(listenerId string) (cloudprovider.ICloudLoadbalancerListener, error) { + ret := &SElbListener{} + err := DoGet(self.region.ecsClient.ElbListeners.Get, listenerId, nil, ret) + if err != nil { + return nil, err + } + + ret.lb = self + return ret, nil +} + +func (self *SRegion) GetLoadbalancer(lgId string) (SLoadbalancer, error) { + elb := SLoadbalancer{} + err := DoGet(self.ecsClient.Elb.Get, lgId, nil, &elb) + if err != nil { + return elb, err + } + + return elb, nil +} + +func (self *SRegion) DeleteLoadBalancer(elbId string) error { + return DoDelete(self.ecsClient.Elb.Delete, elbId, nil, nil) +} + +func (self *SRegion) GetLoadBalancerListeners(lbId string) ([]SElbListener, error) { + params := map[string]string{} + if len(lbId) > 0 { + params["loadbalancer_id"] = lbId + } + + ret := make([]SElbListener, 0) + err := doListAll(self.ecsClient.ElbListeners.List, params, &ret) + if err != nil { + return nil, err + } + + return ret, nil +} + +func (self *SRegion) CreateLoadBalancerListener(listener *cloudprovider.SLoadbalancerListener) (SElbListener, error) { + params := jsonutils.NewDict() + listenerObj := jsonutils.NewDict() + listenerObj.Set("name", jsonutils.NewString(listener.Name)) + listenerObj.Set("description", jsonutils.NewString(listener.Description)) + listenerObj.Set("protocol", jsonutils.NewString(LB_PROTOCOL_MAP[listener.ListenerType])) + listenerObj.Set("protocol_port", jsonutils.NewInt(int64(listener.ListenerPort))) + listenerObj.Set("loadbalancer_id", jsonutils.NewString(listener.LoadbalancerID)) + listenerObj.Set("http2_enable", jsonutils.NewBool(listener.EnableHTTP2)) + if len(listener.BackendGroupID) > 0 { + listenerObj.Set("default_pool_id", jsonutils.NewString(listener.BackendGroupID)) + } + + if listener.ListenerType == api.LB_LISTENER_TYPE_HTTPS { + listenerObj.Set("default_tls_container_ref", jsonutils.NewString(listener.CertificateID)) + } + + if listener.XForwardedFor { + insertObj := jsonutils.NewDict() + insertObj.Set("X-Forwarded-ELB-IP", jsonutils.NewBool(listener.XForwardedFor)) + listenerObj.Set("insert_headers", insertObj) + } + params.Set("listener", listenerObj) + ret := SElbListener{} + err := DoCreate(self.ecsClient.ElbListeners.Create, params, &ret) + if err != nil { + return ret, err + } + + return ret, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561547.html +func (self *SRegion) GetLoadBalancerBackendGroups(elbId string) ([]SElbBackendGroup, error) { + params := map[string]string{} + if len(elbId) > 0 { + params["loadbalancer_id"] = elbId + } + + ret := make([]SElbBackendGroup, 0) + err := doListAll(self.ecsClient.ElbBackendGroup.List, params, &ret) + if err != nil { + return nil, err + } + + for i := range ret { + ret[i].region = self + } + + return ret, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561547.html +func (self *SRegion) CreateLoadBalancerBackendGroup(group *cloudprovider.SLoadbalancerBackendGroup) (SElbBackendGroup, error) { + ret := SElbBackendGroup{} + var protocol, scheduler string + if s, ok := LB_ALGORITHM_MAP[group.Scheduler]; !ok { + return ret, fmt.Errorf("CreateILoadBalancerBackendGroup unsupported scheduler %s", group.Scheduler) + } else { + scheduler = s + } + + if t, ok := LBBG_PROTOCOL_MAP[group.ListenType]; !ok { + return ret, fmt.Errorf("CreateILoadBalancerBackendGroup unsupported listener type %s", group.ListenType) + } else { + protocol = t + } + + params := jsonutils.NewDict() + poolObj := jsonutils.NewDict() + poolObj.Set("project_id", jsonutils.NewString(self.client.projectId)) + poolObj.Set("name", jsonutils.NewString(group.Name)) + poolObj.Set("protocol", jsonutils.NewString(protocol)) + poolObj.Set("lb_algorithm", jsonutils.NewString(scheduler)) + + if len(group.ListenerID) > 0 { + poolObj.Set("listener_id", jsonutils.NewString(group.ListenerID)) + } else if len(group.LoadbalancerID) > 0 { + poolObj.Set("loadbalancer_id", jsonutils.NewString(group.LoadbalancerID)) + } else { + return ret, fmt.Errorf("CreateLoadBalancerBackendGroup one of listener id / loadbalancer id must be specified") + } + + if group.StickySession != nil { + s := jsonutils.NewDict() + timeout := int64(group.StickySession.StickySessionCookieTimeout / 60) + if group.ListenType == api.LB_LISTENER_TYPE_UDP || group.ListenType == api.LB_LISTENER_TYPE_TCP { + s.Set("type", jsonutils.NewString("SOURCE_IP")) + if timeout > 0 { + s.Set("persistence_timeout", jsonutils.NewInt(timeout)) + } + } else { + s.Set("type", jsonutils.NewString(LB_STICKY_SESSION_MAP[group.StickySession.StickySessionType])) + if len(group.StickySession.StickySessionCookie) > 0 { + s.Set("cookie_name", jsonutils.NewString(group.StickySession.StickySessionCookie)) + } else { + if timeout > 0 { + s.Set("persistence_timeout", jsonutils.NewInt(timeout)) + } + } + } + + poolObj.Set("session_persistence", s) + } + params.Set("pool", poolObj) + err := DoCreate(self.ecsClient.ElbBackendGroup.Create, params, &ret) + if err != nil { + return ret, err + } + + if group.HealthCheck != nil { + _, err := self.CreateLoadBalancerHealthCheck(ret.GetId(), group.HealthCheck) + if err != nil { + return ret, err + } + } + + ret.region = self + return ret, nil +} + +func (self *SRegion) CreateLoadBalancerHealthCheck(backendGroupID string, healthCheck *cloudprovider.SLoadbalancerHealthCheck) (SElbHealthCheck, error) { + params := jsonutils.NewDict() + healthObj := jsonutils.NewDict() + healthObj.Set("delay", jsonutils.NewInt(int64(healthCheck.HealthCheckInterval))) + healthObj.Set("max_retries", jsonutils.NewInt(int64(healthCheck.HealthCheckRise))) + healthObj.Set("pool_id", jsonutils.NewString(backendGroupID)) + healthObj.Set("timeout", jsonutils.NewInt(int64(healthCheck.HealthCheckTimeout))) + healthObj.Set("type", jsonutils.NewString(LB_HEALTHCHECK_TYPE_MAP[healthCheck.HealthCheckType])) + if healthCheck.HealthCheckType == api.LB_HEALTH_CHECK_HTTP { + if len(healthCheck.HealthCheckDomain) > 0 { + healthObj.Set("domain_name", jsonutils.NewString(healthCheck.HealthCheckDomain)) + } + + if len(healthCheck.HealthCheckURI) > 0 { + healthObj.Set("url_path", jsonutils.NewString(healthCheck.HealthCheckURI)) + } + + if len(healthCheck.HealthCheckHttpCode) > 0 { + healthObj.Set("expected_codes", jsonutils.NewString(ToHuaweiHealthCheckHttpCode(healthCheck.HealthCheckHttpCode))) + } + } + params.Set("healthmonitor", healthObj) + + ret := SElbHealthCheck{} + err := DoCreate(self.ecsClient.ElbHealthCheck.Create, params, &ret) + if err != nil { + return ret, err + } + + ret.region = self + return ret, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561564.html +func (self *SRegion) UpdateLoadBalancerHealthCheck(healthCheckID string, healthCheck *cloudprovider.SLoadbalancerHealthCheck) (SElbHealthCheck, error) { + params := jsonutils.NewDict() + healthObj := jsonutils.NewDict() + healthObj.Set("delay", jsonutils.NewInt(int64(healthCheck.HealthCheckInterval))) + healthObj.Set("max_retries", jsonutils.NewInt(int64(healthCheck.HealthCheckRise))) + healthObj.Set("timeout", jsonutils.NewInt(int64(healthCheck.HealthCheckTimeout))) + if healthCheck.HealthCheckType == api.LB_HEALTH_CHECK_HTTP { + if len(healthCheck.HealthCheckDomain) > 0 { + healthObj.Set("domain_name", jsonutils.NewString(healthCheck.HealthCheckDomain)) + } + + if len(healthCheck.HealthCheckURI) > 0 { + healthObj.Set("url_path", jsonutils.NewString(healthCheck.HealthCheckURI)) + } + + if len(healthCheck.HealthCheckHttpCode) > 0 { + healthObj.Set("expected_codes", jsonutils.NewString(ToHuaweiHealthCheckHttpCode(healthCheck.HealthCheckHttpCode))) + } + } + params.Set("healthmonitor", healthObj) + + ret := SElbHealthCheck{} + err := DoUpdate(self.ecsClient.ElbHealthCheck.Update, healthCheckID, params, &ret) + if err != nil { + return ret, err + } + + ret.region = self + return ret, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561565.html +func (self *SRegion) DeleteLoadbalancerHealthCheck(healthCheckID string) error { + return DoDelete(self.ecsClient.ElbHealthCheck.Delete, healthCheckID, nil, nil) +} + +func (self *SLoadbalancer) SetTags(tags map[string]string, replace bool) error { + return cloudprovider.ErrNotSupported +} diff --git a/pkg/multicloud/huaweistack/loadbalancer_acl.go b/pkg/multicloud/huaweistack/loadbalancer_acl.go new file mode 100644 index 0000000000..bc892f2d7f --- /dev/null +++ b/pkg/multicloud/huaweistack/loadbalancer_acl.go @@ -0,0 +1,113 @@ +// 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 huaweistack + +import ( + "strings" + + "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 SElbACL struct { + multicloud.SResourceBase + multicloud.HuaweiTags + region *SRegion + + ID string `json:"id"` + ListenerID string `json:"listener_id"` + TenantID string `json:"tenant_id"` + EnableWhitelist bool `json:"enable_whitelist"` + Whitelist string `json:"whitelist"` +} + +func (self *SElbACL) GetAclListenerID() string { + return self.ListenerID +} + +func (self *SElbACL) GetId() string { + return self.ID +} + +func (self *SElbACL) GetName() string { + return self.ID +} + +func (self *SElbACL) GetGlobalId() string { + return self.GetId() +} + +func (self *SElbACL) GetStatus() string { + if self.EnableWhitelist { + return api.LB_BOOL_ON + } + + return api.LB_BOOL_OFF +} + +func (self *SElbACL) Refresh() error { + acl, err := self.region.GetLoadBalancerAclById(self.GetId()) + if err != nil { + return err + } + + err = jsonutils.Update(self, acl) + if err != nil { + return err + } + + return nil +} + +func (self *SElbACL) IsEmulated() bool { + return false +} + +func (self *SElbACL) GetProjectId() string { + return "" +} + +func (self *SElbACL) GetAclEntries() []cloudprovider.SLoadbalancerAccessControlListEntry { + ret := []cloudprovider.SLoadbalancerAccessControlListEntry{} + for _, cidr := range strings.Split(self.Whitelist, ",") { + ret = append(ret, cloudprovider.SLoadbalancerAccessControlListEntry{CIDR: cidr}) + } + + return ret +} + +func (self *SElbACL) Sync(acl *cloudprovider.SLoadbalancerAccessControlList) error { + whiteList := "" + cidrs := []string{} + for _, entry := range acl.Entrys { + cidrs = append(cidrs, entry.CIDR) + } + + whiteList = strings.Join(cidrs, ",") + + params := jsonutils.NewDict() + whiteListObj := jsonutils.NewDict() + whiteListObj.Set("whitelist", jsonutils.NewString(whiteList)) + whiteListObj.Set("enable_whitelist", jsonutils.NewBool(acl.AccessControlEnable)) + params.Set("whitelist", whiteListObj) + return DoUpdate(self.region.ecsClient.ElbWhitelist.Update, self.GetId(), params, nil) +} + +func (self *SElbACL) Delete() error { + return DoDelete(self.region.ecsClient.ElbWhitelist.Delete, self.GetId(), nil, nil) +} diff --git a/pkg/multicloud/huaweistack/loadbalancer_backend.go b/pkg/multicloud/huaweistack/loadbalancer_backend.go new file mode 100644 index 0000000000..016145e915 --- /dev/null +++ b/pkg/multicloud/huaweistack/loadbalancer_backend.go @@ -0,0 +1,171 @@ +// 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 huaweistack + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SElbBackend struct { + multicloud.SResourceBase + multicloud.HuaweiTags + region *SRegion + lb *SLoadbalancer + backendGroup *SElbBackendGroup + + Name string `json:"name"` + Weight int `json:"weight"` + AdminStateUp bool `json:"admin_state_up"` + SubnetID string `json:"subnet_id"` + TenantID string `json:"tenant_id"` + ProjectID string `json:"project_id"` + Address string `json:"address"` + ProtocolPort int `json:"protocol_port"` + OperatingStatus string `json:"operating_status"` + ID string `json:"id"` +} + +func (self *SElbBackend) GetId() string { + return self.ID +} + +func (self *SElbBackend) GetName() string { + return self.Name +} + +func (self *SElbBackend) GetGlobalId() string { + return self.GetId() +} + +func (self *SElbBackend) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (self *SElbBackend) Refresh() error { + m := self.lb.region.ecsClient.ElbBackend + err := m.SetBackendGroupId(self.backendGroup.GetId()) + if err != nil { + return err + } + + backend := SElbBackend{} + err = DoGet(m.Get, self.GetId(), nil, &backend) + if err != nil { + return err + } + + backend.lb = self.lb + backend.backendGroup = self.backendGroup + err = jsonutils.Update(self, backend) + if err != nil { + return err + } + + return nil +} + +func (self *SElbBackend) IsEmulated() bool { + return false +} + +func (self *SElbBackend) GetProjectId() string { + return "" +} + +func (self *SElbBackend) GetWeight() int { + return self.Weight +} + +func (self *SElbBackend) GetPort() int { + return self.ProtocolPort +} + +func (self *SElbBackend) GetBackendType() string { + return api.LB_BACKEND_GUEST +} + +func (self *SElbBackend) GetBackendRole() string { + return api.LB_BACKEND_ROLE_DEFAULT +} + +func (self *SElbBackend) GetBackendId() string { + i, err := self.lb.region.getInstanceByIP(self.Address) + if err != nil { + log.Errorf("ElbBackend GetBackendId %s", err) + } + + if i != nil { + return i.GetId() + } + + return "" +} + +func (self *SElbBackend) GetIpAddress() string { + return "" +} + +func (self *SElbBackend) SyncConf(ctx context.Context, port, weight int) error { + if port > 0 { + log.Warningf("Elb backend SyncConf unsupport modify port") + } + + params := jsonutils.NewDict() + memberObj := jsonutils.NewDict() + memberObj.Set("weight", jsonutils.NewInt(int64(weight))) + params.Set("member", memberObj) + err := self.lb.region.ecsClient.ElbBackend.SetBackendGroupId(self.backendGroup.GetId()) + if err != nil { + return err + } + return DoUpdate(self.lb.region.ecsClient.ElbBackend.Update, self.GetId(), params, nil) +} + +func (self *SRegion) getInstanceByIP(privateIP string) (*SInstance, error) { + queries := make(map[string]string) + + if len(self.client.projectId) > 0 { + queries["project_id"] = self.client.projectId + } + + instances := make([]SInstance, 0) + err := doListAllWithOffset(self.ecsClient.Servers.List, queries, &instances) + if err != nil { + return nil, err + } + + for _, instance := range instances { + ips := []string{} + for _, addresses := range instance.Addresses { + for _, ip := range addresses { + ips = append(ips, ip.Addr) + } + } + + if utils.IsInStringArray(privateIP, ips) { + return &instance, nil + } + } + + return nil, cloudprovider.ErrNotFound +} diff --git a/pkg/multicloud/huaweistack/loadbalancer_backendgroup.go b/pkg/multicloud/huaweistack/loadbalancer_backendgroup.go new file mode 100644 index 0000000000..6e0124fda9 --- /dev/null +++ b/pkg/multicloud/huaweistack/loadbalancer_backendgroup.go @@ -0,0 +1,541 @@ +// 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 huaweistack + +import ( + "context" + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SElbBackendGroup struct { + multicloud.SResourceBase + multicloud.HuaweiTags + lb *SLoadbalancer + region *SRegion + + LBAlgorithm string `json:"lb_algorithm"` + Protocol string `json:"protocol"` + Description string `json:"description"` + AdminStateUp bool `json:"admin_state_up"` + Loadbalancers []Listener `json:"loadbalancers"` + TenantID string `json:"tenant_id"` + ProjectID string `json:"project_id"` + Listeners []Listener `json:"listeners"` + ID string `json:"id"` + Name string `json:"name"` + HealthMonitorID string `json:"healthmonitor_id"` + SessionPersistence StickySession `json:"session_persistence"` +} + +func (self *SElbBackendGroup) GetLoadbalancerId() string { + return self.lb.GetId() +} + +func (self *SElbBackendGroup) GetILoadbalancer() cloudprovider.ICloudLoadbalancer { + return self.lb +} + +type StickySession struct { + Type string `json:"type"` + CookieName string `json:"cookie_name"` + PersistenceTimeout int `json:"persistence_timeout"` +} + +func (self *SElbBackendGroup) GetProtocolType() string { + switch self.Protocol { + case "TCP": + return api.LB_LISTENER_TYPE_TCP + case "UDP": + return api.LB_LISTENER_TYPE_UDP + case "HTTP": + return api.LB_LISTENER_TYPE_HTTP + default: + return "" + } +} + +func (self *SElbBackendGroup) GetScheduler() string { + switch self.LBAlgorithm { + case "ROUND_ROBIN": + return api.LB_SCHEDULER_WRR + case "LEAST_CONNECTIONS": + return api.LB_SCHEDULER_WLC + case "SOURCE_IP": + return api.LB_SCHEDULER_SCH + default: + return "" + } +} + +func ToHuaweiHealthCheckHttpCode(c string) string { + c = strings.TrimSpace(c) + segs := strings.Split(c, ",") + ret := []string{} + for _, seg := range segs { + seg = strings.TrimLeft(seg, "http_") + seg = strings.TrimSpace(seg) + seg = strings.Replace(seg, "xx", "00", -1) + ret = append(ret, seg) + } + + return strings.Join(ret, ",") +} + +func ToOnecloudHealthCheckHttpCode(c string) string { + c = strings.TrimSpace(c) + segs := strings.Split(c, ",") + ret := []string{} + for _, seg := range segs { + seg = strings.TrimSpace(seg) + seg = strings.Replace(seg, "00", "xx", -1) + seg = "http_" + seg + ret = append(ret, seg) + } + + return strings.Join(ret, ",") +} + +func (self *SElbBackendGroup) GetHealthCheck() (*cloudprovider.SLoadbalancerHealthCheck, error) { + if len(self.HealthMonitorID) == 0 { + return nil, nil + } + + health, err := self.region.GetLoadBalancerHealthCheck(self.HealthMonitorID) + if err != nil { + return nil, err + } + + var healthCheckType string + switch health.Type { + case "TCP": + healthCheckType = api.LB_HEALTH_CHECK_TCP + case "UDP_CONNECT": + healthCheckType = api.LB_HEALTH_CHECK_UDP + case "HTTP": + healthCheckType = api.LB_HEALTH_CHECK_HTTP + default: + healthCheckType = "" + } + + ret := cloudprovider.SLoadbalancerHealthCheck{ + HealthCheckType: healthCheckType, + HealthCheckTimeout: health.Timeout, + HealthCheckDomain: health.DomainName, + HealthCheckURI: health.URLPath, + HealthCheckInterval: health.Delay, + HealthCheckRise: health.MaxRetries, + HealthCheckHttpCode: ToOnecloudHealthCheckHttpCode(health.ExpectedCodes), + } + + return &ret, nil +} + +func (self *SElbBackendGroup) GetStickySession() (*cloudprovider.SLoadbalancerStickySession, error) { + if len(self.SessionPersistence.Type) == 0 { + return nil, nil + } + + var stickySessionType string + switch self.SessionPersistence.Type { + case "SOURCE_IP": + stickySessionType = api.LB_STICKY_SESSION_TYPE_INSERT + case "HTTP_COOKIE": + stickySessionType = api.LB_STICKY_SESSION_TYPE_INSERT + case "APP_COOKIE": + stickySessionType = api.LB_STICKY_SESSION_TYPE_SERVER + } + + ret := cloudprovider.SLoadbalancerStickySession{ + StickySession: api.LB_BOOL_ON, + StickySessionCookie: self.SessionPersistence.CookieName, + StickySessionType: stickySessionType, + StickySessionCookieTimeout: self.SessionPersistence.PersistenceTimeout * 60, + } + + return &ret, nil +} + +func (self *SElbBackendGroup) GetId() string { + return self.ID +} + +func (self *SElbBackendGroup) GetName() string { + return self.Name +} + +func (self *SElbBackendGroup) GetGlobalId() string { + return self.GetId() +} + +func (self *SElbBackendGroup) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (self *SElbBackendGroup) Refresh() error { + ret, err := self.lb.region.GetLoadBalancerBackendGroupId(self.GetId()) + if err != nil { + return err + } + ret.lb = self.lb + + err = jsonutils.Update(self, ret) + if err != nil { + return err + } + + return nil +} + +func (self *SElbBackendGroup) IsEmulated() bool { + return false +} + +func (self *SElbBackendGroup) GetProjectId() string { + return self.ProjectID +} + +func (self *SElbBackendGroup) IsDefault() bool { + return false +} + +func (self *SElbBackendGroup) GetType() string { + return api.LB_BACKENDGROUP_TYPE_NORMAL +} + +func (self *SElbBackendGroup) GetILoadbalancerBackends() ([]cloudprovider.ICloudLoadbalancerBackend, error) { + ret, err := self.region.GetLoadBalancerBackends(self.GetId()) + if err != nil { + return nil, err + } + + iret := []cloudprovider.ICloudLoadbalancerBackend{} + for i := range ret { + backend := ret[i] + backend.lb = self.lb + backend.backendGroup = self + + iret = append(iret, &backend) + } + + return iret, nil +} + +func (self *SElbBackendGroup) GetILoadbalancerBackendById(serverId string) (cloudprovider.ICloudLoadbalancerBackend, error) { + m := self.lb.region.ecsClient.ElbBackend + err := m.SetBackendGroupId(self.GetId()) + if err != nil { + return nil, err + } + + backend := SElbBackend{} + err = DoGet(m.Get, serverId, nil, &backend) + if err != nil { + return nil, err + } + + backend.lb = self.lb + backend.backendGroup = self + return &backend, nil +} + +func (self *SElbBackendGroup) AddBackendServer(serverId string, weight int, port int) (cloudprovider.ICloudLoadbalancerBackend, error) { + instance, err := self.lb.region.GetInstanceByID(serverId) + if err != nil { + return nil, err + } + + nics, err := instance.GetINics() + if err != nil { + return nil, err + } else if len(nics) == 0 { + return nil, fmt.Errorf("AddBackendServer %s no network interface found", serverId) + } + + subnets, err := self.lb.region.getSubnetIdsByInstanceId(instance.GetId()) + if err != nil { + return nil, err + } else if len(subnets) == 0 { + return nil, fmt.Errorf("AddBackendServer %s no subnet found", serverId) + } + + net, err := self.lb.region.getNetwork(subnets[0]) + if err != nil { + return nil, err + } + + backend, err := self.region.AddLoadBalancerBackend(self.GetId(), net.NeutronSubnetID, nics[0].GetIP(), port, weight) + if err != nil { + return nil, err + } + + backend.lb = self.lb + backend.backendGroup = self + return &backend, nil +} + +func (self *SElbBackendGroup) RemoveBackendServer(backendId string, weight int, port int) error { + ibackend, err := self.GetILoadbalancerBackendById(backendId) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + return nil + } + + return errors.Wrap(err, "ElbBackendGroup.GetILoadbalancerBackendById") + } + + err = self.region.RemoveLoadBalancerBackend(self.GetId(), backendId) + if err != nil { + return errors.Wrap(err, "ElbBackendGroup.RemoveBackendServer") + } + + return cloudprovider.WaitDeleted(ibackend, 2*time.Second, 30*time.Second) +} + +func (self *SElbBackendGroup) Delete(ctx context.Context) error { + if len(self.HealthMonitorID) > 0 { + err := self.region.DeleteLoadbalancerHealthCheck(self.HealthMonitorID) + if err != nil { + return errors.Wrap(err, "ElbBackendGroup.Delete.DeleteLoadbalancerHealthCheck") + } + } + + // 删除后端服务器组的同时,删除掉无效的后端服务器数据 + { + backends, err := self.region.getLoadBalancerAdminStateDownBackends(self.GetId()) + if err != nil { + return errors.Wrap(err, "SElbBackendGroup.Delete.getLoadBalancerAdminStateDownBackends") + } + + for i := range backends { + backend := backends[i] + err := self.RemoveBackendServer(backend.GetId(), backend.GetPort(), backend.GetWeight()) + if err != nil { + return errors.Wrap(err, "SElbBackendGroup.Delete.RemoveBackendServer") + } + } + } + + err := self.region.DeleteLoadBalancerBackendGroup(self.GetId()) + if err != nil { + return errors.Wrap(err, "ElbBackendGroup.Delete.DeleteLoadBalancerBackendGroup") + } + + return cloudprovider.WaitDeleted(self, 2*time.Second, 30*time.Second) +} + +func (self *SElbBackendGroup) Sync(ctx context.Context, group *cloudprovider.SLoadbalancerBackendGroup) error { + if group == nil { + return nil + } + + _, err := self.region.UpdateLoadBalancerBackendGroup(self.GetId(), group) + return err +} + +func (self *SRegion) GetLoadBalancerBackendGroupId(backendGroupId string) (SElbBackendGroup, error) { + ret := SElbBackendGroup{} + err := DoGet(self.ecsClient.ElbBackendGroup.Get, backendGroupId, nil, &ret) + if err != nil { + return ret, err + } + + ret.region = self + return ret, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561550.html +func (self *SRegion) UpdateLoadBalancerBackendGroup(backendGroupID string, group *cloudprovider.SLoadbalancerBackendGroup) (SElbBackendGroup, error) { + params := jsonutils.NewDict() + poolObj := jsonutils.NewDict() + poolObj.Set("name", jsonutils.NewString(group.Name)) + var scheduler string + if s, ok := LB_ALGORITHM_MAP[group.Scheduler]; !ok { + return SElbBackendGroup{}, fmt.Errorf("UpdateLoadBalancerBackendGroup unsupported scheduler %s", group.Scheduler) + } else { + scheduler = s + } + poolObj.Set("lb_algorithm", jsonutils.NewString(scheduler)) + + if group.StickySession == nil || group.StickySession.StickySession == api.LB_BOOL_OFF { + poolObj.Set("session_persistence", jsonutils.JSONNull) + } else { + s := jsonutils.NewDict() + timeout := int64(group.StickySession.StickySessionCookieTimeout / 60) + if group.ListenType == api.LB_LISTENER_TYPE_UDP || group.ListenType == api.LB_LISTENER_TYPE_TCP { + s.Set("type", jsonutils.NewString("SOURCE_IP")) + if timeout > 0 { + s.Set("persistence_timeout", jsonutils.NewInt(timeout)) + } + } else { + s.Set("type", jsonutils.NewString(LB_STICKY_SESSION_MAP[group.StickySession.StickySessionType])) + if len(group.StickySession.StickySessionCookie) > 0 { + s.Set("cookie_name", jsonutils.NewString(group.StickySession.StickySessionCookie)) + } else { + if timeout > 0 { + s.Set("persistence_timeout", jsonutils.NewInt(timeout)) + } + } + } + + poolObj.Set("session_persistence", s) + } + params.Set("pool", poolObj) + + ret := SElbBackendGroup{} + err := DoUpdate(self.ecsClient.ElbBackendGroup.Update, backendGroupID, params, &ret) + if err != nil { + return ret, errors.Wrap(err, "ElbBackendGroup.Update") + } + + if group.HealthCheck == nil && len(ret.HealthMonitorID) > 0 { + err := self.DeleteLoadbalancerHealthCheck(ret.HealthMonitorID) + if err != nil { + return ret, errors.Wrap(err, "DeleteLoadbalancerHealthCheck") + } + } + + if group.HealthCheck != nil { + if len(ret.HealthMonitorID) == 0 { + _, err := self.CreateLoadBalancerHealthCheck(ret.GetId(), group.HealthCheck) + if err != nil { + return ret, errors.Wrap(err, "CreateLoadBalancerHealthCheck") + } + } else { + _, err := self.UpdateLoadBalancerHealthCheck(ret.HealthMonitorID, group.HealthCheck) + if err != nil { + return ret, errors.Wrap(err, "UpdateLoadBalancerHealthCheck") + } + } + } + + ret.region = self + return ret, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561551.html +func (self *SRegion) DeleteLoadBalancerBackendGroup(backendGroupID string) error { + return DoDelete(self.ecsClient.ElbBackendGroup.Delete, backendGroupID, nil, nil) +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561556.html +func (self *SRegion) AddLoadBalancerBackend(backendGroupId, subnetId, ipaddr string, port, weight int) (SElbBackend, error) { + backend := SElbBackend{} + params := jsonutils.NewDict() + memberObj := jsonutils.NewDict() + memberObj.Set("address", jsonutils.NewString(ipaddr)) + memberObj.Set("protocol_port", jsonutils.NewInt(int64(port))) + memberObj.Set("subnet_id", jsonutils.NewString(subnetId)) + memberObj.Set("weight", jsonutils.NewInt(int64(weight))) + params.Set("member", memberObj) + + m := self.ecsClient.ElbBackend + err := m.SetBackendGroupId(backendGroupId) + if err != nil { + return backend, err + } + + err = DoCreate(m.Create, params, &backend) + if err != nil { + return backend, err + } + + return backend, nil +} + +func (self *SRegion) RemoveLoadBalancerBackend(lbbgId string, backendId string) error { + m := self.ecsClient.ElbBackend + err := m.SetBackendGroupId(lbbgId) + if err != nil { + return err + } + + return DoDelete(m.Delete, backendId, nil, nil) +} + +func (self *SRegion) getLoadBalancerBackends(backendGroupId string) ([]SElbBackend, error) { + m := self.ecsClient.ElbBackend + err := m.SetBackendGroupId(backendGroupId) + if err != nil { + return nil, err + } + + ret := []SElbBackend{} + err = doListAll(m.List, nil, &ret) + if err != nil { + return nil, err + } + + for i := range ret { + backend := ret[i] + backend.region = self + } + + return ret, nil +} + +func (self *SRegion) GetLoadBalancerBackends(backendGroupId string) ([]SElbBackend, error) { + ret, err := self.getLoadBalancerBackends(backendGroupId) + if err != nil { + return nil, errors.Wrap(err, "SRegion.GetLoadBalancerBackends.getLoadBalancerBackends") + } + + // 过滤掉服务器已经被删除的backend。原因是运管平台查询不到已删除的服务器记录,导致同步出错。产生肮数据。 + filtedRet := []SElbBackend{} + for i := range ret { + if ret[i].AdminStateUp { + backend := ret[i] + filtedRet = append(filtedRet, backend) + } + } + + return filtedRet, nil +} + +func (self *SRegion) getLoadBalancerAdminStateDownBackends(backendGroupId string) ([]SElbBackend, error) { + ret, err := self.getLoadBalancerBackends(backendGroupId) + if err != nil { + return nil, errors.Wrap(err, "SRegion.getLoadBalancerAdminStateDownBackends.getLoadBalancerBackends") + } + + filtedRet := []SElbBackend{} + for i := range ret { + if !ret[i].AdminStateUp { + backend := ret[i] + filtedRet = append(filtedRet, backend) + } + } + + return filtedRet, nil +} + +func (self *SRegion) GetLoadBalancerHealthCheck(healthCheckId string) (SElbHealthCheck, error) { + ret := SElbHealthCheck{} + err := DoGet(self.ecsClient.ElbHealthCheck.Get, healthCheckId, nil, &ret) + if err != nil { + return ret, err + } + + ret.region = self + return ret, nil +} diff --git a/pkg/multicloud/huaweistack/loadbalancer_cert.go b/pkg/multicloud/huaweistack/loadbalancer_cert.go new file mode 100644 index 0000000000..d4d4ec3575 --- /dev/null +++ b/pkg/multicloud/huaweistack/loadbalancer_cert.go @@ -0,0 +1,123 @@ +// 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 huaweistack + +import ( + "crypto/sha1" + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SElbCert struct { + multicloud.SResourceBase + multicloud.HuaweiTags + region *SRegion + + Certificate string `json:"certificate"` + CreateTime time.Time `json:"create_time"` + ExpireTime time.Time `json:"expire_time"` + Description string `json:"description"` + Domain string `json:"domain"` + ID string `json:"id"` + AdminStateUp bool `json:"admin_state_up"` + TenantID string `json:"tenant_id"` + Name string `json:"name"` + PrivateKey string `json:"private_key"` + Type string `json:"type"` + UpdateTime time.Time `json:"update_time"` +} + +func (self *SElbCert) GetPublickKey() string { + return self.Certificate +} + +func (self *SElbCert) GetPrivateKey() string { + return self.PrivateKey +} + +func (self *SElbCert) GetId() string { + return self.ID +} + +func (self *SElbCert) GetName() string { + return self.Name +} + +func (self *SElbCert) GetGlobalId() string { + return self.GetId() +} + +func (self *SElbCert) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (self *SElbCert) Refresh() error { + cert, err := self.region.GetLoadBalancerCertificateById(self.GetId()) + if err != nil { + return err + } + + cert.region = self.region + err = jsonutils.Update(self, cert) + if err != nil { + return err + } + + return nil +} + +func (self *SElbCert) IsEmulated() bool { + return false +} + +func (self *SElbCert) GetProjectId() string { + return "" +} + +func (self *SElbCert) Sync(name, privateKey, publickKey string) error { + params := jsonutils.NewDict() + params.Set("name", jsonutils.NewString(name)) + params.Set("private_key", jsonutils.NewString(privateKey)) + params.Set("certificate", jsonutils.NewString(publickKey)) + return DoUpdate(self.region.ecsClient.ElbCertificates.Update, self.GetId(), params, nil) +} + +func (self *SElbCert) Delete() error { + return DoDelete(self.region.ecsClient.ElbCertificates.Delete, self.GetId(), nil, nil) +} + +func (self *SElbCert) GetCommonName() string { + return self.Domain +} + +func (self *SElbCert) GetSubjectAlternativeNames() string { + return self.Domain +} + +func (self *SElbCert) GetFingerprint() string { + _fp := sha1.Sum([]byte(self.Certificate)) + fp := fmt.Sprintf("sha1:% x", _fp) + return strings.Replace(fp, " ", ":", -1) +} + +func (self *SElbCert) GetExpireTime() time.Time { + return self.ExpireTime +} diff --git a/pkg/multicloud/huaweistack/loadbalancer_healthcheck.go b/pkg/multicloud/huaweistack/loadbalancer_healthcheck.go new file mode 100644 index 0000000000..6e8fe86749 --- /dev/null +++ b/pkg/multicloud/huaweistack/loadbalancer_healthcheck.go @@ -0,0 +1,35 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huaweistack + +type SElbHealthCheck struct { + region *SRegion + + Name string `json:"name"` + AdminStateUp bool `json:"admin_state_up"` + TenantID string `json:"tenant_id"` + ProjectID string `json:"project_id"` + DomainName string `json:"domain_name"` + Delay int `json:"delay"` + ExpectedCodes string `json:"expected_codes"` + MaxRetries int `json:"max_retries"` + HTTPMethod string `json:"http_method"` + Timeout int `json:"timeout"` + Pools []Pool `json:"pools"` + URLPath string `json:"url_path"` + Type string `json:"type"` + ID string `json:"id"` + MonitorPort int `json:"monitor_port"` +} diff --git a/pkg/multicloud/huaweistack/loadbalancer_listener.go b/pkg/multicloud/huaweistack/loadbalancer_listener.go new file mode 100644 index 0000000000..8ff1e658ef --- /dev/null +++ b/pkg/multicloud/huaweistack/loadbalancer_listener.go @@ -0,0 +1,694 @@ +// 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 huaweistack + +import ( + "context" + "time" + + "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 InsertHeaders struct { + XForwardedELBIP bool `json:"X-Forwarded-ELB-IP"` +} + +type Loadbalancer struct { + ID string `json:"id"` +} + +type SElbListener struct { + multicloud.SResourceBase + multicloud.SLoadbalancerRedirectBase + multicloud.HuaweiTags + lb *SLoadbalancer + acl *SElbACL + backendgroup *SElbBackendGroup + + ProtocolPort int `json:"protocol_port"` + Protocol string `json:"protocol"` + Description string `json:"description"` + AdminStateUp bool `json:"admin_state_up"` + Http2Enable bool `json:"http2_enable"` + Loadbalancers []Loadbalancer `json:"loadbalancers"` + TenantID string `json:"tenant_id"` + ProjectID string `json:"project_id"` + ConnectionLimit int `json:"connection_limit"` + DefaultPoolID string `json:"default_pool_id"` + ID string `json:"id"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + InsertHeaders InsertHeaders `json:"insert_headers"` + DefaultTlsContainerRef string `json:"default_tls_container_ref"` +} + +func (self *SElbListener) GetId() string { + return self.ID +} + +func (self *SElbListener) GetName() string { + return self.Name +} + +func (self *SElbListener) GetGlobalId() string { + return self.GetId() +} + +func (self *SElbListener) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (self *SElbListener) Refresh() error { + ilistener, err := self.lb.GetILoadBalancerListenerById(self.GetId()) + if err != nil { + return err + } + + listener := ilistener.(*SElbListener) + listener.lb = self.lb + err = jsonutils.Update(self, listener) + if err != nil { + return err + } + + return nil +} + +func (self *SElbListener) IsEmulated() bool { + return false +} + +func (self *SElbListener) GetProjectId() string { + return self.ProjectID +} + +func (self *SElbListener) GetListenerType() string { + switch self.Protocol { + case "TCP": + return api.LB_LISTENER_TYPE_TCP + case "UDP": + return api.LB_LISTENER_TYPE_UDP + case "HTTP": + return api.LB_LISTENER_TYPE_HTTP + case "TERMINATED_HTTPS": + return api.LB_LISTENER_TYPE_HTTPS + case "HTTPS": + return api.LB_LISTENER_TYPE_HTTPS + default: + return "" + } +} + +func (self *SElbListener) GetListenerPort() int { + return self.ProtocolPort +} + +func (self *SElbListener) GetBackendGroup() (*SElbBackendGroup, error) { + if self.backendgroup == nil { + lbbgId := self.GetBackendGroupId() + if len(lbbgId) > 0 { + lbbg, err := self.lb.GetILoadBalancerBackendGroupById(lbbgId) + if err != nil { + return nil, err + } + + self.backendgroup = lbbg.(*SElbBackendGroup) + } + } + + return self.backendgroup, nil +} + +func (self *SElbListener) GetScheduler() string { + lbbg, err := self.GetBackendGroup() + if err != nil { + log.Errorf("ElbListener GetScheduler %s", err.Error()) + } + + if lbbg == nil { + return "" + } + + return lbbg.GetScheduler() +} + +func (self *SElbListener) GetAcl() (*SElbACL, error) { + if self.acl != nil { + return self.acl, nil + } + + acls, err := self.lb.region.GetLoadBalancerAcls(self.GetId()) + if err != nil { + return nil, err + } + + if len(acls) == 0 { + return nil, nil + } else { + self.acl = &acls[0] + return &acls[0], nil + } +} + +func (self *SElbListener) GetAclStatus() string { + acl, err := self.GetAcl() + if err != nil { + log.Debugf("GetAclStatus %s", err) + return "" + } + + if acl != nil && acl.EnableWhitelist { + return api.LB_BOOL_ON + } + + return api.LB_BOOL_OFF +} + +func (self *SElbListener) GetAclType() string { + return api.LB_ACL_TYPE_WHITE +} + +func (self *SElbListener) GetAclId() string { + acl, err := self.GetAcl() + if err != nil { + log.Debugf("GetAclStatus %s", err) + return "" + } + + if acl == nil { + return "" + } + + return acl.GetId() +} + +func (self *SElbListener) GetEgressMbps() int { + return 0 +} + +func (self *SElbListener) GetHealthCheck() string { + lbbg, err := self.GetBackendGroup() + if err != nil { + log.Errorf("ElbListener GetHealthCheck %s", err.Error()) + } + + if lbbg == nil { + return "" + } + + health, err := lbbg.GetHealthCheck() + if err != nil { + log.Errorf("ElbListener GetHealthCheck %s", err.Error()) + } + + if health != nil { + return api.LB_BOOL_ON + } else { + return api.LB_BOOL_OFF + } +} + +func (self *SElbListener) GetHealthCheckType() string { + lbbg, err := self.GetBackendGroup() + if err != nil { + log.Errorf("ElbListener GetHealthCheckType %s", err.Error()) + } + + if lbbg == nil { + return "" + } + + health, err := lbbg.GetHealthCheck() + if err != nil { + log.Errorf("ElbListener GetHealthCheckType %s", err.Error()) + } + + if health != nil { + return health.HealthCheckType + } + + return "" +} + +func (self *SElbListener) GetHealthCheckTimeout() int { + lbbg, err := self.GetBackendGroup() + if err != nil { + log.Errorf("ElbListener GetHealthCheckTimeout %s", err.Error()) + } + + if lbbg == nil { + return 0 + } + + health, err := lbbg.GetHealthCheck() + if err != nil { + log.Errorf("ElbListener GetHealthCheckTimeout %s", err.Error()) + } + + if health != nil { + return health.HealthCheckTimeout + } + + return 0 +} + +func (self *SElbListener) GetHealthCheckInterval() int { + lbbg, err := self.GetBackendGroup() + if err != nil { + log.Errorf("ElbListener GetHealthCheckInterval %s", err.Error()) + } + + if lbbg == nil { + return 0 + } + + health, err := lbbg.GetHealthCheck() + if err != nil { + log.Errorf("ElbListener GetHealthCheckInterval %s", err.Error()) + } + + if health != nil { + return health.HealthCheckInterval + } + + return 0 +} + +func (self *SElbListener) GetHealthCheckRise() int { + lbbg, err := self.GetBackendGroup() + if err != nil { + log.Errorf("ElbListener GetHealthCheckRise %s", err.Error()) + } + + if lbbg == nil { + return 0 + } + + health, err := lbbg.GetHealthCheck() + if err != nil { + log.Errorf("ElbListener GetHealthCheckRise %s", err.Error()) + } + + if health != nil { + return health.HealthCheckRise + } else { + return 0 + } +} + +func (self *SElbListener) GetHealthCheckFail() int { + return 0 +} + +func (self *SElbListener) GetHealthCheckReq() string { + return "" +} + +func (self *SElbListener) GetHealthCheckExp() string { + return "" +} + +func (self *SElbListener) GetBackendGroupId() string { + return self.DefaultPoolID +} + +func (self *SElbListener) GetBackendServerPort() int { + return 0 +} + +func (self *SElbListener) GetHealthCheckDomain() string { + lbbg, err := self.GetBackendGroup() + if err != nil { + log.Errorf("ElbListener GetHealthCheckDomain %s", err.Error()) + } + + if lbbg == nil { + return "" + } + + health, err := lbbg.GetHealthCheck() + if err != nil { + log.Errorf("ElbListener GetHealthCheckDomain %s", err.Error()) + } + + if health != nil { + return health.HealthCheckDomain + } + + return "" +} + +func (self *SElbListener) GetHealthCheckURI() string { + lbbg, err := self.GetBackendGroup() + if err != nil { + log.Errorf("ElbListener GetHealthCheckURI %s", err.Error()) + } + + if lbbg == nil { + return "" + } + + health, err := lbbg.GetHealthCheck() + if err != nil { + log.Errorf("ElbListener GetHealthCheckURI %s", err.Error()) + } + + if health != nil { + return health.HealthCheckURI + } + + return "" +} + +func (self *SElbListener) GetHealthCheckCode() string { + return "" +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0136295317.html +func (self *SElbListener) CreateILoadBalancerListenerRule(rule *cloudprovider.SLoadbalancerListenerRule) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + l7policy, err := self.lb.region.CreateLoadBalancerPolicy(self.GetId(), rule) + if err != nil { + return nil, err + } + + l7policy.region = self.lb.region + l7policy.lb = self.lb + l7policy.listener = self + return &l7policy, nil +} + +func (self *SElbListener) GetILoadBalancerListenerRuleById(ruleId string) (cloudprovider.ICloudLoadbalancerListenerRule, error) { + ret := &SElbListenerPolicy{} + err := DoGet(self.lb.region.ecsClient.ElbL7policies.Get, ruleId, nil, ret) + if err != nil { + return nil, err + } + + ret.region = self.lb.region + ret.lb = self.lb + ret.listener = self + return ret, nil +} + +func (self *SElbListener) GetILoadbalancerListenerRules() ([]cloudprovider.ICloudLoadbalancerListenerRule, error) { + ret, err := self.lb.region.GetLoadBalancerPolicies(self.GetId()) + if err != nil { + return nil, err + } + + iret := []cloudprovider.ICloudLoadbalancerListenerRule{} + for i := range ret { + rule := ret[i] + rule.listener = self + rule.lb = self.lb + iret = append(iret, &rule) + } + + return iret, nil +} + +func (self *SElbListener) GetStickySession() string { + lbbg, err := self.GetBackendGroup() + if err != nil { + log.Errorf("ElbListener GetStickySession %s", err.Error()) + } + + if lbbg == nil { + return "" + } + + stickySession, err := lbbg.GetStickySession() + if err != nil { + log.Errorf("ElbListener GetStickySession %s", err.Error()) + } + + if stickySession != nil { + return stickySession.StickySession + } + + return "" +} + +func (self *SElbListener) GetStickySessionType() string { + lbbg, err := self.GetBackendGroup() + if err != nil { + log.Errorf("ElbListener GetStickySessionType %s", err.Error()) + } + + if lbbg == nil { + return "" + } + + stickySession, err := lbbg.GetStickySession() + if err != nil { + log.Errorf("ElbListener GetStickySessionType %s", err.Error()) + } + + if stickySession != nil { + return stickySession.StickySessionType + } + + return "" +} + +func (self *SElbListener) GetStickySessionCookie() string { + lbbg, err := self.GetBackendGroup() + if err != nil { + log.Errorf("ElbListener GetStickySessionCookie %s", err.Error()) + } + + if lbbg == nil { + return "" + } + + stickySession, err := lbbg.GetStickySession() + if err != nil { + log.Errorf("ElbListener GetStickySessionCookie %s", err.Error()) + } + + if stickySession != nil { + return stickySession.StickySessionCookie + } + + return "" +} + +func (self *SElbListener) GetStickySessionCookieTimeout() int { + lbbg, err := self.GetBackendGroup() + if err != nil { + log.Errorf("ElbListener GetStickySessionCookieTimeout %s", err.Error()) + } + + if lbbg == nil { + return 0 + } + + stickySession, err := lbbg.GetStickySession() + if err != nil { + log.Errorf("ElbListener GetStickySessionCookieTimeout %s", err.Error()) + } + + if stickySession != nil { + return stickySession.StickySessionCookieTimeout + } + + return 0 +} + +func (self *SElbListener) XForwardedForEnabled() bool { + return self.InsertHeaders.XForwardedELBIP +} + +func (self *SElbListener) GzipEnabled() bool { + return false +} + +func (self *SElbListener) GetCertificateId() string { + return self.DefaultTlsContainerRef +} + +func (self *SElbListener) GetTLSCipherPolicy() string { + return "" +} + +func (self *SElbListener) HTTP2Enabled() bool { + return self.Http2Enable +} + +func (self *SElbListener) Start() error { + return nil +} + +func (self *SElbListener) Stop() error { + return cloudprovider.ErrNotSupported +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561544.html +/* +default_pool_id有如下限制: +不能更新为其他监听器的default_pool。 +不能更新为其他监听器的关联的转发策略所使用的pool。 +default_pool_id对应的后端云服务器组的protocol和监听器的protocol有如下关系: +监听器的protocol为TCP时,后端云服务器组的protocol必须为TCP。 +监听器的protocol为UDP时,后端云服务器组的protocol必须为UDP。 +监听器的protocol为HTTP或TERMINATED_HTTPS时,后端云服务器组的protocol必须为HTTP。 +*/ +func (self *SElbListener) Sync(ctx context.Context, listener *cloudprovider.SLoadbalancerListener) error { + return self.lb.region.UpdateLoadBalancerListener(self.GetId(), listener) +} + +func (self *SElbListener) Delete(ctx context.Context) error { + err := DoDelete(self.lb.region.ecsClient.ElbListeners.Delete, self.GetId(), nil, nil) + if err != nil { + return err + } + + return nil +} + +func (self *SRegion) UpdateLoadBalancerListener(listenerId string, listener *cloudprovider.SLoadbalancerListener) error { + params := jsonutils.NewDict() + listenerObj := jsonutils.NewDict() + listenerObj.Set("name", jsonutils.NewString(listener.Name)) + listenerObj.Set("description", jsonutils.NewString(listener.Description)) + listenerObj.Set("http2_enable", jsonutils.NewBool(listener.EnableHTTP2)) + if len(listener.BackendGroupID) > 0 { + listenerObj.Set("default_pool_id", jsonutils.NewString(listener.BackendGroupID)) + } else { + listenerObj.Set("default_pool_id", jsonutils.JSONNull) + } + + if listener.ListenerType == api.LB_LISTENER_TYPE_HTTPS { + listenerObj.Set("default_tls_container_ref", jsonutils.NewString(listener.CertificateID)) + } + + if listener.XForwardedFor { + insertObj := jsonutils.NewDict() + insertObj.Set("X-Forwarded-ELB-IP", jsonutils.NewBool(listener.XForwardedFor)) + listenerObj.Set("insert_headers", insertObj) + } + + params.Set("listener", listenerObj) + err := DoUpdate(self.ecsClient.ElbListeners.Update, listenerId, params, nil) + if err != nil { + return err + } + return nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0136295315.html +func (self *SRegion) GetLoadBalancerPolicies(listenerId string) ([]SElbListenerPolicy, error) { + params := map[string]string{} + if len(listenerId) > 0 { + params["listener_id"] = listenerId + } + + ret := []SElbListenerPolicy{} + err := doListAll(self.ecsClient.ElbL7policies.List, params, &ret) + if err != nil { + return nil, err + } + + for i := range ret { + ret[i].region = self + } + + return ret, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0116649234.html +func (self *SRegion) GetLoadBalancerPolicyRules(policyId string) ([]SElbListenerPolicyRule, error) { + m := self.ecsClient.ElbPolicies + err := m.SetL7policyId(policyId) + if err != nil { + return nil, err + } + + ret := []SElbListenerPolicyRule{} + err = doListAll(m.List, nil, &ret) + if err != nil { + return nil, err + } + + for i := range ret { + ret[i].region = self + } + + return ret, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0136295317.html +func (self *SRegion) CreateLoadBalancerPolicy(listenerID string, rule *cloudprovider.SLoadbalancerListenerRule) ( + SElbListenerPolicy, error) { + l7policy := SElbListenerPolicy{} + params := jsonutils.NewDict() + policyObj := jsonutils.NewDict() + policyObj.Set("name", jsonutils.NewString(rule.Name)) + policyObj.Set("listener_id", jsonutils.NewString(listenerID)) + // todo: REDIRECT_TO_LISTENER? + policyObj.Set("action", jsonutils.NewString("REDIRECT_TO_POOL")) + policyObj.Set("redirect_pool_id", jsonutils.NewString(rule.BackendGroupID)) + params.Set("l7policy", policyObj) + + err := DoCreate(self.ecsClient.ElbL7policies.Create, params, &l7policy) + if err != nil { + return l7policy, err + } + + m := self.ecsClient.ElbPolicies + m.SetL7policyId(l7policy.GetId()) + if len(rule.Domain) > 0 { + p := jsonutils.NewDict() + p.Set("type", jsonutils.NewString("HOST_NAME")) + p.Set("value", jsonutils.NewString(rule.Domain)) + // todo: support more compare_type + p.Set("compare_type", jsonutils.NewString("EQUAL_TO")) + rule := jsonutils.NewDict() + rule.Set("rule", p) + err := DoCreate(m.Create, rule, nil) + if err != nil { + return l7policy, err + } + } + + if len(rule.Path) > 0 { + p := jsonutils.NewDict() + p.Set("type", jsonutils.NewString("PATH")) + p.Set("value", jsonutils.NewString(rule.Path)) + p.Set("compare_type", jsonutils.NewString("EQUAL_TO")) + rule := jsonutils.NewDict() + rule.Set("rule", p) + err := DoCreate(m.Create, rule, nil) + if err != nil { + return l7policy, err + } + } + + return l7policy, nil +} + +func (self *SElbListener) GetClientIdleTimeout() int { + return 0 +} + +func (self *SElbListener) GetBackendConnectTimeout() int { + return 0 +} diff --git a/pkg/multicloud/huaweistack/loadbalancer_listener_rule.go b/pkg/multicloud/huaweistack/loadbalancer_listener_rule.go new file mode 100644 index 0000000000..27db45a133 --- /dev/null +++ b/pkg/multicloud/huaweistack/loadbalancer_listener_rule.go @@ -0,0 +1,172 @@ +// 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 huaweistack + +import ( + "context" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SElbListenerPolicy struct { + multicloud.SResourceBase + multicloud.SLoadbalancerRedirectBase + multicloud.HuaweiTags + region *SRegion + lb *SLoadbalancer + listener *SElbListener + + RedirectPoolID string `json:"redirect_pool_id"` + RedirectListenerID *string `json:"redirect_listener_id"` + Description string `json:"description"` + AdminStateUp bool `json:"admin_state_up"` + Rules []Rule `json:"rules"` + TenantID string `json:"tenant_id"` + ProjectID string `json:"project_id"` + ListenerID string `json:"listener_id"` + RedirectURL *string `json:"redirect_url"` + ProvisioningStatus string `json:"provisioning_status"` + Action string `json:"action"` + Position int64 `json:"position"` + ID string `json:"id"` + Name string `json:"name"` +} + +type Rule struct { + ID string `json:"id"` +} + +type SElbListenerPolicyRule struct { + region *SRegion + policy *SElbListenerPolicy + + CompareType string `json:"compare_type"` + ProvisioningStatus string `json:"provisioning_status"` + AdminStateUp bool `json:"admin_state_up"` + TenantID string `json:"tenant_id"` + ProjectID string `json:"project_id"` + Invert bool `json:"invert"` + Value string `json:"value"` + Key interface{} `json:"key"` + Type string `json:"type"` + ID string `json:"id"` +} + +func (self *SElbListenerPolicy) GetId() string { + return self.ID +} + +func (self *SElbListenerPolicy) GetName() string { + return self.Name +} + +func (self *SElbListenerPolicy) GetGlobalId() string { + return self.GetId() +} + +// 负载均衡没有启用禁用操作 +func (self *SElbListenerPolicy) GetStatus() string { + return api.LB_STATUS_ENABLED +} + +func (self *SElbListenerPolicy) Refresh() error { + ret := &SElbListenerPolicy{} + err := DoGet(self.lb.region.ecsClient.ElbL7policies.Get, self.GetId(), nil, ret) + if err != nil { + return err + } + + err = jsonutils.Update(self, ret) + if err != nil { + return err + } + + return nil +} + +func (self *SElbListenerPolicy) IsDefault() bool { + return false +} + +func (self *SElbListenerPolicy) IsEmulated() bool { + return false +} + +func (self *SElbListenerPolicy) GetProjectId() string { + return "" +} + +func (self *SElbListenerPolicy) GetRules() ([]SElbListenerPolicyRule, error) { + ret, err := self.region.GetLoadBalancerPolicyRules(self.GetId()) + if err != nil { + return nil, err + } + + for i := range ret { + ret[i].policy = self + } + + return ret, nil +} + +func (self *SElbListenerPolicy) GetDomain() string { + rules, err := self.GetRules() + if err != nil { + log.Errorf("loadbalancer rule GetDomain %s", err) + } + + for i := range rules { + if rules[i].Type == "HOST_NAME" { + return rules[i].Value + } + } + + return "" +} + +func (self *SElbListenerPolicy) GetCondition() string { + return "" +} + +func (self *SElbListenerPolicy) GetPath() string { + rules, err := self.GetRules() + if err != nil { + log.Errorf("loadbalancer rule GetPath %s", err) + } + + for i := range rules { + if rules[i].Type == "PATH" { + return rules[i].Value + } + } + + return "" +} + +func (self *SElbListenerPolicy) GetBackendGroupId() string { + return self.RedirectPoolID +} + +func (self *SElbListenerPolicy) Delete(ctx context.Context) error { + return self.region.DeleteLoadBalancerPolicy(self.GetId()) +} + +func (self *SRegion) DeleteLoadBalancerPolicy(policyId string) error { + return DoDelete(self.ecsClient.ElbL7policies.Delete, policyId, nil, nil) +} diff --git a/pkg/multicloud/huaweistack/monitor.go b/pkg/multicloud/huaweistack/monitor.go new file mode 100644 index 0000000000..da138a4a6f --- /dev/null +++ b/pkg/multicloud/huaweistack/monitor.go @@ -0,0 +1,29 @@ +// 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 huaweistack + +import ( + "time" + + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/modules" +) + +func (r *SRegion) GetMetrics() ([]modules.SMetricMeta, error) { + return r.ecsClient.CloudEye.ListMetrics() +} + +func (r *SRegion) GetMetricsData(metrics []modules.SMetricMeta, since time.Time, until time.Time) ([]modules.SMetricData, error) { + return r.ecsClient.CloudEye.GetMetricsData(metrics, since, until) +} diff --git a/pkg/multicloud/huaweistack/natdtable.go b/pkg/multicloud/huaweistack/natdtable.go new file mode 100644 index 0000000000..20faa3e13d --- /dev/null +++ b/pkg/multicloud/huaweistack/natdtable.go @@ -0,0 +1,131 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huaweistack + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SNatDEntry struct { + multicloud.SResourceBase + multicloud.HuaweiTags + gateway *SNatGateway + + ID string `json:"id"` + NatGatewayID string `json:"nat_gateway_id"` + Protocol string `json:"protocol"` + Status string `json:"status"` + ExternalIP string `json:"floating_ip_address"` + ExternalPort int `json:"external_service_port"` + InternalIP string `json:"private_ip"` + InternalPort int `json:"internal_service_port"` + PortID string `json:"port_id"` + AdminStateUp bool `json:"admin_state_up"` +} + +func (nat *SNatDEntry) GetId() string { + return nat.ID +} + +func (nat *SNatDEntry) GetName() string { + // No name so return id + return nat.GetId() +} + +func (nat *SNatDEntry) GetGlobalId() string { + return nat.GetId() +} + +func (nat *SNatDEntry) GetStatus() string { + return NatResouceStatusTransfer(nat.Status) +} + +func (nat *SNatDEntry) GetIpProtocol() string { + return nat.Protocol +} + +func (nat *SNatDEntry) GetExternalIp() string { + return nat.ExternalIP +} + +func (nat *SNatDEntry) GetExternalPort() int { + return nat.ExternalPort +} + +func (nat *SNatDEntry) GetInternalIp() string { + return nat.InternalIP +} + +func (nat *SNatDEntry) GetInternalPort() int { + return nat.InternalPort +} + +func (nat *SNatDEntry) Delete() error { + return nat.gateway.region.DeleteNatDEntry(nat.GetId()) +} + +// getNatSTable return all snat rules of gateway +func (gateway *SNatGateway) getNatDTable() ([]SNatDEntry, error) { + ret, err := gateway.region.GetNatDTable(gateway.GetId()) + if err != nil { + return nil, err + } + for i := range ret { + ret[i].gateway = gateway + } + return ret, nil +} + +func (region *SRegion) GetNatDTable(natGatewayID string) ([]SNatDEntry, error) { + queuies := map[string]string{ + "nat_gateway_id": natGatewayID, + } + dNatSTableEntries := make([]SNatDEntry, 0, 2) + // can't make true that restapi support marker para in Huawei Cloud + err := doListAllWithMarker(region.ecsClient.DNatRules.List, queuies, &dNatSTableEntries) + if err != nil { + return nil, errors.Wrapf(err, `get dnat rule of gateway %q`, natGatewayID) + } + for i := range dNatSTableEntries { + nat := &dNatSTableEntries[i] + if len(nat.InternalIP) == 0 { + port, err := region.GetPort(nat.PortID) + if err != nil { + return nil, errors.Wrapf(err, `get port info for transfer to ip of port_id %q error`, nat.PortID) + } + nat.InternalIP = port.FixedIps[0].IpAddress + } + } + return dNatSTableEntries, nil +} + +func (region *SRegion) DeleteNatDEntry(entryID string) error { + _, err := region.ecsClient.DNatRules.Delete(entryID, nil) + if err != nil { + return errors.Wrapf(err, `delete dnat rule %q failed`, entryID) + } + return nil +} + +func (nat *SNatDEntry) Refresh() error { + new, err := nat.gateway.region.GetNatDEntryByID(nat.ID) + if err != nil { + return err + } + return jsonutils.Update(nat, new) +} diff --git a/pkg/multicloud/huaweistack/natgateway.go b/pkg/multicloud/huaweistack/natgateway.go new file mode 100644 index 0000000000..872a7b82f8 --- /dev/null +++ b/pkg/multicloud/huaweistack/natgateway.go @@ -0,0 +1,353 @@ +// 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 huaweistack + +import ( + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SNatGateway struct { + multicloud.SNatGatewayBase + multicloud.HuaweiTags + region *SRegion + + ID string + Name string + Description string + Spec string + Status string + InternalNetworkId string + CreatedTime string `json:"created_at"` +} + +func (gateway *SNatGateway) GetId() string { + return gateway.ID +} + +func (gateway *SNatGateway) GetName() string { + return gateway.Name +} + +func (gateway *SNatGateway) GetGlobalId() string { + return gateway.GetId() +} + +func (gateway *SNatGateway) GetStatus() string { + return NatResouceStatusTransfer(gateway.Status) +} + +func (self *SNatGateway) Delete() error { + return self.region.DeleteNatGateway(self.ID) +} + +func (self *SNatGateway) Refresh() error { + nat, err := self.region.GetNatGateway(self.ID) + if err != nil { + return errors.Wrapf(err, "GetNatGateway(%s)", self.ID) + } + return jsonutils.Update(self, nat) +} + +func (self *SNatGateway) GetINetworkId() string { + return self.InternalNetworkId +} + +func (gateway *SNatGateway) GetNatSpec() string { + switch gateway.Spec { + case "1": + return api.NAT_SPEC_SMALL + case "2": + return api.NAT_SPEC_MIDDLE + case "3": + return api.NAT_SPEC_LARGE + case "4": + return api.NAT_SPEC_XLARGE + } + return gateway.Spec +} + +func (gateway *SNatGateway) GetDescription() string { + return gateway.Description +} + +func (gateway *SNatGateway) GetBillingType() string { + // Up to 2019.07.17, only support post pay + return billing_api.BILLING_TYPE_POSTPAID +} + +func (gateway *SNatGateway) GetCreatedAt() time.Time { + t, _ := time.Parse("2006-01-02 15:04:05.000000", gateway.CreatedTime) + return t +} + +func (gateway *SNatGateway) GetExpiredAt() time.Time { + // no support for expired time + return time.Time{} +} + +func (gateway *SNatGateway) GetIEips() ([]cloudprovider.ICloudEIP, error) { + IEips, err := gateway.region.GetIEips() + if err != nil { + return nil, errors.Wrapf(err, `get all Eips of region %q error`, gateway.region.GetId()) + } + dNatTables, err := gateway.GetINatDTable() + if err != nil { + return nil, errors.Wrapf(err, `get all DNatTable of gateway %q error`, gateway.GetId()) + } + sNatTables, err := gateway.GetINatSTable() + if err != nil { + return nil, errors.Wrapf(err, `get all SNatTable of gateway %q error`, gateway.GetId()) + } + + // Get natIPSet of nat rules + natIPSet := make(map[string]struct{}) + for _, snat := range sNatTables { + natIPSet[snat.GetIP()] = struct{}{} + } + for _, dnat := range dNatTables { + natIPSet[dnat.GetExternalIp()] = struct{}{} + } + + // Add Eip whose GetIpAddr() in natIPSet to ret + ret := make([]cloudprovider.ICloudEIP, 0, 2) + for i := range IEips { + if _, ok := natIPSet[IEips[i].GetIpAddr()]; ok { + ret = append(ret, IEips[i]) + } + } + return ret, nil +} + +func (gateway *SNatGateway) GetINatDTable() ([]cloudprovider.ICloudNatDEntry, error) { + dNatTable, err := gateway.getNatDTable() + if err != nil { + return nil, errors.Wrapf(err, `get dnat table of nat gateway %q`, gateway.GetId()) + } + ret := make([]cloudprovider.ICloudNatDEntry, len(dNatTable)) + for i := range dNatTable { + ret[i] = &dNatTable[i] + } + return ret, nil +} + +func (gateway *SNatGateway) GetINatSTable() ([]cloudprovider.ICloudNatSEntry, error) { + sNatTable, err := gateway.getNatSTable() + if err != nil { + return nil, errors.Wrapf(err, `get dnat table of nat gateway %q`, gateway.GetId()) + } + ret := make([]cloudprovider.ICloudNatSEntry, len(sNatTable)) + for i := range sNatTable { + ret[i] = &sNatTable[i] + } + return ret, nil +} + +func (gateway *SNatGateway) CreateINatDEntry(rule cloudprovider.SNatDRule) (cloudprovider.ICloudNatDEntry, error) { + dnat, err := gateway.region.CreateNatDEntry(rule, gateway.GetId()) + if err != nil { + return nil, err + } + dnat.gateway = gateway + return &dnat, nil +} + +func (gateway *SNatGateway) CreateINatSEntry(rule cloudprovider.SNatSRule) (cloudprovider.ICloudNatSEntry, error) { + snat, err := gateway.region.CreateNatSEntry(rule, gateway.GetId()) + if err != nil { + return nil, err + } + snat.gateway = gateway + return &snat, nil +} + +func (gateway *SNatGateway) GetINatDEntryByID(id string) (cloudprovider.ICloudNatDEntry, error) { + dnat, err := gateway.region.GetNatDEntryByID(id) + if err != nil { + return nil, err + } + dnat.gateway = gateway + return &dnat, nil +} + +func (gateway *SNatGateway) GetINatSEntryByID(id string) (cloudprovider.ICloudNatSEntry, error) { + snat, err := gateway.region.GetNatSEntryByID(id) + if err != nil { + return nil, err + } + snat.gateway = gateway + return &snat, nil +} + +func (region *SRegion) GetNatGateways(vpcID, natGatewayID string) ([]SNatGateway, error) { + queues := make(map[string]string) + if len(natGatewayID) != 0 { + queues["id"] = natGatewayID + } + if len(vpcID) != 0 { + queues["router_id"] = vpcID + } + natGateways := make([]SNatGateway, 0, 2) + err := doListAllWithMarker(region.ecsClient.NatGateways.List, queues, &natGateways) + if err != nil { + return nil, errors.Wrapf(err, "get nat gateways error by natgatewayid") + } + for i := range natGateways { + natGateways[i].region = region + } + return natGateways, nil +} + +func (region *SRegion) CreateNatDEntry(rule cloudprovider.SNatDRule, gatewayID string) (SNatDEntry, error) { + params := make(map[string]interface{}) + params["nat_gateway_id"] = gatewayID + params["private_ip"] = rule.InternalIP + params["internal_service_port"] = rule.InternalPort + params["floating_ip_id"] = rule.ExternalIPID + params["external_service_port"] = rule.ExternalPort + params["protocol"] = rule.Protocol + + packParams := map[string]map[string]interface{}{ + "dnat_rule": params, + } + + ret := SNatDEntry{} + err := DoCreate(region.ecsClient.DNatRules.Create, jsonutils.Marshal(packParams), &ret) + if err != nil { + return SNatDEntry{}, errors.Wrapf(err, `create dnat rule of nat gateway %q failed`, gatewayID) + } + return ret, nil +} + +func (region *SRegion) CreateNatSEntry(rule cloudprovider.SNatSRule, gatewayID string) (SNatSEntry, error) { + params := make(map[string]interface{}) + params["nat_gateway_id"] = gatewayID + if len(rule.NetworkID) != 0 { + params["network_id"] = rule.NetworkID + } + if len(rule.SourceCIDR) != 0 { + params["cidr"] = rule.SourceCIDR + } + params["floating_ip_id"] = rule.ExternalIPID + + packParams := map[string]map[string]interface{}{ + "snat_rule": params, + } + + ret := SNatSEntry{} + err := DoCreate(region.ecsClient.SNatRules.Create, jsonutils.Marshal(packParams), &ret) + if err != nil { + return SNatSEntry{}, errors.Wrapf(err, `create snat rule of nat gateway %q failed`, gatewayID) + } + return ret, nil +} + +func (region *SRegion) GetNatDEntryByID(id string) (SNatDEntry, error) { + dnat := SNatDEntry{} + err := DoGet(region.ecsClient.DNatRules.Get, id, map[string]string{}, &dnat) + + if err != nil { + return SNatDEntry{}, err + } + return dnat, nil +} + +func (region *SRegion) GetNatSEntryByID(id string) (SNatSEntry, error) { + snat := SNatSEntry{} + err := DoGet(region.ecsClient.SNatRules.Get, id, map[string]string{}, &snat) + if err != nil { + return SNatSEntry{}, cloudprovider.ErrNotFound + } + return snat, nil +} + +func NatResouceStatusTransfer(status string) string { + // In Huawei Cloud, there are isx resource status of Nat, "ACTIVE", "PENDING_CREATE", + // "PENDING_UPDATE", "PENDING_DELETE", "EIP_FREEZED", "INACTIVE". + switch status { + case "ACTIVE": + return api.NAT_STAUTS_AVAILABLE + case "PENDING_CREATE": + return api.NAT_STATUS_ALLOCATE + case "PENDING_UPDATE", "PENDING_DELETE": + return api.NAT_STATUS_DEPLOYING + default: + return api.NAT_STATUS_UNKNOWN + } +} + +func (self *SRegion) GetNatGateway(id string) (*SNatGateway, error) { + resp, err := self.ecsClient.NatGateways.Get(id, nil) + if err != nil { + return nil, errors.Wrapf(err, "NatGateways.Get(%s)", id) + } + nat := &SNatGateway{region: self} + err = resp.Unmarshal(nat) + return nat, errors.Wrap(err, "resp.Unmarshal") +} + +func (self *SVpc) CreateINatGateway(opts *cloudprovider.NatGatewayCreateOptions) (cloudprovider.ICloudNatGateway, error) { + nat, err := self.region.CreateNatGateway(opts) + if err != nil { + return nil, errors.Wrapf(err, "CreateNatGateway") + } + return nat, nil +} + +func (self *SRegion) CreateNatGateway(opts *cloudprovider.NatGatewayCreateOptions) (*SNatGateway, error) { + spec := "" + switch strings.ToLower(opts.NatSpec) { + case api.NAT_SPEC_SMALL: + spec = "1" + case api.NAT_SPEC_MIDDLE: + spec = "2" + case api.NAT_SPEC_LARGE: + spec = "3" + case api.NAT_SPEC_XLARGE: + spec = "4" + } + params := jsonutils.Marshal(map[string]map[string]interface{}{ + "nat_gateway": map[string]interface{}{ + "name": opts.Name, + "description": opts.Desc, + "router_id": opts.VpcId, + "internal_network_id": opts.NetworkId, + "spec": spec, + }, + }) + resp, err := self.ecsClient.NatGateways.Create(params) + if err != nil { + return nil, errors.Wrap(err, "AsyncCreate") + } + nat := &SNatGateway{region: self} + err = resp.Unmarshal(nat) + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + return nat, nil +} + +func (self *SRegion) DeleteNatGateway(id string) error { + _, err := self.ecsClient.NatGateways.Delete(id, nil) + return errors.Wrapf(err, "NatGateways.Delete(%s)", id) +} diff --git a/pkg/multicloud/huaweistack/natstable.go b/pkg/multicloud/huaweistack/natstable.go new file mode 100644 index 0000000000..83a7a3b4fe --- /dev/null +++ b/pkg/multicloud/huaweistack/natstable.go @@ -0,0 +1,121 @@ +// 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 huaweistack + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SNatSEntry struct { + multicloud.SResourceBase + multicloud.HuaweiTags + gateway *SNatGateway + + ID string `json:"id"` + NatGatewayID string `json:"nat_gateway_id"` + NetworkID string `json:"network_id"` + SourceCIDR string `json:"cidr"` + Status string `json:"status"` + SNatIP string `json:"floating_ip_address"` + AdminStateUp bool `json:"admin_state_up"` +} + +func (nat *SNatSEntry) GetId() string { + return nat.ID +} + +func (nat *SNatSEntry) GetName() string { + // Snat rule has no name in Huawei Cloud, so return ID + return nat.GetId() +} + +func (nat *SNatSEntry) GetGlobalId() string { + return nat.GetId() +} + +func (nat *SNatSEntry) GetStatus() string { + return NatResouceStatusTransfer(nat.Status) +} + +func (nat *SNatSEntry) GetIP() string { + return nat.SNatIP +} + +func (nat *SNatSEntry) GetSourceCIDR() string { + return nat.SourceCIDR +} + +func (nat *SNatSEntry) GetNetworkId() string { + return nat.NetworkID +} + +func (nat *SNatSEntry) Delete() error { + return nat.gateway.region.DeleteNatSEntry(nat.GetId()) +} + +// getNatSTable return all snat rules of gateway +func (gateway *SNatGateway) getNatSTable() ([]SNatSEntry, error) { + ret, err := gateway.region.GetNatSTable(gateway.GetId()) + if err != nil { + return nil, err + } + for i := range ret { + ret[i].gateway = gateway + } + return ret, nil +} + +func (region *SRegion) GetNatSTable(natGatewayID string) ([]SNatSEntry, error) { + queuies := map[string]string{ + "nat_gateway_id": natGatewayID, + } + sNatSTableEntris := make([]SNatSEntry, 0, 2) + err := doListAllWithMarker(region.ecsClient.SNatRules.List, queuies, &sNatSTableEntris) + if err != nil { + return nil, errors.Wrapf(err, `get snat rule of gateway %q`, natGatewayID) + } + for i := range sNatSTableEntris { + nat := &sNatSTableEntris[i] + if len(nat.SourceCIDR) != 0 { + continue + } + subnet := SNetwork{} + err := DoGet(region.ecsClient.Subnets.Get, nat.NetworkID, map[string]string{}, &subnet) + if err != nil { + return nil, errors.Wrapf(err, `get cidr of subnet %q`, nat.NetworkID) + } + nat.SourceCIDR = subnet.CIDR + } + return sNatSTableEntris, nil +} + +func (region *SRegion) DeleteNatSEntry(entryID string) error { + _, err := region.ecsClient.SNatRules.Delete(entryID, nil) + if err != nil { + return errors.Wrapf(err, `delete snat rule %q failed`, entryID) + } + return nil +} + +func (nat *SNatSEntry) Refresh() error { + new, err := nat.gateway.region.GetNatSEntryByID(nat.ID) + if err != nil { + return err + } + return jsonutils.Update(nat, new) +} diff --git a/pkg/multicloud/huaweistack/network.go b/pkg/multicloud/huaweistack/network.go new file mode 100644 index 0000000000..5415f15f2d --- /dev/null +++ b/pkg/multicloud/huaweistack/network.go @@ -0,0 +1,174 @@ +// 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 huaweistack + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/util/netutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/modules" + "yunion.io/x/onecloud/pkg/util/rbacutils" +) + +/* +Subnets +*/ + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090590.html +type SNetwork struct { + multicloud.SResourceBase + multicloud.HuaweiTags + wire *SWire + + AvailabilityZone string `json:"availability_zone"` + CIDR string `json:"cidr"` + DHCPEnable bool `json:"dhcp_enable"` + DNSList []string `json:"dnsList"` + GatewayIP string `json:"gateway_ip"` + ID string `json:"id"` + Ipv6Enable bool `json:"ipv6_enable"` + Name string `json:"name"` + NeutronNetworkID string `json:"neutron_network_id"` + NeutronSubnetID string `json:"neutron_subnet_id"` + PrimaryDNS string `json:"primary_dns"` + SecondaryDNS string `json:"secondary_dns"` + Status string `json:"status"` + VpcID string `json:"vpc_id"` +} + +func (self *SNetwork) GetId() string { + return self.ID +} + +func (self *SNetwork) GetName() string { + if len(self.Name) == 0 { + return self.ID + } + + return self.Name +} + +func (self *SNetwork) GetGlobalId() string { + return self.ID +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090591.html +func (self *SNetwork) GetStatus() string { + switch self.Status { + case "ACTIVE", "UNKNOWN": + return api.NETWORK_STATUS_AVAILABLE // ? todo: // UNKNOWN + case "ERROR": + return api.NETWORK_STATUS_UNKNOWN + default: + return api.NETWORK_STATUS_UNKNOWN + } +} + +func (self *SNetwork) Refresh() error { + log.Debugf("network refresh %s", self.GetId()) + new, err := self.wire.region.getNetwork(self.GetId()) + if err != nil { + return err + } + return jsonutils.Update(self, new) +} + +func (self *SNetwork) IsEmulated() bool { + return false +} + +func (self *SNetwork) GetIWire() cloudprovider.ICloudWire { + return self.wire +} + +func (self *SNetwork) GetIpStart() string { + pref, _ := netutils.NewIPV4Prefix(self.CIDR) + startIp := pref.Address.NetAddr(pref.MaskLen) // 0 + startIp = startIp.StepUp() // 1 + startIp = startIp.StepUp() // 2 + return startIp.String() +} + +func (self *SNetwork) GetIpEnd() string { + pref, _ := netutils.NewIPV4Prefix(self.CIDR) + endIp := pref.Address.BroadcastAddr(pref.MaskLen) // 255 + endIp = endIp.StepDown() // 254 + endIp = endIp.StepDown() // 253 + endIp = endIp.StepDown() // 252 + return endIp.String() +} + +func (self *SNetwork) GetIpMask() int8 { + pref, _ := netutils.NewIPV4Prefix(self.CIDR) + return pref.MaskLen +} + +func (self *SNetwork) GetGateway() string { + pref, _ := netutils.NewIPV4Prefix(self.CIDR) + startIp := pref.Address.NetAddr(pref.MaskLen) // 0 + startIp = startIp.StepUp() // 1 + return startIp.String() +} + +func (self *SNetwork) GetServerType() string { + return api.NETWORK_TYPE_GUEST +} + +func (self *SNetwork) GetIsPublic() bool { + return true +} + +func (self *SNetwork) GetPublicScope() rbacutils.TRbacScope { + return rbacutils.ScopeDomain +} + +func (self *SNetwork) Delete() error { + return self.wire.region.deleteNetwork(self.VpcID, self.GetId()) +} + +func (self *SNetwork) GetAllocTimeoutSeconds() int { + return 120 // 2 minutes +} + +func (self *SRegion) getNetwork(networkId string) (*SNetwork, error) { + network := SNetwork{} + err := DoGet(self.ecsClient.Subnets.Get, networkId, nil, &network) + return &network, err +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090592.html +func (self *SRegion) GetNetwroks(vpcId string) ([]SNetwork, error) { + querys := map[string]string{} + if len(vpcId) > 0 { + querys["vpc_id"] = vpcId + } + + networks := make([]SNetwork, 0) + err := doListAllWithMarker(self.ecsClient.Subnets.List, querys, &networks) + return networks, err +} + +func (self *SRegion) deleteNetwork(vpcId string, networkId string) error { + ctx := &modules.SManagerContext{InstanceId: vpcId, InstanceManager: self.ecsClient.Vpcs} + return DoDeleteWithSpec(self.ecsClient.Subnets.DeleteInContextWithSpec, ctx, networkId, "", nil, nil) +} + +func (self *SNetwork) GetProjectId() string { + return self.wire.vpc.EnterpriseProjectID +} diff --git a/pkg/multicloud/huaweistack/object.go b/pkg/multicloud/huaweistack/object.go new file mode 100644 index 0000000000..1d0414d65b --- /dev/null +++ b/pkg/multicloud/huaweistack/object.go @@ -0,0 +1,103 @@ +// 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 huaweistack + +import ( + "context" + "net/http" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/huawei/obs" +) + +type SObject struct { + bucket *SBucket + + cloudprovider.SBaseCloudObject +} + +func (o *SObject) GetIBucket() cloudprovider.ICloudBucket { + return o.bucket +} + +func (o *SObject) GetAcl() cloudprovider.TBucketACLType { + acl := cloudprovider.ACLPrivate + obscli, err := o.bucket.region.getOBSClient() + if err != nil { + log.Errorf("o.bucket.region.GetOssClient error %s", err) + return acl + } + input := &obs.GetObjectAclInput{} + input.Bucket = o.bucket.Name + input.Key = o.Key + output, err := obscli.GetObjectAcl(input) + if err != nil { + log.Errorf("GetObjectAcl error: %v", err) + return acl + } + acl = obsAcl2CannedAcl(output.Grants) + return acl +} + +func (o *SObject) SetAcl(aclStr cloudprovider.TBucketACLType) error { + obscli, err := o.bucket.region.getOBSClient() + if err != nil { + return errors.Wrap(err, "o.bucket.region.getOBSClient") + } + input := &obs.SetObjectAclInput{} + input.Bucket = o.bucket.Name + input.Key = o.Key + input.ACL = obs.AclType(string(aclStr)) + _, err = obscli.SetObjectAcl(input) + if err != nil { + return errors.Wrap(err, "obscli.SetObjectAcl") + } + return nil +} + +func (o *SObject) GetMeta() http.Header { + if o.Meta != nil { + return o.Meta + } + obscli, err := o.bucket.region.getOBSClient() + if err != nil { + log.Errorf("getOBSClient fail %s", err) + return nil + } + input := &obs.GetObjectMetadataInput{} + input.Bucket = o.bucket.Name + input.Key = o.Key + output, err := obscli.GetObjectMetadata(input) + if err != nil { + log.Errorf("obscli.GetObjectMetadata fail %s", err) + return nil + } + meta := http.Header{} + for k, v := range output.Metadata { + meta.Add(k, v) + } + if len(output.ContentType) > 0 { + meta.Add(cloudprovider.META_HEADER_CONTENT_TYPE, output.ContentType) + } + o.Meta = meta + return meta +} + +func (o *SObject) SetMeta(ctx context.Context, meta http.Header) error { + return cloudprovider.ObjectSetMeta(ctx, o.bucket, o, meta) +} diff --git a/pkg/multicloud/huaweistack/order.go b/pkg/multicloud/huaweistack/order.go new file mode 100644 index 0000000000..198641903d --- /dev/null +++ b/pkg/multicloud/huaweistack/order.go @@ -0,0 +1,193 @@ +// 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 huaweistack + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/log" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SOrder struct { + ErrorCode *string `json:"error_code"` // 只有失败时才返回此参数 + ErrorMsg *string `json:"error_msg"` //只有失败时才返回此参数 + TotalSize int `json:"totalSize"` // 只有成功时才返回此参数 + Resources []SResource `json:"resources"` +} + +type SResource struct { + ResourceID string `json:"resourceId"` + CloudServiceType string `json:"cloudServiceType"` + RegionCode string `json:"regionCode"` + ResourceType string `json:"resourceType"` + ResourceSpecCode string `json:"resourceSpecCode"` + Status int64 `json:"status"` +} + +type SResourceDetail struct { + ID string `json:"id"` + Status int64 `json:"status"` + ResourceID string `json:"resource_id"` + ResourceName string `json:"resource_name"` + RegionCode string `json:"region_code"` + CloudServiceTypeCode string `json:"cloud_service_type_code"` + ResourceTypeCode string `json:"resource_type_code"` + ResourceSpecCode string `json:"resource_spec_code"` + ProjectCode string `json:"project_code"` + ProductID string `json:"product_id"` + MainResourceID string `json:"main_resource_id"` + IsMainResource int64 `json:"is_main_resource"` + ValidTime time.Time `json:"valid_time"` + ExpireTime time.Time `json:"expire_time"` + NextOperationPolicy string `json:"next_operation_policy"` +} + +func (self *SRegion) getDomianId() (string, error) { + domains, err := self.client.getEnabledDomains() + if err != nil { + return "", err + } + + if domains == nil || len(domains) == 0 { + return "", fmt.Errorf("GetAllResByOrderId domain is empty") + } else if len(domains) > 1 { + // not supported?? + return "", fmt.Errorf("GetAllResByOrderId mutliple domain(%d) found", len(domains)) + } + + return domains[0].ID, nil +} + +/* +获取订单信息 https://support.huaweicloud.com/api-oce/api_order_00001.html +*/ +func (self *SRegion) GetOrder(orderId string) (SOrder, error) { + var order SOrder + domain, err := self.getDomianId() + if err != nil { + return order, err + } + + err = self.ecsClient.Orders.SetDomainId(domain) + if err != nil { + return order, err + } + + err = DoGet(self.ecsClient.Orders.Get, orderId, nil, &order) + return order, err +} + +/* +获取订单资源详情列表 https://support.huaweicloud.com/api-oce/zh-cn_topic_0084961226.html +*/ +func (self *SRegion) GetOrderResources(orderId string, resource_ids []string, only_main_resource bool) ([]SResourceDetail, error) { + domain, err := self.getDomianId() + if err != nil { + return nil, err + } + + err = self.ecsClient.Orders.SetDomainId(domain) + if err != nil { + return nil, err + } + + resources := make([]SResourceDetail, 0) + queries := map[string]string{"customer_id": domain} + if len(orderId) > 0 { + queries["order_id"] = orderId + } + + if len(resource_ids) > 0 { + queries["resource_ids"] = strings.Join(resource_ids, ",") + } + + if only_main_resource { + queries["only_main_resource"] = "1" + } + + err = doListAll(self.ecsClient.Orders.GetPeriodResourceList, queries, &resources) + return resources, err +} + +/* +获取资源详情 https://support.huaweicloud.com/api-oce/zh-cn_topic_0084961226.html +*/ +func (self *SRegion) GetOrderResourceDetail(resourceId string) (SResourceDetail, error) { + var res SResourceDetail + if len(resourceId) == 0 { + return res, fmt.Errorf("GetOrderResourceDetail resource id should not be empty") + } + + resources, err := self.GetOrderResources("", []string{resourceId}, false) + if err != nil { + return res, err + } + + switch len(resources) { + case 0: + return res, cloudprovider.ErrNotFound + case 1: + return resources[0], nil + default: + return res, fmt.Errorf("%d resources with id %s found, Expect 1", len(resources), resourceId) + } +} + +func (self *SRegion) GetAllResByOrderId(orderId string) ([]SResource, error) { + order, err := self.GetOrder(orderId) + if err != nil { + return nil, err + } + + log.Debugf("GetAllResByOrderId %#v", order.Resources) + return order.Resources, nil +} + +func (self *SRegion) getAllResByType(orderId string, resourceType string) ([]SResource, error) { + res, err := self.GetAllResByOrderId(orderId) + if err != nil { + return nil, err + } + + ret := make([]SResource, 0) + for i := range res { + r := res[i] + if r.ResourceType == resourceType { + ret = append(ret, r) + } + } + + return ret, nil +} + +func (self *SRegion) getAllResIdsByType(orderId string, resourceType string) ([]string, error) { + res, err := self.getAllResByType(orderId, resourceType) + if err != nil { + return nil, err + } + + ids := make([]string, 0) + for _, r := range res { + if len(r.ResourceID) > 0 { + ids = append(ids, r.ResourceID) + } + } + + return ids, nil +} diff --git a/pkg/multicloud/huaweistack/port.go b/pkg/multicloud/huaweistack/port.go new file mode 100644 index 0000000000..cb865b0c22 --- /dev/null +++ b/pkg/multicloud/huaweistack/port.go @@ -0,0 +1,161 @@ +// 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 huaweistack + +import ( + "strings" + + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SFixedIP struct { + IpAddress string + SubnetID string + NetworkId string +} + +func (fixip *SFixedIP) GetGlobalId() string { + return fixip.IpAddress +} + +func (fixip *SFixedIP) GetIP() string { + return fixip.IpAddress +} + +func (fixip *SFixedIP) GetINetworkId() string { + return fixip.NetworkId +} + +func (fixip *SFixedIP) IsPrimary() bool { + return true +} + +type Port struct { + multicloud.SNetworkInterfaceBase + multicloud.HuaweiTags + region *SRegion + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + AdminStateUp string `json:"admin_state_up"` + DNSName string `json:"dns_name"` + MACAddress string `json:"mac_address"` + NetworkID string `json:"network_id"` + TenantID string `json:"tenant_id"` + DeviceID string `json:"device_id"` + DeviceOwner string `json:"device_owner"` + BindingVnicType string `json:"binding:vnic_type"` + FixedIps []SFixedIP +} + +func (port *Port) GetName() string { + if len(port.Name) > 0 { + return port.Name + } + return port.ID +} + +func (port *Port) GetId() string { + return port.ID +} + +func (port *Port) GetGlobalId() string { + return port.ID +} + +func (port *Port) GetMacAddress() string { + return port.MACAddress +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0133195888.html +func (port *Port) GetAssociateType() string { + switch port.DeviceOwner { + case "compute:nova": + return api.NETWORK_INTERFACE_ASSOCIATE_TYPE_SERVER + case "network:router_gateway", "network:router_interface", "network:router_interface_distributed": + return api.NETWORK_INTERFACE_ASSOCIATE_TYPE_RESERVED + case "network:dhcp": + return api.NETWORK_INTERFACE_ASSOCIATE_TYPE_DHCP + case "neutron:LOADBALANCERV2": + return api.NETWORK_INTERFACE_ASSOCIATE_TYPE_LOADBALANCER + case "neutron:VIP_PORT": + return api.NETWORK_INTERFACE_ASSOCIATE_TYPE_VIP + default: + if strings.HasPrefix(port.DeviceOwner, "compute:") { + return api.NETWORK_INTERFACE_ASSOCIATE_TYPE_SERVER + } + } + return port.DeviceOwner +} + +func (port *Port) GetAssociateId() string { + return port.DeviceID +} + +func (port *Port) GetStatus() string { + switch port.Status { + case "ACTIVE", "DOWN": + return api.NETWORK_INTERFACE_STATUS_AVAILABLE + case "BUILD": + return api.NETWORK_INTERFACE_STATUS_CREATING + } + return port.Status +} + +func (port *Port) GetICloudInterfaceAddresses() ([]cloudprovider.ICloudInterfaceAddress, error) { + address := []cloudprovider.ICloudInterfaceAddress{} + for i := 0; i < len(port.FixedIps); i++ { + port.FixedIps[i].NetworkId = port.NetworkID + address = append(address, &port.FixedIps[i]) + } + return address, nil +} + +func (region *SRegion) GetINetworkInterfaces() ([]cloudprovider.ICloudNetworkInterface, error) { + ports, err := region.GetPorts("") + if err != nil { + return nil, err + } + ret := []cloudprovider.ICloudNetworkInterface{} + for i := 0; i < len(ports); i++ { + if len(ports[i].DeviceID) == 0 || !utils.IsInStringArray(ports[i].DeviceOwner, []string{"compute:CCI", "compute:nova", "neutron:LOADBALANCERV2"}) { + ports[i].region = region + ret = append(ret, &ports[i]) + } + } + return ret, nil +} + +func (self *SRegion) GetPort(portId string) (Port, error) { + port := Port{} + err := DoGet(self.ecsClient.Port.Get, portId, nil, &port) + return port, err +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0133195888.html +func (self *SRegion) GetPorts(instanceId string) ([]Port, error) { + ports := make([]Port, 0) + querys := map[string]string{} + if len(instanceId) > 0 { + querys["device_id"] = instanceId + } + + err := doListAllWithMarker(self.ecsClient.Port.List, querys, &ports) + return ports, err +} diff --git a/pkg/multicloud/huaweistack/project.go b/pkg/multicloud/huaweistack/project.go new file mode 100644 index 0000000000..411457d705 --- /dev/null +++ b/pkg/multicloud/huaweistack/project.go @@ -0,0 +1,80 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huaweistack + +import ( + "fmt" + "strings" + + api "yunion.io/x/onecloud/pkg/apis/compute" +) + +// https://support.huaweicloud.com/api-iam/zh-cn_topic_0057845625.html +type SProject struct { + client *SHuaweiClient + + IsDomain bool `json:"is_domain"` + Description string `json:"description"` + Enabled bool `json:"enabled"` + ID string `json:"id"` + ParentID string `json:"parent_id"` + DomainID string `json:"domain_id"` + Name string `json:"name"` +} + +func (self *SProject) GetRegionID() string { + return strings.Split(self.Name, "_")[0] +} + +func (self *SProject) GetHealthStatus() string { + if self.Enabled { + return api.CLOUD_PROVIDER_HEALTH_NORMAL + } + + return api.CLOUD_PROVIDER_HEALTH_SUSPENDED +} + +func (self *SHuaweiClient) fetchProjects() ([]SProject, error) { + if self.projects != nil { + return self.projects, nil + } + + huawei, _ := self.newGeneralAPIClient() + projects := make([]SProject, 0) + err := doListAll(huawei.Projects.List, nil, &projects) + if err == nil { + self.projects = projects + } + + return projects, err +} + +func (self *SHuaweiClient) GetProjectById(projectId string) (SProject, error) { + projects, err := self.fetchProjects() + if err != nil { + return SProject{}, err + } + + for _, project := range projects { + if project.ID == projectId { + return project, nil + } + } + return SProject{}, fmt.Errorf("project %s not found", projectId) +} + +func (self *SHuaweiClient) GetProjects() ([]SProject, error) { + return self.fetchProjects() +} diff --git a/pkg/multicloud/huaweistack/provider/doc.go b/pkg/multicloud/huaweistack/provider/doc.go new file mode 100644 index 0000000000..d2c13154ea --- /dev/null +++ b/pkg/multicloud/huaweistack/provider/doc.go @@ -0,0 +1,15 @@ +// 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 provider // import "yunion.io/x/onecloud/pkg/multicloud/huaweistack/provider" diff --git a/pkg/multicloud/huaweistack/provider/provider.go b/pkg/multicloud/huaweistack/provider/provider.go new file mode 100644 index 0000000000..b2af65ea32 --- /dev/null +++ b/pkg/multicloud/huaweistack/provider/provider.go @@ -0,0 +1,324 @@ +// 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 provider + +import ( + "context" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" +) + +type SHuaweiCloudStackProviderFactory struct { + cloudprovider.SPublicCloudBaseProviderFactory +} + +func (self *SHuaweiCloudStackProviderFactory) GetId() string { + return huawei.CLOUD_PROVIDER_HUAWEI +} + +func (self *SHuaweiCloudStackProviderFactory) GetName() string { + return huawei.CLOUD_PROVIDER_HUAWEI_CN +} + +func (self *SHuaweiCloudStackProviderFactory) IsCloudeventRegional() bool { + return true +} + +func (self *SHuaweiCloudStackProviderFactory) GetMaxCloudEventSyncDays() int { + return 7 +} + +func (self *SHuaweiCloudStackProviderFactory) GetMaxCloudEventKeepDays() int { + return 7 +} + +func (self *SHuaweiCloudStackProviderFactory) IsSupportCloudIdService() bool { + return true +} + +func (self *SHuaweiCloudStackProviderFactory) IsSupportClouduserPolicy() bool { + return false +} + +func (self *SHuaweiCloudStackProviderFactory) IsSupportCreateCloudgroup() bool { + return true +} + +func (factory *SHuaweiCloudStackProviderFactory) IsSupportCrossCloudEnvVpcPeering() bool { + return false +} + +func (factory *SHuaweiCloudStackProviderFactory) IsSupportCrossRegionVpcPeering() bool { + return false +} + +func (factory *SHuaweiCloudStackProviderFactory) IsSupportVpcPeeringVpcCidrOverlap() bool { + return true +} + +func (factory *SHuaweiCloudStackProviderFactory) IsSupportModifyRouteTable() bool { + return true +} + +func (factory *SHuaweiCloudStackProviderFactory) IsSupportSAMLAuth() bool { + return true +} + +func (self *SHuaweiCloudStackProviderFactory) ValidateCreateCloudaccountData(ctx context.Context, userCred mcclient.TokenCredential, input cloudprovider.SCloudaccountCredential) (cloudprovider.SCloudaccount, error) { + output := cloudprovider.SCloudaccount{} + if len(input.AccessKeyId) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "access_key_id") + } + if len(input.AccessKeySecret) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "access_key_secret") + } + + if input.SHuaweiCloudStackEndpoints == nil { + return output, errors.Wrap(httperrors.ErrMissingParameter, "cloud_stack_endpoints") + } + + if len(input.SHuaweiCloudStackEndpoints.DefaultRegion) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "default_region") + } + + if len(input.SHuaweiCloudStackEndpoints.EndpointDomain) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "endpoint_domain") + } + + output.Account = input.AccessKeyId + output.Secret = input.AccessKeySecret + return output, nil +} + +func (self *SHuaweiCloudStackProviderFactory) ValidateUpdateCloudaccountCredential(ctx context.Context, userCred mcclient.TokenCredential, input cloudprovider.SCloudaccountCredential, cloudaccount string) (cloudprovider.SCloudaccount, error) { + output := cloudprovider.SCloudaccount{} + if len(input.AccessKeyId) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "access_key_id") + } + if len(input.AccessKeySecret) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "access_key_secret") + } + + if input.SHuaweiCloudStackEndpoints != nil { + if len(input.SHuaweiCloudStackEndpoints.DefaultRegion) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "default_region") + } + + if len(input.SHuaweiCloudStackEndpoints.EndpointDomain) == 0 { + return output, errors.Wrap(httperrors.ErrMissingParameter, "endpoint_domain") + } + } + + output = cloudprovider.SCloudaccount{ + Account: input.AccessKeyId, + Secret: input.AccessKeySecret, + } + return output, nil +} + +func parseAccount(account string) (accessKey string, projectId string) { + segs := strings.Split(account, "/") + if len(segs) == 2 { + accessKey = segs[0] + projectId = segs[1] + } else { + accessKey = account + projectId = "" + } + + return +} + +func (self *SHuaweiCloudStackProviderFactory) GetProvider(cfg cloudprovider.ProviderConfig) (cloudprovider.ICloudProvider, error) { + accessKey, project_id := parseAccount(cfg.Account) + client, err := huawei.NewHuaweiClient( + huawei.NewHuaweiClientConfig( + accessKey, cfg.Secret, project_id, &cfg.SHuaweiCloudStackEndpoints, + ).CloudproviderConfig(cfg), + ) + if err != nil { + return nil, err + } + return &SHuaweiCloudStackProvider{ + SBaseProvider: cloudprovider.NewBaseProvider(self), + client: client, + }, nil +} + +func (self *SHuaweiCloudStackProviderFactory) GetClientRC(info cloudprovider.SProviderInfo) (map[string]string, error) { + accessKey, projectId := parseAccount(info.Account) + region := "" + data := strings.Split(info.Name, "-") + if len(data) >= 3 { + region = strings.Join(data[2:], "-") + } + return map[string]string{ + "HUAWEI_CLOUD_ENV": info.Url, + "HUAWEI_ACCESS_KEY": accessKey, + "HUAWEI_SECRET": info.Secret, + "HUAWEI_REGION": region, + "HUAWEI_PROJECT": projectId, + }, nil +} + +func init() { + factory := SHuaweiCloudStackProviderFactory{} + cloudprovider.RegisterFactory(&factory) +} + +type SHuaweiCloudStackProvider struct { + cloudprovider.SBaseProvider + client *huawei.SHuaweiClient +} + +func (self *SHuaweiCloudStackProvider) GetVersion() string { + return self.client.GetVersion() +} + +func (self *SHuaweiCloudStackProvider) GetSysInfo() (jsonutils.JSONObject, error) { + regions := self.client.GetIRegions() + info := jsonutils.NewDict() + info.Add(jsonutils.NewInt(int64(len(regions))), "region_count") + info.Add(jsonutils.NewString(huawei.HUAWEI_API_VERSION), "api_version") + return info, nil +} + +func (self *SHuaweiCloudStackProvider) GetIRegions() []cloudprovider.ICloudRegion { + return self.client.GetIRegions() +} + +func (self *SHuaweiCloudStackProvider) GetIRegionById(extId string) (cloudprovider.ICloudRegion, error) { + return self.client.GetIRegionById(extId) +} + +func (self *SHuaweiCloudStackProvider) GetBalance() (float64, string, error) { + balance, err := self.client.QueryAccountBalance() + if err != nil { + return 0.0, api.CLOUD_PROVIDER_HEALTH_UNKNOWN, err + } + status := api.CLOUD_PROVIDER_HEALTH_NORMAL + if balance.AvailableAmount < 0.0 && balance.CreditAmount < 0.0 { + status = api.CLOUD_PROVIDER_HEALTH_ARREARS + } + return balance.AvailableAmount, status, nil +} + +func (self *SHuaweiCloudStackProvider) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { + return self.client.GetSubAccounts() +} + +func (self *SHuaweiCloudStackProvider) GetAccountId() string { + return self.client.GetAccountId() +} + +func (self *SHuaweiCloudStackProvider) GetIamLoginUrl() string { + return self.client.GetIamLoginUrl() +} + +func (self *SHuaweiCloudStackProvider) GetCloudRegionExternalIdPrefix() string { + return self.client.GetCloudRegionExternalIdPrefix() +} + +func (self *SHuaweiCloudStackProvider) GetIProjects() ([]cloudprovider.ICloudProject, error) { + return self.client.GetIProjects() +} + +func (self *SHuaweiCloudStackProvider) CreateIProject(name string) (cloudprovider.ICloudProject, error) { + return self.client.CreateIProject(name) +} + +func (self *SHuaweiCloudStackProvider) GetStorageClasses(regionId string) []string { + return []string{ + "STANDARD", "WARM", "COLD", + } +} + +func (self *SHuaweiCloudStackProvider) GetBucketCannedAcls(regionId string) []string { + return []string{ + string(cloudprovider.ACLPrivate), + string(cloudprovider.ACLAuthRead), + string(cloudprovider.ACLPublicRead), + string(cloudprovider.ACLPublicReadWrite), + } +} + +func (self *SHuaweiCloudStackProvider) GetObjectCannedAcls(regionId string) []string { + return []string{ + string(cloudprovider.ACLPrivate), + string(cloudprovider.ACLAuthRead), + string(cloudprovider.ACLPublicRead), + string(cloudprovider.ACLPublicReadWrite), + } +} + +func (self *SHuaweiCloudStackProvider) GetCapabilities() []string { + return self.client.GetCapabilities() +} + +func (self *SHuaweiCloudStackProvider) CreateIClouduser(conf *cloudprovider.SClouduserCreateConfig) (cloudprovider.IClouduser, error) { + return self.client.CreateIClouduser(conf) +} + +func (self *SHuaweiCloudStackProvider) GetICloudusers() ([]cloudprovider.IClouduser, error) { + return self.client.GetICloudusers() +} + +func (self *SHuaweiCloudStackProvider) GetICloudgroups() ([]cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroups() +} + +func (self *SHuaweiCloudStackProvider) GetICloudgroupByName(name string) (cloudprovider.ICloudgroup, error) { + return self.client.GetICloudgroupByName(name) +} + +func (self *SHuaweiCloudStackProvider) CreateICloudgroup(name, desc string) (cloudprovider.ICloudgroup, error) { + return self.client.CreateICloudgroup(name, desc) +} + +func (self *SHuaweiCloudStackProvider) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return self.client.GetISystemCloudpolicies() +} + +func (self *SHuaweiCloudStackProvider) GetICustomCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + return []cloudprovider.ICloudpolicy{}, nil +} + +func (self *SHuaweiCloudStackProvider) GetIClouduserByName(name string) (cloudprovider.IClouduser, error) { + return self.client.GetIClouduserByName(name) +} + +func (self *SHuaweiCloudStackProvider) GetSamlEntityId() string { + return cloudprovider.SAML_ENTITY_ID_HUAWEI_CLOUD +} + +func (self *SHuaweiCloudStackProvider) GetICloudSAMLProviders() ([]cloudprovider.ICloudSAMLProvider, error) { + return self.client.GetICloudSAMLProviders() +} + +func (self *SHuaweiCloudStackProvider) CreateICloudSAMLProvider(opts *cloudprovider.SAMLProviderCreateOptions) (cloudprovider.ICloudSAMLProvider, error) { + sp, err := self.client.CreateSAMLProvider(opts) + if err != nil { + return nil, errors.Wrapf(err, "CreateSAMLProvider") + } + return sp, nil +} diff --git a/pkg/multicloud/huaweistack/quota.go b/pkg/multicloud/huaweistack/quota.go new file mode 100644 index 0000000000..8c6dc76556 --- /dev/null +++ b/pkg/multicloud/huaweistack/quota.go @@ -0,0 +1,80 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huaweistack + +import ( + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SQuota struct { + Min int + Quota int + Type string + Used int +} + +func (q *SQuota) GetGlobalId() string { + return q.Type +} + +func (q *SQuota) GetQuotaType() string { + return q.Type +} + +func (q *SQuota) GetName() string { + return q.Type +} + +func (q *SQuota) GetDesc() string { + return "" +} + +func (q *SQuota) GetMaxQuotaCount() int { + return q.Quota +} + +func (q *SQuota) GetCurrentQuotaUsedCount() int { + return q.Used +} + +func (self *SRegion) GetQuotas() ([]SQuota, error) { + quotas := []SQuota{} + params := map[string]string{} + result, err := self.ecsClient.Quotas.Get("", params) + if err != nil { + return nil, errors.Wrap(err, "Quotas.List") + } + + err = result.Unmarshal("as, "resources") + if err != nil { + return nil, errors.Wrap(err, "result.Unmarshal") + } + + return quotas, nil +} + +func (region *SRegion) GetICloudQuotas() ([]cloudprovider.ICloudQuota, error) { + quotas, err := region.GetQuotas() + if err != nil { + return nil, errors.Wrap(err, "GetQuotas") + } + ret := []cloudprovider.ICloudQuota{} + for i := range quotas { + ret = append(ret, "as[i]) + } + return ret, nil +} diff --git a/pkg/multicloud/huaweistack/region.go b/pkg/multicloud/huaweistack/region.go new file mode 100644 index 0000000000..172b495d86 --- /dev/null +++ b/pkg/multicloud/huaweistack/region.go @@ -0,0 +1,1085 @@ +// 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 huaweistack + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/secrules" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/multicloud/huawei/obs" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client" +) + +type Locales struct { + EnUs string `json:"en-us"` + ZhCN string `json:"zh-cn"` +} + +// https://support.huaweicloud.com/api-iam/zh-cn_topic_0067148043.html +type SRegion struct { + multicloud.SRegion + + client *SHuaweiClient + ecsClient *client.Client + obsClient *obs.ObsClient // 对象存储client.请勿直接引用。 + + Description string `json:"description"` + ID string `json:"id"` + Locales Locales `json:"locales"` + ParentRegionID string `json:"parent_region_id"` + Type string `json:"type"` + + izones []cloudprovider.ICloudZone + ivpcs []cloudprovider.ICloudVpc + + storageCache *SStoragecache +} + +func (self *SRegion) GetILoadBalancerBackendGroups() ([]cloudprovider.ICloudLoadbalancerBackendGroup, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (self *SRegion) GetClient() *SHuaweiClient { + return self.client +} + +func (self *SRegion) getECSClient() (*client.Client, error) { + var err error + + if len(self.client.projectId) > 0 { + project, err := self.client.GetProjectById(self.client.projectId) + if err != nil { + return nil, err + } + + regionId := strings.Split(project.Name, "_")[0] + if regionId != self.ID { + // log.Debugf("project %s not in region %s", self.client.projectId, self.ID) + return nil, errors.Error("region and project mismatch") + } + } + + if self.ecsClient == nil { + self.ecsClient, err = self.client.newRegionAPIClient(self.ID) + if err != nil { + return nil, err + } + } + + return self.ecsClient, err +} + +func (self *SRegion) getOBSEndpoint() string { + return getOBSEndpoint(self.GetId()) +} + +func (self *SRegion) getOBSClient() (*obs.ObsClient, error) { + if self.obsClient == nil { + obsClient, err := self.client.getOBSClient(self.GetId()) + if err != nil { + return nil, err + } + + self.obsClient = obsClient + } + + return self.obsClient, nil +} + +func (self *SRegion) fetchZones() error { + zones := make([]SZone, 0) + err := doListAll(self.ecsClient.Zones.List, nil, &zones) + if err != nil { + return err + } + + self.izones = make([]cloudprovider.ICloudZone, 0) + for i := range zones { + zone := zones[i] + zone.region = self + self.izones = append(self.izones, &zone) + } + return nil +} + +func (self *SRegion) fetchIVpcs() error { + // https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090625.html + vpcs := make([]SVpc, 0) + querys := map[string]string{ + "limit": "2048", + } + err := doListAllWithMarker(self.ecsClient.Vpcs.List, querys, &vpcs) + if err != nil { + return err + } + + self.ivpcs = make([]cloudprovider.ICloudVpc, 0) + for i := range vpcs { + vpc := vpcs[i] + vpc.region = self + self.ivpcs = append(self.ivpcs, &vpc) + } + return nil +} + +func (self *SRegion) GetIVMById(id string) (cloudprovider.ICloudVM, error) { + if len(id) == 0 { + return nil, errors.Wrap(cloudprovider.ErrNotFound, "SRegion.GetIVMById") + } + + instance, err := self.GetInstanceByID(id) + if err != nil { + return nil, err + } + return &instance, err +} + +func (self *SRegion) GetIDiskById(id string) (cloudprovider.ICloudDisk, error) { + return self.GetDisk(id) +} + +func (self *SRegion) GetGeographicInfo() cloudprovider.SGeographicInfo { + if info, ok := LatitudeAndLongitude[self.ID]; ok { + return info + } + return cloudprovider.SGeographicInfo{} +} + +func (self *SRegion) GetILoadBalancers() ([]cloudprovider.ICloudLoadbalancer, error) { + elbs, err := self.GetLoadBalancers() + if err != nil { + return nil, err + } + + ielbs := make([]cloudprovider.ICloudLoadbalancer, len(elbs)) + for i := range elbs { + ielbs[i] = &elbs[i] + } + + return ielbs, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561531.html +func (self *SRegion) GetLoadBalancers() ([]SLoadbalancer, error) { + params := map[string]string{} + if len(self.client.projectId) > 0 { + params["project_id"] = self.client.projectId + } + + ret := []SLoadbalancer{} + err := doListAll(self.ecsClient.Elb.List, params, &ret) + if err != nil { + return nil, err + } + + for i := range ret { + ret[i].region = self + } + + return ret, nil +} + +func (self *SRegion) GetILoadBalancerById(loadbalancerId string) (cloudprovider.ICloudLoadbalancer, error) { + elb, err := self.GetLoadBalancerById(loadbalancerId) + if err != nil { + return nil, err + } + + return &elb, nil +} + +func (self *SRegion) GetLoadBalancerById(loadbalancerId string) (SLoadbalancer, error) { + elb := SLoadbalancer{} + err := DoGet(self.ecsClient.Elb.Get, loadbalancerId, nil, &elb) + if err != nil { + return elb, err + } + + elb.region = self + return elb, nil +} + +func (self *SRegion) GetILoadBalancerAclById(aclId string) (cloudprovider.ICloudLoadbalancerAcl, error) { + acl, err := self.GetLoadBalancerAclById(aclId) + if err != nil { + return nil, err + } + + return &acl, nil +} + +func (self *SRegion) GetLoadBalancerAclById(aclId string) (SElbACL, error) { + acl := SElbACL{} + err := DoGet(self.ecsClient.ElbWhitelist.Get, aclId, nil, &acl) + if err != nil { + return acl, err + } + + acl.region = self + return acl, nil +} + +func (self *SRegion) GetILoadBalancerCertificateById(certId string) (cloudprovider.ICloudLoadbalancerCertificate, error) { + cert, err := self.GetLoadBalancerCertificateById(certId) + if err != nil { + return nil, err + } + + return &cert, nil +} + +func (self *SRegion) GetLoadBalancerCertificateById(certId string) (SElbCert, error) { + ret := SElbCert{} + err := DoGet(self.ecsClient.ElbCertificates.Get, certId, nil, &ret) + if err != nil { + return ret, err + } + + ret.region = self + return ret, nil +} + +func (self *SRegion) CreateILoadBalancerCertificate(cert *cloudprovider.SLoadbalancerCertificate) (cloudprovider.ICloudLoadbalancerCertificate, error) { + ret, err := self.CreateLoadBalancerCertificate(cert) + if err != nil { + return nil, err + } + + return &ret, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561584.html +func (self *SRegion) CreateLoadBalancerCertificate(cert *cloudprovider.SLoadbalancerCertificate) (SElbCert, error) { + params := jsonutils.NewDict() + params.Set("name", jsonutils.NewString(cert.Name)) + params.Set("private_key", jsonutils.NewString(cert.PrivateKey)) + params.Set("certificate", jsonutils.NewString(cert.Certificate)) + + ret := SElbCert{} + err := DoCreate(self.ecsClient.ElbCertificates.Create, params, &ret) + if err != nil { + return ret, err + } + + ret.region = self + return ret, nil +} + +func (self *SRegion) GetILoadBalancerAcls() ([]cloudprovider.ICloudLoadbalancerAcl, error) { + ret, err := self.GetLoadBalancerAcls("") + if err != nil { + return nil, err + } + + iret := make([]cloudprovider.ICloudLoadbalancerAcl, len(ret)) + for i := range ret { + iret[i] = &ret[i] + } + return iret, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561582.html +func (self *SRegion) GetLoadBalancerAcls(listenerId string) ([]SElbACL, error) { + params := map[string]string{} + if len(listenerId) > 0 { + params["listener_id"] = listenerId + } + + ret := []SElbACL{} + err := doListAll(self.ecsClient.ElbWhitelist.List, params, &ret) + if err != nil { + return nil, err + } + + for i := range ret { + ret[i].region = self + } + + return ret, nil +} + +func (self *SRegion) GetILoadBalancerCertificates() ([]cloudprovider.ICloudLoadbalancerCertificate, error) { + ret, err := self.GetLoadBalancerCertificates() + if err != nil { + return nil, err + } + + iret := make([]cloudprovider.ICloudLoadbalancerCertificate, len(ret)) + for i := range ret { + iret[i] = &ret[i] + } + return iret, nil +} + +func (self *SRegion) GetLoadBalancerCertificates() ([]SElbCert, error) { + ret := []SElbCert{} + err := doListAll(self.ecsClient.ElbCertificates.List, nil, &ret) + if err != nil { + return nil, err + } + + for i := range ret { + ret[i].region = self + } + + return ret, nil +} + +// https://support.huaweicloud.com/api-iam/zh-cn_topic_0057845622.html +func (self *SRegion) GetId() string { + return self.ID +} + +func (self *SRegion) GetName() string { + return fmt.Sprintf("%s %s", CLOUD_PROVIDER_HUAWEI_CN, self.Locales.ZhCN) +} + +func (self *SRegion) GetI18n() cloudprovider.SModelI18nTable { + en := fmt.Sprintf("%s %s", CLOUD_PROVIDER_HUAWEI_EN, self.Locales.EnUs) + table := cloudprovider.SModelI18nTable{} + table["name"] = cloudprovider.NewSModelI18nEntry(self.GetName()).CN(self.GetName()).EN(en) + return table +} + +func (self *SRegion) GetGlobalId() string { + return fmt.Sprintf("%s/%s", self.client.GetAccessEnv(), self.ID) +} + +func (self *SRegion) GetStatus() string { + return api.CLOUD_REGION_STATUS_INSERVER +} + +func (self *SRegion) Refresh() error { + return nil +} + +func (self *SRegion) IsEmulated() bool { + return false +} + +func (self *SRegion) GetLatitude() float32 { + if locationInfo, ok := LatitudeAndLongitude[self.ID]; ok { + return locationInfo.Latitude + } + return 0.0 +} + +func (self *SRegion) GetLongitude() float32 { + if locationInfo, ok := LatitudeAndLongitude[self.ID]; ok { + return locationInfo.Longitude + } + return 0.0 +} + +func (self *SRegion) fetchInfrastructure() error { + _, err := self.getECSClient() + if err != nil { + return err + } + + if err := self.fetchZones(); err != nil { + return err + } + + if err := self.fetchIVpcs(); err != nil { + return err + } + + for i := 0; i < len(self.ivpcs); i += 1 { + vpc := self.ivpcs[i].(*SVpc) + wire := SWire{region: self, vpc: vpc} + vpc.addWire(&wire) + + for j := 0; j < len(self.izones); j += 1 { + zone := self.izones[j].(*SZone) + zone.addWire(&wire) + } + } + return nil +} + +func (self *SRegion) GetIZones() ([]cloudprovider.ICloudZone, error) { + if self.izones == nil { + var err error + err = self.fetchInfrastructure() + if err != nil { + return nil, err + } + } + return self.izones, nil +} + +func (self *SRegion) GetIVpcs() ([]cloudprovider.ICloudVpc, error) { + if self.ivpcs == nil { + err := self.fetchInfrastructure() + if err != nil { + return nil, err + } + } + return self.ivpcs, nil +} + +func (self *SRegion) GetEipById(eipId string) (SEipAddress, error) { + var eip SEipAddress + err := DoGet(self.ecsClient.Eips.Get, eipId, nil, &eip) + eip.region = self + return eip, err +} + +// 返回参数分别为eip 列表、列表长度、error。 +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090598.html +func (self *SRegion) GetEips() ([]SEipAddress, error) { + querys := make(map[string]string) + + eips := make([]SEipAddress, 0) + err := doListAllWithMarker(self.ecsClient.Eips.List, querys, &eips) + for i := range eips { + eips[i].region = self + } + return eips, err +} + +func (self *SRegion) GetIEips() ([]cloudprovider.ICloudEIP, error) { + _, err := self.getECSClient() + if err != nil { + return nil, err + } + + eips, err := self.GetEips() + if err != nil { + return nil, err + } + + ret := make([]cloudprovider.ICloudEIP, len(eips)) + for i := 0; i < len(eips); i += 1 { + eips[i].region = self + ret[i] = &eips[i] + } + return ret, nil +} + +func (self *SRegion) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) { + ivpcs, err := self.GetIVpcs() + if err != nil { + return nil, err + } + for i := 0; i < len(ivpcs); i += 1 { + if ivpcs[i].GetGlobalId() == id { + return ivpcs[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SRegion) GetIZoneById(id string) (cloudprovider.ICloudZone, error) { + izones, err := self.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + if izones[i].GetGlobalId() == id { + return izones[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SRegion) GetIEipById(eipId string) (cloudprovider.ICloudEIP, error) { + eip, err := self.GetEipById(eipId) + return &eip, err +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0060595555.html +func (self *SRegion) DeleteSecurityGroup(secgroupId string) error { + return DoDelete(self.ecsClient.SecurityGroups.Delete, secgroupId, nil, nil) +} + +func (self *SRegion) GetISecurityGroupById(secgroupId string) (cloudprovider.ICloudSecurityGroup, error) { + return self.GetSecurityGroupDetails(secgroupId) +} + +func (self *SRegion) GetISecurityGroupByName(opts *cloudprovider.SecurityGroupFilterOptions) (cloudprovider.ICloudSecurityGroup, error) { + secgroups, err := self.GetSecurityGroups(opts.VpcId, opts.Name) + if err != nil { + return nil, err + } + if len(secgroups) == 0 { + return nil, cloudprovider.ErrNotFound + } + if len(secgroups) > 1 { + return nil, cloudprovider.ErrDuplicateId + } + secgroups[0].region = self + return &secgroups[0], nil +} + +func (self *SRegion) CreateISecurityGroup(conf *cloudprovider.SecurityGroupCreateInput) (cloudprovider.ICloudSecurityGroup, error) { + return self.CreateSecurityGroup(conf.VpcId, conf.Name, conf.Desc) +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090608.html +func (self *SRegion) CreateIVpc(name string, desc string, cidr string) (cloudprovider.ICloudVpc, error) { + return self.CreateVpc(name, cidr, desc) +} + +func (self *SRegion) CreateVpc(name, cidr, desc string) (*SVpc, error) { + params := map[string]interface{}{ + "vpc": map[string]string{ + "name": name, + "cidr": cidr, + "description": desc, + }, + } + vpc := &SVpc{region: self} + return vpc, DoCreate(self.ecsClient.Vpcs.Create, jsonutils.Marshal(params), vpc) +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090596.html +// size: 1Mbit/s~2000Mbit/s +// bgpType: 5_telcom,5_union,5_bgp,5_sbgp. +// 东北-大连:5_telcom、5_union +// 华南-广州:5_sbgp +// 华东-上海二:5_sbgp +// 华北-北京一:5_bgp、5_sbgp +// 亚太-香港:5_bgp +func (self *SRegion) CreateEIP(eip *cloudprovider.SEip) (cloudprovider.ICloudEIP, error) { + var ctype TInternetChargeType + switch eip.ChargeType { + case api.EIP_CHARGE_TYPE_BY_TRAFFIC: + ctype = InternetChargeByTraffic + case api.EIP_CHARGE_TYPE_BY_BANDWIDTH: + ctype = InternetChargeByBandwidth + } + + // todo: 如何避免hardcode。集成到cloudmeta服务中? + if len(eip.BGPType) == 0 { + switch self.GetId() { + case "cn-north-1", "cn-east-2", "cn-south-1": + eip.BGPType = "5_sbgp" + case "cn-northeast-1": + eip.BGPType = "5_telcom" + case "cn-north-4", "ap-southeast-1", "ap-southeast-2", "eu-west-0": + eip.BGPType = "5_bgp" + default: + eip.BGPType = "5_bgp" + } + } + + // 华为云EIP名字最大长度64 + if len(eip.Name) > 64 { + eip.Name = eip.Name[:64] + } + + ieip, err := self.AllocateEIP(eip.Name, eip.BandwidthMbps, ctype, eip.BGPType, eip.ProjectId) + ieip.region = self + if err != nil { + return nil, err + } + + err = cloudprovider.WaitStatus(ieip, api.EIP_STATUS_READY, 5*time.Second, 60*time.Second) + return ieip, err +} + +func (self *SRegion) GetISnapshots() ([]cloudprovider.ICloudSnapshot, error) { + snapshots, err := self.GetSnapshots("", "") + if err != nil { + log.Errorf("self.GetSnapshots fail %s", err) + return nil, err + } + + ret := make([]cloudprovider.ICloudSnapshot, len(snapshots)) + for i := 0; i < len(snapshots); i += 1 { + snapshots[i].region = self + ret[i] = &snapshots[i] + } + return ret, nil +} + +func (self *SRegion) GetISnapshotById(snapshotId string) (cloudprovider.ICloudSnapshot, error) { + snapshot, err := self.GetSnapshotById(snapshotId) + return &snapshot, err +} + +func (self *SRegion) GetIHosts() ([]cloudprovider.ICloudHost, error) { + iHosts := make([]cloudprovider.ICloudHost, 0) + + izones, err := self.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + iZoneHost, err := izones[i].GetIHosts() + if err != nil { + return nil, err + } + iHosts = append(iHosts, iZoneHost...) + } + return iHosts, nil +} + +func (self *SRegion) GetIHostById(id string) (cloudprovider.ICloudHost, error) { + izones, err := self.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + ihost, err := izones[i].GetIHostById(id) + if err == nil { + return ihost, nil + } else if errors.Cause(err) != cloudprovider.ErrNotFound { + return nil, err + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SRegion) GetIStorages() ([]cloudprovider.ICloudStorage, error) { + iStores := make([]cloudprovider.ICloudStorage, 0) + + izones, err := self.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + iZoneStores, err := izones[i].GetIStorages() + if err != nil { + return nil, err + } + iStores = append(iStores, iZoneStores...) + } + return iStores, nil +} + +func (self *SRegion) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + izones, err := self.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + istore, err := izones[i].GetIStorageById(id) + if err == nil { + return istore, nil + } else if errors.Cause(err) != cloudprovider.ErrNotFound { + return nil, err + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SRegion) GetProvider() string { + return CLOUD_PROVIDER_HUAWEI +} + +func (self *SRegion) GetCloudEnv() string { + return "" +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090615.html +// 目前desc字段并没有用到 +func (self *SRegion) CreateSecurityGroup(vpcId string, name string, desc string) (*SSecurityGroup, error) { + // 华为不允许创建名称为default的安全组 + if strings.ToLower(name) == "default" { + name = fmt.Sprintf("%s-%s", vpcId, name) + } + + params := jsonutils.NewDict() + secgroupObj := jsonutils.NewDict() + secgroupObj.Add(jsonutils.NewString(name), "name") + if len(vpcId) > 0 && vpcId != api.NORMAL_VPC_ID { + secgroupObj.Add(jsonutils.NewString(vpcId), "vpc_id") + } + params.Add(secgroupObj, "security_group") + + secgroup := SSecurityGroup{region: self} + err := DoCreate(self.ecsClient.SecurityGroups.Create, params, &secgroup) + return &secgroup, err +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0087467071.html +func (self *SRegion) delSecurityGroupRule(secGrpRuleId string) error { + _, err := self.ecsClient.SecurityGroupRules.DeleteInContextWithSpec(nil, secGrpRuleId, "", nil, nil, "") + return err +} + +func (self *SRegion) DeleteSecurityGroupRule(ruleId string) error { + return self.delSecurityGroupRule(ruleId) +} + +func (self *SRegion) CreateSecurityGroupRule(secgroupId string, rule cloudprovider.SecurityRule) error { + return self.addSecurityGroupRules(secgroupId, rule) +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0087451723.html +// icmp port对应关系:https://support.huaweicloud.com/api-vpc/zh-cn_topic_0024109590.html +func (self *SRegion) addSecurityGroupRules(secGrpId string, rule cloudprovider.SecurityRule) error { + direction := "" + if rule.Direction == secrules.SecurityRuleIngress { + direction = "ingress" + } else { + direction = "egress" + } + + protocal := rule.Protocol + if rule.Protocol == secrules.PROTO_ANY { + protocal = "" + } + + // imcp协议默认为any + if rule.Protocol == secrules.PROTO_ICMP { + return self.addSecurityGroupRule(secGrpId, direction, "-1", "-1", protocal, rule.IPNet.String()) + } + + if len(rule.Ports) > 0 { + for _, port := range rule.Ports { + portStr := fmt.Sprintf("%d", port) + err := self.addSecurityGroupRule(secGrpId, direction, portStr, portStr, protocal, rule.IPNet.String()) + if err != nil { + return err + } + } + } else { + portStart := fmt.Sprintf("%d", rule.PortStart) + portEnd := fmt.Sprintf("%d", rule.PortEnd) + err := self.addSecurityGroupRule(secGrpId, direction, portStart, portEnd, protocal, rule.IPNet.String()) + if err != nil { + return err + } + } + + return nil +} + +func (self *SRegion) addSecurityGroupRule(secGrpId, direction, portStart, portEnd, protocol, ipNet string) error { + params := jsonutils.NewDict() + secgroupObj := jsonutils.NewDict() + secgroupObj.Add(jsonutils.NewString(secGrpId), "security_group_id") + secgroupObj.Add(jsonutils.NewString(direction), "direction") + secgroupObj.Add(jsonutils.NewString(ipNet), "remote_ip_prefix") + secgroupObj.Add(jsonutils.NewString("IPV4"), "ethertype") + // 端口为空或者1-65535 + if len(portStart) > 0 && portStart != "0" && portStart != "-1" { + secgroupObj.Add(jsonutils.NewString(portStart), "port_range_min") + } + if len(portEnd) > 0 && portEnd != "0" && portEnd != "-1" { + secgroupObj.Add(jsonutils.NewString(portEnd), "port_range_max") + } + if len(protocol) > 0 { + secgroupObj.Add(jsonutils.NewString(protocol), "protocol") + } + params.Add(secgroupObj, "security_group_rule") + + rule := SecurityGroupRule{} + return DoCreate(self.ecsClient.SecurityGroupRules.Create, params, &rule) +} + +func (self *SRegion) CreateILoadBalancer(loadbalancer *cloudprovider.SLoadbalancer) (cloudprovider.ICloudLoadbalancer, error) { + ret, err := self.CreateLoadBalancer(loadbalancer) + if err != nil { + return nil, err + } + + return &ret, nil +} + +// https://support.huaweicloud.com/api-elb/zh-cn_topic_0096561535.html +func (self *SRegion) CreateLoadBalancer(loadbalancer *cloudprovider.SLoadbalancer) (SLoadbalancer, error) { + ret := SLoadbalancer{} + subnet, err := self.getNetwork(loadbalancer.NetworkIDs[0]) + if err != nil { + return ret, errors.Wrap(err, "SRegion.CreateLoadBalancer.getNetwork") + } + + params := jsonutils.NewDict() + elbObj := jsonutils.NewDict() + elbObj.Set("name", jsonutils.NewString(loadbalancer.Name)) + elbObj.Set("vip_subnet_id", jsonutils.NewString(subnet.NeutronSubnetID)) + if len(loadbalancer.Address) > 0 { + elbObj.Set("vip_address", jsonutils.NewString(loadbalancer.Address)) + } + elbObj.Set("tenant_id", jsonutils.NewString(self.client.projectId)) + params.Set("loadbalancer", elbObj) + + err = DoCreate(self.ecsClient.Elb.Create, params, &ret) + if err != nil { + return ret, errors.Wrap(err, "SRegion.CreateLoadBalancer.DoCreate") + } + + ret.region = self + + // 创建公网类型ELB + if len(loadbalancer.EipID) > 0 { + err := self.AssociateEipWithPortId(loadbalancer.EipID, ret.VipPortID) + if err != nil { + return ret, errors.Wrap(err, "SRegion.CreateLoadBalancer.AssociateEipWithPortId") + } + } + return ret, nil +} + +func (self *SRegion) CreateILoadBalancerAcl(acl *cloudprovider.SLoadbalancerAccessControlList) (cloudprovider.ICloudLoadbalancerAcl, error) { + ret, err := self.CreateLoadBalancerAcl(acl) + if err != nil { + return nil, err + } + + return &ret, nil +} + +func (self *SRegion) CreateLoadBalancerAcl(acl *cloudprovider.SLoadbalancerAccessControlList) (SElbACL, error) { + params := jsonutils.NewDict() + aclObj := jsonutils.NewDict() + aclObj.Set("listener_id", jsonutils.NewString(acl.ListenerId)) + if len(acl.Entrys) > 0 { + whitelist := []string{} + for i := range acl.Entrys { + whitelist = append(whitelist, acl.Entrys[i].CIDR) + } + + aclObj.Set("enable_whitelist", jsonutils.NewBool(acl.AccessControlEnable)) + aclObj.Set("whitelist", jsonutils.NewString(strings.Join(whitelist, ","))) + } else { + aclObj.Set("enable_whitelist", jsonutils.NewBool(false)) + } + params.Set("whitelist", aclObj) + + ret := SElbACL{} + err := DoCreate(self.ecsClient.ElbWhitelist.Create, params, &ret) + if err != nil { + return ret, err + } + + ret.region = self + return ret, nil +} + +func (region *SRegion) GetIBuckets() ([]cloudprovider.ICloudBucket, error) { + iBuckets, err := region.client.getIBuckets() + if err != nil { + return nil, errors.Wrap(err, "getIBuckets") + } + ret := make([]cloudprovider.ICloudBucket, 0) + for i := range iBuckets { + // huawei OBS is shared across projects + if iBuckets[i].GetLocation() == region.GetId() { + ret = append(ret, iBuckets[i]) + } + } + return ret, nil +} + +func str2StorageClass(storageClassStr string) (obs.StorageClassType, error) { + if strings.EqualFold(storageClassStr, string(obs.StorageClassStandard)) { + return obs.StorageClassStandard, nil + } else if strings.EqualFold(storageClassStr, string(obs.StorageClassWarm)) { + return obs.StorageClassWarm, nil + } else if strings.EqualFold(storageClassStr, string(obs.StorageClassCold)) { + return obs.StorageClassCold, nil + } else { + return obs.StorageClassStandard, errors.Error("unsupported storageClass") + } +} + +func (region *SRegion) CreateIBucket(name string, storageClassStr string, aclStr string) error { + obsClient, err := region.getOBSClient() + if err != nil { + return errors.Wrap(err, "region.getOBSClient") + } + input := &obs.CreateBucketInput{} + input.Bucket = name + input.Location = region.GetId() + if len(aclStr) > 0 { + if strings.EqualFold(aclStr, string(obs.AclPrivate)) { + input.ACL = obs.AclPrivate + } else if strings.EqualFold(aclStr, string(obs.AclPublicRead)) { + input.ACL = obs.AclPublicRead + } else if strings.EqualFold(aclStr, string(obs.AclPublicReadWrite)) { + input.ACL = obs.AclPublicReadWrite + } else { + return errors.Error("unsupported acl") + } + } + if len(storageClassStr) > 0 { + input.StorageClass, err = str2StorageClass(storageClassStr) + if err != nil { + return err + } + } + _, err = obsClient.CreateBucket(input) + if err != nil { + return errors.Wrap(err, "obsClient.CreateBucket") + } + region.client.invalidateIBuckets() + return nil +} + +func obsHttpCode(err error) int { + switch httpErr := err.(type) { + case obs.ObsError: + return httpErr.StatusCode + case *obs.ObsError: + return httpErr.StatusCode + } + return -1 +} + +func (region *SRegion) DeleteIBucket(name string) error { + obsClient, err := region.getOBSClient() + if err != nil { + return errors.Wrap(err, "region.getOBSClient") + } + _, err = obsClient.DeleteBucket(name) + if err != nil { + if obsHttpCode(err) == 404 { + return nil + } + log.Debugf("%#v %s", err, err) + return errors.Wrap(err, "DeleteBucket") + } + region.client.invalidateIBuckets() + return nil +} + +func (region *SRegion) HeadBucket(name string) (*obs.BaseModel, error) { + obsClient, err := region.getOBSClient() + if err != nil { + return nil, errors.Wrap(err, "region.getOBSClient") + } + return obsClient.HeadBucket(name) +} + +func (region *SRegion) IBucketExist(name string) (bool, error) { + _, err := region.HeadBucket(name) + if err != nil { + if obsHttpCode(err) == 404 { + return false, nil + } else { + return false, errors.Wrap(err, "HeadBucket") + } + } + return true, nil +} + +func (region *SRegion) GetIBucketById(name string) (cloudprovider.ICloudBucket, error) { + return cloudprovider.GetIBucketById(region, name) +} + +func (region *SRegion) GetIBucketByName(name string) (cloudprovider.ICloudBucket, error) { + return region.GetIBucketById(name) +} + +func (self *SRegion) GetSkus(zoneId string) ([]cloudprovider.ICloudSku, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (self *SRegion) GetIElasticcaches() ([]cloudprovider.ICloudElasticcache, error) { + caches, err := self.GetElasticCaches() + if err != nil { + return nil, err + } + + icaches := make([]cloudprovider.ICloudElasticcache, len(caches)) + for i := range caches { + caches[i].region = self + icaches[i] = &caches[i] + } + + return icaches, nil +} + +func (region *SRegion) GetCapabilities() []string { + return region.client.GetCapabilities() +} + +func (self *SRegion) GetDiskTypes() ([]SDiskType, error) { + ret, err := self.ecsClient.Disks.GetDiskTypes() + if err != nil { + return nil, errors.Wrap(err, "GetDiskTypes") + } + + dts := []SDiskType{} + _ret := jsonutils.NewArray(ret.Data...) + err = _ret.Unmarshal(&dts) + if err != nil { + return nil, errors.Wrap(err, "Unmarshal") + } + + return dts, nil +} + +func (self *SRegion) GetZoneSupportedDiskTypes(zoneId string) ([]string, error) { + dts, err := self.GetDiskTypes() + if err != nil { + return nil, errors.Wrap(err, "GetDiskTypes") + } + + ret := []string{} + for i := range dts { + if dts[i].IsAvaliableInZone(zoneId) { + ret = append(ret, dts[i].Name) + } + } + + return ret, nil +} + +func (self *SRegion) GetISkus() ([]cloudprovider.ICloudSku, error) { + izones, err := self.GetIZones() + if err != nil { + return nil, errors.Wrap(err, "GetIZones") + } + + ret := make([]cloudprovider.ICloudSku, 0) + for i := range izones { + flavors, err := self.fetchInstanceTypes(izones[i].GetId()) + if err != nil { + return nil, errors.Wrap(err, "fetchInstanceTypes") + } + + for i := range flavors { + ret = append(ret, &flavors[i]) + } + } + + return ret, nil +} + +func (self *SRegion) GetEndpoints() ([]jsonutils.JSONObject, error) { + endpoints := make([]jsonutils.JSONObject, 0) + err := doListAll(self.ecsClient.Endpoints.List, nil, &endpoints) + if err != nil { + return nil, err + } + + return endpoints, nil +} + +func (self *SRegion) GetServices() ([]jsonutils.JSONObject, error) { + services := make([]jsonutils.JSONObject, 0) + err := doListAll(self.ecsClient.Services.List, nil, &services) + if err != nil { + return nil, err + } + + return services, nil +} diff --git a/pkg/multicloud/huaweistack/roles.go b/pkg/multicloud/huaweistack/roles.go new file mode 100644 index 0000000000..3de9f80617 --- /dev/null +++ b/pkg/multicloud/huaweistack/roles.go @@ -0,0 +1,99 @@ +// 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 huaweistack + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SRole struct { + DomainId string + Flag string + DescriptionCn string + Catelog string + Description string + Id string + DisplayName string + Type string + UpdatedTime string + CreatedTime string + Links SLink + Policy jsonutils.JSONDict +} + +func (role *SRole) GetName() string { + return role.DisplayName +} + +func (role *SRole) GetDescription() string { + return role.DescriptionCn +} + +func (role *SRole) GetPolicyType() string { + return "System" +} + +func (role *SRole) GetGlobalId() string { + return role.Id +} + +func (role *SRole) UpdateDocument(document *jsonutils.JSONDict) error { + return cloudprovider.ErrNotImplemented +} + +func (role *SRole) GetDocument() (*jsonutils.JSONDict, error) { + return &role.Policy, nil +} + +func (role *SRole) Delete() error { + return cloudprovider.ErrNotImplemented +} + +func (self *SHuaweiClient) GetISystemCloudpolicies() ([]cloudprovider.ICloudpolicy, error) { + roles, err := self.GetRoles("", "") + if err != nil { + return nil, errors.Wrap(err, "GetRoles") + } + ret := []cloudprovider.ICloudpolicy{} + for i := range roles { + ret = append(ret, &roles[i]) + } + return ret, nil +} + +func (self *SHuaweiClient) GetRoles(domainId, name string) ([]SRole, error) { + params := map[string]string{} + if len(domainId) > 0 { + params["domain_id"] = self.ownerId + } + if len(name) > 0 { + params["name"] = name + } + + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + + roles := []SRole{} + err = doListAllWithNextLink(client.Roles.List, params, &roles) + if err != nil { + return nil, errors.Wrap(err, "doListAllWithOffset") + } + return roles, nil +} diff --git a/pkg/multicloud/huaweistack/routetable.go b/pkg/multicloud/huaweistack/routetable.go new file mode 100644 index 0000000000..a4798892f9 --- /dev/null +++ b/pkg/multicloud/huaweistack/routetable.go @@ -0,0 +1,356 @@ +// 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 huaweistack + +import ( + "fmt" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +// date: 2019.07.15 +// In Huawei cloud, there are only two routing tables in a vpc, which are +// self-defined routing tables and peer-to-peer routing tables. +// The routing in these two tables is different, one's NextHop is a IP address and +// the other one's NextHop address is a instance ID of peer-to-peer connection. +// The former has no id and it's Type is ROUTE_TYPR_IP, and the latter's Type is ROUTE_TYPE_PEER. + +const ( + ROUTE_TYPR_IP = "IP" + ROUTE_TYPE_PEER = "peering" +) + +type SRouteEntry struct { + multicloud.SResourceBase + multicloud.HuaweiTags + routeTable *SRouteTable + + ID string // route ID + Type string // route type + Destination string // route destination + NextHop string // route next hop (ip or id) +} + +func (route *SRouteEntry) GetId() string { + if len(route.ID) == 0 { + return route.Destination + ":" + route.NextHop + } + return route.ID +} + +func (route *SRouteEntry) GetName() string { + return "" +} + +func (route *SRouteEntry) GetGlobalId() string { + return route.GetId() +} + +func (route *SRouteEntry) GetStatus() string { + return api.ROUTE_ENTRY_STATUS_AVAILIABLE +} + +func (route *SRouteEntry) Refresh() error { + return nil +} + +func (route *SRouteEntry) IsEmulated() bool { + return false +} + +func (route *SRouteEntry) GetType() string { + if route.Type == ROUTE_TYPE_PEER { + return api.ROUTE_ENTRY_TYPE_CUSTOM + } + return api.ROUTE_ENTRY_TYPE_SYSTEM +} + +func (route *SRouteEntry) GetCidr() string { + return route.Destination +} + +func (route *SRouteEntry) GetNextHopType() string { + // In Huawei Cloud, NextHopType is same with itself + switch route.Type { + case ROUTE_TYPE_PEER: + return api.Next_HOP_TYPE_VPCPEERING + default: + return "" + } +} + +func (route *SRouteEntry) GetNextHop() string { + return route.NextHop +} + +// SRouteTable has no ID and Name because there is no id or name of route table in huawei cloud. +// And some method such as GetId and GetName of ICloudRouteTable has no practical meaning +type SRouteTable struct { + multicloud.SResourceBase + multicloud.HuaweiTags + region *SRegion + vpc *SVpc + + VpcId string + Description string + Type string + Routes []*SRouteEntry +} + +func NewSRouteTable(vpc *SVpc, Type string) SRouteTable { + return SRouteTable{ + region: vpc.region, + vpc: vpc, + Type: Type, + VpcId: vpc.GetId(), + } + +} + +func (self *SRouteTable) GetId() string { + return self.GetGlobalId() +} + +func (self *SRouteTable) GetName() string { + return "" +} + +func (self *SRouteTable) GetGlobalId() string { + return fmt.Sprintf("%s-%s", self.GetVpcId(), self.GetType()) +} + +func (self *SRouteTable) GetStatus() string { + return api.ROUTE_TABLE_AVAILABLE +} + +func (self *SRouteTable) Refresh() error { + return nil +} + +func (self *SRouteTable) IsEmulated() bool { + return false +} + +func (self *SRouteTable) GetDescription() string { + return self.Description +} + +func (self *SRouteTable) GetRegionId() string { + return self.region.GetId() +} + +func (self *SRouteTable) GetVpcId() string { + return self.VpcId +} + +func (self *SRouteTable) GetType() cloudprovider.RouteTableType { + return cloudprovider.RouteTableTypeSystem +} + +func (self *SRouteTable) GetIRoutes() ([]cloudprovider.ICloudRoute, error) { + if self.Routes == nil { + err := self.fetchRoutes() + if err != nil { + return nil, err + } + } + ret := []cloudprovider.ICloudRoute{} + for i := range self.Routes { + ret = append(ret, self.Routes[i]) + } + return ret, nil +} + +// fetchRoutes fetch Routes +func (self *SRouteTable) fetchRoutes() error { + if self.Type == ROUTE_TYPR_IP { + return self.fetchRoutesForIP() + } + return self.fetchRoutesForPeer() +} + +// fetchRoutesForIP fetch the Routes which Type is ROUTE_TYPR_IP through vpc's get api +func (self *SRouteTable) fetchRoutesForIP() error { + ret, err := self.region.ecsClient.Vpcs.Get(self.GetVpcId(), map[string]string{}) + if err != nil { + return errors.Wrap(err, "get vpc info error") + } + routeArray, err := ret.GetArray("routes") + routes := make([]*SRouteEntry, 0, len(routeArray)) + for i := range routeArray { + destination, err := routeArray[i].GetString("destination") + if err != nil { + return errors.Wrap(err, "get destination of route error") + } + nextHop, err := routeArray[i].GetString("nexthop") + if err != nil { + return errors.Wrap(err, "get nexthop of route error") + } + routes = append(routes, &SRouteEntry{ + routeTable: self, + ID: "", + Type: ROUTE_TYPR_IP, + Destination: destination, + NextHop: nextHop, + }) + } + self.Routes = routes + return nil +} + +// fetchRoutesForPeer fetch the routes which Type is ROUTE_TYPE_PEER through vpcRoute's list api +func (self *SRouteTable) fetchRoutesForPeer() error { + retPeer, err := self.region.ecsClient.VpcRoutes.List(map[string]string{"vpc_id": self.GetVpcId()}) + if err != nil { + return errors.Wrap(err, "get peer route error") + } + routesPeer := make([]*SRouteEntry, 0, retPeer.Total) + for i := range retPeer.Data { + route := retPeer.Data[i] + id, err := route.GetString("id") + if err != nil { + return errors.Wrap(err, "get id of peer route error") + } + destination, err := route.GetString("destination") + if err != nil { + return errors.Wrap(err, "get destination of peer route error") + } + nextHop, err := route.GetString("nexthop") + if err != nil { + return errors.Wrap(err, "get nexthop of peer route error") + } + routesPeer = append(routesPeer, &SRouteEntry{ + routeTable: self, + ID: id, + Type: ROUTE_TYPE_PEER, + Destination: destination, + NextHop: nextHop, + }) + } + self.Routes = routesPeer + return nil +} + +func (self *SRouteTable) GetAssociations() []cloudprovider.RouteTableAssociation { + result := []cloudprovider.RouteTableAssociation{} + return result +} + +func (self *SRouteTable) CreateRoute(route cloudprovider.RouteSet) error { + if route.NextHopType != api.Next_HOP_TYPE_VPCPEERING { + return cloudprovider.ErrNotSupported + } + err := self.region.CreatePeeringRoute(self.vpc.GetId(), route.Destination, route.NextHop) + if err != nil { + return errors.Wrapf(err, " self.region.CreatePeeringRoute(%s,%s,%s)", self.vpc.GetId(), route.Destination, route.NextHop) + } + return nil +} + +func (self *SRouteTable) UpdateRoute(route cloudprovider.RouteSet) error { + err := self.RemoveRoute(route) + if err != nil { + return errors.Wrap(err, "self.RemoveRoute(route)") + } + err = self.CreateRoute(route) + if err != nil { + return errors.Wrap(err, "self.CreateRoute(route)") + } + return nil +} + +func (self *SRouteTable) RemoveRoute(route cloudprovider.RouteSet) error { + err := self.region.DeletePeeringRoute(route.RouteId) + if err != nil { + return errors.Wrapf(err, "self.region.DeletePeeringRoute(%s)", route.RouteId) + } + return nil +} + +// GetRouteTables return []SRouteTable of self +func (self *SVpc) getRouteTables() ([]SRouteTable, error) { + // every Vpc has two route table in Huawei Cloud + routeTableIp := NewSRouteTable(self, ROUTE_TYPR_IP) + routeTablePeer := NewSRouteTable(self, ROUTE_TYPE_PEER) + if err := routeTableIp.fetchRoutesForIP(); err != nil { + return nil, errors.Wrap(err, `get route table whilc type is "ip" error`) + } + if err := routeTablePeer.fetchRoutesForPeer(); err != nil { + return nil, errors.Wrap(err, `get route table whilc type is "peering" error`) + } + ret := make([]SRouteTable, 0, 2) + if len(routeTableIp.Routes) != 0 { + ret = append(ret, routeTableIp) + } + if len(routeTablePeer.Routes) != 0 { + ret = append(ret, routeTablePeer) + } + return ret, nil +} + +// GetRouteTables return []SRouteTable of vpc which id is vpcId if vpcId is no-nil, +// otherwise return []SRouteTable of all vpc in this SRegion +func (self *SRegion) GetRouteTables(vpcId string) ([]SRouteTable, error) { + vpcs, err := self.GetVpcs() + if err != nil { + return nil, errors.Wrap(err, "Get Vpcs error") + } + if vpcId != "" { + for i := range vpcs { + if vpcs[i].GetId() == vpcId { + vpcs = vpcs[i : i+1] + break + } + } + } + ret := make([]SRouteTable, 0, 2*len(vpcs)) + for _, vpc := range vpcs { + routetables, err := vpc.getRouteTables() + if err != nil { + return nil, errors.Wrapf(err, "get vpc's route tables whilch id is %s error", vpc.GetId()) + } + ret = append(ret, routetables...) + + } + return ret, nil +} + +func (self *SRegion) CreatePeeringRoute(vpcId, destinationCidr, target string) error { + params := jsonutils.NewDict() + routeObj := jsonutils.NewDict() + routeObj.Set("type", jsonutils.NewString("peering")) + routeObj.Set("nexthop", jsonutils.NewString(target)) + routeObj.Set("destination", jsonutils.NewString(destinationCidr)) + routeObj.Set("vpc_id", jsonutils.NewString(vpcId)) + params.Set("route", routeObj) + err := DoCreate(self.ecsClient.VpcRoutes.Create, params, nil) + if err != nil { + return errors.Wrapf(err, "DoCreate(self.ecsClient.VpcRoutes.Create, %s, &ret)", jsonutils.Marshal(params).String()) + } + return nil +} + +func (self *SRegion) DeletePeeringRoute(routeId string) error { + err := DoDelete(self.ecsClient.VpcRoutes.Delete, routeId, nil, nil) + if err != nil { + return errors.Wrapf(err, "DoDelete(self.ecsClient.VpcRoutes.Delete,%s,nil)", routeId) + } + return nil +} diff --git a/pkg/multicloud/huaweistack/saml_provider.go b/pkg/multicloud/huaweistack/saml_provider.go new file mode 100644 index 0000000000..c0b5a6eb46 --- /dev/null +++ b/pkg/multicloud/huaweistack/saml_provider.go @@ -0,0 +1,348 @@ +// 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 huaweistack + +import ( + "fmt" + "time" + "unicode" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/stringutils" + + api "yunion.io/x/onecloud/pkg/apis/cloudid" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/modules" + "yunion.io/x/onecloud/pkg/util/samlutils" +) + +type SAMLProviderLinks struct { + Self string + Protocols string +} + +type SAMLProvider struct { + multicloud.SResourceBase + multicloud.HuaweiTags + client *SHuaweiClient + + Id string + Links SAMLProviderLinks + Description string +} + +func (self *SAMLProvider) GetId() string { + return self.Id +} + +func (self *SAMLProvider) GetGlobalId() string { + return self.Id +} + +func (self *SAMLProvider) GetName() string { + return self.Id +} + +func (self *SAMLProvider) GetStatus() string { + mapping, _ := self.client.findMapping() + if mapping != nil { + return api.SAML_PROVIDER_STATUS_AVAILABLE + } + return api.SAML_PROVIDER_STATUS_UNVALIABLE +} + +func (self *SAMLProvider) GetAuthUrl() string { + return fmt.Sprintf("https://auth.huaweicloud.com/authui/federation/websso?domain_id=%s&idp=%s&protocol=saml", self.client.ownerId, self.Id) +} + +func (self *SAMLProvider) Delete() error { + return self.client.DeleteSAMLProvider(self.Id) +} + +func (self *SAMLProvider) GetMetadataDocument() (*samlutils.EntityDescriptor, error) { + info, err := self.client.GetSAMLProviderMetadata(self.Id) + if err != nil { + return nil, errors.Wrapf(err, "GetSAMLProviderMetadata(%s)", self.Id) + } + metadata, err := samlutils.ParseMetadata([]byte(info.Data)) + if err != nil { + return nil, errors.Wrapf(err, "ParseMetadata") + } + return &metadata, nil +} + +func (self *SAMLProvider) UpdateMetadata(metadata samlutils.EntityDescriptor) error { + return self.client.UpdateSAMLProviderMetadata(self.Id, metadata.String()) +} + +func (self *SHuaweiClient) ListSAMLProviders() ([]SAMLProvider, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrapf(err, "newGeneralAPIClient") + } + samls := []SAMLProvider{} + err = doListAllWithNextLink(client.SAMLProviders.List, nil, &samls) + if err != nil { + return nil, errors.Wrapf(err, "doListAll") + } + return samls, nil +} + +type SAMLProviderProtocol struct { + MappingId string + Id string +} + +func (self *SHuaweiClient) GetSAMLProviderProtocols(id string) ([]SAMLProviderProtocol, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + resp, err := client.SAMLProviders.ListInContextWithSpec(nil, fmt.Sprintf("%s/protocols", id), nil, "protocols") + if err != nil { + return nil, errors.Wrapf(err, "ListInContextWithSpec") + } + protocols := []SAMLProviderProtocol{} + return protocols, jsonutils.Update(&protocols, resp.Data) +} + +func (self *SHuaweiClient) DeleteSAMLProviderProtocol(spId, id string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + _, err = client.SAMLProviders.DeleteInContextWithSpec(nil, spId, fmt.Sprintf("protocols/%s", id), nil, nil, "") + return err +} + +type SAMLProviderMetadata struct { + DomainId string + UpdateTime time.Time + Data string + IdpId string + ProtocolId string + Id string + EntityId string + XaccountType string +} + +func (self *SHuaweiClient) GetSAMLProviderMetadata(id string) (*SAMLProviderMetadata, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + client.SAMLProviders.SetVersion("v3-ext/OS-FEDERATION") + resp, err := client.SAMLProviders.GetInContextWithSpec(nil, id, fmt.Sprintf("protocols/saml/metadata"), nil, "") + if err != nil { + return nil, err + } + + metadata := &SAMLProviderMetadata{} + err = resp.Unmarshal(metadata) + if err != nil { + return nil, errors.Wrap(err, "resp.Unmarshal") + } + return metadata, nil +} + +func (self *SHuaweiClient) UpdateSAMLProviderMetadata(id, metadata string) error { + params := map[string]string{ + "domain_id": self.ownerId, + "xaccount_type": "", + "metadata": metadata, + } + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + client.SAMLProviders.SetVersion("v3-ext/OS-FEDERATION") + _, err = client.SAMLProviders.PerformAction2("protocols/saml/metadata", id, jsonutils.Marshal(params), "") + if err != nil { + return errors.Wrapf(err, "SAMLProvider.PerformAction") + } + return nil +} + +func (self *SHuaweiClient) GetICloudSAMLProviders() ([]cloudprovider.ICloudSAMLProvider, error) { + samls, err := self.ListSAMLProviders() + if err != nil { + return nil, errors.Wrapf(err, "ListSAMLProviders") + } + ret := []cloudprovider.ICloudSAMLProvider{} + for i := range samls { + samls[i].client = self + ret = append(ret, &samls[i]) + } + return ret, nil +} + +func (self *SHuaweiClient) DeleteSAMLProvider(id string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + _, err = client.SAMLProviders.Delete(id, nil) + return err +} + +func (self *SHuaweiClient) CreateSAMLProvider(opts *cloudprovider.SAMLProviderCreateOptions) (*SAMLProvider, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + params := jsonutils.Marshal(map[string]interface{}{ + "identity_provider": map[string]interface{}{ + "description": opts.Name, + "enabled": true, + }, + }) + name := []byte{} + for _, c := range opts.Name { + if unicode.IsLetter(c) || unicode.IsNumber(c) || c == '-' || c == '_' { + name = append(name, byte(c)) + } else { + name = append(name, '-') + } + } + opts.Name = string(name) + _, err = client.SAMLProviders.Update(opts.Name, params) + if err != nil { + if he, ok := err.(*modules.HuaweiClientError); ok && he.Code != 409 { + return nil, errors.Wrapf(err, "SAMLProviders.Update") + } + } + ret := SAMLProvider{client: self, Id: opts.Name} + err = self.UpdateSAMLProviderMetadata(opts.Name, opts.Metadata.String()) + if err != nil { + return nil, errors.Wrapf(err, "resp.Unmarshal") + } + err = self.InitSAMLProviderMapping(opts.Name) + if err != nil { + return nil, errors.Wrapf(err, "InitSAMLProviderMapping") + } + return &ret, nil +} + +type SAMLProviderMapping struct { + Id string + Rules jsonutils.JSONObject +} + +var ( + onecloudMappingRules = jsonutils.Marshal(map[string]interface{}{ + "rules": []map[string]interface{}{ + { + "remote": []map[string]interface{}{ + { + "type": "User", + }, + { + "type": "Groups", + }, + }, + "local": []map[string]interface{}{ + { + "groups": "{1}", + "user": map[string]string{"name": "{0}"}, + }, + }, + }, + }, + }) +) + +func (self *SHuaweiClient) ListSAMLProviderMappings() ([]SAMLProviderMapping, error) { + client, err := self.newGeneralAPIClient() + if err != nil { + return nil, errors.Wrap(err, "newGeneralAPIClient") + } + mappings := []SAMLProviderMapping{} + err = doListAllWithNextLink(client.SAMLProviderMappings.List, nil, &mappings) + if err != nil { + return nil, err + } + return mappings, nil +} + +func (self *SHuaweiClient) findMapping() (*SAMLProviderMapping, error) { + mappings, err := self.ListSAMLProviderMappings() + if err != nil { + return nil, errors.Wrapf(err, "ListSAMLProviderMappings") + } + for i := range mappings { + if jsonutils.Marshal(map[string]interface{}{"rules": mappings[i].Rules}).Equals(jsonutils.Marshal(onecloudMappingRules)) { + return &mappings[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SHuaweiClient) InitSAMLProviderMapping(spId string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + + mapping, err := self.findMapping() + if err != nil { + if errors.Cause(err) != cloudprovider.ErrNotFound { + return errors.Wrapf(err, "findMapping") + } + mappingId := stringutils.UUID4() + params := map[string]interface{}{ + "mapping": onecloudMappingRules, + } + _, err = client.SAMLProviderMappings.Update(mappingId, jsonutils.Marshal(params)) + if err != nil { + return errors.Wrapf(err, "create mapping") + } + mapping = &SAMLProviderMapping{ + Id: mappingId, + Rules: onecloudMappingRules, + } + } + protocols, err := self.GetSAMLProviderProtocols(spId) + if err != nil { + return errors.Wrapf(err, "GetSAMLProviderProtocols") + } + params := map[string]interface{}{ + "protocol": map[string]string{ + "mapping_id": mapping.Id, + }, + } + for i := range protocols { + if protocols[i].Id == "saml" { + if protocols[i].MappingId == mapping.Id { + return nil + } + _, err = client.SAMLProviders.PatchInContextWithSpec(nil, spId, "protocols/saml", jsonutils.Marshal(params), "") + return err + } + } + _, err = client.SAMLProviders.UpdateInContextWithSpec(nil, spId, "protocols/saml", jsonutils.Marshal(params), "") + return err +} + +func (self *SHuaweiClient) DeleteSAMLProviderMapping(id string) error { + client, err := self.newGeneralAPIClient() + if err != nil { + return errors.Wrap(err, "newGeneralAPIClient") + } + + _, err = client.SAMLProviderMappings.Delete(id, nil) + return err +} diff --git a/pkg/multicloud/huaweistack/securitygroup.go b/pkg/multicloud/huaweistack/securitygroup.go new file mode 100644 index 0000000000..9e455c425c --- /dev/null +++ b/pkg/multicloud/huaweistack/securitygroup.go @@ -0,0 +1,275 @@ +// 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 huaweistack + +/* +https://support.huaweicloud.com/usermanual-vpc/zh-cn_topic_0073379079.html +安全组的限制 +默认情况下,一个用户可以创建100个安全组。 +默认情况下,一个安全组最多只允许拥有50条安全组规则。 +默认情况下,一个弹性云服务器或辅助网卡最多只能被添加到5个安全组中。 +在创建私网弹性负载均衡时,需要选择弹性负载均衡所在的安全组。请勿删除默认规则或者确保满足以下规则: +出方向:允许发往同一个安全组的报文可以通过,或者允许对端负载均衡器报文通过。 +入方向:允许来自同一个安全组的报文可以通过,或者允许对端负载均衡器报文通过。 +*/ + +import ( + "net" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/secrules" + "yunion.io/x/pkg/utils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SecurityGroupRule struct { + Direction string `json:"direction"` + Ethertype string `json:"ethertype"` + ID string `json:"id"` + Description string `json:"description"` + SecurityGroupID string `json:"security_group_id"` + RemoteGroupID string `json:"remote_group_id"` +} + +type SecurityGroupRuleDetail struct { + Direction string `json:"direction"` + Ethertype string `json:"ethertype"` + ID string `json:"id"` + Description string `json:"description"` + PortRangeMax int64 `json:"port_range_max"` + PortRangeMin int64 `json:"port_range_min"` + Protocol string `json:"protocol"` + RemoteGroupID string `json:"remote_group_id"` + RemoteIPPrefix string `json:"remote_ip_prefix"` + SecurityGroupID string `json:"security_group_id"` + TenantID string `json:"tenant_id"` +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090615.html +type SSecurityGroup struct { + multicloud.SSecurityGroup + multicloud.HuaweiTags + region *SRegion + + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + VpcID string `json:"vpc_id"` + EnterpriseProjectID string `json:"enterprise_project_id "` + SecurityGroupRules []SecurityGroupRule `json:"security_group_rules"` +} + +// 判断是否兼容云端安全组规则 +func compatibleSecurityGroupRule(r SecurityGroupRule) bool { + // 忽略了源地址是安全组的规则 + if len(r.RemoteGroupID) > 0 { + return false + } + + // 忽略IPV6 + if r.Ethertype == "IPv6" { + return false + } + + return true +} + +func (self *SSecurityGroup) GetId() string { + return self.ID +} + +func (self *SSecurityGroup) GetVpcId() string { + return api.NORMAL_VPC_ID +} + +func (self *SSecurityGroup) GetName() string { + if len(self.Name) > 0 { + return self.Name + } + return self.ID +} + +func (self *SSecurityGroup) GetGlobalId() string { + return self.ID +} + +func (self *SSecurityGroup) GetStatus() string { + return "" +} + +func (self *SSecurityGroup) Refresh() error { + if new, err := self.region.GetSecurityGroupDetails(self.GetId()); err != nil { + return err + } else { + return jsonutils.Update(self, new) + } +} + +func (self *SSecurityGroup) IsEmulated() bool { + return false +} + +func (self *SSecurityGroup) GetDescription() string { + if self.Description == self.VpcID { + return "" + } + return self.Description +} + +// todo: 这里需要优化查询太多了 +func (self *SSecurityGroup) GetRules() ([]cloudprovider.SecurityRule, error) { + rules := make([]cloudprovider.SecurityRule, 0) + for _, r := range self.SecurityGroupRules { + if !compatibleSecurityGroupRule(r) { + continue + } + + rule, err := self.GetSecurityRule(r.ID) + if err != nil { + return rules, err + } + + rules = append(rules, rule) + } + + return rules, nil +} + +func (self *SSecurityGroup) GetSecurityRule(ruleId string) (cloudprovider.SecurityRule, error) { + remoteRule := SecurityGroupRuleDetail{} + err := DoGet(self.region.ecsClient.SecurityGroupRules.Get, ruleId, nil, &remoteRule) + if err != nil { + return cloudprovider.SecurityRule{}, err + } + + var direction secrules.TSecurityRuleDirection + if remoteRule.Direction == "ingress" { + direction = secrules.SecurityRuleIngress + } else { + direction = secrules.SecurityRuleEgress + } + + protocol := secrules.PROTO_ANY + if remoteRule.Protocol != "" { + protocol = remoteRule.Protocol + } + + var portStart int + var portEnd int + if protocol == secrules.PROTO_ICMP { + portStart = -1 + portEnd = -1 + } else { + portStart = int(remoteRule.PortRangeMin) + portEnd = int(remoteRule.PortRangeMax) + } + + ipNet := &net.IPNet{} + if len(remoteRule.RemoteIPPrefix) > 0 { + _, ipNet, err = net.ParseCIDR(remoteRule.RemoteIPPrefix) + } else { + _, ipNet, err = net.ParseCIDR("0.0.0.0/0") + } + + if err != nil { + return cloudprovider.SecurityRule{}, err + } + + rule := cloudprovider.SecurityRule{ + ExternalId: ruleId, + SecurityRule: secrules.SecurityRule{ + Priority: 1, + Action: secrules.SecurityRuleAllow, + IPNet: ipNet, + Protocol: protocol, + Direction: direction, + PortStart: portStart, + PortEnd: portEnd, + Ports: nil, + Description: remoteRule.Description, + }, + } + + err = rule.ValidateRule() + return rule, err +} + +func (self *SRegion) GetSecurityGroupDetails(secGroupId string) (*SSecurityGroup, error) { + securitygroup := SSecurityGroup{} + err := DoGet(self.ecsClient.SecurityGroups.Get, secGroupId, nil, &securitygroup) + if err != nil { + return nil, err + } + + securitygroup.region = self + return &securitygroup, err +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090617.html +func (self *SRegion) GetSecurityGroups(vpcId string, name string) ([]SSecurityGroup, error) { + querys := map[string]string{} + if len(vpcId) > 0 && !utils.IsInStringArray(vpcId, []string{"default", api.NORMAL_VPC_ID}) { // vpc_id = default or normal 时报错 '{"code":"VPC.0601","message":"Query security groups error vpcId is invalid."}' + querys["vpc_id"] = vpcId + } + + securitygroups := make([]SSecurityGroup, 0) + err := doListAllWithMarker(self.ecsClient.SecurityGroups.List, querys, &securitygroups) + if err != nil { + return nil, err + } + + // security 中的vpc字段只是一个标识,实际可以跨vpc使用 + for i := range securitygroups { + securitygroup := &securitygroups[i] + securitygroup.region = self + } + + result := []SSecurityGroup{} + for _, secgroup := range securitygroups { + if len(name) == 0 || secgroup.Name == name { + result = append(result, secgroup) + } + } + + return result, nil +} + +func (self *SSecurityGroup) GetProjectId() string { + return "" +} + +func (self *SSecurityGroup) Delete() error { + return self.region.DeleteSecurityGroup(self.ID) +} + +func (self *SSecurityGroup) SyncRules(common, inAdds, outAdds, inDels, outDels []cloudprovider.SecurityRule) error { + for _, r := range append(inDels, outDels...) { + err := self.region.delSecurityGroupRule(r.ExternalId) + if err != nil { + return errors.Wrapf(err, "delSecurityGroupRule(%s %s)", r.ExternalId, r.String()) + } + } + for _, r := range append(inAdds, outAdds...) { + err := self.region.addSecurityGroupRules(self.ID, r) + if err != nil { + return errors.Wrapf(err, "addSecurityGroupRule(%d %s)", r.Priority, r.String()) + } + } + return nil +} diff --git a/pkg/multicloud/huaweistack/sfs-turbo.go b/pkg/multicloud/huaweistack/sfs-turbo.go new file mode 100644 index 0000000000..9307428e1f --- /dev/null +++ b/pkg/multicloud/huaweistack/sfs-turbo.go @@ -0,0 +1,298 @@ +// 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 huaweistack + +import ( + "fmt" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + billing_api "yunion.io/x/onecloud/pkg/apis/billing" + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type SfsTurbo struct { + multicloud.SNasBase + multicloud.HuaweiTags + region *SRegion + + EnterpriseProjectId string + Actions []string + AvailCapacity float64 + AvailabilityZone string + AzName string + CreatedAt time.Time + CryptKeyId string + ExpandType string + ExportLocation string + Id string + Name string + PayModel string + Region string + SecurityGroupId string + ShareProto string + ShareType string + Size float64 + Status string + SubStatus string + SubnetId string + VpcId string + Description string +} + +func (self *SfsTurbo) GetName() string { + return self.Name +} + +func (self *SfsTurbo) GetId() string { + return self.Id +} + +func (self *SfsTurbo) GetGlobalId() string { + return self.Id +} + +func (self *SfsTurbo) GetFileSystemType() string { + return "SFS Turbo" +} + +func (self *SfsTurbo) Refresh() error { + sf, err := self.region.GetSfsTurbo(self.Id) + if err != nil { + return errors.Wrapf(err, "GetSfsTurbo") + } + return jsonutils.Update(self, sf) +} + +func (self *SfsTurbo) GetBillingType() string { + if self.PayModel == "0" { + return billing_api.BILLING_TYPE_POSTPAID + } + return billing_api.BILLING_TYPE_PREPAID +} + +func (self *SfsTurbo) GetStorageType() string { + if len(self.ExpandType) == 0 { + return strings.ToLower(self.ShareType) + } + return strings.ToLower(self.ShareType) + ".enhanced" +} + +func (self *SfsTurbo) GetProtocol() string { + return self.ShareProto +} + +func (self *SfsTurbo) GetStatus() string { + switch self.Status { + case "100": + return api.NAS_STATUS_CREATING + case "200": + return api.NAS_STATUS_AVAILABLE + case "300": + return api.NAS_STATUS_UNKNOWN + case "303": + return api.NAS_STATUS_CREATE_FAILED + case "400": + return api.NAS_STATUS_DELETING + case "800": + return api.NAS_STATUS_UNAVAILABLE + default: + return self.Status + } +} + +func (self *SfsTurbo) GetCreatedAt() time.Time { + return self.CreatedAt +} + +func (self *SfsTurbo) GetCapacityGb() int64 { + return int64(self.Size) +} + +func (self *SfsTurbo) GetUsedCapacityGb() int64 { + return int64(self.Size - self.AvailCapacity) +} + +func (self *SfsTurbo) GetMountTargetCountLimit() int { + return 1 +} + +func (self *SfsTurbo) GetZoneId() string { + return self.AvailabilityZone +} + +func (self *SfsTurbo) GetMountTargets() ([]cloudprovider.ICloudMountTarget, error) { + mt := &sMoutTarget{sfs: self} + return []cloudprovider.ICloudMountTarget{mt}, nil +} + +func (self *SfsTurbo) CreateMountTarget(opts *cloudprovider.SMountTargetCreateOptions) (cloudprovider.ICloudMountTarget, error) { + return nil, errors.Wrap(cloudprovider.ErrNotSupported, "CreateMountTarget") +} + +func (self *SfsTurbo) Delete() error { + return self.region.DeleteSfsTurbo(self.Id) +} + +func (self *SRegion) GetICloudFileSystems() ([]cloudprovider.ICloudFileSystem, error) { + sfs, err := self.GetSfsTurbos() + if err != nil { + return nil, errors.Wrapf(err, "self.GetSfsTurbos") + } + ret := []cloudprovider.ICloudFileSystem{} + for i := range sfs { + sfs[i].region = self + ret = append(ret, &sfs[i]) + } + return ret, nil +} + +func (self *SRegion) GetICloudFileSystemById(id string) (cloudprovider.ICloudFileSystem, error) { + sf, err := self.GetSfsTurbo(id) + if err != nil { + return nil, errors.Wrapf(err, "GetSfsTurbo(%s)", id) + } + return sf, nil +} + +func (self *SRegion) GetSfsTurbos() ([]SfsTurbo, error) { + queues := make(map[string]string) + sfs := make([]SfsTurbo, 0, 2) + err := doListAllWithOffset(self.ecsClient.SfsTurbos.List, queues, &sfs) + if err != nil { + return nil, errors.Wrapf(err, "doListAllWithOffset") + } + return sfs, nil +} + +func (self *SRegion) GetSfsTurbo(id string) (*SfsTurbo, error) { + sf := &SfsTurbo{region: self} + err := DoGet(self.ecsClient.SfsTurbos.Get, id, nil, &sf) + return sf, errors.Wrapf(err, "self.ecsClient.SfsTurbos.Get") +} + +func (self *SRegion) DeleteSfsTurbo(id string) error { + return DoDelete(self.ecsClient.SfsTurbos.Delete, id, nil, nil) +} + +func (self *SRegion) GetSysDefaultSecgroupId() (string, error) { + secs, err := self.GetSecurityGroups("default", "") + if err != nil { + return "", errors.Wrapf(err, "GetSecurityGroups") + } + if len(secs) > 0 { + return secs[0].ID, nil + } + return "", fmt.Errorf("not found default security group") +} + +func (self *SRegion) CreateICloudFileSystem(opts *cloudprovider.FileSystemCraeteOptions) (cloudprovider.ICloudFileSystem, error) { + fs, err := self.CreateSfsTurbo(opts) + if err != nil { + return nil, errors.Wrapf(err, "CreateSfsTurbo") + } + return fs, nil +} + +func (self *SRegion) CreateSfsTurbo(opts *cloudprovider.FileSystemCraeteOptions) (*SfsTurbo, error) { + secId, err := self.GetSysDefaultSecgroupId() + if err != nil { + return nil, errors.Wrapf(err, "GetSysDefaultSecgroupId") + } + metadata := map[string]string{} + if strings.HasSuffix(opts.StorageType, ".enhanced") { + metadata["expand_type"] = "bandwidth" + } + params := map[string]interface{}{ + "share": map[string]interface{}{ + "name": opts.Name, + "share_proto": strings.ToUpper(opts.Protocol), + "share_type": strings.ToUpper(strings.TrimSuffix(opts.StorageType, ".enhanced")), + "size": opts.Capacity, + "availability_zone": opts.ZoneId, + "vpc_id": opts.VpcId, + "subnet_id": opts.NetworkId, + "security_group_id": secId, + "description": opts.Desc, + "metadata": metadata, + }, + } + resp, err := self.ecsClient.SfsTurbos.Create(jsonutils.Marshal(params)) + if err != nil { + return nil, errors.Wrapf(err, "Create") + } + id, err := resp.GetString("id") + if err != nil { + return nil, errors.Wrapf(err, "resp.GetString(id)") + } + return self.GetSfsTurbo(id) +} + +func (self *SRegion) GetICloudAccessGroups() ([]cloudprovider.ICloudAccessGroup, error) { + return []cloudprovider.ICloudAccessGroup{}, nil +} + +func (self *SRegion) CreateICloudAccessGroup(opts *cloudprovider.SAccessGroup) (cloudprovider.ICloudAccessGroup, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotSupported, "CreateICloudAccessGroup") +} + +func (self *SRegion) GetICloudAccessGroupById(id string) (cloudprovider.ICloudAccessGroup, error) { + return nil, errors.Wrapf(cloudprovider.ErrNotFound, "GetICloudAccessGroupById(%s)", id) +} + +type sMoutTarget struct { + sfs *SfsTurbo +} + +func (self *sMoutTarget) GetName() string { + return self.sfs.Name +} + +func (self *sMoutTarget) GetGlobalId() string { + return self.sfs.GetGlobalId() +} + +func (self *sMoutTarget) GetAccessGroupId() string { + return "" +} + +func (self *sMoutTarget) GetDomainName() string { + return self.sfs.ExportLocation +} + +func (self *sMoutTarget) GetNetworkType() string { + return api.NETWORK_TYPE_VPC +} + +func (self *sMoutTarget) GetNetworkId() string { + return self.sfs.SubnetId +} + +func (self *sMoutTarget) GetVpcId() string { + return self.sfs.VpcId +} + +func (self *sMoutTarget) GetStatus() string { + return api.MOUNT_TARGET_STATUS_AVAILABLE +} + +func (self *sMoutTarget) Delete() error { + return nil +} diff --git a/pkg/multicloud/huaweistack/shell/bucket.go b/pkg/multicloud/huaweistack/shell/bucket.go new file mode 100644 index 0000000000..030c78c202 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/bucket.go @@ -0,0 +1,49 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "fmt" + + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/multicloud/objectstore" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + objectstore.S3Shell() + + type BucketNameOptions struct { + NAME string + } + shellutils.R(&BucketNameOptions{}, "bucket-head", "Head bucket", func(cli *huawei.SRegion, args *BucketNameOptions) error { + result, err := cli.HeadBucket(args.NAME) + if err != nil { + return err + } + printObject(result) + return nil + }) + + shellutils.R(&BucketNameOptions{}, "bucket-project", "Show bucket project", func(cli *huawei.SRegion, args *BucketNameOptions) error { + bucket, err := cli.GetIBucketByName(args.NAME) + if err != nil { + return err + } + fmt.Println("project: ", bucket.GetProjectId()) + return nil + }) + +} diff --git a/pkg/multicloud/huaweistack/shell/business.go b/pkg/multicloud/huaweistack/shell/business.go new file mode 100644 index 0000000000..e08454ee0a --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/business.go @@ -0,0 +1,33 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type AccountBalanceOptions struct { + } + shellutils.R(&AccountBalanceOptions{}, "balance", "Get account balance", func(cli *huawei.SRegion, args *AccountBalanceOptions) error { + result, err := cli.GetClient().QueryAccountBalance() + if err != nil { + return err + } + printObject(result) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/cloudgroup.go b/pkg/multicloud/huaweistack/shell/cloudgroup.go new file mode 100644 index 0000000000..ec1f2034ed --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/cloudgroup.go @@ -0,0 +1,97 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type CloudgroupListOptions struct { + DomainId string + Name string + } + shellutils.R(&CloudgroupListOptions{}, "cloud-group-list", "List cloudgroups", func(cli *huawei.SRegion, args *CloudgroupListOptions) error { + groups, err := cli.GetClient().GetGroups(args.DomainId, args.Name) + if err != nil { + return err + } + printList(groups, 0, 0, 0, nil) + return nil + }) + + type CloudgroupIdOptions struct { + ID string + } + + shellutils.R(&CloudgroupIdOptions{}, "cloud-group-delete", "Delete cloudgroup", func(cli *huawei.SRegion, args *CloudgroupIdOptions) error { + return cli.GetClient().DeleteGroup(args.ID) + }) + + type CloudgroupCreateOptions struct { + NAME string + Desc string + } + + shellutils.R(&CloudgroupCreateOptions{}, "cloud-group-create", "Create cloudgroup", func(cli *huawei.SRegion, args *CloudgroupCreateOptions) error { + group, err := cli.GetClient().CreateGroup(args.NAME, args.Desc) + if err != nil { + return err + } + printObject(group) + return nil + }) + + type GroupRoleListOptions struct { + GROUP_ID string + } + + shellutils.R(&GroupRoleListOptions{}, "cloud-group-policy-list", "List role", func(cli *huawei.SRegion, args *GroupRoleListOptions) error { + roles, err := cli.GetClient().GetGroupRoles(args.GROUP_ID) + if err != nil { + return err + } + printList(roles, 0, 0, 0, nil) + return nil + }) + + type GroupRoleOptions struct { + GROUP_ID string + ROLE_ID string + } + + shellutils.R(&GroupRoleOptions{}, "cloud-group-detach-policy", "Detach group role", func(cli *huawei.SRegion, args *GroupRoleOptions) error { + return cli.GetClient().DetachGroupRole(args.GROUP_ID, args.ROLE_ID) + }) + + shellutils.R(&GroupRoleOptions{}, "cloud-group-attach-policy", "Detach group role", func(cli *huawei.SRegion, args *GroupRoleOptions) error { + return cli.GetClient().AttachGroupRole(args.GROUP_ID, args.ROLE_ID) + }) + + type GroupUserListOptions struct { + GROUP_ID string + } + + shellutils.R(&GroupUserListOptions{}, "cloud-group-user-list", "List user", func(cli *huawei.SRegion, args *GroupUserListOptions) error { + users, err := cli.GetClient().GetGroupUsers(args.GROUP_ID) + if err != nil { + return err + } + printList(users, 0, 0, 0, nil) + return nil + }) + +} diff --git a/pkg/multicloud/huaweistack/shell/clouduser.go b/pkg/multicloud/huaweistack/shell/clouduser.go new file mode 100644 index 0000000000..52f77a6ffe --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/clouduser.go @@ -0,0 +1,89 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ClouduserListOptions struct { + Name string + } + shellutils.R(&ClouduserListOptions{}, "cloud-user-list", "List cloudusers", func(cli *huawei.SRegion, args *ClouduserListOptions) error { + users, err := cli.GetClient().GetCloudusers(args.Name) + if err != nil { + return err + } + printList(users, 0, 0, 0, nil) + return nil + }) + + type ClouduserIdOptions struct { + ID string + } + + shellutils.R(&ClouduserIdOptions{}, "cloud-user-delete", "Delete clouduser", func(cli *huawei.SRegion, args *ClouduserIdOptions) error { + return cli.GetClient().DeleteClouduser(args.ID) + }) + + shellutils.R(&ClouduserIdOptions{}, "cloud-user-group-list", "List clouduser groups", func(cli *huawei.SRegion, args *ClouduserIdOptions) error { + groups, err := cli.GetClient().ListUserGroups(args.ID) + if err != nil { + return err + } + printList(groups, 0, 0, 0, nil) + return nil + }) + + type ClouduserCreateOptions struct { + NAME string + Password string + Desc string + } + + shellutils.R(&ClouduserCreateOptions{}, "cloud-user-create", "Create clouduser", func(cli *huawei.SRegion, args *ClouduserCreateOptions) error { + user, err := cli.GetClient().CreateClouduser(args.NAME, args.Password, args.Desc) + if err != nil { + return err + } + printObject(user) + return nil + }) + + type RoleListOptions struct { + DomainId string + Name string + } + + shellutils.R(&RoleListOptions{}, "cloud-policy-list", "List role", func(cli *huawei.SRegion, args *RoleListOptions) error { + roles, err := cli.GetClient().GetRoles(args.DomainId, args.Name) + if err != nil { + return err + } + printList(roles, 0, 0, 0, nil) + return nil + }) + + type ClouduserResetPassword struct { + ID string + PASSWORD string + } + + shellutils.R(&ClouduserResetPassword{}, "cloud-user-reset-password", "Reset clouduser password", func(cli *huawei.SRegion, args *ClouduserResetPassword) error { + return cli.GetClient().ResetClouduserPassword(args.ID, args.PASSWORD) + }) +} diff --git a/pkg/multicloud/huaweistack/shell/dbinstance.go b/pkg/multicloud/huaweistack/shell/dbinstance.go new file mode 100644 index 0000000000..d9897bf37b --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/dbinstance.go @@ -0,0 +1,97 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DBInstanceListOptions struct { + } + shellutils.R(&DBInstanceListOptions{}, "dbinstance-list", "List dbinstances", func(cli *huawei.SRegion, args *DBInstanceListOptions) error { + dbinstances, err := cli.GetDBInstances() + if err != nil { + return err + } + printList(dbinstances, 0, 0, 0, nil) + return nil + }) + + type DBInstanceIdOptions struct { + ID string `help:"DBInstance ID"` + } + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-open-public-connection", "Open dbinstance public connection", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { + return cli.PublicConnectionAction(args.ID, "openRC") + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-close-public-connection", "Close dbinstance public connection", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { + return cli.PublicConnectionAction(args.ID, "closeRC") + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-show", "Show dbinstance", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { + dbinstance, err := cli.GetDBInstance(args.ID) + if err != nil { + return err + } + printObject(dbinstance) + return nil + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-parameter-list", "Show dbinstance parameters", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { + parameters, err := cli.GetDBInstanceParameters(args.ID) + if err != nil { + return err + } + printList(parameters, 0, 0, 0, nil) + return nil + }) + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-backup-list", "Show dbinstance backups", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { + backups, err := cli.GetDBInstanceBackups(args.ID, "") + if err != nil { + return err + } + printList(backups, 0, 0, 0, nil) + return nil + }) + + type DBInstanceFlavorListOption struct { + ENGINE string `help:"DBInstance engine" choices:"MySQL|SQLServer|PostgreSQL"` + VERSION string `help:"DBInstance engine version" choices:"5.6|5.7|9.5|9.6|10.0|2014 SE|2014 EE|2016 SE|2016 EE|2008 R2 EE|2008 R2 WEB|2014 WEB|2016 WEB"` + } + + shellutils.R(&DBInstanceFlavorListOption{}, "dbinstance-flavor-list", "Show dbinstance backups", func(cli *huawei.SRegion, args *DBInstanceFlavorListOption) error { + flavors, err := cli.GetDBInstanceFlavors(args.ENGINE, args.VERSION) + if err != nil { + return err + } + printList(flavors, 0, 0, 0, nil) + return nil + }) + + type DBInstanceChangeConfigOptions struct { + INSTANCE string + InstanceType string + DiskSizeGB int + } + + shellutils.R(&DBInstanceChangeConfigOptions{}, "dbinstance-change-config", "Change dbinstance config", func(cli *huawei.SRegion, args *DBInstanceChangeConfigOptions) error { + return cli.ChangeDBInstanceConfig(args.INSTANCE, args.InstanceType, args.DiskSizeGB) + }) + +} diff --git a/pkg/multicloud/huaweistack/shell/dbinstance_account.go b/pkg/multicloud/huaweistack/shell/dbinstance_account.go new file mode 100644 index 0000000000..7804af97af --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/dbinstance_account.go @@ -0,0 +1,76 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DBInstanceIdOptions struct { + ID string `help:"DBInstance ID"` + } + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-account-list", "Show dbinstance accounts", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { + accounts, err := cli.GetDBInstanceAccounts(args.ID) + if err != nil { + return err + } + printList(accounts, 0, 0, 0, nil) + return nil + }) + + type DBInstanceAccountDeleteOptions struct { + INSTANCE string + ACCOUNT string + } + + shellutils.R(&DBInstanceAccountDeleteOptions{}, "dbinstance-account-delete", "Delete dbinstance account", func(cli *huawei.SRegion, args *DBInstanceAccountDeleteOptions) error { + return cli.DeleteDBInstanceAccount(args.INSTANCE, args.ACCOUNT) + }) + + type DBInstanceAccountCreateOptions struct { + INSTANCE string + ACCOUNT string + PASSWORD string + } + + shellutils.R(&DBInstanceAccountCreateOptions{}, "dbinstance-account-create", "Create dbinstance account", func(cli *huawei.SRegion, args *DBInstanceAccountCreateOptions) error { + return cli.CreateDBInstanceAccount(args.INSTANCE, args.ACCOUNT, args.PASSWORD) + }) + + type DBInstanceAccountRevokePrivilegeOptions struct { + INSTANCE string + ACCOUNT string + DATABASE string + } + + shellutils.R(&DBInstanceAccountRevokePrivilegeOptions{}, "dbinstance-account-revoke-provilege", "Revoke dbinstance account privilege", func(cli *huawei.SRegion, args *DBInstanceAccountRevokePrivilegeOptions) error { + return cli.RevokeDBInstancePrivilege(args.INSTANCE, args.ACCOUNT, args.DATABASE) + }) + + type DBInstanceAccountGrantPrivilegeOptions struct { + INSTANCE string + ACCOUNT string + DATABASE string + PRIVILEGE string `help:"database privilege" choices:"r|rw"` + } + + shellutils.R(&DBInstanceAccountGrantPrivilegeOptions{}, "dbinstance-account-grant-provilege", "Grant dbinstance account privilege", func(cli *huawei.SRegion, args *DBInstanceAccountGrantPrivilegeOptions) error { + return cli.GrantDBInstancePrivilege(args.INSTANCE, args.ACCOUNT, args.DATABASE, args.PRIVILEGE) + }) + +} diff --git a/pkg/multicloud/huaweistack/shell/dbinstance_database.go b/pkg/multicloud/huaweistack/shell/dbinstance_database.go new file mode 100644 index 0000000000..e02e757a8c --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/dbinstance_database.go @@ -0,0 +1,55 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DBInstanceIdOptions struct { + INSTANCE string `help:"DBInstance ID"` + } + + shellutils.R(&DBInstanceIdOptions{}, "dbinstance-database-list", "Show dbinstance databases", func(cli *huawei.SRegion, args *DBInstanceIdOptions) error { + databases, err := cli.GetDBInstanceDatabases(args.INSTANCE) + if err != nil { + return err + } + printList(databases, 0, 0, 0, nil) + return nil + }) + + type DBInstanceDatabaseDeleteOptions struct { + INSTANCE string + DATABASE string + } + + shellutils.R(&DBInstanceDatabaseDeleteOptions{}, "dbinstance-database-delete", "Delete dbinstance database", func(cli *huawei.SRegion, args *DBInstanceDatabaseDeleteOptions) error { + return cli.DeleteDBInstanceDatabase(args.INSTANCE, args.DATABASE) + }) + + type DBInstanceDatabaseCreateOptions struct { + INSTANCE string + DATABASE string + CHARACTER_SET string + } + + shellutils.R(&DBInstanceDatabaseCreateOptions{}, "dbinstance-database-create", "Create dbinstance database", func(cli *huawei.SRegion, args *DBInstanceDatabaseCreateOptions) error { + return cli.CreateDBInstanceDatabase(args.INSTANCE, args.DATABASE, args.CHARACTER_SET) + }) + +} diff --git a/pkg/multicloud/huaweistack/shell/disk.go b/pkg/multicloud/huaweistack/shell/disk.go new file mode 100644 index 0000000000..647609bfee --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/disk.go @@ -0,0 +1,54 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type DiskListOptions struct { + Zone string `help:"Zone ID"` + } + shellutils.R(&DiskListOptions{}, "disk-list", "List disks", func(cli *huawei.SRegion, args *DiskListOptions) error { + disks, e := cli.GetDisks(args.Zone) + if e != nil { + return e + } + printList(disks, 0, 0, 0, nil) + return nil + }) + + type DiskDeleteOptions struct { + ID string `help:"Disk ID"` + } + shellutils.R(&DiskDeleteOptions{}, "disk-delete", "List disks", func(cli *huawei.SRegion, args *DiskDeleteOptions) error { + e := cli.DeleteDisk(args.ID) + if e != nil { + return e + } + return nil + }) + + shellutils.R(&DiskListOptions{}, "disk-types", "List disk types", func(cli *huawei.SRegion, args *DiskListOptions) error { + ret, e := cli.GetDiskTypes() + if e != nil { + return e + } + printList(ret, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/doc.go b/pkg/multicloud/huaweistack/shell/doc.go new file mode 100644 index 0000000000..c9938dad78 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/doc.go @@ -0,0 +1,15 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell // import "yunion.io/x/onecloud/pkg/multicloud/huaweistack/shell" diff --git a/pkg/multicloud/huaweistack/shell/eip.go b/pkg/multicloud/huaweistack/shell/eip.go new file mode 100644 index 0000000000..bf9783a259 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/eip.go @@ -0,0 +1,69 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type EipListOptions struct { + } + shellutils.R(&EipListOptions{}, "eip-list", "List eips", func(cli *huawei.SRegion, args *EipListOptions) error { + eips, e := cli.GetEips() + if e != nil { + return e + } + printList(eips, 0, 0, 0, nil) + return nil + }) + + type EipAllocateOptions struct { + NAME string `help:"eip name"` + BW int `help:"Bandwidth limit in Mbps"` + BGP string `help:"bgp type" choices:"5_telcom|5_union|5_bgp|5_sbgp"` + ProjectId string + } + shellutils.R(&EipAllocateOptions{}, "eip-create", "Allocate an EIP", func(cli *huawei.SRegion, args *EipAllocateOptions) error { + eip, err := cli.AllocateEIP(args.NAME, args.BW, huawei.InternetChargeByTraffic, args.BGP, args.ProjectId) + if err != nil { + return err + } + printObject(eip) + return nil + }) + + type EipReleaseOptions struct { + ID string `help:"EIP allocation ID"` + } + shellutils.R(&EipReleaseOptions{}, "eip-delete", "Release an EIP", func(cli *huawei.SRegion, args *EipReleaseOptions) error { + err := cli.DeallocateEIP(args.ID) + return err + }) + + type EipAssociateOptions struct { + ID string `help:"EIP allocation ID"` + INSTANCE string `help:"Instance ID"` + } + shellutils.R(&EipAssociateOptions{}, "eip-associate", "Associate an EIP", func(cli *huawei.SRegion, args *EipAssociateOptions) error { + err := cli.AssociateEip(args.ID, args.INSTANCE) + return err + }) + shellutils.R(&EipAssociateOptions{}, "eip-dissociate", "Dissociate an EIP", func(cli *huawei.SRegion, args *EipAssociateOptions) error { + err := cli.DissociateEip(args.ID, args.INSTANCE) + return err + }) +} diff --git a/pkg/multicloud/huaweistack/shell/elasticcache.go b/pkg/multicloud/huaweistack/shell/elasticcache.go new file mode 100644 index 0000000000..8e23e83403 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/elasticcache.go @@ -0,0 +1,69 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ElasticcacheListOptions struct { + } + shellutils.R(&ElasticcacheListOptions{}, "dcs-list", "List elasticcaches", func(cli *huawei.SRegion, args *ElasticcacheListOptions) error { + instances, e := cli.GetElasticCaches() + if e != nil { + return e + } + printList(instances, len(instances), 0, 0, []string{}) + return nil + }) + + type ElasticcacheIdOptions struct { + ID string `help:"ID of instances to show"` + } + shellutils.R(&ElasticcacheIdOptions{}, "dcs-show", "Show elasticcache", func(cli *huawei.SRegion, args *ElasticcacheIdOptions) error { + instance, err := cli.GetElasticCache(args.ID) + if err != nil { + return err + } + printObject(instance) + return nil + }) + + type ElasticcacheBackupsListOptions struct { + ID string `help:"ID of instances to show"` + StartTime string `help:"backup start time. format: 20060102150405"` + EndTime string `help:"backup end time. format: 20060102150405 "` + } + + shellutils.R(&ElasticcacheBackupsListOptions{}, "dcs-backup-list", "List elasticcache backups", func(cli *huawei.SRegion, args *ElasticcacheBackupsListOptions) error { + backups, err := cli.GetElasticCacheBackups(args.ID, args.StartTime, args.EndTime) + if err != nil { + return err + } + printList(backups, 0, 0, 0, []string{}) + return nil + }) + + shellutils.R(&ElasticcacheIdOptions{}, "dcs-parameter-list", "List elasticcache parameters", func(cli *huawei.SRegion, args *ElasticcacheIdOptions) error { + parameters, err := cli.GetElasticCacheParameters(args.ID) + if err != nil { + return err + } + printList(parameters, 0, 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/endpoints.go b/pkg/multicloud/huaweistack/shell/endpoints.go new file mode 100644 index 0000000000..a00f1ea0f8 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/endpoints.go @@ -0,0 +1,16 @@ +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type EndpointsListOptions struct { + } + shellutils.R(&EndpointsListOptions{}, "endpoint-list", "List endpoints", func(cli *huawei.SRegion, args *EndpointsListOptions) error { + regions, _ := cli.GetEndpoints() + printList(regions, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/enterpriseprojects.go b/pkg/multicloud/huaweistack/shell/enterpriseprojects.go new file mode 100644 index 0000000000..ccedd4f5a2 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/enterpriseprojects.go @@ -0,0 +1,48 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type EnterpriseProjectListOptions struct { + } + shellutils.R(&EnterpriseProjectListOptions{}, "enterprise-project-list", "List enterprise projects", func(cli *huawei.SRegion, args *EnterpriseProjectListOptions) error { + projects, err := cli.GetClient().GetEnterpriseProjects() + if err != nil { + return err + } + printList(projects, 0, 0, 0, nil) + return nil + }) + + type EnterpriseProjectCreateOptions struct { + NAME string + Desc string + } + + shellutils.R(&EnterpriseProjectCreateOptions{}, "enterprise-project-create", "Create enterprise project", func(cli *huawei.SRegion, args *EnterpriseProjectCreateOptions) error { + project, err := cli.GetClient().CreateExterpriseProject(args.NAME, args.Desc) + if err != nil { + return err + } + printObject(project) + return nil + }) + +} diff --git a/pkg/multicloud/huaweistack/shell/events.go b/pkg/multicloud/huaweistack/shell/events.go new file mode 100644 index 0000000000..cf9556adb9 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/events.go @@ -0,0 +1,37 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "time" + + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type EventListOptions struct { + Start time.Time + End time.Time + } + shellutils.R(&EventListOptions{}, "event-list", "List events", func(cli *huawei.SRegion, args *EventListOptions) error { + events, err := cli.GetEvents(args.Start, args.End) + if err != nil { + return err + } + printList(events, 0, 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/filesystem.go b/pkg/multicloud/huaweistack/shell/filesystem.go new file mode 100644 index 0000000000..e25b0e9d30 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/filesystem.go @@ -0,0 +1,60 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/cloudprovider" + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type FileSystemListOptions struct { + } + shellutils.R(&FileSystemListOptions{}, "file-system-list", "List FileSystem", func(cli *huawei.SRegion, args *FileSystemListOptions) error { + sfs, err := cli.GetSfsTurbos() + if err != nil { + return err + } + printList(sfs, 0, 0, 0, []string{}) + return nil + }) + + type FileSystemIdOptions struct { + ID string `help:"File System ID"` + } + shellutils.R(&FileSystemIdOptions{}, "file-system-delete", "Delete filesystem", func(cli *huawei.SRegion, args *FileSystemIdOptions) error { + return cli.DeleteSfsTurbo(args.ID) + }) + + shellutils.R(&FileSystemIdOptions{}, "file-system-show", "Show filesystem", func(cli *huawei.SRegion, args *FileSystemIdOptions) error { + fs, err := cli.GetSfsTurbo(args.ID) + if err != nil { + return err + } + printObject(fs) + return nil + }) + + shellutils.R(&cloudprovider.FileSystemCraeteOptions{}, "file-system-create", "Create filesystem", func(cli *huawei.SRegion, args *cloudprovider.FileSystemCraeteOptions) error { + fs, err := cli.CreateSfsTurbo(args) + if err != nil { + return err + } + printObject(fs) + return nil + }) + +} diff --git a/pkg/multicloud/huaweistack/shell/iam.go b/pkg/multicloud/huaweistack/shell/iam.go new file mode 100644 index 0000000000..68d11c53b0 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/iam.go @@ -0,0 +1,35 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "fmt" + + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type OwnerShowOptions struct { + } + shellutils.R(&OwnerShowOptions{}, "owner-show", "Get aksk owner id", func(cli *huawei.SRegion, args *OwnerShowOptions) error { + result, err := cli.GetClient().GetOwnerId() + if err != nil { + return err + } + fmt.Println(result) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/image.go b/pkg/multicloud/huaweistack/shell/image.go new file mode 100644 index 0000000000..21c2b6a365 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/image.go @@ -0,0 +1,56 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ImageListOptions struct { + Status string `help:"image status type" choices:"queued|saving|deleted|killed|active"` + Owner string `help:"Owner type" choices:"gold|private|shared"` + // Id []string `help:"Image ID"` + Name string `help:"image name"` + // Marker string `help:"marker"` + // Limit int `help:"page Limit"` + Env string `help:"virtualization env, e.g. FusionCompute, Ironic" choices:"FusionCompute|Ironic"` + } + shellutils.R(&ImageListOptions{}, "image-list", "List images", func(cli *huawei.SRegion, args *ImageListOptions) error { + images, e := cli.GetImages(args.Status, huawei.TImageOwnerType(args.Owner), args.Name, args.Env) + if e != nil { + return e + } + printList(images, 0, 0, 0, []string{}) + return nil + }) + + type ImageDeleteOptions struct { + ID string `help:"ID or Name to delete"` + } + shellutils.R(&ImageDeleteOptions{}, "image-delete", "Delete image", func(cli *huawei.SRegion, args *ImageDeleteOptions) error { + return cli.DeleteImage(args.ID) + }) + + shellutils.R(&ImageDeleteOptions{}, "image-show", "Show image", func(cli *huawei.SRegion, args *ImageDeleteOptions) error { + img, err := cli.GetImage(args.ID) + if err != nil { + return err + } + printObject(img) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/instance.go b/pkg/multicloud/huaweistack/shell/instance.go new file mode 100644 index 0000000000..b559b9cdd6 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/instance.go @@ -0,0 +1,231 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "context" + "fmt" + "strings" + + "yunion.io/x/onecloud/pkg/cloudprovider" + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type InstanceListOptions struct { + } + shellutils.R(&InstanceListOptions{}, "instance-list", "List intances", func(cli *huawei.SRegion, args *InstanceListOptions) error { + instances, e := cli.GetInstances() + if e != nil { + return e + } + printList(instances, 0, 0, 0, nil) + return nil + }) + + type InstanceDiskOperationOptions struct { + ID string `help:"instance ID"` + DISK string `help:"disk ID"` + } + + type InstanceDiskAttachOptions struct { + ID string `help:"instance ID"` + DISK string `help:"disk ID"` + DEVICE string `help:"disk device name. eg. /dev/sdb"` + } + + shellutils.R(&InstanceDiskAttachOptions{}, "instance-attach-disk", "Attach a disk to instance", func(cli *huawei.SRegion, args *InstanceDiskAttachOptions) error { + err := cli.AttachDisk(args.ID, args.DISK, args.DEVICE) + if err != nil { + return err + } + return nil + }) + + shellutils.R(&InstanceDiskOperationOptions{}, "instance-detach-disk", "Detach a disk to instance", func(cli *huawei.SRegion, args *InstanceDiskOperationOptions) error { + err := cli.DetachDisk(args.ID, args.DISK) + if err != nil { + return err + } + return nil + }) + + type InstanceOperationOptions struct { + ID string `help:"instance ID"` + } + shellutils.R(&InstanceOperationOptions{}, "instance-start", "Start a instance", func(cli *huawei.SRegion, args *InstanceOperationOptions) error { + err := cli.StartVM(args.ID) + if err != nil { + return err + } + return nil + }) + + type InstanceStopOptions struct { + ID string `help:"instance ID"` + Force bool `help:"Force stop instance"` + } + shellutils.R(&InstanceStopOptions{}, "instance-stop", "Stop a instance", func(cli *huawei.SRegion, args *InstanceStopOptions) error { + err := cli.StopVM(args.ID, args.Force) + if err != nil { + return err + } + return nil + }) + shellutils.R(&InstanceOperationOptions{}, "instance-delete", "Delete a instance", func(cli *huawei.SRegion, args *InstanceOperationOptions) error { + err := cli.DeleteVM(args.ID) + if err != nil { + return err + } + return nil + }) + + /* + server-change-config 更改系统配置 + server-reset + */ + type InstanceDeployOptions struct { + ID string `help:"instance ID"` + Name string `help:"new instance name"` + Hostname string `help:"new hostname"` + Keypair string `help:"Keypair Name"` + DeleteKeypair bool `help:"Remove SSH keypair"` + Password string `help:"new password"` + // ResetPassword bool `help:"Force reset password"` + Description string `help:"new instances description"` + } + + shellutils.R(&InstanceDeployOptions{}, "instance-deploy", "Deploy keypair/password to a stopped virtual server", func(cli *huawei.SRegion, args *InstanceDeployOptions) error { + err := cli.DeployVM(args.ID, args.Name, args.Password, args.Keypair, args.DeleteKeypair, args.Description) + if err != nil { + return err + } + return nil + }) + + type InstanceRebuildRootOptions struct { + ID string `help:"instance ID"` + UserId string `help:"instance user ID"` + Image string `help:"Image ID"` + Password string `help:"admin password"` + PublicKeyName string `help:"public key name"` + UserData string `help:"cloud-init user data"` + } + + shellutils.R(&InstanceRebuildRootOptions{}, "instance-rebuild-root", "Reinstall virtual server system image", func(cli *huawei.SRegion, args *InstanceRebuildRootOptions) error { + ctx := context.Background() + jobId, err := cli.ChangeRoot(ctx, args.UserId, args.ID, args.Image, args.Password, args.PublicKeyName, args.UserData) + if err != nil { + return err + } + fmt.Printf("ChangeRoot jobID is %s", jobId) + return nil + }) + + type InstanceChangeConfigOptions struct { + ID string `help:"instance ID"` + InstanceType string `help:"instance type"` + } + + shellutils.R(&InstanceChangeConfigOptions{}, "instance-change-config", "Deploy keypair/password to a stopped virtual server", func(cli *huawei.SRegion, args *InstanceChangeConfigOptions) error { + err := cli.ChangeVMConfig(args.ID, args.InstanceType) + if err != nil { + return err + } + return nil + }) + + type InstanceOrderUnsubscribeOptions struct { + ID string `help:"instance ID"` + DOMAIN string `help:"domain ID"` + } + + shellutils.R(&InstanceOrderUnsubscribeOptions{}, "instance-order-unsubscribe", "Unsubscribe a prepaid server", func(cli *huawei.SRegion, args *InstanceOrderUnsubscribeOptions) error { + instance, e := cli.GetInstanceByID(args.ID) + if e != nil { + return e + } + + _, err := cli.UnsubscribeInstance(instance.GetId(), args.DOMAIN) + if err != nil { + return err + } + return nil + }) + + type InstanceSaveImageOptions struct { + ID string `help:"Instance ID"` + IMAGE_NAME string `help:"Image name"` + Notes string `hlep:"Image desc"` + } + shellutils.R(&InstanceSaveImageOptions{}, "instance-save-image", "Save instance to image", func(cli *huawei.SRegion, args *InstanceSaveImageOptions) error { + opts := cloudprovider.SaveImageOptions{ + Name: args.IMAGE_NAME, + Notes: args.Notes, + } + image, err := cli.SaveImage(args.ID, &opts) + if err != nil { + return err + } + printObject(image) + return nil + }) + + type InstanceSetTagsOptions struct { + ID string `help:"Instance ID"` + Tags []string + } + shellutils.R(&InstanceSetTagsOptions{}, "instance-set-tags", "get intance metadata", func(cli *huawei.SRegion, args *InstanceSetTagsOptions) error { + tags := map[string]string{} + for i := range args.Tags { + splited := strings.Split(args.Tags[i], "=") + if len(splited) == 2 { + tags[splited[0]] = splited[1] + } + } + err := cli.CreateServerTags(args.ID, tags) + if err != nil { + return err + } + return nil + }) + + type InstanceDelTagsOptions struct { + ID string `help:"Instance ID"` + Tags []string + } + shellutils.R(&InstanceDelTagsOptions{}, "instance-del-tags", "del intance metadata", func(cli *huawei.SRegion, args *InstanceDelTagsOptions) error { + + err := cli.DeleteServerTags(args.ID, args.Tags) + if err != nil { + return err + } + return nil + }) + + type InstanceUpdateNameOptions struct { + ID string `help:"Instance ID"` + Name string + } + shellutils.R(&InstanceUpdateNameOptions{}, "instance-set-name", "set intance name", func(cli *huawei.SRegion, args *InstanceUpdateNameOptions) error { + + err := cli.UpdateVM(args.ID, args.Name) + if err != nil { + return err + } + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/instancetype.go b/pkg/multicloud/huaweistack/shell/instancetype.go new file mode 100644 index 0000000000..c62e1806d6 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/instancetype.go @@ -0,0 +1,36 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type InstanceMatchOptions struct { + CPU int `help:"CPU count"` + MEM int `help:"Memory in MB"` + Zone string `help:"Test in zone"` + } + shellutils.R(&InstanceMatchOptions{}, "instance-type-select", "Select matching instance types", func(cli *huawei.SRegion, args *InstanceMatchOptions) error { + instanceTypes, e := cli.GetMatchInstanceTypes(args.CPU, args.MEM, args.Zone) + if e != nil { + return e + } + printList(instanceTypes, 0, 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/keypair.go b/pkg/multicloud/huaweistack/shell/keypair.go new file mode 100644 index 0000000000..153f2efeb1 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/keypair.go @@ -0,0 +1,47 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + // todo: 需要进一步确认 + type KeyPairListOptions struct { + } + shellutils.R(&KeyPairListOptions{}, "keypair-list", "List keypairs", func(cli *huawei.SRegion, args *KeyPairListOptions) error { + keypairs, total, e := cli.GetKeypairs() + if e != nil { + return e + } + printList(keypairs, total, 0, 0, []string{}) + return nil + }) + + type KeyPairImportOptions struct { + NAME string `help:"Name of new keypair"` + PUBKEY string `help:"Public key string"` + } + shellutils.R(&KeyPairImportOptions{}, "keypair-import", "Import a keypair", func(cli *huawei.SRegion, args *KeyPairImportOptions) error { + keypair, err := cli.ImportKeypair(args.NAME, args.PUBKEY) + if err != nil { + return err + } + printObject(keypair) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/loadbalancer.go b/pkg/multicloud/huaweistack/shell/loadbalancer.go new file mode 100644 index 0000000000..41c6e426dd --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/loadbalancer.go @@ -0,0 +1,337 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/cloudprovider" + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ElbListOptions struct { + } + shellutils.R(&ElbListOptions{}, "elb-list", "List loadbalancers", func(cli *huawei.SRegion, args *ElbListOptions) error { + elbs, err := cli.GetILoadBalancers() + if err != nil { + return err + } + + printList(elbs, len(elbs), 0, 0, []string{}) + return nil + }) + + type ElbCreateOptions struct { + Name string `help:"loadblancer name"` + SUBNET string `help:"subnet id"` + PrivateIP string `help:"loadbalancer private ip address"` + EipID string `help:"loadbalancer public ip id"` + } + shellutils.R(&ElbCreateOptions{}, "elb-create", "create loadbalancer", func(cli *huawei.SRegion, args *ElbCreateOptions) error { + loadbalancer := &cloudprovider.SLoadbalancer{ + Name: args.Name, + NetworkIDs: []string{args.SUBNET}, + EipID: args.EipID, + Address: args.PrivateIP, + } + + elb, err := cli.CreateLoadBalancer(loadbalancer) + if err != nil { + return err + } + + printObject(elb) + return nil + }) + + type ElbDeleteOptions struct { + ID string `help:"loadblancer id"` + } + shellutils.R(&ElbDeleteOptions{}, "elb-delete", "delete loadbalancer", func(cli *huawei.SRegion, args *ElbDeleteOptions) error { + err := cli.DeleteLoadBalancer(args.ID) + if err != nil { + return err + } + + return nil + }) + + type ElbListenerListOptions struct { + ElbID string `help:"loadblancer id"` + } + shellutils.R(&ElbListenerListOptions{}, "elb-listener-list", "list loadbalancer listener", func(cli *huawei.SRegion, args *ElbListenerListOptions) error { + listeners, err := cli.GetLoadBalancerListeners(args.ElbID) + if err != nil { + return err + } + + printList(listeners, len(listeners), 0, 0, []string{}) + return nil + }) + + type ElbListenerCreateOptions struct { + Name string `help:"listener name"` + Desc string `help:"listener Description"` + Http2 bool `help:"http2 enable status"` + PoolID string `help:"default backend group id"` + CertId string `help:"default certification id"` + XForwardedFor bool `help:"XForwardedFor enable status"` + LISTENER_TYPE string `help:"listener type" choices:"tcp|udp|http|https"` + LISTENER_PORT int `help:"listener port"` + ELB_ID string `help:"loadbalancer id"` + } + shellutils.R(&ElbListenerCreateOptions{}, "elb-listener-create", "create loadbalancer listener", func(cli *huawei.SRegion, args *ElbListenerCreateOptions) error { + input := &cloudprovider.SLoadbalancerListener{ + Name: args.Name, + LoadbalancerID: args.ELB_ID, + ListenerType: args.LISTENER_TYPE, + ListenerPort: args.LISTENER_PORT, + BackendGroupID: args.PoolID, + EnableHTTP2: args.Http2, + CertificateID: args.CertId, + Description: args.Desc, + XForwardedFor: args.XForwardedFor, + } + listener, err := cli.CreateLoadBalancerListener(input) + if err != nil { + return err + } + + printObject(listener) + return nil + }) + + type ElbListenerUpdateOptions struct { + Name string `help:"listener name"` + Desc string `help:"listener Description"` + Http2 bool `help:"http2 enable status"` + PoolID string `help:"default backend group id"` + CertId string `help:"default certification id"` + XForwardedFor bool `help:"XForwardedFor enable status"` + LISTENER_ID string `help:"listener id"` + } + shellutils.R(&ElbListenerUpdateOptions{}, "elb-listener-update", "update loadbalancer listener", func(cli *huawei.SRegion, args *ElbListenerUpdateOptions) error { + input := &cloudprovider.SLoadbalancerListener{ + Name: args.Name, + BackendGroupID: args.PoolID, + EnableHTTP2: args.Http2, + CertificateID: args.CertId, + Description: args.Desc, + XForwardedFor: args.XForwardedFor, + } + + err := cli.UpdateLoadBalancerListener(args.LISTENER_ID, input) + if err != nil { + return err + } + + return nil + }) + + type ElbBackendGroupListOptions struct { + ElbID string `help:"loadbalancer id"` + } + shellutils.R(&ElbBackendGroupListOptions{}, "elb-backend-group-list", "List backend groups", func(cli *huawei.SRegion, args *ElbBackendGroupListOptions) error { + elbbg, err := cli.GetLoadBalancerBackendGroups(args.ElbID) + if err != nil { + return err + } + + printList(elbbg, len(elbbg), 0, 0, []string{}) + return nil + }) + + type ElbBackendGroupCreateOptions struct { + Name string `help:"backend group name"` + Desc string `help:"backend group description"` + PROTOCOL string `help:"backend group protocol" choices:"tcp|udp|http"` + ALGORITHM string `help:"backend group algorithm" choices:"rr|wlc|sch"` + ListenerID string `help:"listener id to binding"` + ElbID string `help:"loadbalancer id belong to"` + StickySessionType string `help:"sticky session type" choices:"insert|server"` + StickySessionCookieName string `help:"sticky session cookie name"` + StickySessionTimeout int `help:"sticky session timeout. udp/tcp 1~60. http 1~1440"` + HealthCheck bool `help:"enable health check"` + HealthCheckType string `help:"health check type protocol" choices:"tcp|udp|http"` + HealthCheckTimeout int `help:"health check timeout"` + HealthCheckDomain string `help:"health check domain"` + HealthCheckURI string `help:"health check uri path"` + HealthCheckInterval int `help:"health check interval"` + HealthCheckRise int `help:"health check max retries"` + } + shellutils.R(&ElbBackendGroupCreateOptions{}, "elb-backend-group-create", "Create backend groups", func(cli *huawei.SRegion, args *ElbBackendGroupCreateOptions) error { + var health *cloudprovider.SLoadbalancerHealthCheck + if args.HealthCheck { + health = &cloudprovider.SLoadbalancerHealthCheck{ + HealthCheckType: args.HealthCheckType, + HealthCheckTimeout: args.HealthCheckTimeout, + HealthCheckDomain: args.HealthCheckDomain, + HealthCheckURI: args.HealthCheckURI, + HealthCheckInterval: args.HealthCheckInterval, + HealthCheckRise: args.HealthCheckRise, + } + } + + var sticky *cloudprovider.SLoadbalancerStickySession + if len(args.StickySessionType) > 0 { + sticky = &cloudprovider.SLoadbalancerStickySession{ + StickySessionCookie: args.StickySessionCookieName, + StickySessionType: args.StickySessionType, + StickySessionCookieTimeout: args.StickySessionTimeout, + } + } + + group := &cloudprovider.SLoadbalancerBackendGroup{ + Name: args.Name, + LoadbalancerID: args.ElbID, + ListenerID: args.ListenerID, + ListenType: args.PROTOCOL, + Scheduler: args.ALGORITHM, + StickySession: sticky, + HealthCheck: health, + } + + elbbg, err := cli.CreateLoadBalancerBackendGroup(group) + if err != nil { + return err + } + + printObject(elbbg) + return nil + }) + + type ElbBackendGroupUpdateOptions struct { + POOL_ID string `help:"backend group id"` + Name string `help:"backend group name"` + } + shellutils.R(&ElbBackendGroupUpdateOptions{}, "elb-backend-group-update", "Update backend groups", func(cli *huawei.SRegion, args *ElbBackendGroupUpdateOptions) error { + group := &cloudprovider.SLoadbalancerBackendGroup{ + Name: args.Name, + } + + elbbg, err := cli.UpdateLoadBalancerBackendGroup(args.POOL_ID, group) + if err != nil { + return err + } + + printObject(elbbg) + return nil + }) + + type ElbBackendGroupDeleteOptions struct { + POOL_ID string `help:"backend group id"` + } + shellutils.R(&ElbBackendGroupDeleteOptions{}, "elb-backend-group-delete", "Delete backend group", func(cli *huawei.SRegion, args *ElbBackendGroupDeleteOptions) error { + err := cli.DeleteLoadBalancerBackendGroup(args.POOL_ID) + if err != nil { + return err + } + + return nil + }) + + type ElbBackendAddOptions struct { + Name string `help:"backend name"` + POOL_ID string `help:"backend group id"` + SUBNET_ID string `help:"instance subnet id"` + ADDRESS string `help:"instance ip address"` + PORT int `help:"backend protocol port [1,65535]"` + Weight int `help:"backend weight [0,100]" default:"1"` + } + shellutils.R(&ElbBackendAddOptions{}, "elb-backend-add", "Add backend to backendgroup", func(cli *huawei.SRegion, args *ElbBackendAddOptions) error { + elbb, err := cli.AddLoadBalancerBackend(args.POOL_ID, args.SUBNET_ID, args.ADDRESS, args.PORT, args.Weight) + if err != nil { + return err + } + + printObject(elbb) + return nil + }) + + type ElbBackendListOptions struct { + POOL_ID string `help:"backend group id"` + } + shellutils.R(&ElbBackendListOptions{}, "elb-backend-list", "list backend", func(cli *huawei.SRegion, args *ElbBackendListOptions) error { + elbb, err := cli.GetLoadBalancerBackends(args.POOL_ID) + if err != nil { + return err + } + + printList(elbb, len(elbb), 0, 0, []string{}) + return nil + }) + + type ElbListenerPolicyListOptions struct { + ListenerID string `help:"listener id"` + } + shellutils.R(&ElbListenerPolicyListOptions{}, "elb-listener-policy-list", "List listener policies", func(cli *huawei.SRegion, args *ElbListenerPolicyListOptions) error { + elblp, err := cli.GetLoadBalancerPolicies(args.ListenerID) + if err != nil { + return err + } + + printList(elblp, len(elblp), 0, 0, []string{}) + return nil + }) + + type ElbListenerPolicyCreateOptions struct { + LISTENER_ID string `help:"listener id"` + Name string `help:"policy name"` + Domain string `help:"policy domain"` + Path string `help:"policy path"` + PoolID string `help:"backend group name"` + } + shellutils.R(&ElbListenerPolicyCreateOptions{}, "elb-listener-policy-create", "Create listener policy", func(cli *huawei.SRegion, args *ElbListenerPolicyCreateOptions) error { + rule := &cloudprovider.SLoadbalancerListenerRule{ + Name: args.Name, + Domain: args.Domain, + Path: args.Path, + BackendGroupID: args.PoolID, + } + + elblp, err := cli.CreateLoadBalancerPolicy(args.LISTENER_ID, rule) + if err != nil { + return err + } + + printObject(elblp) + return nil + }) + + type ElbListenerPolicyDeleteOptions struct { + POLICY_ID string `help:"policy id"` + } + shellutils.R(&ElbListenerPolicyDeleteOptions{}, "elb-listener-policy-delete", "Delete listener policy", func(cli *huawei.SRegion, args *ElbListenerPolicyDeleteOptions) error { + err := cli.DeleteLoadBalancerPolicy(args.POLICY_ID) + if err != nil { + return err + } + + return nil + }) + + type ElbListenerPolicyRuleListOptions struct { + POLICY_ID string `help:"policy id"` + } + shellutils.R(&ElbListenerPolicyRuleListOptions{}, "elb-listener-policyrule-list", "List listener policy rules", func(cli *huawei.SRegion, args *ElbListenerPolicyRuleListOptions) error { + elblpr, err := cli.GetLoadBalancerPolicyRules(args.POLICY_ID) + if err != nil { + return err + } + + printList(elblpr, len(elblpr), 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/monitor.go b/pkg/multicloud/huaweistack/shell/monitor.go new file mode 100644 index 0000000000..52616256ac --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/monitor.go @@ -0,0 +1,62 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/pkg/util/timeutils" + + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type MetricListOptions struct { + } + shellutils.R(&MetricListOptions{}, "metrics-list", "List metrics", func(cli *huawei.SRegion, args *MetricListOptions) error { + metrics, err := cli.GetMetrics() + if err != nil { + return err + } + printList(metrics, 0, 0, 0, nil) + return nil + }) + + type MetricDataOptions struct { + START int `help:"Start metrics"` + Count int `help:"Metric count" default:"1"` + SINCE string `help:"since"` + UNTIL string `help:"until"` + } + shellutils.R(&MetricDataOptions{}, "metrics-data-list", "List metrics", func(cli *huawei.SRegion, args *MetricDataOptions) error { + metrics, err := cli.GetMetrics() + if err != nil { + return err + } + since, err := timeutils.ParseTimeStr(args.SINCE) + if err != nil { + return err + } + until, err := timeutils.ParseTimeStr(args.UNTIL) + if err != nil { + return err + } + data, err := cli.GetMetricsData(metrics[args.START:args.START+args.Count], since, until) + if err != nil { + return err + } + printList(data, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/natdtable.go b/pkg/multicloud/huaweistack/shell/natdtable.go new file mode 100644 index 0000000000..ca268ecf0e --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/natdtable.go @@ -0,0 +1,34 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SNatDTableOptions struct { + NatGatewayID string `help:"Nat Gateway ID" positional:"true"` + } + shellutils.R(&SNatDTableOptions{}, "dnat-table-list", "List dnat table", func(region *huawei.SRegion, args *SNatDTableOptions) error { + DNatTable, err := region.GetNatDTable(args.NatGatewayID) + if err != nil { + return err + } + printList(DNatTable, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/natgateway.go b/pkg/multicloud/huaweistack/shell/natgateway.go new file mode 100644 index 0000000000..d84180aa08 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/natgateway.go @@ -0,0 +1,141 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/cloudprovider" + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type NatGatewayOptions struct { + NatGatewayID string `help:"Nat Gateway ID"` + VpcID string `help:"Vpc ID"` + } + shellutils.R(&NatGatewayOptions{}, "nat-list", "List nat gateway", func(region *huawei.SRegion, args *NatGatewayOptions) error { + natGateways, err := region.GetNatGateways(args.VpcID, args.NatGatewayID) + if err != nil { + return err + } + printList(natGateways, 0, 0, 0, nil) + return nil + }) + + type NatGatewayIdOptions struct { + ID string `help:"Nat Id"` + } + + shellutils.R(&NatGatewayIdOptions{}, "nat-delete", "Delete nat gateways", func(cli *huawei.SRegion, args *NatGatewayIdOptions) error { + return cli.DeleteNatGateway(args.ID) + }) + + shellutils.R(&NatGatewayIdOptions{}, "nat-show", "Show nat gateways", func(cli *huawei.SRegion, args *NatGatewayIdOptions) error { + nat, err := cli.GetNatGateway(args.ID) + if err != nil { + return err + } + printObject(nat) + return nil + }) + + type SCreateDNatOptions struct { + GatewayID string `help:"Nat Gateway ID" positional:"true"` + Protocol string `help:"Protocol(tcp/udp)" positional:"true"` + ExternalIPID string `help:"External IP ID" positional:"true"` + ExternalPort int `help:"External Port" positional:"true"` + InternalIP string `help:"Internal IP" positional:"true"` + InternalPort int `help:"Nat Gateway ID" positional:"true"` + } + shellutils.R(&SCreateDNatOptions{}, "dnat-create", "Create dnat", func(region *huawei.SRegion, args *SCreateDNatOptions) error { + rule := cloudprovider.SNatDRule{ + Protocol: args.Protocol, + ExternalIPID: args.ExternalIPID, + ExternalPort: args.ExternalPort, + InternalIP: args.InternalIP, + InternalPort: args.InternalPort, + } + dnat, err := region.CreateNatDEntry(rule, args.GatewayID) + if err != nil { + return err + } + printObject(dnat) + return nil + }) + + type SCreateSNatOptions struct { + GatewayID string `help:"Nat Gateway ID" positional:"true"` + SourceCIDR string `help:"Source cidr" positional:"true"` + ExternalIPID string `help:"External IP ID" positional:"true"` + } + shellutils.R(&SCreateSNatOptions{}, "snat-create", "Create snat", func(region *huawei.SRegion, args *SCreateSNatOptions) error { + rule := cloudprovider.SNatSRule{ + SourceCIDR: args.SourceCIDR, + ExternalIPID: args.ExternalIPID, + } + snat, err := region.CreateNatSEntry(rule, args.GatewayID) + if err != nil { + return err + } + printObject(snat) + return nil + }) + + type SShowSNatOptions struct { + NatID string `help:"SNat ID" positional:"true"` + } + shellutils.R(&SShowSNatOptions{}, "snat-show", "Show snat", func(region *huawei.SRegion, args *SShowSNatOptions) error { + snat, err := region.GetNatSEntryByID(args.NatID) + if err != nil { + return err + } + printObject(snat) + return nil + }) + + type SShowDNatOptions struct { + NatID string `help:"DNat ID" positional:"true"` + } + shellutils.R(&SShowDNatOptions{}, "dnat-show", "Show dnat", func(region *huawei.SRegion, args *SShowDNatOptions) error { + dnat, err := region.GetNatDEntryByID(args.NatID) + if err != nil { + return err + } + printObject(dnat) + return nil + }) + + type SDeleteSNatOptions struct { + NatID string `help:"SNat ID" positional:"true"` + } + shellutils.R(&SDeleteSNatOptions{}, "snat-delete", "Delete snat", func(region *huawei.SRegion, args *SDeleteSNatOptions) error { + err := region.DeleteNatSEntry(args.NatID) + if err != nil { + return err + } + return nil + }) + + type SDeleteDNatOptions struct { + NatID string `help:"DNat ID" positional:"true"` + } + shellutils.R(&SDeleteDNatOptions{}, "dnat-delete", "Delete dnat", func(region *huawei.SRegion, args *SDeleteDNatOptions) error { + err := region.DeleteNatDEntry(args.NatID) + if err != nil { + return err + } + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/natstable.go b/pkg/multicloud/huaweistack/shell/natstable.go new file mode 100644 index 0000000000..89a9b60eb8 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/natstable.go @@ -0,0 +1,34 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SNatSTableOptions struct { + NatGatewayID string `help:"Nat Gateway ID" positional:"true"` + } + shellutils.R(&SNatSTableOptions{}, "snat-table-list", "List snat table", func(region *huawei.SRegion, args *SNatSTableOptions) error { + sNatTable, err := region.GetNatSTable(args.NatGatewayID) + if err != nil { + return err + } + printList(sNatTable, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/network.go b/pkg/multicloud/huaweistack/shell/network.go new file mode 100644 index 0000000000..af1c5578f1 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/network.go @@ -0,0 +1,34 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type VSwitchListOptions struct { + Vpc string `help:"Vpc ID"` + } + shellutils.R(&VSwitchListOptions{}, "subnet-list", "List subnets", func(cli *huawei.SRegion, args *VSwitchListOptions) error { + vswitches, e := cli.GetNetwroks(args.Vpc) + if e != nil { + return e + } + printList(vswitches, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/obs.go b/pkg/multicloud/huaweistack/shell/obs.go new file mode 100644 index 0000000000..1503bd5c58 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/obs.go @@ -0,0 +1,75 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/printutils" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ObsBucketListOptions struct { + } + shellutils.R(&ObsBucketListOptions{}, "obs-list", "List all buckets", func(cli *huawei.SRegion, args *ObsBucketListOptions) error { + buckets, err := cli.GetIBuckets() + if err != nil { + return err + } + printList(buckets, 0, 0, 0, nil) + return nil + }) + shellutils.R(&ObsBucketListOptions{}, "bucket-list", "List all buckets", func(cli *huawei.SRegion, args *ObsBucketListOptions) error { + buckets, err := cli.GetIBuckets() + if err != nil { + return err + } + printutils.PrintGetterList(buckets, nil) + return nil + }) + + type ObsBucketShowOptions struct { + BUCKET string `help:"bucket name to show"` + } + shellutils.R(&ObsBucketShowOptions{}, "obs-show", "Show bucket detail", func(cli *huawei.SRegion, args *ObsBucketShowOptions) error { + bucket, err := cli.GetIBucketById(args.BUCKET) + if err != nil { + return err + } + printObject(bucket) + return nil + }) + + type ObsBucketCreateOptions struct { + BUCKET string `help:"bucket name to show"` + StorageClass string `help:"storage class"` + Acl string `help:"acl"` + } + shellutils.R(&ObsBucketCreateOptions{}, "obs-create", "Create new OBS bucket", func(cli *huawei.SRegion, args *ObsBucketCreateOptions) error { + err := cli.CreateIBucket(args.BUCKET, args.StorageClass, args.Acl) + if err != nil { + return err + } + return nil + }) + + shellutils.R(&ObsBucketShowOptions{}, "obs-delete", "Delete OBS bucket", func(cli *huawei.SRegion, args *ObsBucketShowOptions) error { + err := cli.DeleteIBucket(args.BUCKET) + if err != nil { + return err + } + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/order.go b/pkg/multicloud/huaweistack/shell/order.go new file mode 100644 index 0000000000..bde61f3df0 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/order.go @@ -0,0 +1,36 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type OrderListOptions struct { + OrderId string `help:"Order Id"` + ResourceIds []string `help:"ResourceIds"` + MainResource bool `help:"Main resource"` + } + shellutils.R(&OrderListOptions{}, "order-list", "List order", func(cli *huawei.SRegion, args *OrderListOptions) error { + orders, err := cli.GetOrderResources(args.OrderId, args.ResourceIds, args.MainResource) + if err != nil { + return err + } + printList(orders, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/port.go b/pkg/multicloud/huaweistack/shell/port.go new file mode 100644 index 0000000000..d16c50876d --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/port.go @@ -0,0 +1,34 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type PortListOptions struct { + InstanceId string + } + shellutils.R(&PortListOptions{}, "port-list", "List ports", func(cli *huawei.SRegion, args *PortListOptions) error { + ports, err := cli.GetPorts(args.InstanceId) + if err != nil { + return err + } + printList(ports, 0, 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/printutils.go b/pkg/multicloud/huaweistack/shell/printutils.go new file mode 100644 index 0000000000..1b46ec704a --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/printutils.go @@ -0,0 +1,25 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import "yunion.io/x/onecloud/pkg/util/printutils" + +func printList(data interface{}, total, offset, limit int, columns []string) { + printutils.PrintInterfaceList(data, total, offset, limit, columns) +} + +func printObject(obj interface{}) { + printutils.PrintInterfaceObject(obj) +} diff --git a/pkg/multicloud/huaweistack/shell/quota.go b/pkg/multicloud/huaweistack/shell/quota.go new file mode 100644 index 0000000000..8065a33b5a --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/quota.go @@ -0,0 +1,34 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type QuotaListOptions struct { + } + shellutils.R(&QuotaListOptions{}, "quota-list", "List Quotas", func(cli *huawei.SRegion, args *QuotaListOptions) error { + quotas, err := cli.GetQuotas() + if err != nil { + return err + } + printList(quotas, 0, 0, 0, nil) + return nil + }) + +} diff --git a/pkg/multicloud/huaweistack/shell/region.go b/pkg/multicloud/huaweistack/shell/region.go new file mode 100644 index 0000000000..9648d65b14 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/region.go @@ -0,0 +1,66 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "fmt" + + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type RegionListOptions struct { + } + shellutils.R(&RegionListOptions{}, "region-list", "List regions", func(cli *huawei.SRegion, args *RegionListOptions) error { + regions := cli.GetClient().GetRegions() + printList(regions, 0, 0, 0, nil) + return nil + }) + + shellutils.R(&RegionListOptions{}, "project-list", "List projects", func(cli *huawei.SRegion, args *RegionListOptions) error { + projects, err := cli.GetClient().GetProjects() + if err != nil { + return err + } + printList(projects, 0, 0, 0, nil) + return nil + }) + + shellutils.R(&RegionListOptions{}, "domain-list", "List domains", func(cli *huawei.SRegion, args *RegionListOptions) error { + domains, err := cli.GetClient().GetDomains() + if err != nil { + return err + } + printList(domains, 0, 0, 0, nil) + return nil + }) + + shellutils.R(&RegionListOptions{}, "capabilities", "Get capabilities", func(cli *huawei.SRegion, args *RegionListOptions) error { + capabilities := cli.GetClient().GetCapabilities() + fmt.Println(capabilities) + return nil + }) + + shellutils.R(&RegionListOptions{}, "subaccount-list", "List account", func(cli *huawei.SRegion, args *RegionListOptions) error { + accounts, err := cli.GetClient().GetSubAccounts() + if err != nil { + return err + } + printList(accounts, 0, 0, 0, nil) + return nil + }) + +} diff --git a/pkg/multicloud/huaweistack/shell/role.go b/pkg/multicloud/huaweistack/shell/role.go new file mode 100644 index 0000000000..19c6d2de91 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/role.go @@ -0,0 +1,35 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type RoleListOptions struct { + DomainId string + Name string + } + shellutils.R(&RoleListOptions{}, "cloud-policy-list", "List cloudpolicy", func(cli *huawei.SRegion, args *RoleListOptions) error { + roles, err := cli.GetClient().GetRoles(args.DomainId, args.Name) + if err != nil { + return err + } + printList(roles, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/routetable.go b/pkg/multicloud/huaweistack/shell/routetable.go new file mode 100644 index 0000000000..c0bf7febe2 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/routetable.go @@ -0,0 +1,34 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type RouteTableListOption struct { + VpcId string `help:"Vpc ID"` + } + shellutils.R(&RouteTableListOption{}, "routetable-list", "List vpc route tables", func(cli *huawei.SRegion, args *RouteTableListOption) error { + routetables, err := cli.GetRouteTables(args.VpcId) + if err != nil { + return err + } + printList(routetables, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/saml_provider.go b/pkg/multicloud/huaweistack/shell/saml_provider.go new file mode 100644 index 0000000000..94c942d024 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/saml_provider.go @@ -0,0 +1,106 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SAMLProviderListOptions struct { + } + shellutils.R(&SAMLProviderListOptions{}, "saml-provider-list", "List saml provider", func(cli *huawei.SRegion, args *SAMLProviderListOptions) error { + result, err := cli.GetClient().ListSAMLProviders() + if err != nil { + return err + } + printList(result, 0, 0, 0, nil) + return nil + }) + + type SAMLProviderIdOptions struct { + ID string + } + + shellutils.R(&SAMLProviderIdOptions{}, "saml-provider-delete", "Delete saml provider", func(cli *huawei.SRegion, args *SAMLProviderIdOptions) error { + return cli.GetClient().DeleteSAMLProvider(args.ID) + }) + + shellutils.R(&SAMLProviderIdOptions{}, "saml-provider-protocol-list", "List saml provider protocol", func(cli *huawei.SRegion, args *SAMLProviderIdOptions) error { + result, err := cli.GetClient().GetSAMLProviderProtocols(args.ID) + if err != nil { + return err + } + printList(result, 0, 0, 0, nil) + return nil + }) + + type SAMLProviderProtocolDeleteOptions struct { + SAML_PROVIDER string + PROTOCOL string + } + + shellutils.R(&SAMLProviderProtocolDeleteOptions{}, "saml-provider-protocol-delete", "Delete saml provider protocol", func(cli *huawei.SRegion, args *SAMLProviderProtocolDeleteOptions) error { + return cli.GetClient().DeleteSAMLProviderProtocol(args.SAML_PROVIDER, args.PROTOCOL) + }) + + shellutils.R(&SAMLProviderIdOptions{}, "saml-provider-metadata-show", "Show saml provider metadata", func(cli *huawei.SRegion, args *SAMLProviderIdOptions) error { + result, err := cli.GetClient().GetSAMLProviderMetadata(args.ID) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type SAMLProviderMetadataOptions struct { + ID string + METADATA string + } + + shellutils.R(&SAMLProviderMetadataOptions{}, "saml-provider-metadata-update", "Update saml provider metadata", func(cli *huawei.SRegion, args *SAMLProviderMetadataOptions) error { + return cli.GetClient().UpdateSAMLProviderMetadata(args.ID, args.METADATA) + }) + + type MappingListOptions struct { + } + + shellutils.R(&MappingListOptions{}, "saml-provider-mapping-list", "List saml provider mapping", func(cli *huawei.SRegion, args *MappingListOptions) error { + mappings, err := cli.GetClient().ListSAMLProviderMappings() + if err != nil { + return err + } + printList(mappings, 0, 0, 0, nil) + return nil + }) + + type MappingInitOptions struct { + SAML_PROVIDER string + } + + shellutils.R(&MappingInitOptions{}, "saml-provider-mapping-init", "Init saml provider mapping", func(cli *huawei.SRegion, args *MappingInitOptions) error { + return cli.GetClient().InitSAMLProviderMapping(args.SAML_PROVIDER) + }) + + type MappingDeleteOptions struct { + ID string + } + + shellutils.R(&MappingDeleteOptions{}, "saml-provider-mapping-delete", "Delete saml provider mapping", func(cli *huawei.SRegion, args *MappingDeleteOptions) error { + return cli.GetClient().DeleteSAMLProviderMapping(args.ID) + }) + +} diff --git a/pkg/multicloud/huaweistack/shell/secgroup.go b/pkg/multicloud/huaweistack/shell/secgroup.go new file mode 100644 index 0000000000..ab47b01362 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/secgroup.go @@ -0,0 +1,90 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/secrules" + + "yunion.io/x/onecloud/pkg/cloudprovider" + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SecurityGroupListOptions struct { + VpcId string `help:"VPC ID"` + Name string `help:"Secgroup name"` + } + shellutils.R(&SecurityGroupListOptions{}, "security-group-list", "List security group", func(cli *huawei.SRegion, args *SecurityGroupListOptions) error { + secgrps, e := cli.GetSecurityGroups(args.VpcId, args.Name) + if e != nil { + return e + } + printList(secgrps, 0, 0, 0, nil) + return nil + }) + + type SecurityGroupShowOptions struct { + ID string `help:"ID or name of security group"` + } + shellutils.R(&SecurityGroupShowOptions{}, "security-group-show", "Show details of a security group", func(cli *huawei.SRegion, args *SecurityGroupShowOptions) error { + secgrp, err := cli.GetSecurityGroupDetails(args.ID) + if err != nil { + return err + } + printObject(secgrp) + return nil + }) + + type SecurityGroupCreateOptions struct { + NAME string `help:"secgroup name"` + VPC string `help:"ID of VPC"` + Desc string `help:"description"` + } + shellutils.R(&SecurityGroupCreateOptions{}, "security-group-create", "Create security group", func(cli *huawei.SRegion, args *SecurityGroupCreateOptions) error { + result, err := cli.CreateSecurityGroup(args.VPC, args.NAME, args.Desc) + if err != nil { + return err + } + printObject(result) + return nil + }) + + type SecurityGroupRuleIdOptions struct { + ID string + } + + shellutils.R(&SecurityGroupRuleIdOptions{}, "security-group-rule-delete", "Delete security group rule", func(cli *huawei.SRegion, args *SecurityGroupRuleIdOptions) error { + return cli.DeleteSecurityGroupRule(args.ID) + }) + + type SecurityGroupRuleCreateOptions struct { + SECGROUP_ID string + RULE string + } + + shellutils.R(&SecurityGroupRuleCreateOptions{}, "security-group-rule-create", "Create security group rule", func(cli *huawei.SRegion, args *SecurityGroupRuleCreateOptions) error { + _rule, err := secrules.ParseSecurityRule(args.RULE) + if err != nil { + return errors.Wrapf(err, "invalid rule %s", args.RULE) + } + rule := cloudprovider.SecurityRule{ + SecurityRule: *_rule, + } + return cli.CreateSecurityGroupRule(args.SECGROUP_ID, rule) + }) + +} diff --git a/pkg/multicloud/huaweistack/shell/service.go b/pkg/multicloud/huaweistack/shell/service.go new file mode 100644 index 0000000000..b7ef16b712 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/service.go @@ -0,0 +1,16 @@ +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ServicesListOptions struct { + } + shellutils.R(&ServicesListOptions{}, "service-list", "List services", func(cli *huawei.SRegion, args *ServicesListOptions) error { + services, _ := cli.GetServices() + printList(services, 0, 0, 0, nil) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/snapshot.go b/pkg/multicloud/huaweistack/shell/snapshot.go new file mode 100644 index 0000000000..a258b3148f --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/snapshot.go @@ -0,0 +1,55 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type SnapshotListOptions struct { + DiskId string `help:"Disk ID"` + Name string `help:"Snapshot Name"` + } + shellutils.R(&SnapshotListOptions{}, "snapshot-list", "List snapshot", func(cli *huawei.SRegion, args *SnapshotListOptions) error { + snapshots, err := cli.GetSnapshots(args.DiskId, args.Name) + if err != nil { + return err + } + printList(snapshots, 0, 0, 0, nil) + return nil + }) + + type SnapshotDeleteOptions struct { + ID string `help:"Snapshot ID"` + } + + shellutils.R(&SnapshotDeleteOptions{}, "snapshot-delete", "Delete snapshot", func(cli *huawei.SRegion, args *SnapshotDeleteOptions) error { + return cli.DeleteSnapshot(args.ID) + }) + + type SnapshotCreateOptions struct { + DiskId string `help:"Disk ID"` + Name string `help:"Snapeshot Name"` + Desc string `help:"Snapshot Desc"` + } + + shellutils.R(&SnapshotCreateOptions{}, "snapshot-create", "Create snapshot", func(cli *huawei.SRegion, args *SnapshotCreateOptions) error { + _, err := cli.CreateSnapshot(args.DiskId, args.Name, args.Desc) + return err + }) + +} diff --git a/pkg/multicloud/huaweistack/shell/vpc.go b/pkg/multicloud/huaweistack/shell/vpc.go new file mode 100644 index 0000000000..9958b35daa --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/vpc.go @@ -0,0 +1,122 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + "yunion.io/x/onecloud/pkg/cloudprovider" + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type VpcListOptions struct { + } + shellutils.R(&VpcListOptions{}, "vpc-list", "List vpcs", func(cli *huawei.SRegion, args *VpcListOptions) error { + vpcs, e := cli.GetVpcs() + if e != nil { + return e + } + printList(vpcs, 0, 0, 0, nil) + return nil + }) + + type VpcCreateOptions struct { + NAME string + CIDR string + Desc string + } + + shellutils.R(&VpcCreateOptions{}, "vpc-create", "Create vpc", func(cli *huawei.SRegion, args *VpcCreateOptions) error { + vpc, err := cli.CreateVpc(args.NAME, args.CIDR, args.Desc) + if err != nil { + return err + } + printObject(vpc) + return nil + }) + + type VpcIdOption struct { + ID string + } + + shellutils.R(&VpcIdOption{}, "vpc-delete", "Delete vpc", func(cli *huawei.SRegion, args *VpcIdOption) error { + return cli.DeleteVpc(args.ID) + }) + + type VpcPeeringListOPtion struct { + VPCID string + } + shellutils.R(&VpcPeeringListOPtion{}, "vpcPeering-list", "List vpcPeering", func(cli *huawei.SRegion, args *VpcPeeringListOPtion) error { + vpcPeerings, err := cli.GetVpcPeerings(args.VPCID) + if err != nil { + return err + } + printList(vpcPeerings, 0, 0, 0, nil) + return nil + }) + + type VpcPeeringShowOPtion struct { + VPCPEERINGID string + } + shellutils.R(&VpcPeeringShowOPtion{}, "vpcPeering-show", "show vpcPeering", func(cli *huawei.SRegion, args *VpcPeeringShowOPtion) error { + vpcPeering, err := cli.GetVpcPeering(args.VPCPEERINGID) + if err != nil { + return err + } + printObject(vpcPeering) + return nil + }) + + type VpcPeeringCreateOPtion struct { + NAME string + VPCID string + PEERVPCID string + PEEROWNERID string + } + shellutils.R(&VpcPeeringCreateOPtion{}, "vpcPeering-create", "create vpcPeering", func(cli *huawei.SRegion, args *VpcPeeringCreateOPtion) error { + opts := cloudprovider.VpcPeeringConnectionCreateOptions{} + opts.Name = args.NAME + opts.PeerVpcId = args.PEERVPCID + opts.PeerAccountId = args.PEEROWNERID + vpcPeering, err := cli.CreateVpcPeering(args.VPCID, &opts) + if err != nil { + return err + } + printObject(vpcPeering) + return nil + }) + + type VpcPeeringAcceptOPtion struct { + VPCPEERINGID string + } + shellutils.R(&VpcPeeringAcceptOPtion{}, "vpcPeering-accept", "Accept vpcPeering", func(cli *huawei.SRegion, args *VpcPeeringAcceptOPtion) error { + err := cli.AcceptVpcPeering(args.VPCPEERINGID) + if err != nil { + return err + } + return nil + }) + + type VpcPeeringDeleteOPtion struct { + VPCPEERINGID string + } + shellutils.R(&VpcPeeringDeleteOPtion{}, "vpcPeering-delete", "Delete vpcPeering", func(cli *huawei.SRegion, args *VpcPeeringDeleteOPtion) error { + err := cli.DeleteVpcPeering(args.VPCPEERINGID) + if err != nil { + return err + } + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/shell/zone.go b/pkg/multicloud/huaweistack/shell/zone.go new file mode 100644 index 0000000000..8f55f655f0 --- /dev/null +++ b/pkg/multicloud/huaweistack/shell/zone.go @@ -0,0 +1,34 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package shell + +import ( + huawei "yunion.io/x/onecloud/pkg/multicloud/huaweistack" + "yunion.io/x/onecloud/pkg/util/shellutils" +) + +func init() { + type ZoneListOptions struct { + } + shellutils.R(&ZoneListOptions{}, "zone-list", "List zones", func(cli *huawei.SRegion, args *ZoneListOptions) error { + zones, e := cli.GetIZones() + if e != nil { + return e + } + + printList(zones, 0, 0, 0, []string{}) + return nil + }) +} diff --git a/pkg/multicloud/huaweistack/snapshot.go b/pkg/multicloud/huaweistack/snapshot.go new file mode 100644 index 0000000000..b0590269fb --- /dev/null +++ b/pkg/multicloud/huaweistack/snapshot.go @@ -0,0 +1,189 @@ +// 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 huaweistack + +import ( + "fmt" + + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/multicloud" +) + +/* +限制: +https://support.huaweicloud.com/api-evs/zh-cn_topic_0058762427.html +1. 从快照创建云硬盘时,volume_type字段必须和快照源云硬盘保持一致。 +2. 当指定的云硬盘类型在avaliability_zone内不存在时,则创建云硬盘失败。 +*/ + +type SnapshotStatusType string + +const ( + SnapshotStatusCreating SnapshotStatusType = "creating" + SnapshotStatusAvailable SnapshotStatusType = "available" // 云硬盘快照创建成功,可以使用。 + SnapshotStatusError SnapshotStatusType = "error" // 云硬盘快照在创建过程中出现错误。 + SnapshotStatusDeleting SnapshotStatusType = "deleting" // 云硬盘快照处于正在删除的过程中。 + SnapshotStatusErrorDeleting SnapshotStatusType = "error_deleting" // 云硬盘快照在删除过程中出现错误 + SnapshotStatusRollbacking SnapshotStatusType = "rollbacking" // 云硬盘快照处于正在回滚数据的过程中。 + SnapshotStatusBackingUp SnapshotStatusType = "backing-up" // 通过快照创建备份,快照状态就会变为backing-up +) + +type Metadata struct { + SystemEnableActive string `json:"__system__enableActive"` // 如果为true。则表明是系统盘快照 +} + +// https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408624.html +type SSnapshot struct { + multicloud.SResourceBase + multicloud.HuaweiTags + region *SRegion + + Metadata Metadata `json:"metadata"` + CreatedAt string `json:"created_at"` + Description string `json:"description"` + ID string `json:"id"` + Name string `json:"name"` + OSExtendedSnapshotAttributesProgress string `json:"os-extended-snapshot-attributes:progress"` + OSExtendedSnapshotAttributesProjectID string `json:"os-extended-snapshot-attributes:project_id"` + Size int32 `json:"size"` // GB + Status string `json:"status"` + UpdatedAt string `json:"updated_at"` + VolumeID string `json:"volume_id"` +} + +func (self *SSnapshot) GetId() string { + return self.ID +} + +func (self *SSnapshot) GetName() string { + return self.Name +} + +func (self *SSnapshot) GetGlobalId() string { + return self.ID +} + +func (self *SSnapshot) GetStatus() string { + switch SnapshotStatusType(self.Status) { + case SnapshotStatusAvailable: + return api.SNAPSHOT_READY + case SnapshotStatusCreating: + return api.SNAPSHOT_CREATING + case SnapshotStatusDeleting: + return api.SNAPSHOT_DELETING + case SnapshotStatusErrorDeleting, SnapshotStatusError: + return api.SNAPSHOT_FAILED + case SnapshotStatusRollbacking: + return api.SNAPSHOT_ROLLBACKING + default: + return api.SNAPSHOT_UNKNOWN + } +} + +func (self *SSnapshot) Refresh() error { + snapshot, err := self.region.GetSnapshotById(self.GetId()) + if err != nil { + return err + } + + if err := jsonutils.Update(self, snapshot); err != nil { + return err + } + + return nil +} + +func (self *SSnapshot) IsEmulated() bool { + return false +} + +func (self *SSnapshot) GetSizeMb() int32 { + return self.Size * 1024 +} + +func (self *SSnapshot) GetDiskId() string { + return self.VolumeID +} + +func (self *SSnapshot) GetDiskType() string { + if self.Metadata.SystemEnableActive == "true" { + return api.DISK_TYPE_SYS + } else { + return api.DISK_TYPE_DATA + } +} + +func (self *SSnapshot) Delete() error { + if self.region == nil { + return fmt.Errorf("not init region for snapshot %s", self.GetId()) + } + return self.region.DeleteSnapshot(self.GetId()) +} + +// https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408627.html +func (self *SRegion) GetSnapshots(diskId string, snapshotName string) ([]SSnapshot, error) { + params := make(map[string]string) + + if len(diskId) > 0 { + params["volume_id"] = diskId + } + + if len(snapshotName) > 0 { + params["name"] = snapshotName + } + + snapshots := make([]SSnapshot, 0) + err := doListAllWithOffset(self.ecsClient.Snapshots.List, params, &snapshots) + for i := range snapshots { + snapshots[i].region = self + } + + return snapshots, err +} + +func (self *SRegion) GetSnapshotById(snapshotId string) (SSnapshot, error) { + var snapshot SSnapshot + err := DoGet(self.ecsClient.Snapshots.Get, snapshotId, nil, &snapshot) + snapshot.region = self + return snapshot, err +} + +// 不能删除以autobk_snapshot_为前缀的快照。 +// 当快照状态为available、error状态时,才可以删除。 +func (self *SRegion) DeleteSnapshot(snapshotId string) error { + return DoDelete(self.ecsClient.Snapshots.Delete, snapshotId, nil, nil) +} + +// https://support.huaweicloud.com/api-evs/zh-cn_topic_0051408624.html +// 目前已设置force字段。云硬盘处于挂载状态时,能强制创建快照。 +func (self *SRegion) CreateSnapshot(diskId, name, desc string) (string, error) { + params := jsonutils.NewDict() + snapshotObj := jsonutils.NewDict() + snapshotObj.Add(jsonutils.NewString(name), "name") + snapshotObj.Add(jsonutils.NewString(desc), "description") + snapshotObj.Add(jsonutils.NewString(diskId), "volume_id") + snapshotObj.Add(jsonutils.JSONTrue, "force") + params.Add(snapshotObj, "snapshot") + + snapshot := SSnapshot{} + err := DoCreate(self.ecsClient.Snapshots.Create, params, &snapshot) + return snapshot.ID, err +} + +func (self *SSnapshot) GetProjectId() string { + return "" +} diff --git a/pkg/multicloud/huaweistack/storage.go b/pkg/multicloud/huaweistack/storage.go new file mode 100644 index 0000000000..d5f5c4fed0 --- /dev/null +++ b/pkg/multicloud/huaweistack/storage.go @@ -0,0 +1,161 @@ +// 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 huaweistack + +import ( + "fmt" + "time" + + "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 SStorage struct { + zone *SZone + multicloud.SStorageBase + multicloud.HuaweiTags + storageType string // volume_type 目前支持“SSD”,“SAS”和“SATA”三种 +} + +func (self *SStorage) GetId() string { + return fmt.Sprintf("%s-%s-%s", self.zone.region.client.cpcfg.Id, self.zone.GetId(), self.storageType) +} + +func (self *SStorage) GetName() string { + return fmt.Sprintf("%s-%s-%s", self.zone.region.client.cpcfg.Name, self.zone.GetId(), self.storageType) +} + +func (self *SStorage) GetGlobalId() string { + return fmt.Sprintf("%s-%s-%s", self.zone.region.client.cpcfg.Id, self.zone.GetGlobalId(), self.storageType) +} + +func (self *SStorage) GetStatus() string { + return api.STORAGE_ONLINE +} + +func (self *SStorage) Refresh() error { + return nil +} + +func (self *SStorage) IsEmulated() bool { + return true +} + +func (self *SStorage) GetIStoragecache() cloudprovider.ICloudStoragecache { + return self.zone.region.getStoragecache() +} + +func (self *SStorage) GetIZone() cloudprovider.ICloudZone { + return self.zone +} + +func (self *SStorage) GetIDisks() ([]cloudprovider.ICloudDisk, error) { + disks, err := self.zone.region.GetDisks(self.zone.GetId()) + if err != nil { + return nil, err + } + + // 按storage type 过滤出disk + filtedDisks := make([]SDisk, 0) + for i := range disks { + disk := disks[i] + if disk.VolumeType == self.storageType { + filtedDisks = append(filtedDisks, disk) + } + } + + idisks := make([]cloudprovider.ICloudDisk, len(filtedDisks)) + for i := 0; i < len(filtedDisks); i += 1 { + filtedDisks[i].storage = self + idisks[i] = &filtedDisks[i] + } + return idisks, nil +} + +func (self *SStorage) GetStorageType() string { + return self.storageType +} + +func (self *SStorage) GetMediumType() string { + if self.storageType == api.STORAGE_HUAWEI_SSD { + return api.DISK_TYPE_SSD + } else { + return api.DISK_TYPE_ROTATE + } +} + +func (self *SStorage) GetCapacityMB() int64 { + return 0 // unlimited +} + +func (self *SStorage) GetCapacityUsedMB() int64 { + return 0 +} + +func (self *SStorage) GetStorageConf() jsonutils.JSONObject { + conf := jsonutils.NewDict() + return conf +} + +func (self *SStorage) GetEnabled() bool { + return true +} + +func (self *SStorage) CreateIDisk(conf *cloudprovider.DiskCreateConfig) (cloudprovider.ICloudDisk, error) { + diskId, err := self.zone.region.CreateDisk(self.zone.GetId(), self.storageType, conf.Name, conf.SizeGb, "", conf.Desc, conf.ProjectId) + if err != nil { + log.Errorf("createDisk fail %s", err) + return nil, err + } + disk, err := self.zone.region.GetDisk(diskId) + if err != nil { + log.Errorf("getDisk fail %s", err) + return nil, err + } + disk.storage = self + + err = cloudprovider.WaitStatus(disk, api.DISK_READY, 5*time.Second, 120*time.Second) + if err != nil { + return nil, err + } + + return disk, nil +} + +func (self *SStorage) GetIDiskById(idStr string) (cloudprovider.ICloudDisk, error) { + if len(idStr) == 0 { + log.Debugf("GetIDiskById disk id should not be empty") + return nil, cloudprovider.ErrNotFound + } + + if disk, err := self.zone.region.GetDisk(idStr); err != nil { + return nil, err + } else { + disk.storage = self + return disk, nil + } +} + +func (self *SStorage) GetMountPoint() string { + return "" +} + +func (self *SStorage) IsSysDiskStore() bool { + return true +} diff --git a/pkg/multicloud/huaweistack/storagecache.go b/pkg/multicloud/huaweistack/storagecache.go new file mode 100644 index 0000000000..25a7a222bf --- /dev/null +++ b/pkg/multicloud/huaweistack/storagecache.go @@ -0,0 +1,311 @@ +// 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 huaweistack + +import ( + "context" + "fmt" + "math" + "strings" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/auth" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/multicloud" + "yunion.io/x/onecloud/pkg/util/qemuimg" +) + +type SStoragecache struct { + multicloud.SResourceBase + multicloud.HuaweiTags + region *SRegion +} + +func GetBucketName(regionId string, imageId string) string { + return fmt.Sprintf("imgcache-%s-%s", strings.ToLower(regionId), imageId) +} + +func (self *SStoragecache) GetId() string { + return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetId()) +} + +func (self *SStoragecache) GetName() string { + return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Name, self.region.GetId()) +} + +func (self *SStoragecache) GetGlobalId() string { + return fmt.Sprintf("%s-%s", self.region.client.cpcfg.Id, self.region.GetGlobalId()) +} + +func (self *SStoragecache) GetStatus() string { + return "available" +} + +func (self *SStoragecache) Refresh() error { + return nil +} + +func (self *SStoragecache) IsEmulated() bool { + return false +} + +func (self *SStoragecache) GetICloudImages() ([]cloudprovider.ICloudImage, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (self *SStoragecache) GetICustomizedCloudImages() ([]cloudprovider.ICloudImage, error) { + imagesSelf, err := self.region.GetImages("", ImageOwnerSelf, "", EnvFusionCompute) + if err != nil { + return nil, errors.Wrapf(err, "GetImages") + } + + ret := []cloudprovider.ICloudImage{} + for i := range imagesSelf { + imagesSelf[i].storageCache = self + ret = append(ret, &imagesSelf[i]) + } + return ret, nil +} + +func (self *SStoragecache) GetIImageById(extId string) (cloudprovider.ICloudImage, error) { + image, err := self.region.GetImage(extId) + if err != nil { + return nil, errors.Wrap(err, "self.region.GetImage") + } + image.storageCache = self + return image, nil +} + +func (self *SStoragecache) GetPath() string { + return "" +} + +// 目前支持使用vhd、zvhd、vmdk、qcow2、raw、zvhd2、vhdx、qcow、vdi或qed格式镜像文件创建私有镜像。 +// 快速通道功能可快速完成镜像制作,但镜像文件需转换为raw或zvhd2格式并完成镜像优化。 +// https://support.huaweicloud.com/api-ims/zh-cn_topic_0083905788.html +func (self *SStoragecache) CreateIImage(snapshotId, imageName, osType, imageDesc string) (cloudprovider.ICloudImage, error) { + if imageId, err := self.region.createIImage(snapshotId, imageName, imageDesc); err != nil { + return nil, err + } else if image, err := self.region.GetImage(imageId); err != nil { + return nil, err + } else { + image.storageCache = self + iimage := make([]cloudprovider.ICloudImage, 1) + iimage[0] = image + if err := cloudprovider.WaitStatus(iimage[0], "avaliable", 15*time.Second, 3600*time.Second); err != nil { + return nil, err + } + return iimage[0], nil + } +} + +func (self *SStoragecache) DownloadImage(userCred mcclient.TokenCredential, imageId string, extId string, path string) (jsonutils.JSONObject, error) { + return self.downloadImage(userCred, imageId, extId) +} + +func (self *SStoragecache) downloadImage(userCred mcclient.TokenCredential, imageId string, extId string) (jsonutils.JSONObject, error) { + return nil, cloudprovider.ErrNotImplemented +} + +func (self *SStoragecache) UploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, isForce bool) (string, error) { + if len(image.ExternalId) > 0 { + log.Debugf("UploadImage: Image external ID exists %s", image.ExternalId) + + img, err := self.region.GetImage(image.ExternalId) + if err != nil { + log.Errorf("GetImageStatus error %s", err) + } + if img.Status == ImageStatusActive && !isForce { + return image.ExternalId, nil + } + } else { + log.Debugf("UploadImage: no external ID") + } + + return self.uploadImage(ctx, userCred, image, isForce) +} + +func (self *SStoragecache) uploadImage(ctx context.Context, userCred mcclient.TokenCredential, image *cloudprovider.SImageCreateOption, isForce bool) (string, error) { + bucketName := GetBucketName(self.region.GetId(), image.ImageId) + + exist, _ := self.region.IBucketExist(bucketName) + if !exist { + err := self.region.CreateIBucket(bucketName, "", "") + if err != nil { + return "", errors.Wrap(err, "CreateIBucket") + } + } + defer self.region.DeleteIBucket(bucketName) + + // upload to huawei cloud + s := auth.GetAdminSession(ctx, options.Options.Region, "") + meta, reader, sizeByte, err := modules.Images.Download(s, image.ImageId, string(qemuimg.VMDK), false) + if err != nil { + return "", errors.Wrap(err, "Images.Download") + } + log.Debugf("Images meta data %s", meta) + + minDiskMB, _ := meta.Int("min_disk") + minDiskGB := int64(math.Ceil(float64(minDiskMB) / 1024)) + // 在使用OBS桶的外部镜像文件制作镜像时生效且为必选字段。取值为40~1024GB。 + if minDiskGB < 40 { + minDiskGB = 40 + } else if minDiskGB > 1024 { + minDiskGB = 1024 + } + + bucket, err := self.region.GetIBucketByName(bucketName) + if err != nil { + return "", errors.Wrapf(err, "GetIBucketByName %s", bucketName) + } + + err = cloudprovider.UploadObject(context.Background(), bucket, image.ImageId, 0, reader, sizeByte, "", "", nil, false) + if err != nil { + return "", errors.Wrap(err, "cloudprovider.UploadObject") + } + + defer bucket.DeleteObject(context.Background(), image.ImageId) + + // check image name, avoid name conflict + imageBaseName := image.ImageId + if imageBaseName[0] >= '0' && imageBaseName[0] <= '9' { + imageBaseName = fmt.Sprintf("img%s", image.ImageId) + } + imageName := imageBaseName + nameIdx := 1 + + for { + _, err = self.region.GetImageByName(imageName) + if err != nil { + if errors.Cause(err) == cloudprovider.ErrNotFound { + break + } else { + return "", err + } + } + + imageName = fmt.Sprintf("%s-%d", imageBaseName, nameIdx) + nameIdx += 1 + log.Debugf("uploadImage Match remote name %s", imageName) + } + + jobId, err := self.region.ImportImageJob(imageName, image.OsDistribution, image.OsVersion, image.OsArch, bucketName, image.ImageId, int64(minDiskGB)) + + if err != nil { + log.Errorf("ImportImage error %s %s %s %s", jobId, image.ImageId, bucketName, err) + return "", err + } + + // timeout: 1hour = 3600 seconds + serviceType := self.region.ecsClient.Images.ServiceType() + err = self.region.waitTaskStatus(serviceType, jobId, TASK_SUCCESS, 15*time.Second, 3600*time.Second) + if err != nil { + log.Errorf("waitTaskStatus %s", err) + return "", err + } + + // https://support.huaweicloud.com/api-ims/zh-cn_topic_0022473688.html + return self.region.GetTaskEntityID(serviceType, jobId, "image_id") +} + +func (self *SRegion) getStoragecache() *SStoragecache { + if self.storageCache == nil { + self.storageCache = &SStoragecache{region: self} + } + return self.storageCache +} + +type SJob struct { + Status string `json:"status"` + Entities map[string]string `json:"entities"` + JobID string `json:"job_id"` + JobType string `json:"job_type"` + BeginTime string `json:"begin_time"` + EndTime string `json:"end_time"` + ErrorCode string `json:"error_code"` + FailReason string `json:"fail_reason"` +} + +// https://support.huaweicloud.com/api-ims/zh-cn_topic_0020092109.html +func (self *SRegion) createIImage(snapshotId, imageName, imageDesc string) (string, error) { + snapshot, err := self.GetSnapshotById(snapshotId) + if err != nil { + return "", err + } + + disk, err := self.GetDisk(snapshot.VolumeID) + if err != nil { + return "", err + } + + if disk.GetDiskType() != api.DISK_TYPE_SYS { + return "", fmt.Errorf("disk type err, expected disk type %s", api.DISK_TYPE_SYS) + } + + if len(disk.Attachments) == 0 { + return "", fmt.Errorf("disk is not attached.") + } + + imageObj := jsonutils.NewDict() + imageObj.Add(jsonutils.NewString(disk.Attachments[0].ServerID), "instance_id") + imageObj.Add(jsonutils.NewString(imageName), "name") + imageObj.Add(jsonutils.NewString(imageDesc), "description") + + ret, err := self.ecsClient.Images.PerformAction2("action", "", imageObj, "") + if err != nil { + return "", err + } + + job := SJob{} + jobId, err := ret.GetString("job_id") + querys := map[string]string{"service_type": self.ecsClient.Images.ServiceType()} + err = DoGet(self.ecsClient.Jobs.Get, jobId, querys, &job) + if err != nil { + return "", err + } + + if job.Status == "SUCCESS" { + imageId, exists := job.Entities["image_id"] + if exists { + return imageId, nil + } else { + return "", fmt.Errorf("image id not found in create image job %s", job.JobID) + } + } else { + return "", fmt.Errorf("create image failed, %s", job.FailReason) + } + +} + +func (self *SRegion) GetIStoragecaches() ([]cloudprovider.ICloudStoragecache, error) { + storageCache := self.getStoragecache() + return []cloudprovider.ICloudStoragecache{storageCache}, nil +} + +func (self *SRegion) GetIStoragecacheById(idstr string) (cloudprovider.ICloudStoragecache, error) { + storageCache := self.getStoragecache() + if storageCache.GetGlobalId() == idstr { + return storageCache, nil + } + return nil, cloudprovider.ErrNotFound +} diff --git a/pkg/multicloud/huaweistack/task.go b/pkg/multicloud/huaweistack/task.go new file mode 100644 index 0000000000..002a053a7a --- /dev/null +++ b/pkg/multicloud/huaweistack/task.go @@ -0,0 +1,104 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huaweistack + +import ( + "fmt" + "time" + + "yunion.io/x/log" +) + +func (self *SRegion) waitTaskStatus(serviceType string, taskId string, targetStatus string, interval time.Duration, timeout time.Duration) error { + start := time.Now() + for time.Now().Sub(start) < timeout { + status, err := self.GetTaskStatus(serviceType, taskId) + if err != nil { + return err + } + if status == targetStatus { + break + } else if status == TASK_FAIL { + return fmt.Errorf("task %s failed", taskId) + } else { + time.Sleep(interval) + } + } + return nil +} + +func (self *SRegion) GetTaskStatus(serviceType string, taskId string) (string, error) { + querys := map[string]string{"service_type": serviceType} + task, err := self.ecsClient.Jobs.Get(taskId, querys) + if err != nil { + return "", err + } + + status, err := task.GetString("status") + if status == TASK_FAIL { + log.Debugf("task %s failed: %s", taskId, task.String()) + } + + return status, err +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0022225398.html +// 数据结构 entities -> []job +func (self *SRegion) GetAllSubTaskEntityIDs(serviceType string, taskId string, entityKeyName string) ([]string, error) { + err := self.waitTaskStatus(serviceType, taskId, TASK_SUCCESS, 10*time.Second, 600*time.Second) + if err != nil { + return nil, err + } + + querys := map[string]string{"service_type": serviceType} + ret, err := self.ecsClient.Jobs.Get(taskId, querys) + if err != nil { + return nil, err + } + + entities, err := ret.GetArray("entities", "sub_jobs") + if err != nil { + return nil, err + } + + ids := make([]string, 0) + for i := range entities { + entity := entities[i] + rid, err := entity.GetString("entities", entityKeyName) + if err != nil { + return nil, err + } + + ids = append(ids, rid) + } + + return ids, nil +} + +// 数据结构 entities -> job +func (self *SRegion) GetTaskEntityID(serviceType string, taskId string, entityKeyName string) (string, error) { + err := self.waitTaskStatus(serviceType, taskId, TASK_SUCCESS, 10*time.Second, 600*time.Second) + if err != nil { + return "", err + } + + querys := map[string]string{"service_type": serviceType} + ret, err := self.ecsClient.Jobs.Get(taskId, querys) + if err != nil { + return "", err + } + + return ret.GetString("entities", entityKeyName) +} diff --git a/pkg/multicloud/huaweistack/traces.go b/pkg/multicloud/huaweistack/traces.go new file mode 100644 index 0000000000..0919b37071 --- /dev/null +++ b/pkg/multicloud/huaweistack/traces.go @@ -0,0 +1,131 @@ +// Copyright 2019 Yunion +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package huaweistack + +import ( + "fmt" + "strconv" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudprovider" +) + +type SUser struct { + Domain map[string]string + Name string + Id string +} + +type SEvent struct { + TraceId string + Code string + TraceName string + ResourceType string + ApiVersion string + SourceIp string + TraceType string + ServiceType string + EventType string + ProjectId string + Request string + Response string + TrackerName string + TraceStatus string + Time int64 + ResourceId string + ResourceName string + User SUser + RecordTime int64 +} + +func (event *SEvent) GetName() string { + if len(event.ResourceId) > 0 { + return event.ResourceId + } + if len(event.ResourceName) > 0 { + return event.ResourceName + } + return event.TraceName +} + +func (event *SEvent) GetService() string { + return event.ServiceType +} + +func (event *SEvent) GetAction() string { + return event.TraceName +} + +func (event *SEvent) GetResourceType() string { + return event.ResourceType +} + +func (event *SEvent) GetRequestId() string { + return event.TraceId +} + +func (event *SEvent) GetRequest() jsonutils.JSONObject { + return jsonutils.Marshal(event) +} + +func (event *SEvent) GetAccount() string { + return event.User.Name +} + +func (event *SEvent) IsSuccess() bool { + code, _ := strconv.Atoi(event.Code) + return code < 400 +} + +func (event *SEvent) GetCreatedAt() time.Time { + return time.Unix(event.Time/1000, event.Time%1000) +} + +func (self *SRegion) GetICloudEvents(start time.Time, end time.Time, withReadEvent bool) ([]cloudprovider.ICloudEvent, error) { + if !self.client.isMainProject { + return nil, cloudprovider.ErrNotSupported + } + events, err := self.GetEvents(start, end) + if err != nil { + return nil, err + } + iEvents := []cloudprovider.ICloudEvent{} + for i := range events { + iEvents = append(iEvents, &events[i]) + } + return iEvents, nil +} + +func (self *SRegion) GetEvents(start time.Time, end time.Time) ([]SEvent, error) { + events := []SEvent{} + params := map[string]string{} + if start.IsZero() { + start = time.Now().AddDate(0, 0, -7) + } + if end.IsZero() { + end = time.Now() + } + params["from"] = fmt.Sprintf("%d000", start.Unix()) + params["to"] = fmt.Sprintf("%d000", end.Unix()) + + err := doListAllWithMarker(self.ecsClient.Traces.List, params, &events) + if err != nil { + return nil, errors.Wrap(err, "doListAllWithMarker") + } + return events, nil +} diff --git a/pkg/multicloud/huaweistack/utils.go b/pkg/multicloud/huaweistack/utils.go new file mode 100644 index 0000000000..e8aa35bc47 --- /dev/null +++ b/pkg/multicloud/huaweistack/utils.go @@ -0,0 +1,270 @@ +// 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 huaweistack + +import ( + "fmt" + "reflect" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/utils" + + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/manager" + "yunion.io/x/onecloud/pkg/multicloud/huaweistack/client/responses" + "yunion.io/x/onecloud/pkg/util/httputils" +) + +// 常用的方法 +type listFunc func(querys map[string]string) (*responses.ListResult, error) +type getFunc func(id string, querys map[string]string) (jsonutils.JSONObject, error) +type createFunc func(params jsonutils.JSONObject) (jsonutils.JSONObject, error) +type updateFunc func(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) +type updateFunc2 func(ctx manager.IManagerContext, id string, spec string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) +type deleteFunc func(id string, params jsonutils.JSONObject) (jsonutils.JSONObject, error) +type deleteFunc2 func(ctx manager.IManagerContext, id string, spec string, queries map[string]string, params jsonutils.JSONObject, responseKey string) (jsonutils.JSONObject, error) +type listInCtxFunc func(ctx manager.IManagerContext, querys map[string]string) (*responses.ListResult, error) +type listInCtxWithSpecFunc func(ctx manager.IManagerContext, spec string, querys map[string]string, responseKey string) (*responses.ListResult, error) + +func unmarshalResult(resp jsonutils.JSONObject, respErr error, result interface{}, method string) error { + if respErr != nil { + switch e := respErr.(type) { + case *httputils.JSONClientError: + if (e.Code == 404 || utils.IsInStringArray(e.Class, NOT_FOUND_CODES)) && method != "POST" { + return cloudprovider.ErrNotFound + } + return e + default: + return e + } + } + + if result == nil { + return nil + } + + err := resp.Unmarshal(result) + if err != nil { + log.Errorf("unmarshal json error %s", err) + } + + return err +} + +var pageLimit = 100 + +// offset 表示的是页码 +func doListAllWithPagerOffset(doList listFunc, queries map[string]string, result interface{}) error { + startIndex := 0 + resultValue := reflect.Indirect(reflect.ValueOf(result)) + queries["limit"] = fmt.Sprintf("%d", pageLimit) + queries["offset"] = fmt.Sprintf("%d", startIndex) + for { + total, part, err := doListPart(doList, queries, result) + if err != nil { + return err + } + if (total > 0 && resultValue.Len() >= total) || (total == 0 && pageLimit > part) { + break + } + + startIndex++ + queries["offset"] = fmt.Sprintf("%d", startIndex) + } + return nil +} + +func doListAllWithNextLink(doList listFunc, querys map[string]string, result interface{}) error { + values := []jsonutils.JSONObject{} + for { + ret, err := doList(querys) + if err != nil { + return errors.Wrap(err, "doList") + } + values = append(values, ret.Data...) + if len(ret.NextLink) == 0 || ret.NextLink == "null" { + break + } + } + if result != nil { + return jsonutils.Update(result, values) + } + return nil +} + +func doListAllWithOffset(doList listFunc, queries map[string]string, result interface{}) error { + startIndex := 0 + resultValue := reflect.Indirect(reflect.ValueOf(result)) + queries["limit"] = fmt.Sprintf("%d", pageLimit) + queries["offset"] = fmt.Sprintf("%d", startIndex) + for { + total, part, err := doListPart(doList, queries, result) + if err != nil { + return err + } + if (total > 0 && resultValue.Len() >= total) || (total == 0 && pageLimit > part) { + break + } + queries["offset"] = fmt.Sprintf("%d", startIndex+resultValue.Len()) + } + return nil +} + +func doListAllWithPage(doList listFunc, queries map[string]string, result interface{}) error { + startIndex := 1 + resultValue := reflect.Indirect(reflect.ValueOf(result)) + queries["limit"] = fmt.Sprintf("%d", pageLimit) + queries["page"] = fmt.Sprintf("%d", startIndex) + for { + total, part, err := doListPart(doList, queries, result) + if err != nil { + return err + } + if (total > 0 && resultValue.Len() >= total) || (total == 0 && pageLimit > part) { + break + } + queries["page"] = fmt.Sprintf("%d", startIndex+resultValue.Len()) + } + return nil +} + +func doListAllWithMarker(doList listFunc, queries map[string]string, result interface{}) error { + resultValue := reflect.Indirect(reflect.ValueOf(result)) + queries["limit"] = fmt.Sprintf("%d", pageLimit) + for { + total, part, err := doListPart(doList, queries, result) + if err != nil { + return err + } + if (total > 0 && resultValue.Len() >= total) || (total == 0 && pageLimit > part) { + break + } + lastValue := resultValue.Index(resultValue.Len() - 1) + markerValue := lastValue.FieldByNameFunc(func(key string) bool { + if strings.ToLower(key) == "id" { + return true + } + return false + }) + queries["marker"] = markerValue.String() + } + return nil +} + +func doListAll(doList listFunc, queries map[string]string, result interface{}) error { + total, _, err := doListPart(doList, queries, result) + if err != nil { + return err + } + resultValue := reflect.Indirect(reflect.ValueOf(result)) + if total > 0 && resultValue.Len() < total { + log.Warningf("INCOMPLETE QUERY, total %d queried %d", total, resultValue.Len()) + } + return nil +} + +func doListPart(doList listFunc, queries map[string]string, result interface{}) (int, int, error) { + ret, err := doList(queries) + if err != nil { + return 0, 0, err + } + resultValue := reflect.Indirect(reflect.ValueOf(result)) + elemType := resultValue.Type().Elem() + for i := range ret.Data { + elemPtr := reflect.New(elemType) + err = ret.Data[i].Unmarshal(elemPtr.Interface()) + if err != nil { + return 0, 0, err + } + resultValue.Set(reflect.Append(resultValue, elemPtr.Elem())) + } + return ret.Total, len(ret.Data), nil +} + +func DoGet(doGet getFunc, id string, queries map[string]string, result interface{}) error { + if len(id) == 0 { + resultType := reflect.Indirect(reflect.ValueOf(result)).Type() + return errors.Wrap(cloudprovider.ErrNotFound, fmt.Sprintf(" Get %s id should not be empty", resultType.Name())) + } + + ret, err := doGet(id, queries) + return unmarshalResult(ret, err, result, "GET") +} + +func DoListInContext(listFunc listInCtxFunc, ctx manager.IManagerContext, querys map[string]string, result interface{}) error { + ret, err := listFunc(ctx, querys) + if err != nil { + return err + } + + obj := responses.ListResult2JSON(ret) + err = obj.Unmarshal(result, "data") + if err != nil { + log.Errorf("unmarshal json error %s", err) + return err + } + + return nil +} + +func DoCreate(createFunc createFunc, params jsonutils.JSONObject, result interface{}) error { + ret, err := createFunc(params) + return unmarshalResult(ret, err, result, "POST") +} + +func DoUpdate(updateFunc updateFunc, id string, params jsonutils.JSONObject, result interface{}) error { + ret, err := updateFunc(id, params) + return unmarshalResult(ret, err, result, "PUT") +} + +func DoUpdateWithSpec(updateFunc updateFunc2, id string, spec string, params jsonutils.JSONObject) error { + _, err := updateFunc(nil, id, spec, params, "") + return err +} + +func DoUpdateWithSpec2(updateFunc updateFunc2, id string, spec string, params jsonutils.JSONObject, result interface{}) error { + ret, err := updateFunc(nil, id, spec, params, "") + return unmarshalResult(ret, err, result, "PUT") +} + +func DoDelete(deleteFunc deleteFunc, id string, params jsonutils.JSONObject, result interface{}) error { + if len(id) == 0 { + return fmt.Errorf(" id should not be empty") + } + + ret, err := deleteFunc(id, params) + return unmarshalResult(ret, err, result, "DELETE") +} + +func DoDeleteWithSpec(deleteFunc deleteFunc2, ctx manager.IManagerContext, id string, spec string, queries map[string]string, params jsonutils.JSONObject) error { + if len(id) == 0 { + return fmt.Errorf(" id should not be empty") + } + + _, err := deleteFunc(ctx, id, spec, queries, params, "") + return err +} + +func ErrMessage(err error) string { + switch v := err.(type) { + case *httputils.JSONClientError: + return fmt.Sprintf("%d(%s):%s", v.Code, v.Class, v.Details) + default: + return err.Error() + } +} diff --git a/pkg/multicloud/huaweistack/vpc.go b/pkg/multicloud/huaweistack/vpc.go new file mode 100644 index 0000000000..dd0e0abcb5 --- /dev/null +++ b/pkg/multicloud/huaweistack/vpc.go @@ -0,0 +1,368 @@ +// 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 huaweistack + +import ( + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090625.html +type SVpc struct { + multicloud.SVpc + multicloud.HuaweiTags + + region *SRegion + + iwires []cloudprovider.ICloudWire + secgroups []cloudprovider.ICloudSecurityGroup + routeTables []cloudprovider.ICloudRouteTable + + ID string `json:"id"` + Name string `json:"name"` + CIDR string `json:"cidr"` + Status string `json:"status"` + EnterpriseProjectID string `json:"enterprise_project_id"` +} + +func (self *SVpc) addWire(wire *SWire) { + if self.iwires == nil { + self.iwires = make([]cloudprovider.ICloudWire, 0) + } + self.iwires = append(self.iwires, wire) +} + +func (self *SVpc) getWireByRegionId(regionId string) *SWire { + if len(regionId) == 0 { + return nil + } + + for i := 0; i < len(self.iwires); i++ { + wire := self.iwires[i].(*SWire) + + if wire.region.GetId() == regionId { + return wire + } + } + + return nil +} + +func (self *SVpc) fetchNetworks() error { + networks, err := self.region.GetNetwroks(self.ID) + if err != nil { + return err + } + + // ??????? + if len(networks) == 0 { + self.iwires = append(self.iwires, &SWire{region: self.region, vpc: self}) + return nil + } + + for i := 0; i < len(networks); i += 1 { + wire := self.getWireByRegionId(self.region.GetId()) + networks[i].wire = wire + wire.addNetwork(&networks[i]) + } + return nil +} + +// 华为云安全组可以被同region的VPC使用 +func (self *SVpc) fetchSecurityGroups() error { + secgroups, err := self.region.GetSecurityGroups("", "") + if err != nil { + return err + } + + self.secgroups = make([]cloudprovider.ICloudSecurityGroup, len(secgroups)) + for i := 0; i < len(secgroups); i++ { + self.secgroups[i] = &secgroups[i] + } + return nil +} + +func (self *SVpc) GetId() string { + return self.ID +} + +func (self *SVpc) GetName() string { + if len(self.Name) > 0 { + return self.Name + } + return self.ID +} + +func (self *SVpc) GetGlobalId() string { + return self.ID +} + +func (self *SVpc) GetStatus() string { + return api.VPC_STATUS_AVAILABLE +} + +func (self *SVpc) Refresh() error { + new, err := self.region.getVpc(self.GetId()) + if err != nil { + return err + } + return jsonutils.Update(self, new) +} + +func (self *SVpc) IsEmulated() bool { + return false +} + +func (self *SVpc) GetRegion() cloudprovider.ICloudRegion { + return self.region +} + +func (self *SVpc) GetIsDefault() bool { + // 华为云没有default vpc. + return false +} + +func (self *SVpc) GetCidrBlock() string { + return self.CIDR +} + +func (self *SVpc) GetIWires() ([]cloudprovider.ICloudWire, error) { + if self.iwires == nil { + err := self.fetchNetworks() + if err != nil { + return nil, err + } + } + return self.iwires, nil +} + +func (self *SVpc) GetISecurityGroups() ([]cloudprovider.ICloudSecurityGroup, error) { + if self.secgroups == nil { + err := self.fetchSecurityGroups() + if err != nil { + return nil, err + } + } + return self.secgroups, nil +} + +func (self *SVpc) GetIRouteTables() ([]cloudprovider.ICloudRouteTable, error) { + if self.routeTables == nil { + routeTables, err := self.getRouteTables() + if err != nil { + return nil, errors.Wrap(err, "get route table error") + } + defaultRouteTable := NewSRouteTable(self, string(cloudprovider.RouteTableTypeSystem)) + for i := range routeTables { + defaultRouteTable.Routes = append(defaultRouteTable.Routes, routeTables[i].Routes...) + } + self.routeTables = []cloudprovider.ICloudRouteTable{&defaultRouteTable} + } + return self.routeTables, nil +} + +// 华为云 路由表资源方法 api 暂未支持,只能直接对vpc对象增加,删除 路由 +func (self *SVpc) GetIRouteTableById(routeTableId string) (cloudprovider.ICloudRouteTable, error) { + routeTables, err := self.getRouteTables() + if err != nil { + return nil, errors.Wrap(err, "get route table error") + } + defaultRouteTable := NewSRouteTable(self, string(cloudprovider.RouteTableTypeSystem)) + for i := range routeTables { + defaultRouteTable.Routes = append(defaultRouteTable.Routes, routeTables[i].Routes...) + } + return &defaultRouteTable, nil +} + +func (self *SVpc) Delete() error { + // todo: 确定删除VPC的逻辑 + return self.region.DeleteVpc(self.GetId()) +} + +func (self *SVpc) GetIWireById(wireId string) (cloudprovider.ICloudWire, error) { + if self.iwires == nil { + err := self.fetchNetworks() + if err != nil { + return nil, err + } + } + for i := 0; i < len(self.iwires); i += 1 { + if self.iwires[i].GetGlobalId() == wireId { + return self.iwires[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SVpc) GetINatGateways() ([]cloudprovider.ICloudNatGateway, error) { + nats, err := self.region.GetNatGateways(self.GetId(), "") + if err != nil { + return nil, err + } + ret := make([]cloudprovider.ICloudNatGateway, len(nats)) + for i := 0; i < len(nats); i++ { + ret[i] = &nats[i] + } + return ret, nil +} + +func (self *SVpc) GetICloudVpcPeeringConnections() ([]cloudprovider.ICloudVpcPeeringConnection, error) { + svpcPCs, err := self.getVpcPeeringConnections() + if err != nil { + return nil, errors.Wrap(err, "self.getVpcPeeringConnections()") + } + ivpcPCs := []cloudprovider.ICloudVpcPeeringConnection{} + for i := range svpcPCs { + ivpcPCs = append(ivpcPCs, &svpcPCs[i]) + } + return ivpcPCs, nil +} + +func (self *SVpc) GetICloudAccepterVpcPeeringConnections() ([]cloudprovider.ICloudVpcPeeringConnection, error) { + svpcPCs, err := self.getAccepterVpcPeeringConnections() + if err != nil { + return nil, errors.Wrap(err, "self.getAccepterVpcPeeringConnections()") + } + ivpcPCs := []cloudprovider.ICloudVpcPeeringConnection{} + for i := range svpcPCs { + ivpcPCs = append(ivpcPCs, &svpcPCs[i]) + } + return ivpcPCs, nil +} + +func (self *SVpc) GetICloudVpcPeeringConnectionById(id string) (cloudprovider.ICloudVpcPeeringConnection, error) { + svpcPC, err := self.getVpcPeeringConnectionById(id) + if err != nil { + return nil, errors.Wrapf(err, "self.getVpcPeeringConnectionById(%s)", id) + } + return svpcPC, nil +} + +func (self *SVpc) CreateICloudVpcPeeringConnection(opts *cloudprovider.VpcPeeringConnectionCreateOptions) (cloudprovider.ICloudVpcPeeringConnection, error) { + svpcPC, err := self.region.CreateVpcPeering(self.GetId(), opts) + if err != nil { + return nil, errors.Wrapf(err, "self.region.CreateVpcPeering(%s,%s)", self.GetId(), jsonutils.Marshal(opts).String()) + } + svpcPC.vpc = self + return svpcPC, nil +} +func (self *SVpc) AcceptICloudVpcPeeringConnection(id string) error { + vpcPC, err := self.getVpcPeeringConnectionById(id) + if err != nil { + return errors.Wrapf(err, "self.getVpcPeeringConnectionById(%s)", id) + } + if vpcPC.GetStatus() == api.VPC_PEERING_CONNECTION_STATUS_ACTIVE { + return nil + } + if vpcPC.GetStatus() == api.VPC_PEERING_CONNECTION_STATUS_UNKNOWN { + return errors.Wrapf(cloudprovider.ErrInvalidStatus, "vpcPC: %s", jsonutils.Marshal(vpcPC).String()) + } + err = self.region.AcceptVpcPeering(id) + if err != nil { + return errors.Wrapf(err, "self.region.AcceptVpcPeering(%s)", id) + } + return nil +} + +func (self *SVpc) GetAuthorityOwnerId() string { + return self.region.client.projectId +} + +func (self *SVpc) getVpcPeeringConnections() ([]SVpcPeering, error) { + svpcPeerings, err := self.region.GetVpcPeerings(self.GetId()) + if err != nil { + return nil, errors.Wrapf(err, "self.region.GetVpcPeerings(%s)", self.GetId()) + } + vpcPCs := []SVpcPeering{} + for i := range svpcPeerings { + if svpcPeerings[i].GetVpcId() == self.GetId() { + svpcPeerings[i].vpc = self + vpcPCs = append(vpcPCs, svpcPeerings[i]) + } + } + return vpcPCs, nil +} + +func (self *SVpc) getAccepterVpcPeeringConnections() ([]SVpcPeering, error) { + svpcPeerings, err := self.region.GetVpcPeerings(self.GetId()) + if err != nil { + return nil, errors.Wrapf(err, "self.region.GetVpcPeerings(%s)", self.GetId()) + } + vpcPCs := []SVpcPeering{} + for i := range svpcPeerings { + if svpcPeerings[i].GetPeerVpcId() == self.GetId() { + svpcPeerings[i].vpc = self + vpcPCs = append(vpcPCs, svpcPeerings[i]) + } + } + return vpcPCs, nil +} + +func (self *SVpc) getVpcPeeringConnectionById(id string) (*SVpcPeering, error) { + svpcPC, err := self.region.GetVpcPeering(id) + if err != nil { + return nil, errors.Wrapf(err, "self.region.GetVpcPeering(%s)", id) + } + svpcPC.vpc = self + return svpcPC, nil +} + +func (self *SRegion) getVpc(vpcId string) (*SVpc, error) { + vpc := SVpc{} + err := DoGet(self.ecsClient.Vpcs.Get, vpcId, nil, &vpc) + if err != nil && strings.Contains(err.Error(), "RouterNotFound") { + return nil, cloudprovider.ErrNotFound + } + vpc.region = self + return &vpc, err +} + +func (self *SRegion) DeleteVpc(vpcId string) error { + if vpcId != "default" { + secgroups, err := self.GetSecurityGroups(vpcId, "") + if err != nil { + return errors.Wrap(err, "GetSecurityGroups") + } + for _, secgroup := range secgroups { + err = self.DeleteSecurityGroup(secgroup.ID) + if err != nil { + return errors.Wrapf(err, "DeleteSecurityGroup(%s)", secgroup.ID) + } + } + } + return DoDelete(self.ecsClient.Vpcs.Delete, vpcId, nil, nil) +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090625.html +func (self *SRegion) GetVpcs() ([]SVpc, error) { + querys := make(map[string]string) + + vpcs := make([]SVpc, 0) + err := doListAllWithMarker(self.ecsClient.Vpcs.List, querys, &vpcs) + if err != nil { + return nil, err + } + + for i := range vpcs { + vpcs[i].region = self + } + return vpcs, err +} diff --git a/pkg/multicloud/huaweistack/vpc_peering.go b/pkg/multicloud/huaweistack/vpc_peering.go new file mode 100644 index 0000000000..31429d954d --- /dev/null +++ b/pkg/multicloud/huaweistack/vpc_peering.go @@ -0,0 +1,159 @@ +// 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 huaweistack + +import ( + "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +type RequestVpcInfo struct { + VpcID string `json:"vpc_id"` + TenantID string `json:"tenant_id"` +} +type AcceptVpcInfo struct { + VpcID string `json:"vpc_id"` + TenantID string `json:"tenant_id"` +} +type SVpcPeering struct { + multicloud.SResourceBase + multicloud.HuaweiTags + vpc *SVpc + + RequestVpcInfo RequestVpcInfo `json:"request_vpc_info"` + AcceptVpcInfo AcceptVpcInfo `json:"accept_vpc_info"` + Name string `json:"name"` + ID string `json:"id"` + Status string `json:"status"` +} + +func (self *SRegion) GetVpcPeerings(vpcId string) ([]SVpcPeering, error) { + querys := make(map[string]string) + querys["vpc_id"] = vpcId + vpcPeerings := make([]SVpcPeering, 0) + err := doListAllWithMarker(self.ecsClient.VpcPeerings.List, querys, &vpcPeerings) + if err != nil { + return nil, errors.Wrapf(err, "oListAllWithMarker(self.ecsClient.VpcPeerings.List, %s, &vpcPeerings)", jsonutils.Marshal(querys).String()) + } + return vpcPeerings, nil +} + +func (self *SRegion) GetVpcPeering(vpcPeeringId string) (*SVpcPeering, error) { + if len(vpcPeeringId) == 0 { + return nil, cloudprovider.ErrNotFound + } + vpcPeering := SVpcPeering{} + err := DoGet(self.ecsClient.VpcPeerings.Get, vpcPeeringId, nil, &vpcPeering) + if err != nil { + return nil, errors.Wrapf(err, "DoGet(self.ecsClient.VpcPeerings.Get, %s, nil, &vpcPeering)", vpcPeeringId) + } + return &vpcPeering, nil +} + +func (self *SRegion) CreateVpcPeering(vpcId string, opts *cloudprovider.VpcPeeringConnectionCreateOptions) (*SVpcPeering, error) { + params := jsonutils.NewDict() + vpcPeeringObj := jsonutils.NewDict() + requestVpcObj := jsonutils.NewDict() + acceptVpcObj := jsonutils.NewDict() + vpcPeeringObj.Set("name", jsonutils.NewString(opts.Name)) + requestVpcObj.Set("vpc_id", jsonutils.NewString(vpcId)) + requestVpcObj.Set("tenant_id", jsonutils.NewString(self.client.projectId)) + vpcPeeringObj.Set("request_vpc_info", requestVpcObj) + acceptVpcObj.Set("vpc_id", jsonutils.NewString(opts.PeerVpcId)) + acceptVpcObj.Set("tenant_id", jsonutils.NewString(opts.PeerAccountId)) + vpcPeeringObj.Set("accept_vpc_info", acceptVpcObj) + params.Set("peering", vpcPeeringObj) + ret := SVpcPeering{} + err := DoCreate(self.ecsClient.VpcPeerings.Create, params, &ret) + if err != nil { + return nil, errors.Wrapf(err, "DoCreate(self.ecsClient.VpcPeerings.Create, %s, &ret)", jsonutils.Marshal(params).String()) + } + return &ret, nil +} + +func (self *SRegion) AcceptVpcPeering(vpcPeeringId string) error { + err := DoUpdateWithSpec(self.ecsClient.VpcPeerings.UpdateInContextWithSpec, vpcPeeringId, "accept", nil) + if err != nil { + return errors.Wrapf(err, "DoUpdateWithSpec(self.ecsClient.VpcPeerings.UpdateInContextWithSpec, %s, accept, nil)", vpcPeeringId) + } + return nil +} + +func (self *SRegion) DeleteVpcPeering(vpcPeeringId string) error { + err := DoDelete(self.ecsClient.VpcPeerings.Delete, vpcPeeringId, nil, nil) + if err != nil { + return errors.Wrapf(err, "DoDelete(self.ecsClient.VpcPeerings.Delete,%s,nil)", vpcPeeringId) + } + return nil +} + +func (self *SVpcPeering) GetId() string { + return self.ID +} + +func (self *SVpcPeering) GetName() string { + return self.Name +} + +func (self *SVpcPeering) GetGlobalId() string { + return self.GetId() +} + +func (self *SVpcPeering) GetStatus() string { + switch self.Status { + case "PENDING_ACCEPTANCE": + return api.VPC_PEERING_CONNECTION_STATUS_PENDING_ACCEPT + case "ACTIVE": + return api.VPC_PEERING_CONNECTION_STATUS_ACTIVE + default: + return api.VPC_PEERING_CONNECTION_STATUS_UNKNOWN + } +} + +func (self *SVpcPeering) Refresh() error { + peer, err := self.vpc.region.GetVpcPeering(self.ID) + if err != nil { + return errors.Wrapf(err, "self.region.GetVpcPeering(%s)", self.ID) + } + return jsonutils.Update(self, peer) +} + +func (self *SVpcPeering) GetVpcId() string { + return self.RequestVpcInfo.VpcID +} + +func (self *SVpcPeering) GetPeerVpcId() string { + return self.AcceptVpcInfo.VpcID +} + +func (self *SVpcPeering) GetPeerAccountId() string { + return self.AcceptVpcInfo.TenantID +} + +func (self *SVpcPeering) GetEnabled() bool { + return true +} + +func (self *SVpcPeering) Delete() error { + err := self.vpc.region.DeleteVpcPeering(self.ID) + if err != nil { + return errors.Wrapf(err, "self.region.DeleteVpcPeering(%s)", self.ID) + } + return nil +} diff --git a/pkg/multicloud/huaweistack/wire.go b/pkg/multicloud/huaweistack/wire.go new file mode 100644 index 0000000000..94e20e2eb8 --- /dev/null +++ b/pkg/multicloud/huaweistack/wire.go @@ -0,0 +1,192 @@ +// 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 huaweistack + +import ( + "fmt" + "time" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/netutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +// 华为云的子网有点特殊。子网在整个region可用。 +type SWire struct { + multicloud.SResourceBase + multicloud.HuaweiTags + region *SRegion + vpc *SVpc + + inetworks []cloudprovider.ICloudNetwork +} + +func (self *SWire) GetId() string { + return fmt.Sprintf("%s-%s", self.vpc.GetId(), self.region.GetId()) +} + +func (self *SWire) GetName() string { + return self.GetId() +} + +func (self *SWire) GetGlobalId() string { + return fmt.Sprintf("%s-%s", self.vpc.GetGlobalId(), self.region.GetGlobalId()) +} + +func (self *SWire) GetStatus() string { + return api.WIRE_STATUS_AVAILABLE +} + +func (self *SWire) Refresh() error { + return nil +} + +func (self *SWire) IsEmulated() bool { + return true +} + +func (self *SWire) GetIVpc() cloudprovider.ICloudVpc { + return self.vpc +} + +func (self *SWire) GetIZone() cloudprovider.ICloudZone { + return nil +} + +func (self *SWire) GetINetworks() ([]cloudprovider.ICloudNetwork, error) { + if self.inetworks == nil { + err := self.vpc.fetchNetworks() + if err != nil { + return nil, err + } + } + return self.inetworks, nil +} + +func (self *SWire) GetBandwidth() int { + return 10000 +} + +func (self *SWire) GetINetworkById(netid string) (cloudprovider.ICloudNetwork, error) { + networks, err := self.GetINetworks() + if err != nil { + return nil, err + } + for i := 0; i < len(networks); i += 1 { + if networks[i].GetGlobalId() == netid { + return networks[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +/* +华为云子网可用区,类似一个zone标签。即使指定了zone子网在整个region依然是可用。 +通过华为web控制台创建子网需要指定可用区。这里是不指定的。 +*/ +func (self *SWire) CreateINetwork(opts *cloudprovider.SNetworkCreateOptions) (cloudprovider.ICloudNetwork, error) { + networkId, err := self.region.createNetwork(self.vpc.GetId(), opts.Name, opts.Cidr, opts.Desc) + if err != nil { + log.Errorf("createNetwork error %s", err) + return nil, err + } + + var network *SNetwork + err = cloudprovider.WaitCreated(5*time.Second, 60*time.Second, func() bool { + self.inetworks = nil + network = self.getNetworkById(networkId) + if network == nil { + return false + } else { + return true + } + }) + + if err != nil { + log.Errorf("cannot find network after create????") + return nil, err + } + + network.wire = self + return network, nil +} + +func (self *SWire) addNetwork(network *SNetwork) { + if self.inetworks == nil { + self.inetworks = make([]cloudprovider.ICloudNetwork, 0) + } + find := false + for i := 0; i < len(self.inetworks); i += 1 { + if self.inetworks[i].GetId() == network.ID { + find = true + break + } + } + if !find { + self.inetworks = append(self.inetworks, network) + } +} + +func (self *SWire) getNetworkById(networkId string) *SNetwork { + networks, err := self.GetINetworks() + if err != nil { + return nil + } + log.Debugf("search for networks %d", len(networks)) + for i := 0; i < len(networks); i += 1 { + log.Debugf("search %s", networks[i].GetName()) + network := networks[i] + if network.GetId() == networkId { + return network.(*SNetwork) + } + } + return nil +} + +func getDefaultGateWay(cidr string) (string, error) { + pref, err := netutils.NewIPV4Prefix(cidr) + if err != nil { + return "", errors.Wrap(err, "getDefaultGateWay.NewIPV4Prefix") + } + startIp := pref.Address.NetAddr(pref.MaskLen) // 0 + startIp = startIp.StepUp() // 1 + return startIp.String(), nil +} + +// https://support.huaweicloud.com/api-vpc/zh-cn_topic_0020090590.html +// cidr 掩码长度不能大于28 +func (self *SRegion) createNetwork(vpcId string, name string, cidr string, desc string) (string, error) { + gateway, err := getDefaultGateWay(cidr) + if err != nil { + return "", err + } + + params := jsonutils.NewDict() + subnetObj := jsonutils.NewDict() + subnetObj.Add(jsonutils.NewString(name), "name") + subnetObj.Add(jsonutils.NewString(vpcId), "vpc_id") + subnetObj.Add(jsonutils.NewString(cidr), "cidr") + subnetObj.Add(jsonutils.NewString(gateway), "gateway_ip") + params.Add(subnetObj, "subnet") + + subnet := SNetwork{} + err = DoCreate(self.ecsClient.Subnets.Create, params, &subnet) + return subnet.ID, err +} diff --git a/pkg/multicloud/huaweistack/zone.go b/pkg/multicloud/huaweistack/zone.go new file mode 100644 index 0000000000..a711c7946c --- /dev/null +++ b/pkg/multicloud/huaweistack/zone.go @@ -0,0 +1,204 @@ +// 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 huaweistack + +import ( + "fmt" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud" +) + +var StorageTypes = []string{ + api.STORAGE_HUAWEI_SAS, + api.STORAGE_HUAWEI_SATA, + api.STORAGE_HUAWEI_SSD, +} + +type ZoneState struct { + Available bool `json:"available"` +} + +// https://support.huaweicloud.com/api-ecs/zh-cn_topic_0065817728.html +type SZone struct { + multicloud.SResourceBase + multicloud.HuaweiTags + region *SRegion + host *SHost + + iwires []cloudprovider.ICloudWire + istorages []cloudprovider.ICloudStorage + + ZoneState ZoneState `json:"zoneState"` + ZoneName string `json:"zoneName"` + + /* 支持的磁盘种类集合 */ + storageTypes []string +} + +func (self *SZone) addWire(wire *SWire) { + if self.iwires == nil { + self.iwires = make([]cloudprovider.ICloudWire, 0) + } + self.iwires = append(self.iwires, wire) +} + +func (self *SZone) getStorageType() { + if len(self.storageTypes) == 0 { + if sts, err := self.region.GetZoneSupportedDiskTypes(self.GetId()); err == nil { + self.storageTypes = sts + } else { + log.Errorf("GetZoneSupportedDiskTypes %s %s", self.GetId(), err) + self.storageTypes = StorageTypes + } + } +} + +func (self *SZone) fetchStorages() error { + self.getStorageType() + self.istorages = make([]cloudprovider.ICloudStorage, len(self.storageTypes)) + + for i, sc := range self.storageTypes { + storage := SStorage{zone: self, storageType: sc} + self.istorages[i] = &storage + } + return nil +} + +func (self *SZone) getHost() *SHost { + if self.host == nil { + self.host = &SHost{zone: self, projectId: self.region.client.projectId} + } + return self.host +} + +func (self *SZone) GetId() string { + return self.ZoneName +} + +func (self *SZone) GetName() string { + return fmt.Sprintf("%s %s", CLOUD_PROVIDER_HUAWEI_CN, self.ZoneName) +} + +func (self *SZone) GetI18n() cloudprovider.SModelI18nTable { + en := fmt.Sprintf("%s %s", CLOUD_PROVIDER_HUAWEI_EN, self.ZoneName) + table := cloudprovider.SModelI18nTable{} + table["name"] = cloudprovider.NewSModelI18nEntry(self.GetName()).CN(self.GetName()).EN(en) + return table +} + +func (self *SZone) GetGlobalId() string { + return fmt.Sprintf("%s/%s", self.region.GetGlobalId(), self.ZoneName) +} + +func (self *SZone) GetStatus() string { + return "enable" +} + +func (self *SZone) Refresh() error { + return nil +} + +func (self *SZone) IsEmulated() bool { + return false +} + +func (self *SZone) GetIRegion() cloudprovider.ICloudRegion { + return self.region +} + +func (self *SZone) GetIHosts() ([]cloudprovider.ICloudHost, error) { + return []cloudprovider.ICloudHost{self.getHost()}, nil +} + +func (self *SZone) GetIHostById(id string) (cloudprovider.ICloudHost, error) { + host := self.getHost() + if host.GetGlobalId() == id { + return host, nil + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SZone) GetIStorages() ([]cloudprovider.ICloudStorage, error) { + if self.istorages == nil { + err := self.fetchStorages() + if err != nil { + return nil, errors.Wrapf(err, "fetchStorages") + } + } + return self.istorages, nil +} + +func (self *SZone) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { + if self.istorages == nil { + err := self.fetchStorages() + if err != nil { + return nil, errors.Wrapf(err, "fetchStorages") + } + } + for i := 0; i < len(self.istorages); i += 1 { + if self.istorages[i].GetGlobalId() == id { + return self.istorages[i], nil + } + } + return nil, cloudprovider.ErrNotFound +} + +func (self *SZone) GetIWires() ([]cloudprovider.ICloudWire, error) { + return self.iwires, nil +} + +func (self *SZone) getStorageByCategory(category string) (*SStorage, error) { + storages, err := self.GetIStorages() + if err != nil { + return nil, err + } + for i := 0; i < len(storages); i += 1 { + storage := storages[i].(*SStorage) + if storage.storageType == category { + return storage, nil + } + } + return nil, fmt.Errorf("No such storage %s", category) +} + +func (self *SRegion) getZoneById(id string) (*SZone, error) { + izones, err := self.GetIZones() + if err != nil { + return nil, err + } + for i := 0; i < len(izones); i += 1 { + zone := izones[i].(*SZone) + if zone.GetId() == id { + return zone, nil + } + } + return nil, fmt.Errorf("no such zone %s", id) +} + +func (self *SZone) getNetworkById(networkId string) *SNetwork { + for i := 0; i < len(self.iwires); i += 1 { + wire := self.iwires[i].(*SWire) + net := wire.getNetworkById(networkId) + if net != nil { + return net + } + } + return nil +} diff --git a/pkg/multicloud/loader/loader.go b/pkg/multicloud/loader/loader.go index 51ec69035f..bc8bbef09e 100644 --- a/pkg/multicloud/loader/loader.go +++ b/pkg/multicloud/loader/loader.go @@ -27,6 +27,7 @@ import ( _ "yunion.io/x/onecloud/pkg/multicloud/esxi/provider" // private clouds _ "yunion.io/x/onecloud/pkg/multicloud/google/provider" // public clouds _ "yunion.io/x/onecloud/pkg/multicloud/huawei/provider" + _ "yunion.io/x/onecloud/pkg/multicloud/huaweistack/provider" _ "yunion.io/x/onecloud/pkg/multicloud/jdcloud/provider" // public clouds _ "yunion.io/x/onecloud/pkg/multicloud/objectstore/ceph/provider" _ "yunion.io/x/onecloud/pkg/multicloud/objectstore/provider"