yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcs/hcs.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 hcs
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net/http"
    21  	"net/url"
    22  	"strconv"
    23  	"strings"
    24  	"time"
    25  
    26  	"github.com/huaweicloud/huaweicloud-sdk-go-obs/obs"
    27  
    28  	"yunion.io/x/jsonutils"
    29  	"yunion.io/x/log"
    30  	"yunion.io/x/pkg/errors"
    31  	"yunion.io/x/pkg/gotypes"
    32  	"yunion.io/x/pkg/utils"
    33  
    34  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    35  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    36  	"yunion.io/x/cloudmux/pkg/multicloud/hcso/client/auth"
    37  	"yunion.io/x/onecloud/pkg/util/httputils"
    38  )
    39  
    40  const (
    41  	VERSION_SPEC = "x-version"
    42  
    43  	CLOUD_PROVIDER_HCS    = api.CLOUD_PROVIDER_HCS
    44  	CLOUD_PROVIDER_HCS_CN = "华为云Stack"
    45  	CLOUD_PROVIDER_HCS_EN = "HCS"
    46  
    47  	HCS_API_VERSION = ""
    48  )
    49  
    50  type HcsConfig struct {
    51  	cpcfg   cloudprovider.ProviderConfig
    52  	authUrl string
    53  
    54  	projectId    string // 华为云项目ID.
    55  	accessKey    string
    56  	accessSecret string
    57  
    58  	debug bool
    59  }
    60  
    61  func NewHcsConfig(accessKey, accessSecret, projectId, url string) *HcsConfig {
    62  	cfg := &HcsConfig{
    63  		projectId:    projectId,
    64  		accessKey:    accessKey,
    65  		accessSecret: accessSecret,
    66  		authUrl:      url,
    67  	}
    68  
    69  	return cfg
    70  }
    71  
    72  func (cfg *HcsConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *HcsConfig {
    73  	cfg.cpcfg = cpcfg
    74  	return cfg
    75  }
    76  
    77  func (cfg *HcsConfig) Debug(debug bool) *HcsConfig {
    78  	cfg.debug = debug
    79  	return cfg
    80  }
    81  
    82  type SHcsClient struct {
    83  	*HcsConfig
    84  
    85  	signer auth.Signer
    86  
    87  	isMainProject bool // whether the project is the main project in the region
    88  
    89  	domainId    string
    90  	projectName string
    91  
    92  	regions  []SRegion
    93  	projects []SProject
    94  	buckets  []SBucket
    95  
    96  	defaultRegion string
    97  
    98  	httpClient *http.Client
    99  	lock       sThrottlingThreshold
   100  }
   101  
   102  func NewHcsClient(cfg *HcsConfig) (*SHcsClient, error) {
   103  	client := SHcsClient{
   104  		HcsConfig: cfg,
   105  		lock:      sThrottlingThreshold{locked: false, lockTime: time.Time{}},
   106  	}
   107  	if len(client.projectId) > 0 {
   108  		project, err := client.GetProjectById(client.projectId)
   109  		if err != nil {
   110  			return nil, err
   111  		}
   112  		client.domainId = project.DomainId
   113  		client.projectName = project.Name
   114  	}
   115  	return &client, client.fetchRegions()
   116  }
   117  
   118  func (self *SHcsClient) GetAccountId() string {
   119  	return self.authUrl
   120  }
   121  
   122  func (self *SHcsClient) GetRegion(id string) (*SRegion, error) {
   123  	for i := range self.regions {
   124  		if self.regions[i].GetGlobalId() == id || self.regions[i].GetId() == id {
   125  			self.regions[i].client = self
   126  			return &self.regions[i], nil
   127  		}
   128  	}
   129  	return nil, cloudprovider.ErrNotFound
   130  }
   131  
   132  func (self *SHcsClient) GetCloudRegionExternalIdPrefix() string {
   133  	if len(self.projectId) > 0 {
   134  		if iregions := self.GetIRegions(); len(iregions) > 0 {
   135  			return iregions[0].GetGlobalId()
   136  		}
   137  	}
   138  	return CLOUD_PROVIDER_HCS
   139  }
   140  
   141  type akClient struct {
   142  	client *http.Client
   143  	signer *Signer
   144  }
   145  
   146  func (self *SHcsClient) getDefaultClient() *http.Client {
   147  	if self.httpClient != nil {
   148  		return self.httpClient
   149  	}
   150  	self.httpClient = self.cpcfg.AdaptiveTimeoutHttpClient()
   151  	ts, _ := self.httpClient.Transport.(*http.Transport)
   152  	self.httpClient.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response), error) {
   153  		service, method, path := strings.Split(req.URL.Host, ".")[0], req.Method, req.URL.Path
   154  		respCheck := func(resp *http.Response) {
   155  			if resp.StatusCode == 403 {
   156  				if self.cpcfg.UpdatePermission != nil {
   157  					self.cpcfg.UpdatePermission(service, fmt.Sprintf("%s %s", method, path))
   158  				}
   159  			}
   160  		}
   161  		if self.cpcfg.ReadOnly {
   162  			if req.Method == "GET" {
   163  				return respCheck, nil
   164  			}
   165  			return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
   166  		}
   167  		return respCheck, nil
   168  	})
   169  	return self.httpClient
   170  }
   171  
   172  func (self *akClient) Do(req *http.Request) (*http.Response, error) {
   173  	req.Header.Del("Accept")
   174  	if req.Method == string(httputils.GET) || req.Method == string(httputils.DELETE) || req.Method == string(httputils.PATCH) {
   175  		req.Header.Del("Content-Length")
   176  	}
   177  	err := self.signer.Sign(req)
   178  	if err != nil {
   179  		return nil, errors.Wrapf(err, "Sign")
   180  	}
   181  	return self.client.Do(req)
   182  }
   183  
   184  func (self *SHcsClient) getAkClient() *akClient {
   185  	return &akClient{
   186  		client: self.getDefaultClient(),
   187  		signer: &Signer{
   188  			Key:    self.accessKey,
   189  			Secret: self.accessSecret,
   190  		},
   191  	}
   192  }
   193  
   194  type hcsError struct {
   195  	Url      string
   196  	Params   map[string]interface{}
   197  	Code     int    `json:"code,omitzero"`
   198  	Class    string `json:"class,omitempty"`
   199  	ErrorMsg string `json:"error_msg,omitempty"`
   200  	Details  string `json:"details,omitempty"`
   201  }
   202  
   203  func (ce *hcsError) Error() string {
   204  	return jsonutils.Marshal(ce).String()
   205  }
   206  
   207  func (ce *hcsError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error {
   208  	body.Unmarshal(ce)
   209  	if ce.Code == 0 {
   210  		ce.Code = statusCode
   211  	}
   212  	if len(ce.Class) == 0 {
   213  		ce.Class = http.StatusText(statusCode)
   214  	}
   215  	if len(ce.Details) == 0 {
   216  		ce.Details = body.String()
   217  	}
   218  	return ce
   219  }
   220  
   221  type sThrottlingThreshold struct {
   222  	locked   bool
   223  	lockTime time.Time
   224  }
   225  
   226  func (t *sThrottlingThreshold) CheckingLock() {
   227  	if !t.locked {
   228  		return
   229  	}
   230  
   231  	for {
   232  		if t.lockTime.Sub(time.Now()).Seconds() < 0 {
   233  			return
   234  		}
   235  		log.Debugf("throttling threshold has been reached. release at %s", t.lockTime)
   236  		time.Sleep(5 * time.Second)
   237  	}
   238  }
   239  
   240  func (t *sThrottlingThreshold) Lock() {
   241  	// 锁定至少15秒
   242  	t.locked = true
   243  	t.lockTime = time.Now().Add(15 * time.Second)
   244  }
   245  
   246  func (self *SHcsClient) request(method httputils.THttpMethod, url string, query url.Values, params map[string]interface{}) (jsonutils.JSONObject, error) {
   247  	self.lock.CheckingLock()
   248  	client := self.getAkClient()
   249  	if len(query) > 0 {
   250  		url = fmt.Sprintf("%s?%s", url, query.Encode())
   251  	}
   252  	url = strings.TrimPrefix(url, "http://")
   253  	if !strings.HasPrefix(url, "https://") {
   254  		url = fmt.Sprintf("https://%s", url)
   255  	}
   256  	var body jsonutils.JSONObject = nil
   257  	if len(params) > 0 {
   258  		body = jsonutils.Marshal(params)
   259  	}
   260  	header := http.Header{}
   261  	if len(self.projectId) > 0 && !strings.Contains(url, "v3/regions") {
   262  		header.Set("X-Project-Id", self.projectId)
   263  	}
   264  	if len(self.domainId) > 0 {
   265  		//header.Set("X-Domain-Id", self.domainId)
   266  	}
   267  	cli := httputils.NewJsonClient(client)
   268  	req := httputils.NewJsonRequest(method, url, body)
   269  	req.SetHeader(header)
   270  	var resp jsonutils.JSONObject
   271  	var err error
   272  	for i := 0; i < 4; i++ {
   273  		_, resp, err = cli.Send(context.Background(), req, &hcsError{Url: url, Params: params}, self.debug)
   274  		if err == nil {
   275  			break
   276  		}
   277  		if err != nil {
   278  			e, ok := err.(*hcsError)
   279  			if ok {
   280  				if e.Code == 404 {
   281  					return nil, errors.Wrapf(cloudprovider.ErrNotFound, err.Error())
   282  				}
   283  				if e.Code == 429 {
   284  					log.Errorf("request %s %v try later", url, err)
   285  					self.lock.Lock()
   286  					time.Sleep(time.Second * 15)
   287  					continue
   288  				}
   289  			}
   290  			return nil, err
   291  		}
   292  	}
   293  	return resp, err
   294  }
   295  
   296  func (self *SHcsClient) iamGet(resource string, query url.Values) (jsonutils.JSONObject, error) {
   297  	url := fmt.Sprintf("iam-apigateway-proxy.%s/%s", self.authUrl, resource)
   298  	return self.request(httputils.GET, url, query, nil)
   299  }
   300  
   301  func (self *SHcsClient) fetchRegions() error {
   302  	if len(self.regions) > 0 {
   303  		return nil
   304  	}
   305  	resp, err := self.iamGet("v3/regions", nil)
   306  	if err != nil {
   307  		return err
   308  	}
   309  	self.regions = []SRegion{}
   310  	err = resp.Unmarshal(&self.regions, "regions")
   311  	if err != nil {
   312  		return err
   313  	}
   314  	for i := range self.regions {
   315  		self.defaultRegion = self.regions[i].Id
   316  	}
   317  	return nil
   318  }
   319  
   320  func (hcscli *SHcsClient) rdsList(regionId string, resource string, query url.Values, retVal interface{}) error {
   321  	return hcscli.list("rds", "v3", regionId, resource, query, retVal)
   322  }
   323  
   324  func (hcscli *SHcsClient) rdsGet(regionId string, resource string, retVal interface{}) error {
   325  	return hcscli.get("rds", "v3", regionId, resource, retVal)
   326  }
   327  
   328  func (hcscli *SHcsClient) rdsDelete(regionId string, resource string) error {
   329  	return hcscli._delete("rds", "v3", regionId, resource)
   330  }
   331  
   332  func (hcscli *SHcsClient) rdsCreate(regionId string, resource string, body map[string]interface{}, retVal interface{}) error {
   333  	return hcscli._create("rds", "v3", regionId, resource, body, retVal)
   334  }
   335  
   336  func (self *SHcsClient) rdsPerform(regionId string, resource, action string, params map[string]interface{}, retVal interface{}) error {
   337  	return self._perform("rds", "v3", regionId, resource, action, params, retVal)
   338  }
   339  
   340  func (hcscli *SHcsClient) rdsJobGet(regionId string, resource string, query url.Values, retVal interface{}) error {
   341  	url := hcscli._url("rds", "v3", regionId, resource)
   342  	resp, err := hcscli.request(httputils.GET, url, query, nil)
   343  	if err != nil {
   344  		return err
   345  	}
   346  	err = resp.Unmarshal(retVal)
   347  	return err
   348  }
   349  
   350  func (hcscli *SHcsClient) rdsDBPrivvilegesDelete(regionId string, resource string, params map[string]interface{}) error {
   351  	return hcscli._deleteWithBody("rds", "v3", regionId, resource, params)
   352  }
   353  
   354  func (hcscli *SHcsClient) rdsDBPrivilegesGrant(regionId string, resource string, params map[string]interface{}, retVal interface{}) error {
   355  	url := hcscli._url("rds", "v3", regionId, resource)
   356  	resp, err := hcscli.request(httputils.GET, url, nil, params)
   357  	if err != nil {
   358  		return err
   359  	}
   360  	err = resp.Unmarshal(retVal)
   361  	return err
   362  }
   363  
   364  func (self *SHcsClient) ecsList(regionId string, resource string, query url.Values, retVal interface{}) error {
   365  	return self.list("ecs", "v2", regionId, resource, query, retVal)
   366  }
   367  
   368  func (self *SHcsClient) list(product, version, regionId string, resource string, query url.Values, retVal interface{}) error {
   369  	resp, err := self._list(product, version, regionId, resource, query)
   370  	if err != nil {
   371  		return errors.Wrapf(err, "_list")
   372  	}
   373  	return resp.Unmarshal(retVal)
   374  }
   375  
   376  func (self *SHcsClient) ecsGet(regionId string, resource string, retVal interface{}) error {
   377  	return self.get("ecs", "v2", regionId, resource, retVal)
   378  }
   379  
   380  func (self *SHcsClient) ecsDelete(regionId string, resource string) error {
   381  	return self._delete("ecs", "v2", regionId, resource)
   382  }
   383  
   384  func (self *SHcsClient) ecsPerform(regionId string, resource, action string, params map[string]interface{}, retVal interface{}) error {
   385  	return self._perform("ecs", "v2", regionId, resource, action, params, retVal)
   386  }
   387  
   388  func (self *SHcsClient) evsDelete(regionId string, resource string) error {
   389  	return self._delete("evs", "v2", regionId, resource)
   390  }
   391  
   392  func (self *SHcsClient) evsList(regionId string, resource string, query url.Values, retVal interface{}) error {
   393  	return self.list("evs", "v2", regionId, resource, query, retVal)
   394  }
   395  
   396  func (self *SHcsClient) evsGet(regionId string, resource string, retVal interface{}) error {
   397  	return self.get("evs", "v2", regionId, resource, retVal)
   398  }
   399  
   400  func (self *SHcsClient) evsPerform(regionId string, resource, action string, params map[string]interface{}) error {
   401  	return self._perform("evs", "v2", regionId, resource, action, params, nil)
   402  }
   403  
   404  func (self *SHcsClient) ecsCreate(regionId string, resource string, body map[string]interface{}, retVal interface{}) error {
   405  	return self._create("ecs", "v2", regionId, resource, body, retVal)
   406  }
   407  
   408  func (self *SHcsClient) evsCreate(regionId string, resource string, body map[string]interface{}, retVal interface{}) error {
   409  	return self._create("evs", "v2", regionId, resource, body, retVal)
   410  }
   411  
   412  func (self *SHcsClient) vpcCreate(regionId string, resource string, body map[string]interface{}, retVal interface{}) error {
   413  	return self._create("vpc", "v1", regionId, resource, body, retVal)
   414  }
   415  
   416  func (self *SHcsClient) vpcDelete(regionId string, resource string) error {
   417  	return self._delete("vpc", "v1", regionId, resource)
   418  }
   419  
   420  func (self *SHcsClient) vpcGet(regionId string, resource string, retVal interface{}) error {
   421  	return self.get("vpc", "v1", regionId, resource, retVal)
   422  }
   423  
   424  func (self *SHcsClient) vpcList(regionId string, resource string, query url.Values, retVal interface{}) error {
   425  	return self.list("vpc", "v1", regionId, resource, query, retVal)
   426  }
   427  
   428  func (self *SHcsClient) vpcUpdate(regionId string, resource string, body map[string]interface{}) error {
   429  	return self._update("vpc", "v1", regionId, resource, body)
   430  }
   431  
   432  func (self *SHcsClient) imsCreate(regionId string, resource string, body map[string]interface{}, retVal interface{}) error {
   433  	return self._create("ims", "v2", regionId, resource, body, retVal)
   434  }
   435  
   436  func (self *SHcsClient) imsDelete(regionId string, resource string) error {
   437  	return self._delete("ims", "v2", regionId, resource)
   438  }
   439  
   440  func (self *SHcsClient) imsGet(regionId string, resource string, retVal interface{}) error {
   441  	return self.get("ims", "v2", regionId, resource, retVal)
   442  }
   443  
   444  func (self *SHcsClient) imsList(regionId string, resource string, query url.Values, retVal interface{}) error {
   445  	return self.list("ims", "v2", regionId, resource, query, retVal)
   446  }
   447  
   448  func (self *SHcsClient) imsPerform(regionId string, resource, action string, params map[string]interface{}, retVal interface{}) error {
   449  	return self._perform("ims", "v2", regionId, resource, action, params, retVal)
   450  }
   451  
   452  func (self *SHcsClient) imsUpdate(regionId string, resource string, body map[string]interface{}) error {
   453  	return self._update("ims", "v2", regionId, resource, body)
   454  }
   455  
   456  func (self *SHcsClient) dcsCreate(regionId string, resource string, body map[string]interface{}, retVal interface{}) error {
   457  	return self._create("dcs", "v1.0", regionId, resource, body, retVal)
   458  }
   459  
   460  func (self *SHcsClient) dcsDelete(regionId string, resource string) error {
   461  	return self._delete("dcs", "v1.0", regionId, resource)
   462  }
   463  
   464  func (self *SHcsClient) dcsGet(regionId string, resource string, retVal interface{}) error {
   465  	return self.get("dcs", "v1.0", regionId, resource, retVal)
   466  }
   467  
   468  func (self *SHcsClient) dcsList(regionId string, resource string, query url.Values, retVal interface{}) error {
   469  	return self.list("dcs", "v1.0", regionId, resource, query, retVal)
   470  }
   471  
   472  func (self *SHcsClient) dcsPerform(regionId string, resource, action string, params map[string]interface{}, retVal interface{}) error {
   473  	return self._perform("dcs", "v1.0", regionId, resource, action, params, retVal)
   474  }
   475  
   476  func (self *SHcsClient) dcsUpdate(regionId string, resource string, body map[string]interface{}) error {
   477  	return self._update("dcs", "v1.0", regionId, resource, body)
   478  }
   479  
   480  func (self *SHcsClient) _url(product, version, regionId string, resource string) string {
   481  	url := fmt.Sprintf("%s.%s.%s/%s/%s/%s", product, regionId, self.authUrl, version, self.projectId, resource)
   482  	for _, prefix := range []string{
   483  		"images", "cloudimages", "nat_gateways",
   484  		"lbaas", "products", "snat_rules",
   485  		"dnat_rules", "vpc/peerings",
   486  	} {
   487  		if strings.HasPrefix(resource, prefix) {
   488  			url = fmt.Sprintf("%s.%s.%s/%s/%s", product, regionId, self.authUrl, version, resource)
   489  			break
   490  		}
   491  	}
   492  	return url
   493  }
   494  
   495  func (self *SHcsClient) _list(product, version, regionId string, resource string, query url.Values) (*jsonutils.JSONArray, error) {
   496  	ret := jsonutils.NewArray()
   497  	url := self._url(product, version, regionId, resource)
   498  	offset, _ := strconv.Atoi(query.Get("offset"))
   499  	total := int64(0)
   500  	for {
   501  		resp, err := self.request(httputils.GET, url, query, nil)
   502  		if err != nil {
   503  			return nil, err
   504  		}
   505  		if gotypes.IsNil(resp) {
   506  			log.Warningf("%s return empty", resource)
   507  			return ret, nil
   508  		}
   509  		objMap, err := resp.GetMap()
   510  		if err != nil {
   511  			return nil, errors.Wrapf(err, "resp.GetMap")
   512  		}
   513  		next := false
   514  		for k, v := range objMap {
   515  			if k == "links" {
   516  				url, _ = v.GetString("next")
   517  				next = true
   518  				continue
   519  			}
   520  			if k == "count" {
   521  				offset++
   522  				query.Set("offset", fmt.Sprintf("%d", offset))
   523  				total, _ = v.Int()
   524  				next = true
   525  				continue
   526  			}
   527  			if strings.Contains(resource, k) ||
   528  				strings.Contains(strings.ReplaceAll(resource, "-", "_"), k) ||
   529  				utils.IsInStringArray(k, []string{
   530  					"availabilityZoneInfo",
   531  				}) {
   532  				objs, err := v.GetArray()
   533  				if err != nil {
   534  					return nil, errors.Wrapf(err, "v.GetArray")
   535  				}
   536  				ret.Add(objs...)
   537  			}
   538  		}
   539  		if !next || len(url) == 0 || (ret.Length() == int(total)) {
   540  			break
   541  		}
   542  	}
   543  	return ret, nil
   544  }
   545  
   546  func (self *SHcsClient) get(product, version, regionId string, resource string, retVal interface{}) error {
   547  	resp, err := self._get(product, version, regionId, resource)
   548  	if err != nil {
   549  		return err
   550  	}
   551  	obj, err := resp.GetMap()
   552  	if err != nil {
   553  		return errors.Wrapf(err, "GetMap")
   554  	}
   555  	for v := range obj {
   556  		if len(obj) == 1 {
   557  			return obj[v].Unmarshal(retVal)
   558  		}
   559  	}
   560  	return resp.Unmarshal(retVal)
   561  }
   562  
   563  func (self *SHcsClient) _getJob(product, regionId string, jobId string) (jsonutils.JSONObject, error) {
   564  	url := self._url(product, "v1", regionId, fmt.Sprintf("jobs/%s", jobId))
   565  	resp, err := self.request(httputils.GET, url, nil, nil)
   566  	if err != nil {
   567  		return nil, err
   568  	}
   569  	return resp, nil
   570  }
   571  
   572  func (self *SHcsClient) _get(product, version, regionId string, resource string) (jsonutils.JSONObject, error) {
   573  	url := self._url(product, version, regionId, resource)
   574  	resp, err := self.request(httputils.GET, url, nil, nil)
   575  	if err != nil {
   576  		return nil, err
   577  	}
   578  	return resp, nil
   579  }
   580  
   581  func (self *SHcsClient) delete(product, version, regionId string, resource string) error {
   582  	return self._delete(product, version, regionId, resource)
   583  }
   584  
   585  func (self *SHcsClient) _delete(product, version, regionId string, resource string) error {
   586  	url := self._url(product, version, regionId, resource)
   587  	resp, err := self.request(httputils.DELETE, url, nil, nil)
   588  	if !gotypes.IsNil(resp) && resp.Contains("job_id") {
   589  		jobId, _ := resp.GetString("job_id")
   590  		_, err := self.waitJobSuccess(product, regionId, jobId, time.Second*10, time.Hour*2)
   591  		if err != nil {
   592  			return errors.Wrapf(err, "wait create %s %s job", product, resource)
   593  		}
   594  		return nil
   595  	}
   596  	return err
   597  }
   598  
   599  func (self *SHcsClient) _deleteWithBody(product, version, regionId string, resource string, params map[string]interface{}) error {
   600  	url := self._url(product, version, regionId, resource)
   601  	_, err := self.request(httputils.DELETE, url, nil, params)
   602  	return err
   603  }
   604  
   605  func (self *SHcsClient) update(product, version, regionId string, resource string, params map[string]interface{}) error {
   606  	return self._update(product, version, regionId, resource, params)
   607  }
   608  
   609  func (self *SHcsClient) perform(product, version, regionId string, resource, action string, params map[string]interface{}, retVal interface{}) error {
   610  	return self._perform(product, version, regionId, resource, action, params, retVal)
   611  }
   612  
   613  func (self *SHcsClient) _perform(product, version, regionId string, resource, action string, params map[string]interface{}, retVal interface{}) error {
   614  	url := self._url(product, version, regionId, resource+"/"+action)
   615  	resp, err := self.request(httputils.POST, url, nil, params)
   616  	if err != nil {
   617  		return err
   618  	}
   619  	if !gotypes.IsNil(resp) && resp.Contains("job_id") {
   620  		jobId, _ := resp.GetString("job_id")
   621  		job, err := self.waitJobSuccess(product, regionId, jobId, time.Second*10, time.Hour*2)
   622  		if err != nil {
   623  			return errors.Wrapf(err, "wait create %s %s job", product, resource)
   624  		}
   625  		if retVal != nil {
   626  			return jsonutils.Update(retVal, jsonutils.Marshal(job))
   627  		}
   628  		return nil
   629  	}
   630  	if retVal != nil {
   631  		return resp.Unmarshal(retVal)
   632  	}
   633  	return nil
   634  }
   635  
   636  func (self *SHcsClient) create(product, version, regionId string, resource string, body map[string]interface{}, retVal interface{}) error {
   637  	return self._create(product, version, regionId, resource, body, retVal)
   638  }
   639  
   640  func (self *SHcsClient) _create(product, version, regionId string, resource string, body map[string]interface{}, retVal interface{}) error {
   641  	url := self._url(product, version, regionId, resource)
   642  	resp, err := self.request(httputils.POST, url, nil, body)
   643  	if err != nil {
   644  		return err
   645  	}
   646  	if resp.Contains("job_id") {
   647  		jobId, _ := resp.GetString("job_id")
   648  		job, err := self.waitJobSuccess(product, regionId, jobId, time.Second*10, time.Hour*2)
   649  		if err != nil {
   650  			return errors.Wrapf(err, "wait create %s %s job", product, resource)
   651  		}
   652  		if retVal != nil {
   653  			return jsonutils.Update(retVal, jsonutils.Marshal(job))
   654  		}
   655  		return nil
   656  	}
   657  	obj, err := resp.GetMap()
   658  	if err != nil {
   659  		return errors.Wrapf(err, "GetMap")
   660  	}
   661  	for v := range obj {
   662  		if len(obj) == 1 && retVal != nil {
   663  			return obj[v].Unmarshal(retVal)
   664  		}
   665  	}
   666  	if retVal != nil {
   667  		return resp.Unmarshal(retVal)
   668  	}
   669  	return nil
   670  }
   671  
   672  func (self *SHcsClient) _update(product, version, regionId string, resource string, body map[string]interface{}) error {
   673  	url := self._url(product, version, regionId, resource)
   674  	_, err := self.request(httputils.PUT, url, nil, body)
   675  	return err
   676  }
   677  
   678  func (self *SHcsClient) GetRegions() []SRegion {
   679  	return self.regions
   680  }
   681  
   682  func (self *SHcsClient) GetIRegions() []cloudprovider.ICloudRegion {
   683  	ret := []cloudprovider.ICloudRegion{}
   684  	if len(self.projectId) == 0 {
   685  		for i := range self.regions {
   686  			self.regions[i].client = self
   687  			self.defaultRegion = self.regions[i].Id
   688  			ret = append(ret, &self.regions[i])
   689  		}
   690  		return ret
   691  	} else {
   692  		for i := range self.regions {
   693  			if strings.Contains(self.projectName, self.regions[i].Id) {
   694  				self.defaultRegion = self.regions[i].Id
   695  				self.regions[i].client = self
   696  				ret = append(ret, &self.regions[i])
   697  			}
   698  			if self.projectName == self.regions[i].Id {
   699  				self.isMainProject = true
   700  			}
   701  		}
   702  	}
   703  	return ret
   704  }
   705  
   706  func (self *SHcsClient) GetCapabilities() []string {
   707  	caps := []string{
   708  		cloudprovider.CLOUD_CAPABILITY_PROJECT,
   709  		cloudprovider.CLOUD_CAPABILITY_COMPUTE,
   710  		cloudprovider.CLOUD_CAPABILITY_NETWORK,
   711  		cloudprovider.CLOUD_CAPABILITY_CACHE,
   712  		cloudprovider.CLOUD_CAPABILITY_RDS,
   713  		cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
   714  		cloudprovider.CLOUD_CAPABILITY_NAT,
   715  	}
   716  	// huawei objectstore is shared across projects(subscriptions)
   717  	// to avoid multiple project access the same bucket
   718  	// only main project is allow to access objectstore bucket
   719  	if self.isMainProject {
   720  		caps = append(caps, cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE)
   721  	}
   722  	return caps
   723  }
   724  
   725  func (self *SHcsClient) getOBSEndpoint(regionId string) string {
   726  	return fmt.Sprintf("obs.%s.%s", regionId, self.authUrl)
   727  }
   728  
   729  func (self *SHcsClient) getOBSClient(regionId string) (*obs.ObsClient, error) {
   730  	endpoint := self.getOBSEndpoint(regionId)
   731  	ts, _ := self.httpClient.Transport.(*http.Transport)
   732  	conf := obs.WithHttpTransport(ts)
   733  	cli, err := obs.New(self.accessKey, self.accessSecret, endpoint, conf)
   734  	if err != nil {
   735  		return nil, err
   736  	}
   737  	return cli, nil
   738  }
   739  
   740  func (self *SHcsClient) GetBuckets() ([]SBucket, error) {
   741  	if len(self.buckets) > 0 {
   742  		return self.buckets, nil
   743  	}
   744  	obscli, err := self.getOBSClient(self.regions[0].Id)
   745  	if err != nil {
   746  		return nil, errors.Wrap(err, "getOBSClient")
   747  	}
   748  	input := &obs.ListBucketsInput{QueryLocation: true}
   749  	output, err := obscli.ListBuckets(input)
   750  	if err != nil {
   751  		return nil, errors.Wrap(err, "obscli.ListBuckets")
   752  	}
   753  	self.buckets = []SBucket{}
   754  	for i := range output.Buckets {
   755  		bInfo := output.Buckets[i]
   756  		region, err := self.GetRegion(bInfo.Location)
   757  		if err != nil {
   758  			log.Errorf("fail to find region %s", bInfo.Location)
   759  			continue
   760  		}
   761  		b := SBucket{
   762  			region: region,
   763  
   764  			Name:         bInfo.Name,
   765  			Location:     bInfo.Location,
   766  			CreationDate: bInfo.CreationDate,
   767  		}
   768  		self.buckets = append(self.buckets, b)
   769  	}
   770  	return self.buckets, nil
   771  }