yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/aliyun/aliyun.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package aliyun
    16  
    17  import (
    18  	"bytes"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"net/http"
    22  	"net/url"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
    27  	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
    28  	alierr "github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
    29  	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
    30  	"github.com/aliyun/aliyun-oss-go-sdk/oss"
    31  	"github.com/pkg/errors"
    32  
    33  	"yunion.io/x/jsonutils"
    34  	"yunion.io/x/log"
    35  	v "yunion.io/x/pkg/util/version"
    36  	"yunion.io/x/pkg/utils"
    37  
    38  	"yunion.io/x/onecloud/pkg/compute/options"
    39  	"yunion.io/x/onecloud/pkg/httperrors"
    40  	"yunion.io/x/onecloud/pkg/util/httputils"
    41  
    42  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    43  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    44  )
    45  
    46  const (
    47  	ALIYUN_INTERNATIONAL_CLOUDENV = "InternationalCloud"
    48  	ALIYUN_FINANCE_CLOUDENV       = "FinanceCloud"
    49  
    50  	CLOUD_PROVIDER_ALIYUN    = api.CLOUD_PROVIDER_ALIYUN
    51  	CLOUD_PROVIDER_ALIYUN_CN = "阿里云"
    52  	CLOUD_PROVIDER_ALIYUN_EN = "Aliyun"
    53  
    54  	ALIYUN_DEFAULT_REGION = "cn-hangzhou"
    55  
    56  	ALIYUN_API_VERSION     = "2014-05-26"
    57  	ALIYUN_API_VERSION_VPC = "2016-04-28"
    58  	ALIYUN_API_VERSION_LB  = "2014-05-15"
    59  	ALIYUN_API_VERSION_KVS = "2015-01-01"
    60  
    61  	ALIYUN_API_VERSION_TRIAL = "2020-07-06"
    62  
    63  	ALIYUN_BSS_API_VERSION = "2017-12-14"
    64  
    65  	ALIYUN_RAM_API_VERSION      = "2015-05-01"
    66  	ALIYUN_RDS_API_VERSION      = "2014-08-15"
    67  	ALIYUN_RM_API_VERSION       = "2020-03-31"
    68  	ALIYUN_STS_API_VERSION      = "2015-04-01"
    69  	ALIYUN_PVTZ_API_VERSION     = "2018-01-01"
    70  	ALIYUN_ALIDNS_API_VERSION   = "2015-01-09"
    71  	ALIYUN_CBN_API_VERSION      = "2017-09-12"
    72  	ALIYUN_CDN_API_VERSION      = "2018-05-10"
    73  	ALIYUN_IMS_API_VERSION      = "2019-08-15"
    74  	ALIYUN_NAS_API_VERSION      = "2017-06-26"
    75  	ALIYUN_WAF_API_VERSION      = "2019-09-10"
    76  	ALIYUN_MONGO_DB_API_VERSION = "2015-12-01"
    77  	ALIYUN_ES_API_VERSION       = "2017-06-13"
    78  	ALIYUN_KAFKA_API_VERSION    = "2019-09-16"
    79  	ALIYUN_K8S_API_VERSION      = "2015-12-15"
    80  	ALIYUN_OTS_API_VERSION      = "2016-06-20"
    81  
    82  	ALIYUN_SERVICE_ECS      = "ecs"
    83  	ALIYUN_SERVICE_VPC      = "vpc"
    84  	ALIYUN_SERVICE_RDS      = "rds"
    85  	ALIYUN_SERVICE_SLB      = "slb"
    86  	ALIYUN_SERVICE_KVS      = "kvs"
    87  	ALIYUN_SERVICE_NAS      = "nas"
    88  	ALIYUN_SERVICE_CDN      = "cdn"
    89  	ALIYUN_SERVICE_MONGO_DB = "mongodb"
    90  )
    91  
    92  var (
    93  	// https://help.aliyun.com/document_detail/31837.html?spm=a2c4g.11186623.2.18.675f2b8cu8CN5K#concept-zt4-cvy-5db
    94  	OSS_FINANCE_REGION_MAP = map[string]string{
    95  		"cn-hzfinance":              "cn-hangzhou",
    96  		"cn-shanghai-finance-1-pub": "cn-shanghai-finance-1",
    97  		"cn-szfinance":              "cn-shenzhen-finance-1",
    98  
    99  		"cn-hzjbp":              "cn-hangzhou",
   100  		"cn-shanghai-finance-1": "cn-shanghai-finance-1",
   101  		"cn-shenzhen-finance-1": "cn-shenzhen-finance-1",
   102  	}
   103  )
   104  
   105  type AliyunClientConfig struct {
   106  	cpcfg        cloudprovider.ProviderConfig
   107  	cloudEnv     string // 服务区域 InternationalCloud | FinanceCloud
   108  	accessKey    string
   109  	accessSecret string
   110  	debug        bool
   111  }
   112  
   113  func NewAliyunClientConfig(cloudEnv, accessKey, accessSecret string) *AliyunClientConfig {
   114  	cfg := &AliyunClientConfig{
   115  		cloudEnv:     cloudEnv,
   116  		accessKey:    accessKey,
   117  		accessSecret: accessSecret,
   118  	}
   119  	return cfg
   120  }
   121  
   122  func (cfg *AliyunClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *AliyunClientConfig {
   123  	cfg.cpcfg = cpcfg
   124  	return cfg
   125  }
   126  
   127  func (cfg *AliyunClientConfig) Debug(debug bool) *AliyunClientConfig {
   128  	cfg.debug = debug
   129  	return cfg
   130  }
   131  
   132  func (cfg AliyunClientConfig) Copy() AliyunClientConfig {
   133  	return cfg
   134  }
   135  
   136  type SAliyunClient struct {
   137  	*AliyunClientConfig
   138  
   139  	ownerId string
   140  
   141  	nasEndpoints map[string]string
   142  	vpcEndpoints map[string]string
   143  
   144  	resourceGroups []SResourceGroup
   145  
   146  	iregions []cloudprovider.ICloudRegion
   147  	iBuckets []cloudprovider.ICloudBucket
   148  }
   149  
   150  func NewAliyunClient(cfg *AliyunClientConfig) (*SAliyunClient, error) {
   151  	client := SAliyunClient{
   152  		AliyunClientConfig: cfg,
   153  		nasEndpoints:       map[string]string{},
   154  		vpcEndpoints:       map[string]string{},
   155  	}
   156  	return &client, client.fetchRegions()
   157  }
   158  
   159  func jsonRequest(client *sdk.Client, domain, apiVersion, apiName string, params map[string]string, debug bool) (jsonutils.JSONObject, error) {
   160  	if debug {
   161  		log.Debugf("request %s %s %s %s", domain, apiVersion, apiName, params)
   162  	}
   163  	var resp jsonutils.JSONObject
   164  	var err error
   165  	for i := 1; i < 4; i++ {
   166  		resp, err = _jsonRequest(client, domain, apiVersion, apiName, params)
   167  		retry := false
   168  		if err != nil {
   169  			for _, code := range []string{
   170  				"ErrorClusterNotFound",
   171  			} {
   172  				if strings.Contains(err.Error(), code) {
   173  					return nil, errors.Wrap(cloudprovider.ErrNotFound, err.Error())
   174  				}
   175  			}
   176  			if e, ok := errors.Cause(err).(*alierr.ServerError); ok {
   177  				code := e.ErrorCode()
   178  				switch code {
   179  				case "InternalError":
   180  					if apiName == "QueryAccountBalance" {
   181  						return nil, errors.Wrapf(httperrors.ErrNoPermission, err.Error())
   182  					}
   183  					return nil, err
   184  				case "InvalidAccessKeyId.NotFound",
   185  					"InvalidAccessKeyId",
   186  					"NoEnabledAccessKey",
   187  					"InvalidAccessKeyId.Inactive",
   188  					"Forbidden.AccessKeyDisabled",
   189  					"Forbidden.AccessKey":
   190  					return nil, errors.Wrapf(httperrors.ErrInvalidAccessKey, err.Error())
   191  				case "404 Not Found", "InstanceNotFound":
   192  					return nil, errors.Wrap(cloudprovider.ErrNotFound, err.Error())
   193  				case "OperationDenied.NoStock":
   194  					return nil, errors.Wrapf(err, "所请求的套餐在指定的区域内已售罄;尝试其他套餐或选择其他区域和可用区。")
   195  				case "InvalidInstance.NotSupported",
   196  					"SignatureNonceUsed",                  // SignatureNonce 重复。每次请求的 SignatureNonce 在 15 分钟内不能重复。
   197  					"BackendServer.configuring",           // 负载均衡的前一个配置项正在配置中,请稍后再试。
   198  					"Operation.Conflict",                  // 您当前的操作可能与其他人的操作产生了冲突,请稍后重试。
   199  					"OperationDenied.ResourceControl",     // 指定的区域处于资源控制中,请稍后再试。
   200  					"ServiceIsStopping",                   // 监听正在停止,请稍后重试。
   201  					"ProcessingSameRequest",               // 正在处理相同的请求。请稍后再试。
   202  					"ResourceInOperating",                 // 当前资源正在操作中,请求稍后重试。
   203  					"InvalidFileSystemStatus.Ordering",    // Message: The filesystem is ordering now, please check it later.
   204  					"OperationUnsupported.EipNatBWPCheck": // create nat snat
   205  					retry = true
   206  				default:
   207  					if strings.HasPrefix(code, "EntityNotExist.") || strings.HasSuffix(code, ".NotFound") || strings.HasSuffix(code, "NotExist") {
   208  						if strings.HasPrefix(apiName, "Delete") {
   209  							return jsonutils.NewDict(), nil
   210  						}
   211  						return nil, errors.Wrap(cloudprovider.ErrNotFound, err.Error())
   212  					}
   213  					return nil, err
   214  				}
   215  			} else {
   216  				for _, code := range []string{
   217  					"EOF",
   218  					"i/o timeout",
   219  					"TLS handshake timeout",
   220  					"Client.Timeout exceeded while awaiting headers",
   221  					"connection reset by peer",
   222  					"server misbehaving",
   223  					"try later",
   224  					"Another operation is being performed", // Another operation is being performed on the DB instance or the DB instance is faulty(赋予RDS账号权限)
   225  				} {
   226  					if strings.Contains(err.Error(), code) {
   227  						retry = true
   228  						break
   229  					}
   230  				}
   231  			}
   232  		}
   233  		if retry {
   234  			if debug {
   235  				log.Debugf("Retry %d...", i)
   236  			}
   237  			time.Sleep(time.Second * time.Duration(i*10))
   238  			continue
   239  		}
   240  		if debug {
   241  			log.Debugf("Response: %s", resp)
   242  		}
   243  		return resp, err
   244  	}
   245  	return resp, err
   246  }
   247  
   248  func _jsonRequest(client *sdk.Client, domain string, version string, apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   249  	req := requests.NewCommonRequest()
   250  	req.Domain = domain
   251  	req.Version = version
   252  	req.ApiName = apiName
   253  	if params != nil {
   254  		for k, v := range params {
   255  			req.QueryParams[k] = v
   256  		}
   257  	}
   258  	req.Scheme = "https"
   259  	req.GetHeaders()["User-Agent"] = "vendor/yunion-OneCloud@" + v.Get().GitVersion
   260  	method := requests.POST
   261  	for prefix, _method := range map[string]string{
   262  		"Get":      requests.GET,
   263  		"Describe": requests.GET,
   264  		"List":     requests.GET,
   265  		"Delete":   requests.DELETE,
   266  	} {
   267  		if strings.HasPrefix(apiName, prefix) {
   268  			method = _method
   269  			break
   270  		}
   271  	}
   272  	if strings.HasPrefix(domain, "elasticsearch") {
   273  		req.Product = "elasticsearch"
   274  		req.ServiceCode = "elasticsearch"
   275  		pathPattern, ok := params["PathPattern"]
   276  		if !ok {
   277  			return nil, errors.Errorf("Roa request missing pathPattern")
   278  		}
   279  		delete(params, "PathPattern")
   280  		req.PathPattern = pathPattern
   281  		req.Method = method
   282  	} else if strings.HasPrefix(domain, "alikafka") { //alikafka DeleteInstance必须显式指定Method
   283  		req.Method = requests.POST
   284  	} else if strings.HasPrefix(domain, "cs") { //容器
   285  		pathPattern, ok := params["PathPattern"]
   286  		if !ok {
   287  			return nil, errors.Errorf("Roa request missing pathPattern")
   288  		}
   289  		delete(params, "PathPattern")
   290  		req.PathPattern = pathPattern
   291  		req.Method = method
   292  		req.GetHeaders()["Content-Type"] = "application/json"
   293  	}
   294  
   295  	resp, err := processCommonRequest(client, req)
   296  	if err != nil {
   297  		return nil, errors.Wrapf(err, "processCommonRequest with params %s", params)
   298  	}
   299  	body, err := jsonutils.Parse(resp.GetHttpContentBytes())
   300  	if err != nil {
   301  		return nil, errors.Wrapf(err, "jsonutils.Parse")
   302  	}
   303  	//{"Code":"InvalidInstanceType.ValueNotSupported","HostId":"ecs.aliyuncs.com","Message":"The specified instanceType beyond the permitted range.","RequestId":"0042EE30-0EDF-48A7-A414-56229D4AD532"}
   304  	//{"Code":"200","Message":"successful","PageNumber":1,"PageSize":50,"RequestId":"BB4C970C-0E23-48DC-A3B0-EB21FFC70A29","RouterTableList":{"RouterTableListType":[{"CreationTime":"2017-03-19T13:37:40Z","Description":"","ResourceGroupId":"rg-acfmwie3cqoobmi","RouteTableId":"vtb-j6c60lectdi80rk5xz43g","RouteTableName":"","RouteTableType":"System","RouterId":"vrt-j6c00qrol733dg36iq4qj","RouterType":"VRouter","VSwitchIds":{"VSwitchId":["vsw-j6c3gig5ub4fmi2veyrus"]},"VpcId":"vpc-j6c86z3sh8ufhgsxwme0q"}]},"Success":true,"TotalCount":1}
   305  	//{"Code":"Success","Data":{"CashCoupon":[]},"Message":"Successful!","RequestId":"87AD7E9A-3F8F-460F-9934-FFFE502325EE","Success":true}
   306  	if body.Contains("Code") {
   307  		code, _ := body.GetString("Code")
   308  		if len(code) > 0 && !utils.IsInStringArray(code, []string{"200", "Success"}) {
   309  			return nil, fmt.Errorf(body.String())
   310  		}
   311  	}
   312  	return body, nil
   313  }
   314  
   315  func (self *SAliyunClient) getNasEndpoint(regionId string) string {
   316  	err := self.fetchNasEndpoints()
   317  	if err != nil {
   318  		return "nas.aliyuncs.com"
   319  	}
   320  	ep, ok := self.nasEndpoints[regionId]
   321  	if ok && len(ep) > 0 {
   322  		return ep
   323  	}
   324  	return "nas.aliyuncs.com"
   325  }
   326  
   327  func (self *SAliyunClient) fetchNasEndpoints() error {
   328  	if len(self.nasEndpoints) > 0 {
   329  		return nil
   330  	}
   331  	client, err := self.getDefaultClient()
   332  	if err != nil {
   333  		return errors.Wrapf(err, "getDefaultClient")
   334  	}
   335  	resp, err := jsonRequest(client, "nas.aliyuncs.com", ALIYUN_NAS_API_VERSION, "DescribeRegions", nil, self.debug)
   336  	if err != nil {
   337  		return errors.Wrapf(err, "DescribeRegions")
   338  	}
   339  	regions := []SRegion{}
   340  	err = resp.Unmarshal(&regions, "Regions", "Region")
   341  	if err != nil {
   342  		return errors.Wrapf(err, "resp.Unmarshal")
   343  	}
   344  	for _, region := range regions {
   345  		self.nasEndpoints[region.RegionId] = region.RegionEndpoint
   346  	}
   347  	return nil
   348  }
   349  
   350  func (self *SAliyunClient) getDefaultClient() (*sdk.Client, error) {
   351  	client, err := self.getSdkClient(ALIYUN_DEFAULT_REGION)
   352  	return client, err
   353  }
   354  
   355  func (self *SAliyunClient) getVpcEndpoint(regionId string) string {
   356  	err := self.fetchVpcEndpoints()
   357  	if err != nil {
   358  		return "vpc.aliyuncs.com"
   359  	}
   360  	ep, ok := self.vpcEndpoints[regionId]
   361  	if ok && len(ep) > 0 {
   362  		return ep
   363  	}
   364  	return "vpc.aliyuncs.com"
   365  }
   366  
   367  func (self *SAliyunClient) fetchVpcEndpoints() error {
   368  	if len(self.vpcEndpoints) > 0 {
   369  		return nil
   370  	}
   371  	client, err := self.getDefaultClient()
   372  	if err != nil {
   373  		return errors.Wrapf(err, "getDefaultClient")
   374  	}
   375  	resp, err := jsonRequest(client, "vpc.aliyuncs.com", ALIYUN_API_VERSION_VPC, "DescribeRegions", nil, self.debug)
   376  	if err != nil {
   377  		return errors.Wrapf(err, "DescribeRegions")
   378  	}
   379  	regions := []SRegion{}
   380  	err = resp.Unmarshal(&regions, "Regions", "Region")
   381  	if err != nil {
   382  		return errors.Wrapf(err, "resp.Unmarshal")
   383  	}
   384  	for _, region := range regions {
   385  		self.vpcEndpoints[region.RegionId] = region.RegionEndpoint
   386  	}
   387  	return nil
   388  }
   389  
   390  func (self *SAliyunClient) getSdkClient(regionId string) (*sdk.Client, error) {
   391  	transport := httputils.GetAdaptiveTransport(true)
   392  	transport.Proxy = self.cpcfg.ProxyFunc
   393  	client, err := sdk.NewClientWithOptions(
   394  		regionId,
   395  		&sdk.Config{
   396  			HttpTransport: transport,
   397  			Transport: cloudprovider.GetCheckTransport(transport, func(req *http.Request) (func(resp *http.Response), error) {
   398  				params, err := url.ParseQuery(req.URL.RawQuery)
   399  				if err != nil {
   400  					return nil, errors.Wrapf(err, "ParseQuery(%s)", req.URL.RawQuery)
   401  				}
   402  				service := strings.Split(req.URL.Host, ".")[0]
   403  				action := params.Get("Action")
   404  				respCheck := func(resp *http.Response) {
   405  					if self.cpcfg.UpdatePermission != nil && resp.StatusCode >= 400 && resp.ContentLength > 0 {
   406  						body, err := ioutil.ReadAll(resp.Body)
   407  						if err != nil {
   408  							return
   409  						}
   410  						resp.Body = ioutil.NopCloser(bytes.NewBuffer(body))
   411  						obj, err := jsonutils.Parse(body)
   412  						if err != nil {
   413  							return
   414  						}
   415  						ret := struct{ Code string }{}
   416  						obj.Unmarshal(&ret)
   417  						if utils.IsInStringArray(ret.Code, []string{
   418  							"NoPermission",
   419  							"SubAccountNoPermission",
   420  						}) || utils.HasPrefix(ret.Code, "Forbidden") ||
   421  							action == "QueryAccountBalance" && ret.Code == "InternalError" {
   422  							self.cpcfg.UpdatePermission(service, action)
   423  						}
   424  					}
   425  				}
   426  				for _, prefix := range []string{"Get", "List", "Describe", "Query"} {
   427  					if strings.HasPrefix(action, prefix) {
   428  						return respCheck, nil
   429  					}
   430  				}
   431  				if self.cpcfg.ReadOnly {
   432  					return respCheck, errors.Wrapf(cloudprovider.ErrAccountReadOnly, action)
   433  				}
   434  				return respCheck, nil
   435  			}),
   436  		},
   437  		&credentials.BaseCredential{
   438  			AccessKeyId:     self.accessKey,
   439  			AccessKeySecret: self.accessSecret,
   440  		},
   441  	)
   442  	return client, err
   443  }
   444  
   445  func (self *SAliyunClient) imsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   446  	cli, err := self.getDefaultClient()
   447  	if err != nil {
   448  		return nil, err
   449  	}
   450  	params = self.SetResourceGropuId(params)
   451  	return jsonRequest(cli, "ims.aliyuncs.com", ALIYUN_IMS_API_VERSION, apiName, params, self.debug)
   452  }
   453  
   454  func (self *SAliyunClient) rmRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   455  	cli, err := self.getDefaultClient()
   456  	if err != nil {
   457  		return nil, err
   458  	}
   459  	return jsonRequest(cli, "resourcemanager.aliyuncs.com", ALIYUN_RM_API_VERSION, apiName, params, self.debug)
   460  }
   461  
   462  func (self *SAliyunClient) ecsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   463  	cli, err := self.getDefaultClient()
   464  	if err != nil {
   465  		return nil, err
   466  	}
   467  	params = self.SetResourceGropuId(params)
   468  	return jsonRequest(cli, "ecs.aliyuncs.com", ALIYUN_API_VERSION, apiName, params, self.debug)
   469  }
   470  
   471  func (self *SAliyunClient) pvtzRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   472  	cli, err := self.getDefaultClient()
   473  	if err != nil {
   474  		return nil, err
   475  	}
   476  	params = self.SetResourceGropuId(params)
   477  	return jsonRequest(cli, "pvtz.aliyuncs.com", ALIYUN_PVTZ_API_VERSION, apiName, params, self.debug)
   478  }
   479  
   480  func (self *SAliyunClient) alidnsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   481  	cli, err := self.getDefaultClient()
   482  	if err != nil {
   483  		return nil, err
   484  	}
   485  	params = self.SetResourceGropuId(params)
   486  	return jsonRequest(cli, "alidns.aliyuncs.com", ALIYUN_ALIDNS_API_VERSION, apiName, params, self.debug)
   487  }
   488  
   489  func (self *SAliyunClient) cbnRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   490  	cli, err := self.getDefaultClient()
   491  	if err != nil {
   492  		return nil, err
   493  	}
   494  	params = self.SetResourceGropuId(params)
   495  	return jsonRequest(cli, "cbn.aliyuncs.com", ALIYUN_CBN_API_VERSION, apiName, params, self.debug)
   496  }
   497  
   498  func (self *SAliyunClient) cdnRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   499  	cli, err := self.getDefaultClient()
   500  	if err != nil {
   501  		return nil, err
   502  	}
   503  	params = self.SetResourceGropuId(params)
   504  	return jsonRequest(cli, "cdn.aliyuncs.com", ALIYUN_CDN_API_VERSION, apiName, params, self.debug)
   505  }
   506  
   507  func (self *SAliyunClient) fetchRegions() error {
   508  	body, err := self.ecsRequest("DescribeRegions", map[string]string{"AcceptLanguage": "zh-CN"})
   509  	if err != nil {
   510  		return errors.Wrapf(err, "DescribeRegions")
   511  	}
   512  
   513  	regions := make([]SRegion, 0)
   514  	err = body.Unmarshal(&regions, "Regions", "Region")
   515  	if err != nil {
   516  		return errors.Wrapf(err, "resp.Unmarshal")
   517  	}
   518  	self.iregions = make([]cloudprovider.ICloudRegion, len(regions))
   519  	for i := 0; i < len(regions); i += 1 {
   520  		regions[i].client = self
   521  		self.iregions[i] = &regions[i]
   522  	}
   523  	return nil
   524  }
   525  
   526  // oss endpoint
   527  // https://help.aliyun.com/document_detail/31837.html?spm=a2c4g.11186623.2.6.6E8ZkO
   528  func getOSSExternalDomain(regionId string) string {
   529  	return fmt.Sprintf("oss-%s.aliyuncs.com", regionId)
   530  }
   531  
   532  func getOSSInternalDomain(regionId string) string {
   533  	return fmt.Sprintf("oss-%s-internal.aliyuncs.com", regionId)
   534  }
   535  
   536  // https://help.aliyun.com/document_detail/31837.html?spm=a2c4g.11186623.2.6.XqEgD1
   537  func (client *SAliyunClient) getOssClientByEndpoint(endpoint string) (*oss.Client, error) {
   538  	// NOTE
   539  	//
   540  	// oss package as of version 20181116160301-c6838fdc33ed does not
   541  	// respect http.ProxyFromEnvironment.
   542  	//
   543  	// The ClientOption Proxy, AuthProxy lacks the feature NO_PROXY has
   544  	// which can be used to whitelist ips, domains from http_proxy,
   545  	// https_proxy setting
   546  	// oss use no timeout client so as to send/download large files
   547  	httpClient := client.cpcfg.AdaptiveTimeoutHttpClient()
   548  	transport, _ := httpClient.Transport.(*http.Transport)
   549  	httpClient.Transport = cloudprovider.GetCheckTransport(transport, func(req *http.Request) (func(resp *http.Response), error) {
   550  		path, method := req.URL.Path, req.Method
   551  		respCheck := func(resp *http.Response) {
   552  			if client.cpcfg.UpdatePermission != nil && resp.StatusCode == 403 {
   553  				client.cpcfg.UpdatePermission("oss", fmt.Sprintf("%s %s", method, path))
   554  			}
   555  		}
   556  		if client.cpcfg.ReadOnly {
   557  			if req.Method == "GET" || req.Method == "HEAD" {
   558  				return respCheck, nil
   559  			}
   560  			return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.RawPath)
   561  		}
   562  		return respCheck, nil
   563  	})
   564  	cliOpts := []oss.ClientOption{
   565  		oss.HTTPClient(httpClient),
   566  	}
   567  	if !strings.HasPrefix(endpoint, "http") {
   568  		endpoint = "https://" + endpoint
   569  	}
   570  	cli, err := oss.New(endpoint, client.accessKey, client.accessSecret, cliOpts...)
   571  	if err != nil {
   572  		return nil, errors.Wrap(err, "oss.New")
   573  	}
   574  	return cli, nil
   575  }
   576  
   577  func (client *SAliyunClient) getOssClient(regionId string) (*oss.Client, error) {
   578  	ep := getOSSExternalDomain(regionId)
   579  	return client.getOssClientByEndpoint(ep)
   580  }
   581  
   582  func (self *SAliyunClient) getRegionByRegionId(id string) (cloudprovider.ICloudRegion, error) {
   583  	_id, ok := OSS_FINANCE_REGION_MAP[id]
   584  	if ok {
   585  		id = _id
   586  	}
   587  	for i := 0; i < len(self.iregions); i += 1 {
   588  		if self.iregions[i].GetId() == id {
   589  			return self.iregions[i], nil
   590  		}
   591  	}
   592  	return nil, cloudprovider.ErrNotFound
   593  }
   594  
   595  func (self *SAliyunClient) invalidateIBuckets() {
   596  	self.iBuckets = nil
   597  }
   598  
   599  func (self *SAliyunClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) {
   600  	if self.iBuckets == nil {
   601  		err := self.fetchBuckets()
   602  		if err != nil {
   603  			return nil, errors.Wrap(err, "fetchBuckets")
   604  		}
   605  	}
   606  	return self.iBuckets, nil
   607  }
   608  
   609  func (self *SAliyunClient) fetchBuckets() error {
   610  	osscli, err := self.getOssClient(ALIYUN_DEFAULT_REGION)
   611  	if err != nil {
   612  		return errors.Wrap(err, "self.getOssClient")
   613  	}
   614  	result, err := osscli.ListBuckets()
   615  	if err != nil {
   616  		return errors.Wrap(err, "oss.ListBuckets")
   617  	}
   618  
   619  	if len(self.ownerId) == 0 {
   620  		self.ownerId = result.Owner.ID
   621  	}
   622  
   623  	ret := make([]cloudprovider.ICloudBucket, 0)
   624  	for _, bInfo := range result.Buckets {
   625  		regionId := bInfo.Location[4:]
   626  		region, err := self.getRegionByRegionId(regionId)
   627  		if err != nil {
   628  			log.Errorf("cannot find bucket's region %s", regionId)
   629  			continue
   630  		}
   631  		b := SBucket{
   632  			region:       region.(*SRegion),
   633  			Name:         bInfo.Name,
   634  			Location:     bInfo.Location,
   635  			CreationDate: bInfo.CreationDate,
   636  			StorageClass: bInfo.StorageClass,
   637  		}
   638  		ret = append(ret, &b)
   639  	}
   640  	self.iBuckets = ret
   641  	return nil
   642  }
   643  
   644  func (self *SAliyunClient) GetRegions() []SRegion {
   645  	regions := make([]SRegion, len(self.iregions))
   646  	for i := 0; i < len(regions); i += 1 {
   647  		region := self.iregions[i].(*SRegion)
   648  		regions[i] = *region
   649  	}
   650  	return regions
   651  }
   652  
   653  func (self *SAliyunClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
   654  	err := self.fetchRegions()
   655  	if err != nil {
   656  		return nil, err
   657  	}
   658  	subAccount := cloudprovider.SSubAccount{}
   659  	subAccount.Name = self.cpcfg.Name
   660  	subAccount.Account = self.accessKey
   661  	subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
   662  	projects, err := self.GetIProjects()
   663  	if err != nil {
   664  		return nil, errors.Wrapf(err, "GetIProject")
   665  	}
   666  	for i := range projects {
   667  		if projects[i].GetName() == "默认资源组" {
   668  			subAccount.DefaultProjectId = projects[i].GetGlobalId()
   669  			break
   670  		}
   671  	}
   672  	return []cloudprovider.SSubAccount{subAccount}, nil
   673  }
   674  
   675  func (self *SAliyunClient) GetAccountId() string {
   676  	if len(self.ownerId) > 0 {
   677  		return self.ownerId
   678  	}
   679  	caller, err := self.GetCallerIdentity()
   680  	if err != nil {
   681  		return ""
   682  	}
   683  	self.ownerId = caller.AccountId
   684  	return self.ownerId
   685  }
   686  
   687  func (self *SAliyunClient) GetIRegions() []cloudprovider.ICloudRegion {
   688  	return self.iregions
   689  }
   690  
   691  func (self *SAliyunClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
   692  	for i := 0; i < len(self.iregions); i += 1 {
   693  		if self.iregions[i].GetGlobalId() == id {
   694  			return self.iregions[i], nil
   695  		}
   696  	}
   697  	return nil, cloudprovider.ErrNotFound
   698  }
   699  
   700  func (self *SAliyunClient) GetRegion(regionId string) *SRegion {
   701  	if len(regionId) == 0 {
   702  		regionId = ALIYUN_DEFAULT_REGION
   703  	}
   704  	for i := 0; i < len(self.iregions); i += 1 {
   705  		if self.iregions[i].GetId() == regionId {
   706  			return self.iregions[i].(*SRegion)
   707  		}
   708  	}
   709  	return nil
   710  }
   711  
   712  func (self *SAliyunClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
   713  	for i := 0; i < len(self.iregions); i += 1 {
   714  		ihost, err := self.iregions[i].GetIHostById(id)
   715  		if err == nil {
   716  			return ihost, nil
   717  		} else if errors.Cause(err) != cloudprovider.ErrNotFound {
   718  			return nil, err
   719  		}
   720  	}
   721  	return nil, cloudprovider.ErrNotFound
   722  }
   723  
   724  func (self *SAliyunClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) {
   725  	for i := 0; i < len(self.iregions); i += 1 {
   726  		ihost, err := self.iregions[i].GetIVpcById(id)
   727  		if err == nil {
   728  			return ihost, nil
   729  		} else if errors.Cause(err) != cloudprovider.ErrNotFound {
   730  			return nil, err
   731  		}
   732  	}
   733  	return nil, cloudprovider.ErrNotFound
   734  }
   735  
   736  func (self *SAliyunClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
   737  	for i := 0; i < len(self.iregions); i += 1 {
   738  		ihost, err := self.iregions[i].GetIStorageById(id)
   739  		if err == nil {
   740  			return ihost, nil
   741  		} else if errors.Cause(err) != cloudprovider.ErrNotFound {
   742  			return nil, err
   743  		}
   744  	}
   745  	return nil, cloudprovider.ErrNotFound
   746  }
   747  
   748  func (self *SAliyunClient) GetProjects() ([]SResourceGroup, error) {
   749  	if len(self.resourceGroups) > 0 {
   750  		return self.resourceGroups, nil
   751  	}
   752  	pageSize, pageNumber := 50, 1
   753  	self.resourceGroups = []SResourceGroup{}
   754  	for {
   755  		parts, total, err := self.GetResourceGroups(pageNumber, pageSize)
   756  		if err != nil {
   757  			return nil, errors.Wrap(err, "GetResourceGroups")
   758  		}
   759  		self.resourceGroups = append(self.resourceGroups, parts...)
   760  		if len(self.resourceGroups) >= total {
   761  			break
   762  		}
   763  		pageNumber += 1
   764  	}
   765  	return self.resourceGroups, nil
   766  }
   767  
   768  func (self *SAliyunClient) SetResourceGropuId(params map[string]string) map[string]string {
   769  	if params == nil {
   770  		params = map[string]string{}
   771  	}
   772  	for _, groupId := range options.Options.AliyunResourceGroups {
   773  		if utils.IsInStringArray(groupId, self.GetResourceGroupIds()) {
   774  			params["ResourceGroupId"] = groupId
   775  		}
   776  	}
   777  	return params
   778  }
   779  
   780  func (self *SAliyunClient) GetResourceGroupIds() []string {
   781  	ret := []string{}
   782  	resourceGroups, err := self.GetProjects()
   783  	if err != nil {
   784  		return ret
   785  	}
   786  	for i := range resourceGroups {
   787  		ret = append(ret, resourceGroups[i].Id)
   788  	}
   789  	return ret
   790  }
   791  
   792  func (self *SAliyunClient) GetIProjects() ([]cloudprovider.ICloudProject, error) {
   793  	resourceGroups, err := self.GetProjects()
   794  	if err != nil {
   795  		return nil, err
   796  	}
   797  	ret := []cloudprovider.ICloudProject{}
   798  	for i := range resourceGroups {
   799  		ret = append(ret, &resourceGroups[i])
   800  	}
   801  	return ret, nil
   802  }
   803  
   804  func (region *SAliyunClient) GetCapabilities() []string {
   805  	caps := []string{
   806  		cloudprovider.CLOUD_CAPABILITY_PROJECT,
   807  		cloudprovider.CLOUD_CAPABILITY_COMPUTE,
   808  		cloudprovider.CLOUD_CAPABILITY_NETWORK,
   809  		cloudprovider.CLOUD_CAPABILITY_EIP,
   810  		cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
   811  		cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
   812  		cloudprovider.CLOUD_CAPABILITY_RDS,
   813  		cloudprovider.CLOUD_CAPABILITY_CACHE,
   814  		cloudprovider.CLOUD_CAPABILITY_EVENT,
   815  		cloudprovider.CLOUD_CAPABILITY_CLOUDID,
   816  		cloudprovider.CLOUD_CAPABILITY_DNSZONE,
   817  		cloudprovider.CLOUD_CAPABILITY_INTERVPCNETWORK,
   818  		cloudprovider.CLOUD_CAPABILITY_SAML_AUTH,
   819  		cloudprovider.CLOUD_CAPABILITY_NAT,
   820  		cloudprovider.CLOUD_CAPABILITY_NAS,
   821  		cloudprovider.CLOUD_CAPABILITY_WAF,
   822  		cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX,
   823  		cloudprovider.CLOUD_CAPABILITY_MONGO_DB + cloudprovider.READ_ONLY_SUFFIX,
   824  		cloudprovider.CLOUD_CAPABILITY_ES + cloudprovider.READ_ONLY_SUFFIX,
   825  		cloudprovider.CLOUD_CAPABILITY_KAFKA + cloudprovider.READ_ONLY_SUFFIX,
   826  		cloudprovider.CLOUD_CAPABILITY_CDN + cloudprovider.READ_ONLY_SUFFIX,
   827  		cloudprovider.CLOUD_CAPABILITY_CONTAINER + cloudprovider.READ_ONLY_SUFFIX,
   828  		cloudprovider.CLOUD_CAPABILITY_TABLESTORE + cloudprovider.READ_ONLY_SUFFIX,
   829  	}
   830  	return caps
   831  }
   832  
   833  func (self *SAliyunClient) GetAccessEnv() string {
   834  	switch self.cloudEnv {
   835  	case ALIYUN_INTERNATIONAL_CLOUDENV:
   836  		return api.CLOUD_ACCESS_ENV_ALIYUN_GLOBAL
   837  	case ALIYUN_FINANCE_CLOUDENV:
   838  		return api.CLOUD_ACCESS_ENV_ALIYUN_FINANCE
   839  	default:
   840  		return api.CLOUD_ACCESS_ENV_ALIYUN_GLOBAL
   841  	}
   842  }