yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/qcloud/qcloud.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 qcloud
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"net/url"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
    30  	sdkerrors "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
    31  	tchttp "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/http"
    32  	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
    33  	"github.com/tencentyun/cos-go-sdk-v5"
    34  	"github.com/tencentyun/cos-go-sdk-v5/debug"
    35  
    36  	"yunion.io/x/jsonutils"
    37  	"yunion.io/x/log"
    38  	"yunion.io/x/pkg/errors"
    39  	"yunion.io/x/pkg/util/timeutils"
    40  	"yunion.io/x/pkg/utils"
    41  
    42  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    43  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    44  	"yunion.io/x/onecloud/pkg/httperrors"
    45  	"yunion.io/x/onecloud/pkg/util/httputils"
    46  )
    47  
    48  const (
    49  	CLOUD_PROVIDER_QCLOUD    = api.CLOUD_PROVIDER_QCLOUD
    50  	CLOUD_PROVIDER_QCLOUD_CN = "腾讯云"
    51  	CLOUD_PROVIDER_QCLOUD_EN = "QCloud"
    52  
    53  	QCLOUD_DEFAULT_REGION = "ap-beijing"
    54  
    55  	QCLOUD_API_VERSION           = "2017-03-12"
    56  	QCLOUD_CLB_API_VERSION       = "2018-03-17"
    57  	QCLOUD_BILLING_API_VERSION   = "2018-07-09"
    58  	QCLOUD_AUDIT_API_VERSION     = "2019-03-19"
    59  	QCLOUD_CAM_API_VERSION       = "2019-01-16"
    60  	QCLOUD_CDB_API_VERSION       = "2017-03-20"
    61  	QCLOUD_MARIADB_API_VERSION   = "2017-03-12"
    62  	QCLOUD_POSTGRES_API_VERSION  = "2017-03-12"
    63  	QCLOUD_SQLSERVER_API_VERSION = "2018-03-28"
    64  	QCLOUD_REDIS_API_VERSION     = "2018-04-12"
    65  	QCLOUD_MEMCACHED_API_VERSION = "2019-03-18"
    66  	QCLOUD_SSL_API_VERSION       = "2019-12-05"
    67  	QCLOUD_CDN_API_VERSION       = "2018-06-06"
    68  	QCLOUD_MONGODB_API_VERSION   = "2019-07-25"
    69  	QCLOUD_ES_API_VERSION        = "2018-04-16"
    70  	QCLOUD_DCDB_API_VERSION      = "2018-04-11"
    71  	QCLOUD_KAFKA_API_VERSION     = "2019-08-19"
    72  	QCLOUD_TKE_API_VERSION       = "2018-05-25"
    73  	QCLOUD_DNS_API_VERSION       = "2021-03-23"
    74  	QCLOUD_STS_API_VERSION       = "2018-08-13"
    75  )
    76  
    77  type QcloudClientConfig struct {
    78  	cpcfg cloudprovider.ProviderConfig
    79  
    80  	secretId  string
    81  	secretKey string
    82  	appId     string
    83  
    84  	debug bool
    85  }
    86  
    87  func NewQcloudClientConfig(secretId, secretKey string) *QcloudClientConfig {
    88  	cfg := &QcloudClientConfig{
    89  		secretId:  secretId,
    90  		secretKey: secretKey,
    91  	}
    92  	return cfg
    93  }
    94  
    95  func (cfg *QcloudClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *QcloudClientConfig {
    96  	cfg.cpcfg = cpcfg
    97  	return cfg
    98  }
    99  
   100  func (cfg *QcloudClientConfig) AppId(appId string) *QcloudClientConfig {
   101  	cfg.appId = appId
   102  	return cfg
   103  }
   104  
   105  func (cfg *QcloudClientConfig) Debug(debug bool) *QcloudClientConfig {
   106  	cfg.debug = debug
   107  	return cfg
   108  }
   109  
   110  type SQcloudClient struct {
   111  	*QcloudClientConfig
   112  	ownerId   string
   113  	ownerName string
   114  
   115  	iregions []cloudprovider.ICloudRegion
   116  	ibuckets []cloudprovider.ICloudBucket
   117  }
   118  
   119  func NewQcloudClient(cfg *QcloudClientConfig) (*SQcloudClient, error) {
   120  	client := SQcloudClient{
   121  		QcloudClientConfig: cfg,
   122  	}
   123  	err := client.fetchRegions()
   124  	if err != nil {
   125  		return nil, errors.Wrap(err, "fetchRegions")
   126  	}
   127  	return &client, nil
   128  }
   129  
   130  // 默认接口请求频率限制:20次/秒
   131  // 部分接口支持金融区地域。由于金融区和非金融区是隔离不互通的,因此当公共参数 Region 为金融区地域(例如 ap-shanghai-fsi)时,需要同时指定带金融区地域的域名,最好和 Region 的地域保持一致,例如:clb.ap-shanghai-fsi.tencentcloudapi.com
   132  // https://cloud.tencent.com/document/product/416/6479
   133  func apiDomain(product string, params map[string]string) string {
   134  	regionId, _ := params["Region"]
   135  	return apiDomainByRegion(product, regionId)
   136  }
   137  
   138  func apiDomainByRegion(product, regionId string) string {
   139  	if strings.HasSuffix(regionId, "-fsi") {
   140  		return product + "." + regionId + ".tencentcloudapi.com"
   141  	} else {
   142  		return product + ".tencentcloudapi.com"
   143  	}
   144  }
   145  
   146  func jsonRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool, retry bool) (jsonutils.JSONObject, error) {
   147  	domain := apiDomain("cvm", params)
   148  	return _jsonRequest(client, domain, QCLOUD_API_VERSION, apiName, params, updateFunc, debug, retry)
   149  }
   150  
   151  func tkeRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   152  	domain := "tke.tencentcloudapi.com"
   153  	return _jsonRequest(client, domain, QCLOUD_TKE_API_VERSION, apiName, params, updateFunc, debug, true)
   154  }
   155  
   156  func vpcRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   157  	domain := apiDomain("vpc", params)
   158  	return _jsonRequest(client, domain, QCLOUD_API_VERSION, apiName, params, updateFunc, debug, true)
   159  }
   160  
   161  func auditRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   162  	domain := apiDomain("cloudaudit", params)
   163  	return _jsonRequest(client, domain, QCLOUD_AUDIT_API_VERSION, apiName, params, updateFunc, debug, true)
   164  }
   165  
   166  func cbsRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   167  	domain := apiDomain("cbs", params)
   168  	return _jsonRequest(client, domain, QCLOUD_API_VERSION, apiName, params, updateFunc, debug, true)
   169  }
   170  
   171  func accountRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   172  	domain := "account.api.qcloud.com"
   173  	return _phpJsonRequest(client, &wssJsonResponse{}, domain, "/v2/index.php", "", apiName, params, updateFunc, debug)
   174  }
   175  
   176  // es
   177  func esRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   178  	domain := apiDomain("es", params)
   179  	return _jsonRequest(client, domain, QCLOUD_ES_API_VERSION, apiName, params, updateFunc, debug, true)
   180  }
   181  
   182  // kafka
   183  func kafkaRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   184  	domain := apiDomain("ckafka", params)
   185  	return _jsonRequest(client, domain, QCLOUD_KAFKA_API_VERSION, apiName, params, updateFunc, debug, true)
   186  }
   187  
   188  // redis
   189  func redisRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   190  	domain := apiDomain("redis", params)
   191  	return _jsonRequest(client, domain, QCLOUD_REDIS_API_VERSION, apiName, params, updateFunc, debug, true)
   192  }
   193  
   194  // tdsql
   195  func dcdbRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   196  	domain := apiDomain("dcdb", params)
   197  	return _jsonRequest(client, domain, QCLOUD_DCDB_API_VERSION, apiName, params, updateFunc, debug, true)
   198  }
   199  
   200  // mongodb
   201  func mongodbRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   202  	domain := apiDomain("mongodb", params)
   203  	return _jsonRequest(client, domain, QCLOUD_MONGODB_API_VERSION, apiName, params, updateFunc, debug, true)
   204  }
   205  
   206  // memcached
   207  func memcachedRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   208  	domain := apiDomain("memcached", params)
   209  	return _jsonRequest(client, domain, QCLOUD_MEMCACHED_API_VERSION, apiName, params, updateFunc, debug, true)
   210  }
   211  
   212  // loadbalancer服务 api 3.0
   213  func clbRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   214  	domain := apiDomain("clb", params)
   215  	return _jsonRequest(client, domain, QCLOUD_CLB_API_VERSION, apiName, params, updateFunc, debug, true)
   216  }
   217  
   218  // loadbalancer服务 api 2017
   219  func lbRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   220  	domain := "lb.api.qcloud.com"
   221  	return _phpJsonRequest(client, &lbJsonResponse{}, domain, "/v2/index.php", "", apiName, params, updateFunc, debug)
   222  }
   223  
   224  // cdb
   225  func cdbRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   226  	domain := apiDomain("cdb", params)
   227  	return _jsonRequest(client, domain, QCLOUD_CDB_API_VERSION, apiName, params, updateFunc, debug, true)
   228  }
   229  
   230  // mariadb
   231  func mariadbRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   232  	domain := apiDomain("mariadb", params)
   233  	return _jsonRequest(client, domain, QCLOUD_MARIADB_API_VERSION, apiName, params, updateFunc, debug, true)
   234  }
   235  
   236  // postgres
   237  func postgresRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   238  	domain := apiDomain("postgres", params)
   239  	return _jsonRequest(client, domain, QCLOUD_POSTGRES_API_VERSION, apiName, params, updateFunc, debug, true)
   240  }
   241  
   242  // sqlserver
   243  func sqlserverRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   244  	domain := apiDomain("sqlserver", params)
   245  	return _jsonRequest(client, domain, QCLOUD_SQLSERVER_API_VERSION, apiName, params, updateFunc, debug, true)
   246  }
   247  
   248  // deprecated: ssl 证书服务
   249  func wssRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   250  	domain := "wss.api.qcloud.com"
   251  	return _phpJsonRequest(client, &wssJsonResponse{}, domain, "/v2/index.php", "", apiName, params, updateFunc, debug)
   252  }
   253  
   254  // ssl 证书服务
   255  func sslRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   256  	domain := "ssl.tencentcloudapi.com"
   257  	return _jsonRequest(client, domain, QCLOUD_SSL_API_VERSION, apiName, params, updateFunc, debug, true)
   258  }
   259  
   260  // dnspod 解析服务
   261  func dnsRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   262  	domain := "dnspod.tencentcloudapi.com"
   263  	return _jsonRequest(client, domain, QCLOUD_DNS_API_VERSION, apiName, params, updateFunc, debug, true)
   264  }
   265  
   266  // 2017版API
   267  func vpc2017Request(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   268  	domain := "vpc.api.qcloud.com"
   269  	return _phpJsonRequest(client, &vpc2017JsonResponse{}, domain, "/v2/index.php", "", apiName, params, updateFunc, debug)
   270  }
   271  
   272  func billingRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   273  	domain := "billing.tencentcloudapi.com"
   274  	return _jsonRequest(client, domain, QCLOUD_BILLING_API_VERSION, apiName, params, updateFunc, debug, true)
   275  }
   276  
   277  func camRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   278  	domain := "cam.tencentcloudapi.com"
   279  	return _jsonRequest(client, domain, QCLOUD_CAM_API_VERSION, apiName, params, updateFunc, debug, true)
   280  }
   281  
   282  func monitorRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string),
   283  	debug bool) (jsonutils.JSONObject, error) {
   284  	domain := "monitor.tencentcloudapi.com"
   285  	return _jsonRequest(client, domain, QCLOUD_API_VERSION_METRICS, apiName, params, updateFunc, debug, true)
   286  }
   287  
   288  func cdnRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string),
   289  	debug bool) (jsonutils.JSONObject, error) {
   290  	domain := "cdn.tencentcloudapi.com"
   291  	return _jsonRequest(client, domain, QCLOUD_CDN_API_VERSION, apiName, params, updateFunc, debug, true)
   292  }
   293  
   294  func stsRequest(client *common.Client, apiName string, params map[string]string, updateFunc func(string, string),
   295  	debug bool) (jsonutils.JSONObject, error) {
   296  	domain := "sts.tencentcloudapi.com"
   297  	return _jsonRequest(client, domain, QCLOUD_STS_API_VERSION, apiName, params, updateFunc, debug, true)
   298  }
   299  
   300  // ============phpJsonRequest============
   301  type qcloudResponse interface {
   302  	tchttp.Response
   303  	GetResponse() *interface{}
   304  }
   305  
   306  type phpJsonRequest struct {
   307  	tchttp.BaseRequest
   308  	Path string
   309  }
   310  
   311  func (r *phpJsonRequest) GetUrl() string {
   312  	url := r.BaseRequest.GetUrl()
   313  	if url == "" {
   314  		return url
   315  	}
   316  
   317  	index := strings.Index(url, "?")
   318  	if index == -1 {
   319  		// POST request
   320  		return strings.TrimSuffix(url, "/") + r.Path
   321  	}
   322  
   323  	p1, p2 := url[:index], url[index:]
   324  	p1 = strings.TrimSuffix(p1, "/")
   325  	return p1 + r.Path + p2
   326  }
   327  
   328  func (r *phpJsonRequest) GetPath() string {
   329  	return r.Path
   330  }
   331  
   332  // 2017vpc专用response
   333  type vpc2017JsonResponse struct {
   334  	Code                int          `json:"code"`
   335  	CodeDesc            string       `json:"codeDesc"`
   336  	Message             string       `json:"message"`
   337  	TotalCount          int          `json:"totalCount"`
   338  	TaskId              int64        `json:"taskId"`
   339  	PeeringConnectionId string       `json:"peeringConnectionId"`
   340  	Response            *interface{} `json:"data"`
   341  }
   342  
   343  func (r *vpc2017JsonResponse) ParseErrorFromHTTPResponse(body []byte) (err error) {
   344  	resp := &vpc2017JsonResponse{}
   345  	err = json.Unmarshal(body, resp)
   346  	if err != nil {
   347  		return
   348  	}
   349  	if resp.Code != 0 {
   350  		return sdkerrors.NewTencentCloudSDKError(resp.CodeDesc, resp.Message, "")
   351  	}
   352  
   353  	return nil
   354  }
   355  
   356  func (r *vpc2017JsonResponse) GetResponse() *interface{} {
   357  	if r.Response == nil {
   358  		result, _ := jsonutils.Parse([]byte(`{"data":[],"totalCount":0}`))
   359  		resultMap := result.(*jsonutils.JSONDict)
   360  		resultMap.Update(jsonutils.Marshal(r))
   361  		return func(resp interface{}) *interface{} {
   362  			return &resp
   363  		}(resultMap)
   364  	}
   365  	return func(resp interface{}) *interface{} {
   366  		return &resp
   367  	}(jsonutils.Marshal(r))
   368  }
   369  
   370  // SSL证书专用response
   371  type wssJsonResponse struct {
   372  	Code      int          `json:"code"`
   373  	CodeDesc  string       `json:"codeDesc"`
   374  	ProjectId int          `json:"projectId"`
   375  	Message   string       `json:"message"`
   376  	Response  *interface{} `json:"data"`
   377  }
   378  
   379  func (r *wssJsonResponse) ParseErrorFromHTTPResponse(body []byte) (err error) {
   380  	resp := &wssJsonResponse{}
   381  	err = json.Unmarshal(body, resp)
   382  	if err != nil {
   383  		return
   384  	}
   385  	if resp.Code != 0 {
   386  		return sdkerrors.NewTencentCloudSDKError(resp.CodeDesc, resp.Message, "")
   387  	}
   388  
   389  	return nil
   390  }
   391  
   392  func (r *wssJsonResponse) GetResponse() *interface{} {
   393  	if r.Response == nil {
   394  		return func(resp interface{}) *interface{} {
   395  			return &resp
   396  		}(jsonutils.Marshal(r))
   397  	}
   398  	return r.Response
   399  }
   400  
   401  // 2017版负载均衡API专用response
   402  type lbJsonResponse struct {
   403  	Response map[string]interface{}
   404  }
   405  
   406  func (r *lbJsonResponse) ParseErrorFromHTTPResponse(body []byte) (err error) {
   407  	resp := &wssJsonResponse{}
   408  	err = json.Unmarshal(body, resp)
   409  	if err != nil {
   410  		return
   411  	}
   412  	if resp.Code != 0 {
   413  		return sdkerrors.NewTencentCloudSDKError(resp.CodeDesc, resp.Message, "")
   414  	}
   415  
   416  	// hook 由于目前只能从这个方法中拿到原始的body.这里将原始body hook 到 Response
   417  	err = json.Unmarshal(body, &r.Response)
   418  	if err != nil {
   419  		return
   420  	}
   421  
   422  	return nil
   423  }
   424  
   425  func (r *lbJsonResponse) GetResponse() *interface{} {
   426  	return func(resp interface{}) *interface{} {
   427  		return &resp
   428  	}(r.Response)
   429  }
   430  
   431  // 3.0版本通用response
   432  type QcloudResponse struct {
   433  	*tchttp.BaseResponse
   434  	Response *interface{} `json:"Response"`
   435  }
   436  
   437  func (r *QcloudResponse) GetResponse() *interface{} {
   438  	return r.Response
   439  }
   440  
   441  func _jsonRequest(client *common.Client, domain string, version string, apiName string, params map[string]string, updateFun func(string, string), debug bool, retry bool) (jsonutils.JSONObject, error) {
   442  	req := &tchttp.BaseRequest{}
   443  	_profile := profile.NewClientProfile()
   444  	_profile.SignMethod = common.SHA256
   445  	client.WithProfile(_profile)
   446  	service := strings.Split(domain, ".")[0]
   447  	req.Init().WithApiInfo(service, version, apiName)
   448  	req.SetDomain(domain)
   449  
   450  	for k, v := range params {
   451  		if strings.HasSuffix(k, "Ids.0") && len(v) == 0 {
   452  			return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s = %s", k, v)
   453  		}
   454  		req.GetParams()[k] = v
   455  	}
   456  
   457  	resp := &QcloudResponse{
   458  		BaseResponse: &tchttp.BaseResponse{},
   459  	}
   460  	ret, err := _baseJsonRequest(client, req, resp, apiName, debug, retry)
   461  	if err != nil {
   462  		if errors.Cause(err) == httperrors.ErrNoPermission && updateFun != nil {
   463  			updateFun(service, apiName)
   464  		}
   465  		return nil, err
   466  	}
   467  	return ret, nil
   468  }
   469  
   470  // 老版本腾讯云api。 适用于类似 https://cvm.api.qcloud.com/v2/index.php 这样的带/v2/index.php路径的接口
   471  // todo: 添加自定义response参数
   472  func _phpJsonRequest(client *common.Client, resp qcloudResponse, domain string, path string, version string, apiName string, params map[string]string, updateFunc func(string, string), debug bool) (jsonutils.JSONObject, error) {
   473  	req := &phpJsonRequest{Path: path}
   474  	_profile := profile.NewClientProfile()
   475  	_profile.SignMethod = common.SHA256
   476  	client.WithProfile(_profile)
   477  	service := strings.Split(domain, ".")[0]
   478  	req.Init().WithApiInfo(service, version, apiName)
   479  	req.SetDomain(domain)
   480  
   481  	for k, v := range params {
   482  		if strings.HasSuffix(k, "Ids.0") && len(v) == 0 {
   483  			return nil, cloudprovider.ErrNotFound
   484  		}
   485  		req.GetParams()[k] = v
   486  	}
   487  
   488  	ret, err := _baseJsonRequest(client, req, resp, apiName, debug, true)
   489  	if err != nil {
   490  		if errors.Cause(err) == httperrors.ErrNoPermission && updateFunc != nil {
   491  			updateFunc(service, apiName)
   492  		}
   493  		return nil, err
   494  	}
   495  	return ret, nil
   496  }
   497  
   498  func _baseJsonRequest(client *common.Client, req tchttp.Request, resp qcloudResponse, apiName string, debug bool, retry bool) (jsonutils.JSONObject, error) {
   499  	tryMax := 1
   500  	if retry {
   501  		tryMax = 3
   502  	}
   503  	var err error
   504  	for i := 1; i <= tryMax; i++ {
   505  		err = client.Send(req, resp)
   506  		if err == nil {
   507  			break
   508  		}
   509  		needRetry := false
   510  		e, ok := err.(*sdkerrors.TencentCloudSDKError)
   511  		if ok {
   512  			if strings.HasPrefix(e.Code, "UnauthorizedOperation.") ||
   513  				strings.HasPrefix(e.Code, "AuthFailure.") ||
   514  				utils.IsInStringArray(e.Code, []string{
   515  					"SecretidNotAuthAccessResource",
   516  					"UnauthorizedOperation",
   517  					"InvalidParameter.PermissionDenied",
   518  					"AuthFailure",
   519  				}) {
   520  				return nil, errors.Wrapf(httperrors.ErrNoPermission, err.Error())
   521  			}
   522  			if utils.IsInStringArray(e.Code, []string{
   523  				"AuthFailure.SecretIdNotFound",
   524  				"AuthFailure.SignatureFailure",
   525  			}) {
   526  				return nil, errors.Wrapf(httperrors.ErrInvalidAccessKey, err.Error())
   527  			}
   528  			if utils.IsInStringArray(e.Code, []string{
   529  				"InvalidParameter.RoleNotExist",
   530  				"ResourceNotFound",
   531  				"FailedOperation.CertificateNotFound",
   532  			}) {
   533  				return nil, errors.Wrapf(cloudprovider.ErrNotFound, err.Error())
   534  			}
   535  
   536  			if e.Code == "UnsupportedRegion" {
   537  				return nil, cloudprovider.ErrNotSupported
   538  			}
   539  			if e.Code == "InvalidParameterValue" && apiName == "GetMonitorData" && strings.Contains(e.Message, "the instance has been destroyed") {
   540  				return nil, cloudprovider.ErrNotFound
   541  			}
   542  
   543  			if utils.IsInStringArray(e.Code, []string{
   544  				"InternalError",
   545  				"MutexOperation.TaskRunning",       // Code=DesOperation.MutexTaskRunning, Message=Mutex task is running, please try later
   546  				"InvalidInstance.NotSupported",     // Code=InvalidInstance.NotSupported, Message=The request does not support the instances `ins-bg54517v` which are in operation or in a special state., 重装系统后立即关机有可能会引发 Code=InvalidInstance.NotSupported 错误, 重试可以避免任务失败
   547  				"InvalidAddressId.StatusNotPermit", // Code=InvalidAddressId.StatusNotPermit, Message=The status `"UNBINDING"` for AddressId `"eip-m3kix9kx"` is not permit to do this operation., EIP删除需要重试
   548  				"RequestLimitExceeded",
   549  				"OperationDenied.InstanceOperationInProgress", // 调整配置后开机 Code=OperationDenied.InstanceOperationInProgress, Message=实例`['ins-nksicizg']`操作进行中,请等待, RequestId=c9951005-b22c-43c1-84aa-d923d49addcf
   550  			}) {
   551  				needRetry = true
   552  			}
   553  		}
   554  
   555  		if !needRetry {
   556  			for _, msg := range []string{
   557  				"EOF",
   558  				"TLS handshake timeout",
   559  				"try later",
   560  				"i/o timeout",
   561  			} {
   562  				if strings.Contains(err.Error(), msg) {
   563  					needRetry = true
   564  					break
   565  				}
   566  			}
   567  		}
   568  
   569  		if needRetry {
   570  			log.Errorf("request url %s\nparams: %s\nerror: %v\ntry after %d seconds", req.GetDomain(), jsonutils.Marshal(req.GetParams()).PrettyString(), err, i*10)
   571  			time.Sleep(time.Second * time.Duration(i*10))
   572  			continue
   573  		}
   574  		log.Errorf("request url: %s\nparams: %s\nresponse: %v\nerror: %v", req.GetDomain(), jsonutils.Marshal(req.GetParams()).PrettyString(), resp.GetResponse(), err)
   575  		return nil, err
   576  	}
   577  	if debug {
   578  		log.Debugf("request: %s", req.GetParams())
   579  		response := resp.GetResponse()
   580  		if response != nil {
   581  			log.Debugf("response: %s", jsonutils.Marshal(response).PrettyString())
   582  		}
   583  	}
   584  	if err != nil {
   585  		return nil, err
   586  	}
   587  	return jsonutils.Marshal(resp.GetResponse()), nil
   588  }
   589  
   590  func (client *SQcloudClient) GetRegions() []SRegion {
   591  	regions := make([]SRegion, len(client.iregions))
   592  	for i := 0; i < len(regions); i++ {
   593  		region := client.iregions[i].(*SRegion)
   594  		regions[i] = *region
   595  	}
   596  	return regions
   597  }
   598  
   599  func (client *SQcloudClient) getDefaultClient(params map[string]string) (*common.Client, error) {
   600  	regionId := QCLOUD_DEFAULT_REGION
   601  	if len(params) > 0 {
   602  		if region, ok := params["Region"]; ok {
   603  			regionId = region
   604  		}
   605  	}
   606  	return client.getSdkClient(regionId)
   607  }
   608  
   609  func (client *SQcloudClient) getSdkClient(regionId string) (*common.Client, error) {
   610  	cli, err := common.NewClientWithSecretId(client.secretId, client.secretKey, regionId)
   611  	if err != nil {
   612  		return nil, err
   613  	}
   614  	httpClient := client.cpcfg.AdaptiveTimeoutHttpClient()
   615  	ts, _ := httpClient.Transport.(*http.Transport)
   616  	cli.WithHttpTransport(cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response), error) {
   617  		body, err := ioutil.ReadAll(req.Body)
   618  		if err != nil {
   619  			return nil, errors.Wrapf(err, "ioutil.ReadAll")
   620  		}
   621  		req.Body = ioutil.NopCloser(bytes.NewBuffer(body))
   622  		params, err := url.ParseQuery(string(body))
   623  		if err != nil {
   624  			return nil, errors.Wrapf(err, "ParseQuery(%s)", string(body))
   625  		}
   626  		service := strings.Split(req.URL.Host, ".")[0]
   627  		action := params.Get("Action")
   628  		respCheck := func(resp *http.Response) {
   629  			if client.cpcfg.UpdatePermission != nil {
   630  				client.cpcfg.UpdatePermission(service, action)
   631  			}
   632  		}
   633  		if client.cpcfg.ReadOnly {
   634  			for _, prefix := range []string{"Get", "List", "Describe"} {
   635  				if strings.HasPrefix(action, prefix) {
   636  					return respCheck, nil
   637  				}
   638  			}
   639  			return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, action)
   640  		}
   641  		return respCheck, nil
   642  	}))
   643  	return cli, nil
   644  }
   645  
   646  func (client *SQcloudClient) tkeRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   647  	cli, err := client.getDefaultClient(params)
   648  	if err != nil {
   649  		return nil, err
   650  	}
   651  	return tkeRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   652  }
   653  
   654  func (client *SQcloudClient) vpcRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   655  	cli, err := client.getDefaultClient(params)
   656  	if err != nil {
   657  		return nil, err
   658  	}
   659  	return vpcRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   660  }
   661  
   662  func (client *SQcloudClient) auditRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   663  	cli, err := client.getDefaultClient(params)
   664  	if err != nil {
   665  		return nil, err
   666  	}
   667  	return auditRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   668  }
   669  
   670  func (client *SQcloudClient) cbsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   671  	cli, err := client.getDefaultClient(params)
   672  	if err != nil {
   673  		return nil, err
   674  	}
   675  	return cbsRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   676  }
   677  
   678  func (client *SQcloudClient) accountRequestRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   679  	cli, err := client.getDefaultClient(params)
   680  	if err != nil {
   681  		return nil, err
   682  	}
   683  	return accountRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   684  }
   685  
   686  func (client *SQcloudClient) clbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   687  	cli, err := client.getDefaultClient(params)
   688  	if err != nil {
   689  		return nil, err
   690  	}
   691  	return clbRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   692  }
   693  
   694  func (client *SQcloudClient) lbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   695  	cli, err := client.getDefaultClient(params)
   696  	if err != nil {
   697  		return nil, err
   698  	}
   699  	return lbRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   700  }
   701  
   702  func (client *SQcloudClient) cdbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   703  	cli, err := client.getDefaultClient(params)
   704  	if err != nil {
   705  		return nil, err
   706  	}
   707  	return cdbRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   708  }
   709  
   710  func (client *SQcloudClient) esRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   711  	cli, err := client.getDefaultClient(params)
   712  	if err != nil {
   713  		return nil, err
   714  	}
   715  
   716  	return esRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   717  }
   718  
   719  func (client *SQcloudClient) kafkaRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   720  	cli, err := client.getDefaultClient(params)
   721  	if err != nil {
   722  		return nil, err
   723  	}
   724  
   725  	return kafkaRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   726  }
   727  
   728  func (client *SQcloudClient) redisRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   729  	cli, err := client.getDefaultClient(params)
   730  	if err != nil {
   731  		return nil, err
   732  	}
   733  
   734  	return redisRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   735  }
   736  
   737  func (client *SQcloudClient) dcdbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   738  	cli, err := client.getDefaultClient(params)
   739  	if err != nil {
   740  		return nil, err
   741  	}
   742  
   743  	return dcdbRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   744  }
   745  
   746  func (client *SQcloudClient) mongodbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   747  	cli, err := client.getDefaultClient(params)
   748  	if err != nil {
   749  		return nil, err
   750  	}
   751  
   752  	return mongodbRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   753  }
   754  
   755  func (client *SQcloudClient) memcachedRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   756  	cli, err := client.getDefaultClient(params)
   757  	if err != nil {
   758  		return nil, err
   759  	}
   760  
   761  	return memcachedRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   762  }
   763  
   764  func (client *SQcloudClient) mariadbRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   765  	cli, err := client.getDefaultClient(params)
   766  	if err != nil {
   767  		return nil, err
   768  	}
   769  	return mariadbRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   770  }
   771  
   772  func (client *SQcloudClient) postgresRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   773  	cli, err := client.getDefaultClient(params)
   774  	if err != nil {
   775  		return nil, err
   776  	}
   777  	return postgresRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   778  }
   779  
   780  func (client *SQcloudClient) sqlserverRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   781  	cli, err := client.getDefaultClient(params)
   782  	if err != nil {
   783  		return nil, err
   784  	}
   785  	return sqlserverRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   786  }
   787  
   788  // deprecated
   789  func (client *SQcloudClient) wssRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   790  	cli, err := client.getDefaultClient(params)
   791  	if err != nil {
   792  		return nil, err
   793  	}
   794  	return wssRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   795  }
   796  
   797  func (client *SQcloudClient) sslRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   798  	cli, err := client.getDefaultClient(params)
   799  	if err != nil {
   800  		return nil, err
   801  	}
   802  	return sslRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   803  }
   804  
   805  func (client *SQcloudClient) dnsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   806  	cli, err := client.getDefaultClient(params)
   807  	if err != nil {
   808  		return nil, err
   809  	}
   810  	return dnsRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   811  }
   812  
   813  func (client *SQcloudClient) vpc2017Request(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   814  	cli, err := client.getDefaultClient(params)
   815  	if err != nil {
   816  		return nil, err
   817  	}
   818  	return vpc2017Request(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   819  }
   820  
   821  func (client *SQcloudClient) billingRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   822  	cli, err := client.getDefaultClient(params)
   823  	if err != nil {
   824  		return nil, err
   825  	}
   826  	return billingRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   827  }
   828  
   829  func (client *SQcloudClient) camRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   830  	cli, err := client.getDefaultClient(params)
   831  	if err != nil {
   832  		return nil, err
   833  	}
   834  	return camRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   835  }
   836  
   837  func (client *SQcloudClient) cdnRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   838  	cli, err := client.getDefaultClient(params)
   839  	if err != nil {
   840  		return nil, err
   841  	}
   842  	return cdnRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   843  }
   844  
   845  func (client *SQcloudClient) stsRequest(apiName string, params map[string]string) (jsonutils.JSONObject, error) {
   846  	cli, err := client.getDefaultClient(params)
   847  	if err != nil {
   848  		return nil, err
   849  	}
   850  	return stsRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug)
   851  }
   852  
   853  func (client *SQcloudClient) jsonRequest(apiName string, params map[string]string, retry bool) (jsonutils.JSONObject, error) {
   854  	cli, err := client.getDefaultClient(params)
   855  	if err != nil {
   856  		return nil, err
   857  	}
   858  	return jsonRequest(cli, apiName, params, client.cpcfg.UpdatePermission, client.debug, retry)
   859  }
   860  
   861  func (client *SQcloudClient) fetchRegions() error {
   862  	body, err := client.jsonRequest("DescribeRegions", nil, true)
   863  	if err != nil {
   864  		log.Errorf("fetchRegions fail %s", err)
   865  		return err
   866  	}
   867  
   868  	regions := make([]SRegion, 0)
   869  	err = body.Unmarshal(&regions, "RegionSet")
   870  	if err != nil {
   871  		log.Errorf("unmarshal json error %s", err)
   872  		return err
   873  	}
   874  	client.iregions = make([]cloudprovider.ICloudRegion, len(regions))
   875  	for i := 0; i < len(regions); i++ {
   876  		regions[i].client = client
   877  		client.iregions[i] = &regions[i]
   878  	}
   879  	return nil
   880  }
   881  
   882  func (client *SQcloudClient) getCosClient(bucket *SBucket) (*cos.Client, error) {
   883  	var baseUrl *cos.BaseURL
   884  	if bucket != nil {
   885  		u, _ := url.Parse(bucket.getBucketUrl())
   886  		baseUrl = &cos.BaseURL{
   887  			BucketURL: u,
   888  		}
   889  	}
   890  	ts := &http.Transport{
   891  		Proxy: client.cpcfg.ProxyFunc,
   892  	}
   893  	cosClient := cos.NewClient(
   894  		baseUrl,
   895  		&http.Client{
   896  			Transport: &cos.AuthorizationTransport{
   897  				SecretID:  client.secretId,
   898  				SecretKey: client.secretKey,
   899  				Transport: &debug.DebugRequestTransport{
   900  					RequestHeader:  client.debug,
   901  					RequestBody:    client.debug,
   902  					ResponseHeader: client.debug,
   903  					ResponseBody:   client.debug,
   904  					Transport: cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response), error) {
   905  						method, path := req.Method, req.URL.Path
   906  						respCheck := func(resp *http.Response) {
   907  							if resp.StatusCode == 403 {
   908  								if client.cpcfg.UpdatePermission != nil {
   909  									client.cpcfg.UpdatePermission("cos", fmt.Sprintf("%s %s", method, path))
   910  								}
   911  							}
   912  						}
   913  						if client.cpcfg.ReadOnly {
   914  							if req.Method == "GET" || req.Method == "HEAD" {
   915  								return respCheck, nil
   916  							}
   917  							return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
   918  						}
   919  						return respCheck, nil
   920  					}),
   921  				},
   922  			},
   923  		},
   924  	)
   925  	return cosClient, nil
   926  }
   927  
   928  func (self *SQcloudClient) invalidateIBuckets() {
   929  	self.ibuckets = nil
   930  }
   931  
   932  func (self *SQcloudClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) {
   933  	if self.ibuckets == nil {
   934  		err := self.fetchBuckets()
   935  		if err != nil {
   936  			return nil, errors.Wrap(err, "fetchBuckets")
   937  		}
   938  	}
   939  	return self.ibuckets, nil
   940  }
   941  
   942  func (client *SQcloudClient) verifyAppId() error {
   943  	region, err := client.getDefaultRegion()
   944  	if err != nil {
   945  		return errors.Wrap(err, "getDefaultRegion")
   946  	}
   947  	bucket := SBucket{
   948  		region: region,
   949  		Name:   "yuniondocument",
   950  	}
   951  	cli, err := client.getCosClient(&bucket)
   952  	if err != nil {
   953  		return errors.Wrap(err, "getCosClient")
   954  	}
   955  	resp, err := cli.Bucket.Head(context.Background())
   956  	if resp != nil {
   957  		defer httputils.CloseResponse(resp.Response)
   958  		if resp.StatusCode < 400 || resp.StatusCode == 404 {
   959  			return nil
   960  		}
   961  		return errors.Error(fmt.Sprintf("invalid AppId: %d", resp.StatusCode))
   962  	}
   963  	return errors.Wrap(err, "Head")
   964  }
   965  
   966  func (client *SQcloudClient) fetchBuckets() error {
   967  	coscli, err := client.getCosClient(nil)
   968  	if err != nil {
   969  		return errors.Wrap(err, "GetCosClient")
   970  	}
   971  	s, _, err := coscli.Service.Get(context.Background())
   972  	if err != nil {
   973  		return errors.Wrap(err, "coscli.Service.Get")
   974  	}
   975  	client.ownerId = s.Owner.ID
   976  	client.ownerName = s.Owner.DisplayName
   977  
   978  	ret := make([]cloudprovider.ICloudBucket, 0)
   979  	for i := range s.Buckets {
   980  		bInfo := s.Buckets[i]
   981  		createAt, _ := timeutils.ParseTimeStr(bInfo.CreationDate)
   982  		slashPos := strings.LastIndexByte(bInfo.Name, '-')
   983  		appId := bInfo.Name[slashPos+1:]
   984  		if appId != client.appId {
   985  			log.Errorf("[%s %s] Inconsistent appId: %s expect %s", bInfo.Name, bInfo.Region, appId, client.appId)
   986  		}
   987  		name := bInfo.Name[:slashPos]
   988  		region, err := client.getIRegionByRegionId(bInfo.Region)
   989  		var zone cloudprovider.ICloudZone
   990  		if err != nil {
   991  			log.Errorf("fail to find region %s", bInfo.Region)
   992  			// possibly a zone, try zone
   993  			regionStr := func() string {
   994  				info := strings.Split(bInfo.Region, "-")
   995  				if num, _ := strconv.Atoi(info[len(info)-1]); num > 0 {
   996  					return strings.TrimSuffix(bInfo.Region, fmt.Sprintf("-%d", num))
   997  				}
   998  				return bInfo.Region
   999  			}()
  1000  			region, err = client.getIRegionByRegionId(regionStr)
  1001  			if err != nil {
  1002  				log.Errorf("fail to find region %s", regionStr)
  1003  				continue
  1004  			}
  1005  			zone, _ = region.(*SRegion).getZoneById(bInfo.Region)
  1006  			log.Debugf("find zonal bucket %s", zone.GetId())
  1007  		}
  1008  		b := SBucket{
  1009  			region:     region.(*SRegion),
  1010  			appId:      appId,
  1011  			Name:       name,
  1012  			Location:   bInfo.Region,
  1013  			CreateDate: createAt,
  1014  		}
  1015  		if zone != nil {
  1016  			b.zone = zone.(*SZone)
  1017  		}
  1018  		ret = append(ret, &b)
  1019  	}
  1020  	client.ibuckets = ret
  1021  	return nil
  1022  }
  1023  
  1024  func (client *SQcloudClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
  1025  	err := client.fetchRegions()
  1026  	if err != nil {
  1027  		return nil, err
  1028  	}
  1029  	subAccount := cloudprovider.SSubAccount{}
  1030  	subAccount.Name = client.cpcfg.Name
  1031  	subAccount.Account = client.secretId
  1032  	subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
  1033  	subAccount.DefaultProjectId = "0"
  1034  	if len(client.appId) > 0 {
  1035  		subAccount.Account = fmt.Sprintf("%s/%s", client.secretId, client.appId)
  1036  	}
  1037  	return []cloudprovider.SSubAccount{subAccount}, nil
  1038  }
  1039  
  1040  func (self *SQcloudClient) GetAccountId() string {
  1041  	if len(self.ownerId) == 0 {
  1042  		caller, err := self.GetCallerIdentity()
  1043  		if err == nil {
  1044  			self.ownerId = caller.AccountId
  1045  		}
  1046  	}
  1047  	return self.ownerId
  1048  }
  1049  
  1050  func (client *SQcloudClient) GetIamLoginUrl() string {
  1051  	return fmt.Sprintf("https://cloud.tencent.com/login/subAccount")
  1052  }
  1053  
  1054  func (client *SQcloudClient) GetIRegions() []cloudprovider.ICloudRegion {
  1055  	return client.iregions
  1056  }
  1057  
  1058  func (client *SQcloudClient) getDefaultRegion() (*SRegion, error) {
  1059  	iregion, err := client.getIRegionByRegionId(QCLOUD_DEFAULT_REGION)
  1060  	if err != nil {
  1061  		return nil, errors.Wrap(err, "getIRegionByRegionId")
  1062  	}
  1063  	return iregion.(*SRegion), nil
  1064  }
  1065  
  1066  func (client *SQcloudClient) getIRegionByRegionId(id string) (cloudprovider.ICloudRegion, error) {
  1067  	for i := 0; i < len(client.iregions); i++ {
  1068  		if client.iregions[i].GetId() == id {
  1069  			return client.iregions[i], nil
  1070  		}
  1071  	}
  1072  	return nil, cloudprovider.ErrNotFound
  1073  }
  1074  
  1075  func (client *SQcloudClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
  1076  	for i := 0; i < len(client.iregions); i++ {
  1077  		if client.iregions[i].GetGlobalId() == id {
  1078  			return client.iregions[i], nil
  1079  		}
  1080  	}
  1081  	return nil, cloudprovider.ErrNotFound
  1082  }
  1083  
  1084  func (client *SQcloudClient) GetRegion(regionId string) *SRegion {
  1085  	for i := 0; i < len(client.iregions); i++ {
  1086  		if client.iregions[i].GetId() == regionId {
  1087  			return client.iregions[i].(*SRegion)
  1088  		}
  1089  	}
  1090  	return nil
  1091  }
  1092  
  1093  func (client *SQcloudClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
  1094  	for i := 0; i < len(client.iregions); i++ {
  1095  		ihost, err := client.iregions[i].GetIHostById(id)
  1096  		if err == nil {
  1097  			return ihost, nil
  1098  		} else if errors.Cause(err) != cloudprovider.ErrNotFound {
  1099  			return nil, err
  1100  		}
  1101  	}
  1102  	return nil, cloudprovider.ErrNotFound
  1103  }
  1104  
  1105  func (client *SQcloudClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) {
  1106  	for i := 0; i < len(client.iregions); i++ {
  1107  		ihost, err := client.iregions[i].GetIVpcById(id)
  1108  		if err == nil {
  1109  			return ihost, nil
  1110  		} else if errors.Cause(err) != cloudprovider.ErrNotFound {
  1111  			return nil, err
  1112  		}
  1113  	}
  1114  	return nil, cloudprovider.ErrNotFound
  1115  }
  1116  
  1117  func (client *SQcloudClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
  1118  	for i := 0; i < len(client.iregions); i++ {
  1119  		ihost, err := client.iregions[i].GetIStorageById(id)
  1120  		if err == nil {
  1121  			return ihost, nil
  1122  		} else if errors.Cause(err) != cloudprovider.ErrNotFound {
  1123  			return nil, err
  1124  		}
  1125  	}
  1126  	return nil, cloudprovider.ErrNotFound
  1127  }
  1128  
  1129  type SAccountBalance struct {
  1130  	AvailableAmount     float64
  1131  	AvailableCashAmount float64
  1132  	CreditAmount        float64
  1133  	MybankCreditAmount  float64
  1134  	Currency            string
  1135  }
  1136  
  1137  func (client *SQcloudClient) QueryAccountBalance() (*SAccountBalance, error) {
  1138  	balance := SAccountBalance{}
  1139  	body, err := client.billingRequest("DescribeAccountBalance", nil)
  1140  	if err != nil {
  1141  		if isError(err, []string{"UnauthorizedOperation.NotFinanceAuth"}) {
  1142  			return nil, cloudprovider.ErrNoBalancePermission
  1143  		}
  1144  		log.Errorf("DescribeAccountBalance fail %s", err)
  1145  		return nil, err
  1146  	}
  1147  	log.Debugf("%s", body)
  1148  	balanceCent, _ := body.Float("Balance")
  1149  	balance.AvailableAmount = balanceCent / 100.0
  1150  	return &balance, nil
  1151  }
  1152  
  1153  func (client *SQcloudClient) GetIProjects() ([]cloudprovider.ICloudProject, error) {
  1154  	projects, err := client.GetProjects()
  1155  	if err != nil {
  1156  		return nil, errors.Wrap(err, "GetProjects")
  1157  	}
  1158  	projects = append(projects, SProject{ProjectId: "0", ProjectName: "默认项目"})
  1159  	iprojects := []cloudprovider.ICloudProject{}
  1160  	for i := 0; i < len(projects); i++ {
  1161  		projects[i].client = client
  1162  		iprojects = append(iprojects, &projects[i])
  1163  	}
  1164  	return iprojects, nil
  1165  }
  1166  
  1167  func (self *SQcloudClient) GetCapabilities() []string {
  1168  	caps := []string{
  1169  		cloudprovider.CLOUD_CAPABILITY_PROJECT,
  1170  		cloudprovider.CLOUD_CAPABILITY_COMPUTE,
  1171  		cloudprovider.CLOUD_CAPABILITY_NETWORK,
  1172  		cloudprovider.CLOUD_CAPABILITY_EIP,
  1173  		cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
  1174  		cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
  1175  		cloudprovider.CLOUD_CAPABILITY_RDS,
  1176  		cloudprovider.CLOUD_CAPABILITY_CACHE,
  1177  		cloudprovider.CLOUD_CAPABILITY_EVENT,
  1178  		cloudprovider.CLOUD_CAPABILITY_CLOUDID,
  1179  		cloudprovider.CLOUD_CAPABILITY_DNSZONE,
  1180  		cloudprovider.CLOUD_CAPABILITY_PUBLIC_IP,
  1181  		cloudprovider.CLOUD_CAPABILITY_INTERVPCNETWORK,
  1182  		cloudprovider.CLOUD_CAPABILITY_SAML_AUTH,
  1183  		cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX,
  1184  		cloudprovider.CLOUD_CAPABILITY_MONGO_DB + cloudprovider.READ_ONLY_SUFFIX,
  1185  		cloudprovider.CLOUD_CAPABILITY_ES + cloudprovider.READ_ONLY_SUFFIX,
  1186  		cloudprovider.CLOUD_CAPABILITY_KAFKA + cloudprovider.READ_ONLY_SUFFIX,
  1187  		cloudprovider.CLOUD_CAPABILITY_CDN + cloudprovider.READ_ONLY_SUFFIX,
  1188  		cloudprovider.CLOUD_CAPABILITY_CONTAINER + cloudprovider.READ_ONLY_SUFFIX,
  1189  	}
  1190  	return caps
  1191  }