yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/huawei/huawei.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 huawei
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net/http"
    21  	"net/url"
    22  	"strconv"
    23  	"strings"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/huaweicloud/huaweicloud-sdk-go/auth/aksk"
    28  
    29  	"yunion.io/x/jsonutils"
    30  	"yunion.io/x/log"
    31  	"yunion.io/x/pkg/errors"
    32  	"yunion.io/x/pkg/gotypes"
    33  	"yunion.io/x/pkg/util/timeutils"
    34  
    35  	"yunion.io/x/onecloud/pkg/util/httputils"
    36  
    37  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    38  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    39  	"yunion.io/x/cloudmux/pkg/multicloud/huawei/client"
    40  	"yunion.io/x/cloudmux/pkg/multicloud/huawei/client/auth"
    41  	"yunion.io/x/cloudmux/pkg/multicloud/huawei/client/auth/credentials"
    42  	"yunion.io/x/cloudmux/pkg/multicloud/huawei/obs"
    43  )
    44  
    45  /*
    46  待解决问题:
    47  1.同步的子账户中有一条空记录.需要查原因
    48  2.安全组同步需要进一步确认
    49  3.实例接口需要进一步确认
    50  4.BGP type 目前是hard code在代码中。需要考虑从cloudmeta服务中查询
    51  */
    52  
    53  const (
    54  	CLOUD_PROVIDER_HUAWEI    = api.CLOUD_PROVIDER_HUAWEI
    55  	CLOUD_PROVIDER_HUAWEI_CN = "华为云"
    56  	CLOUD_PROVIDER_HUAWEI_EN = "Huawei"
    57  
    58  	HUAWEI_INTERNATIONAL_CLOUDENV = "InternationalCloud"
    59  	HUAWEI_CHINA_CLOUDENV         = "ChinaCloud"
    60  
    61  	HUAWEI_DEFAULT_REGION = "cn-north-1"
    62  	HUAWEI_API_VERSION    = "2018-12-25"
    63  )
    64  
    65  var HUAWEI_REGION_CACHES sync.Map
    66  
    67  type userRegionsCache struct {
    68  	UserId   string
    69  	ExpireAt time.Time
    70  	Regions  []SRegion
    71  }
    72  
    73  type HuaweiClientConfig struct {
    74  	cpcfg cloudprovider.ProviderConfig
    75  
    76  	projectId    string // 华为云项目ID.
    77  	cloudEnv     string // 服务区域 ChinaCloud | InternationalCloud
    78  	accessKey    string
    79  	accessSecret string
    80  
    81  	debug bool
    82  }
    83  
    84  func NewHuaweiClientConfig(cloudEnv, accessKey, accessSecret, projectId string) *HuaweiClientConfig {
    85  	cfg := &HuaweiClientConfig{
    86  		projectId:    projectId,
    87  		cloudEnv:     cloudEnv,
    88  		accessKey:    accessKey,
    89  		accessSecret: accessSecret,
    90  	}
    91  	return cfg
    92  }
    93  
    94  func (cfg *HuaweiClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *HuaweiClientConfig {
    95  	cfg.cpcfg = cpcfg
    96  	return cfg
    97  }
    98  
    99  func (cfg *HuaweiClientConfig) Debug(debug bool) *HuaweiClientConfig {
   100  	cfg.debug = debug
   101  	return cfg
   102  }
   103  
   104  type SHuaweiClient struct {
   105  	*HuaweiClientConfig
   106  
   107  	signer auth.Signer
   108  
   109  	isMainProject bool // whether the project is the main project in the region
   110  	clientRegion  string
   111  
   112  	ownerId         string
   113  	ownerName       string
   114  	ownerCreateTime time.Time
   115  
   116  	iregions []cloudprovider.ICloudRegion
   117  	iBuckets []cloudprovider.ICloudBucket
   118  
   119  	projects []SProject
   120  	regions  []SRegion
   121  
   122  	httpClient *http.Client
   123  }
   124  
   125  // 进行资源操作时参数account 对应数据库cloudprovider表中的account字段,由accessKey和projectID两部分组成,通过"/"分割。
   126  // 初次导入Subaccount时,参数account对应cloudaccounts表中的account字段,即accesskey。此时projectID为空,
   127  // 只能进行同步子账号、查询region列表等projectId无关的操作。
   128  // todo: 通过accessurl支持国际站。目前暂时未支持国际站
   129  func NewHuaweiClient(cfg *HuaweiClientConfig) (*SHuaweiClient, error) {
   130  	client := SHuaweiClient{
   131  		HuaweiClientConfig: cfg,
   132  	}
   133  	err := client.init()
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	return &client, nil
   138  }
   139  
   140  func (self *SHuaweiClient) init() error {
   141  	err := self.fetchRegions()
   142  	if err != nil {
   143  		return err
   144  	}
   145  	err = self.initSigner()
   146  	if err != nil {
   147  		return errors.Wrap(err, "initSigner")
   148  	}
   149  	err = self.initOwner()
   150  	if err != nil {
   151  		return errors.Wrap(err, "fetchOwner")
   152  	}
   153  	if self.debug {
   154  		log.Debugf("OwnerId: %s name: %s", self.ownerId, self.ownerName)
   155  	}
   156  	return nil
   157  }
   158  
   159  func (self *SHuaweiClient) initSigner() error {
   160  	var err error
   161  	cred := credentials.NewAccessKeyCredential(self.accessKey, self.accessKey)
   162  	self.signer, err = auth.NewSignerWithCredential(cred)
   163  	if err != nil {
   164  		return err
   165  	}
   166  	return nil
   167  }
   168  
   169  func (self *SHuaweiClient) getDefaultClient() *http.Client {
   170  	if self.httpClient != nil {
   171  		return self.httpClient
   172  	}
   173  	self.httpClient = self.cpcfg.AdaptiveTimeoutHttpClient()
   174  	ts, _ := self.httpClient.Transport.(*http.Transport)
   175  	self.httpClient.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response), error) {
   176  		service, method, path := strings.Split(req.URL.Host, ".")[0], req.Method, req.URL.Path
   177  		respCheck := func(resp *http.Response) {
   178  			if resp.StatusCode == 403 {
   179  				if self.cpcfg.UpdatePermission != nil {
   180  					self.cpcfg.UpdatePermission(service, fmt.Sprintf("%s %s", method, path))
   181  				}
   182  			}
   183  		}
   184  		if self.cpcfg.ReadOnly {
   185  			if req.Method == "GET" {
   186  				return respCheck, nil
   187  			}
   188  			return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
   189  		}
   190  		return respCheck, nil
   191  	})
   192  	return self.httpClient
   193  }
   194  
   195  func (self *SHuaweiClient) newRegionAPIClient(regionId string) (*client.Client, error) {
   196  	projectId := self.projectId
   197  	if len(regionId) == 0 {
   198  		projectId = ""
   199  	}
   200  	cli, err := client.NewPublicCloudClientWithAccessKey(regionId, self.ownerId, projectId, self.accessKey, self.accessSecret, self.debug)
   201  	if err != nil {
   202  		return nil, err
   203  	}
   204  
   205  	httpClient := self.getDefaultClient()
   206  	cli.SetHttpClient(httpClient)
   207  
   208  	return cli, nil
   209  }
   210  
   211  type sPageInfo struct {
   212  	NextMarker string
   213  }
   214  
   215  func (self *SHuaweiClient) newGeneralAPIClient() (*client.Client, error) {
   216  	return self.newRegionAPIClient("")
   217  }
   218  
   219  func (self *SHuaweiClient) lbList(regionId, resource string, query url.Values) (jsonutils.JSONObject, error) {
   220  	url := fmt.Sprintf("https://elb.%s.myhuaweicloud.com/v2/%s/%s", regionId, self.projectId, resource)
   221  	return self.request(httputils.GET, url, query, nil)
   222  }
   223  
   224  func (self *SHuaweiClient) monitorList(resource string, query url.Values) (jsonutils.JSONObject, error) {
   225  	url := fmt.Sprintf("https://ces.%s.myhuaweicloud.com/v1.0/%s/%s", self.clientRegion, self.projectId, resource)
   226  	return self.request(httputils.GET, url, query, nil)
   227  }
   228  
   229  func (self *SHuaweiClient) monitorPost(resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
   230  	url := fmt.Sprintf("https://ces.%s.myhuaweicloud.com/V1.0/%s/%s", self.clientRegion, self.projectId, resource)
   231  	return self.request(httputils.POST, url, nil, params)
   232  }
   233  
   234  func (self *SHuaweiClient) modelartsPoolNetworkList(resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
   235  	uri := fmt.Sprintf("https://modelarts.%s.myhuaweicloud.com/v1/%s/networks", self.clientRegion, self.projectId)
   236  	return self.request(httputils.GET, uri, url.Values{}, params)
   237  }
   238  
   239  func (self *SHuaweiClient) modelartsPoolNetworkCreate(params map[string]interface{}) (jsonutils.JSONObject, error) {
   240  	uri := fmt.Sprintf("https://modelarts.%s.myhuaweicloud.com/v1/%s/networks", self.clientRegion, self.projectId)
   241  	return self.request(httputils.POST, uri, url.Values{}, params)
   242  }
   243  
   244  func (self *SHuaweiClient) modelartsPoolById(poolName string) (jsonutils.JSONObject, error) {
   245  	uri := fmt.Sprintf("https://modelarts.%s.myhuaweicloud.com/v2/%s/pools/%s", self.clientRegion, self.projectId, poolName)
   246  	return self.request(httputils.GET, uri, url.Values{}, nil)
   247  }
   248  
   249  func (self *SHuaweiClient) modelartsPoolList(resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
   250  	uri := fmt.Sprintf("https://modelarts.%s.myhuaweicloud.com/v2/%s/%s", self.clientRegion, self.projectId, resource)
   251  	return self.request(httputils.GET, uri, url.Values{}, params)
   252  }
   253  
   254  func (self *SHuaweiClient) modelartsPoolCreate(resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
   255  	uri := fmt.Sprintf("https://modelarts.%s.myhuaweicloud.com/v2/%s/%s", self.clientRegion, self.projectId, resource)
   256  	return self.request(httputils.POST, uri, url.Values{}, params)
   257  }
   258  
   259  func (self *SHuaweiClient) modelartsPoolDelete(resource, poolName string, params map[string]interface{}) (jsonutils.JSONObject, error) {
   260  	uri := fmt.Sprintf("https://modelarts.%s.myhuaweicloud.com/v2/%s/pools/%s", self.clientRegion, self.projectId, poolName)
   261  	return self.request(httputils.DELETE, uri, url.Values{}, params)
   262  }
   263  
   264  func (self *SHuaweiClient) modelartsPoolUpdate(poolName string, params map[string]interface{}) (jsonutils.JSONObject, error) {
   265  	uri := fmt.Sprintf("https://modelarts.%s.myhuaweicloud.com/v2/%s/pools/%s", self.clientRegion, self.projectId, poolName)
   266  	urlValue := url.Values{}
   267  	urlValue.Add("time_range", "")
   268  	urlValue.Add("statistics", "")
   269  	urlValue.Add("period", "")
   270  	return self.patchRequest(httputils.PATCH, uri, urlValue, params)
   271  }
   272  
   273  func (self *SHuaweiClient) modelartsPoolMonitor(poolName string, params map[string]interface{}) (jsonutils.JSONObject, error) {
   274  	uri := fmt.Sprintf("https://modelarts.%s.myhuaweicloud.com/v2/%s/pools/%s/monitor", self.clientRegion, self.projectId, poolName)
   275  	return self.request(httputils.GET, uri, url.Values{}, params)
   276  }
   277  
   278  func (self *SHuaweiClient) modelartsResourceflavors(resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
   279  	uri := fmt.Sprintf("https://modelarts.%s.myhuaweicloud.com/v1/%s/%s", self.clientRegion, self.projectId, resource)
   280  	return self.request(httputils.GET, uri, url.Values{}, params)
   281  }
   282  
   283  func (self *SHuaweiClient) getAKSKList(userId string) (jsonutils.JSONObject, error) {
   284  	params := url.Values{}
   285  	params.Set("user_id", userId)
   286  	uri := fmt.Sprintf("https://iam.cn-north-4.myhuaweicloud.com/v3.0/OS-CREDENTIAL/credentials")
   287  	return self.request(httputils.GET, uri, params, nil)
   288  }
   289  
   290  func (self *SHuaweiClient) deleteAKSK(accesskey string) (jsonutils.JSONObject, error) {
   291  	uri := fmt.Sprintf("https://iam.cn-north-4.myhuaweicloud.com/v3.0/OS-CREDENTIAL/credentials/%s", accesskey)
   292  	return self.request(httputils.DELETE, uri, url.Values{}, nil)
   293  }
   294  
   295  func (self *SHuaweiClient) createAKSK(params map[string]interface{}) (jsonutils.JSONObject, error) {
   296  	uri := fmt.Sprintf("https://iam.cn-north-4.myhuaweicloud.com/v3.0/OS-CREDENTIAL/credentials")
   297  	return self.request(httputils.POST, uri, url.Values{}, params)
   298  }
   299  
   300  func (self *SHuaweiClient) lbGet(regionId, resource string) (jsonutils.JSONObject, error) {
   301  	uri := fmt.Sprintf("https://elb.%s.myhuaweicloud.com/v2/%s/%s", regionId, self.projectId, resource)
   302  	return self.request(httputils.GET, uri, url.Values{}, nil)
   303  }
   304  
   305  func (self *SHuaweiClient) lbCreate(regionId, resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
   306  	uri := fmt.Sprintf("https://elb.%s.myhuaweicloud.com/v2/%s/%s", regionId, self.projectId, resource)
   307  	return self.request(httputils.POST, uri, url.Values{}, params)
   308  }
   309  
   310  func (self *SHuaweiClient) lbUpdate(regionId, resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
   311  	uri := fmt.Sprintf("https://elb.%s.myhuaweicloud.com/v2/%s/%s", regionId, self.projectId, resource)
   312  	return self.request(httputils.PUT, uri, url.Values{}, params)
   313  }
   314  
   315  func (self *SHuaweiClient) lbDelete(regionId, resource string) (jsonutils.JSONObject, error) {
   316  	uri := fmt.Sprintf("https://elb.%s.myhuaweicloud.com/v2/%s/%s", regionId, self.projectId, resource)
   317  	return self.request(httputils.DELETE, uri, url.Values{}, nil)
   318  }
   319  
   320  func (self *SHuaweiClient) vpcList(regionId, resource string, query url.Values) (jsonutils.JSONObject, error) {
   321  	url := fmt.Sprintf("https://vpc.%s.myhuaweicloud.com/v1/%s/%s", regionId, self.projectId, resource)
   322  	return self.request(httputils.GET, url, query, nil)
   323  }
   324  
   325  func (self *SHuaweiClient) vpcCreate(regionId, resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
   326  	uri := fmt.Sprintf("https://vpc.%s.myhuaweicloud.com/v1/%s/%s", regionId, self.projectId, resource)
   327  	return self.request(httputils.POST, uri, url.Values{}, params)
   328  }
   329  
   330  func (self *SHuaweiClient) vpcGet(regionId, resource string) (jsonutils.JSONObject, error) {
   331  	uri := fmt.Sprintf("https://vpc.%s.myhuaweicloud.com/v1/%s/%s", regionId, self.projectId, resource)
   332  	return self.request(httputils.GET, uri, url.Values{}, nil)
   333  }
   334  
   335  func (self *SHuaweiClient) vpcDelete(regionId, resource string) (jsonutils.JSONObject, error) {
   336  	uri := fmt.Sprintf("https://vpc.%s.myhuaweicloud.com/v1/%s/%s", regionId, self.projectId, resource)
   337  	return self.request(httputils.DELETE, uri, url.Values{}, nil)
   338  }
   339  
   340  func (self *SHuaweiClient) vpcUpdate(regionId, resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
   341  	uri := fmt.Sprintf("https://vpc.%s.myhuaweicloud.com/v1/%s/%s", regionId, self.projectId, resource)
   342  	return self.request(httputils.PUT, uri, url.Values{}, params)
   343  }
   344  
   345  type akClient struct {
   346  	client *http.Client
   347  	aksk   aksk.SignOptions
   348  }
   349  
   350  func (self *akClient) Do(req *http.Request) (*http.Response, error) {
   351  	req.Header.Del("Accept")
   352  	if req.Method == string(httputils.GET) || req.Method == string(httputils.DELETE) || req.Method == string(httputils.PATCH) {
   353  		req.Header.Del("Content-Length")
   354  	}
   355  	aksk.Sign(req, self.aksk)
   356  	return self.client.Do(req)
   357  }
   358  
   359  func (self *SHuaweiClient) getAkClient() *akClient {
   360  	return &akClient{
   361  		client: self.getDefaultClient(),
   362  		aksk: aksk.SignOptions{
   363  			AccessKey: self.accessKey,
   364  			SecretKey: self.accessSecret,
   365  		},
   366  	}
   367  }
   368  
   369  func (self *SHuaweiClient) request(method httputils.THttpMethod, url string, query url.Values, params map[string]interface{}) (jsonutils.JSONObject, error) {
   370  	client := self.getAkClient()
   371  	if len(query) > 0 {
   372  		url = fmt.Sprintf("%s?%s", url, query.Encode())
   373  	}
   374  	var body jsonutils.JSONObject = nil
   375  	if len(params) > 0 {
   376  		body = jsonutils.Marshal(params)
   377  	}
   378  	header := http.Header{}
   379  	if len(self.projectId) > 0 {
   380  		header.Set("X-Project-Id", self.projectId)
   381  	}
   382  	if strings.Contains(url, "/OS-CREDENTIAL/credentials") && len(self.ownerId) > 0 {
   383  		header.Set("X-Domain-Id", self.ownerId)
   384  	}
   385  	_, resp, err := httputils.JSONRequest(client, context.Background(), method, url, header, body, self.debug)
   386  	if err != nil {
   387  		if e, ok := err.(*httputils.JSONClientError); ok && e.Code == 404 {
   388  			return nil, errors.Wrapf(cloudprovider.ErrNotFound, err.Error())
   389  		}
   390  		return nil, err
   391  	}
   392  	return resp, err
   393  }
   394  
   395  func (self *SHuaweiClient) fetchRegions() error {
   396  	huawei, _ := self.newGeneralAPIClient()
   397  	if self.regions == nil {
   398  		userId, err := self.GetUserId()
   399  		if err != nil {
   400  			return errors.Wrap(err, "GetUserId")
   401  		}
   402  
   403  		if regionsCache, ok := HUAWEI_REGION_CACHES.Load(userId); !ok || regionsCache.(*userRegionsCache).ExpireAt.Sub(time.Now()).Seconds() > 0 {
   404  			regions := make([]SRegion, 0)
   405  			err := doListAll(huawei.Regions.List, nil, &regions)
   406  			if err != nil {
   407  				return errors.Wrap(err, "Regions.List")
   408  			}
   409  
   410  			HUAWEI_REGION_CACHES.Store(userId, &userRegionsCache{ExpireAt: time.Now().Add(24 * time.Hour), UserId: userId, Regions: regions})
   411  		}
   412  
   413  		if regionsCache, ok := HUAWEI_REGION_CACHES.Load(userId); ok {
   414  			self.regions = regionsCache.(*userRegionsCache).Regions
   415  		}
   416  	}
   417  
   418  	filtedRegions := make([]SRegion, 0)
   419  	if len(self.projectId) > 0 {
   420  		project, err := self.GetProjectById(self.projectId)
   421  		if err != nil {
   422  			return err
   423  		}
   424  
   425  		for _, region := range self.regions {
   426  			if strings.Count(project.Name, region.ID) >= 1 {
   427  				self.clientRegion = region.ID
   428  				filtedRegions = append(filtedRegions, region)
   429  			}
   430  			if project.Name == region.ID {
   431  				self.isMainProject = true
   432  			}
   433  		}
   434  	} else {
   435  		filtedRegions = self.regions
   436  	}
   437  
   438  	if len(filtedRegions) == 0 {
   439  		return errors.Wrapf(cloudprovider.ErrNotFound, "empty regions")
   440  	}
   441  
   442  	self.iregions = make([]cloudprovider.ICloudRegion, len(filtedRegions))
   443  	for i := 0; i < len(filtedRegions); i += 1 {
   444  		filtedRegions[i].client = self
   445  		_, err := filtedRegions[i].getECSClient()
   446  		if err != nil {
   447  			return err
   448  		}
   449  		self.iregions[i] = &filtedRegions[i]
   450  	}
   451  	return nil
   452  }
   453  
   454  func (self *SHuaweiClient) invalidateIBuckets() {
   455  	self.iBuckets = nil
   456  }
   457  
   458  func (self *SHuaweiClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) {
   459  	if self.iBuckets == nil {
   460  		err := self.fetchBuckets()
   461  		if err != nil {
   462  			return nil, errors.Wrap(err, "fetchBuckets")
   463  		}
   464  	}
   465  	return self.iBuckets, nil
   466  }
   467  
   468  func getOBSEndpoint(regionId string) string {
   469  	return fmt.Sprintf("obs.%s.myhuaweicloud.com", regionId)
   470  }
   471  
   472  func (self *SHuaweiClient) getOBSClient(regionId string) (*obs.ObsClient, error) {
   473  	endpoint := getOBSEndpoint(regionId)
   474  	cli, err := obs.New(self.accessKey, self.accessSecret, endpoint)
   475  	if err != nil {
   476  		return nil, err
   477  	}
   478  	client := cli.GetClient()
   479  	ts, _ := client.Transport.(*http.Transport)
   480  	client.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response), error) {
   481  		method, path := req.Method, req.URL.Path
   482  		respCheck := func(resp *http.Response) {
   483  			if resp.StatusCode == 403 {
   484  				if self.cpcfg.UpdatePermission != nil {
   485  					self.cpcfg.UpdatePermission("obs", fmt.Sprintf("%s %s", method, path))
   486  				}
   487  			}
   488  		}
   489  		if self.cpcfg.ReadOnly {
   490  			if req.Method == "GET" || req.Method == "HEAD" {
   491  				return respCheck, nil
   492  			}
   493  			return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
   494  		}
   495  		return respCheck, nil
   496  	})
   497  
   498  	return cli, nil
   499  }
   500  
   501  func (self *SHuaweiClient) fetchBuckets() error {
   502  	obscli, err := self.getOBSClient(HUAWEI_DEFAULT_REGION)
   503  	if err != nil {
   504  		return errors.Wrap(err, "getOBSClient")
   505  	}
   506  	input := &obs.ListBucketsInput{QueryLocation: true}
   507  	output, err := obscli.ListBuckets(input)
   508  	if err != nil {
   509  		return errors.Wrap(err, "obscli.ListBuckets")
   510  	}
   511  	self.ownerId = output.Owner.ID
   512  
   513  	ret := make([]cloudprovider.ICloudBucket, 0)
   514  	for i := range output.Buckets {
   515  		bInfo := output.Buckets[i]
   516  		region, err := self.getIRegionByRegionId(bInfo.Location)
   517  		if err != nil {
   518  			log.Errorf("fail to find region %s", bInfo.Location)
   519  			continue
   520  		}
   521  		b := SBucket{
   522  			region: region.(*SRegion),
   523  
   524  			Name:         bInfo.Name,
   525  			Location:     bInfo.Location,
   526  			CreationDate: bInfo.CreationDate,
   527  		}
   528  		ret = append(ret, &b)
   529  	}
   530  	self.iBuckets = ret
   531  	return nil
   532  }
   533  
   534  func (self *SHuaweiClient) GetCloudRegionExternalIdPrefix() string {
   535  	if len(self.projectId) > 0 {
   536  		return self.iregions[0].GetGlobalId()
   537  	} else {
   538  		return CLOUD_PROVIDER_HUAWEI
   539  	}
   540  }
   541  
   542  func (self *SHuaweiClient) UpdateAccount(accessKey, secret string) error {
   543  	if self.accessKey != accessKey || self.accessSecret != secret {
   544  		self.accessKey = accessKey
   545  		self.accessSecret = secret
   546  		return self.fetchRegions()
   547  	} else {
   548  		return nil
   549  	}
   550  }
   551  
   552  func (self *SHuaweiClient) GetRegions() []SRegion {
   553  	regions := make([]SRegion, len(self.iregions))
   554  	for i := 0; i < len(regions); i += 1 {
   555  		region := self.iregions[i].(*SRegion)
   556  		regions[i] = *region
   557  	}
   558  	return regions
   559  }
   560  
   561  func (self *SHuaweiClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
   562  	projects, err := self.fetchProjects()
   563  	if err != nil {
   564  		return nil, err
   565  	}
   566  
   567  	// https://support.huaweicloud.com/api-iam/zh-cn_topic_0074171149.html
   568  	subAccounts := make([]cloudprovider.SSubAccount, 0)
   569  	for i := range projects {
   570  		project := projects[i]
   571  		// name 为MOS的project是华为云内部的一个特殊project。不需要同步到本地
   572  		if strings.ToLower(project.Name) == "mos" {
   573  			continue
   574  		}
   575  		// https://www.huaweicloud.com/notice/2018/20190618171312411.html
   576  		// expiredAt, _ := timeutils.ParseTimeStr("2020-09-16 00:00:00")
   577  		// if !self.ownerCreateTime.IsZero() && self.ownerCreateTime.After(expiredAt) && strings.ToLower(project.Name) == "cn-north-1" {
   578  		// 	continue
   579  		// }
   580  		s := cloudprovider.SSubAccount{
   581  			Name:             fmt.Sprintf("%s-%s", self.cpcfg.Name, project.Name),
   582  			Account:          fmt.Sprintf("%s/%s", self.accessKey, project.ID),
   583  			HealthStatus:     project.GetHealthStatus(),
   584  			DefaultProjectId: "0",
   585  		}
   586  		for j := range self.iregions {
   587  			region := self.iregions[j].(*SRegion)
   588  			if strings.Contains(project.Name, region.ID) {
   589  				s.Desc = region.Locales.ZhCN
   590  				break
   591  			}
   592  		}
   593  
   594  		subAccounts = append(subAccounts, s)
   595  	}
   596  
   597  	return subAccounts, nil
   598  }
   599  
   600  func (client *SHuaweiClient) GetAccountId() string {
   601  	return client.ownerId
   602  }
   603  
   604  func (client *SHuaweiClient) GetIamLoginUrl() string {
   605  	return fmt.Sprintf("https://auth.huaweicloud.com/authui/login.html?account=%s#/login", client.ownerName)
   606  }
   607  
   608  func (self *SHuaweiClient) GetIRegions() []cloudprovider.ICloudRegion {
   609  	return self.iregions
   610  }
   611  
   612  func (self *SHuaweiClient) getIRegionByRegionId(id string) (cloudprovider.ICloudRegion, error) {
   613  	for i := 0; i < len(self.iregions); i += 1 {
   614  		log.Debugf("%d ID: %s", i, self.iregions[i].GetId())
   615  		if self.iregions[i].GetId() == id {
   616  			return self.iregions[i], nil
   617  		}
   618  	}
   619  	return nil, cloudprovider.ErrNotFound
   620  }
   621  
   622  func (self *SHuaweiClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
   623  	for i := 0; i < len(self.iregions); i += 1 {
   624  		if self.iregions[i].GetGlobalId() == id {
   625  			return self.iregions[i], nil
   626  		}
   627  	}
   628  	return nil, cloudprovider.ErrNotFound
   629  }
   630  
   631  func (self *SHuaweiClient) GetRegion(regionId string) *SRegion {
   632  	if len(regionId) == 0 {
   633  		regionId = HUAWEI_DEFAULT_REGION
   634  	}
   635  	for i := 0; i < len(self.iregions); i += 1 {
   636  		if self.iregions[i].GetId() == regionId {
   637  			return self.iregions[i].(*SRegion)
   638  		}
   639  	}
   640  	return nil
   641  }
   642  
   643  func (self *SHuaweiClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) {
   644  	for i := 0; i < len(self.iregions); i += 1 {
   645  		ihost, err := self.iregions[i].GetIHostById(id)
   646  		if err == nil {
   647  			return ihost, nil
   648  		} else if errors.Cause(err) != cloudprovider.ErrNotFound {
   649  			return nil, err
   650  		}
   651  	}
   652  	return nil, cloudprovider.ErrNotFound
   653  }
   654  
   655  func (self *SHuaweiClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) {
   656  	for i := 0; i < len(self.iregions); i += 1 {
   657  		ivpc, err := self.iregions[i].GetIVpcById(id)
   658  		if err == nil {
   659  			return ivpc, nil
   660  		} else if errors.Cause(err) != cloudprovider.ErrNotFound {
   661  			return nil, err
   662  		}
   663  	}
   664  	return nil, cloudprovider.ErrNotFound
   665  }
   666  
   667  func (self *SHuaweiClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) {
   668  	for i := 0; i < len(self.iregions); i += 1 {
   669  		istorage, err := self.iregions[i].GetIStorageById(id)
   670  		if err == nil {
   671  			return istorage, nil
   672  		} else if errors.Cause(err) != cloudprovider.ErrNotFound {
   673  			return nil, err
   674  		}
   675  	}
   676  	return nil, cloudprovider.ErrNotFound
   677  }
   678  
   679  // 总账户余额
   680  type SAccountBalance struct {
   681  	AvailableAmount  float64
   682  	CreditAmount     float64
   683  	DesignatedAmount float64
   684  }
   685  
   686  // 账户余额
   687  // https://support.huaweicloud.com/api-oce/zh-cn_topic_0109685133.html
   688  type SBalance struct {
   689  	Amount           float64 `json:"amount"`
   690  	Currency         string  `json:"currency"`
   691  	AccountID        string  `json:"account_id"`
   692  	AccountType      int64   `json:"account_type"`
   693  	DesignatedAmount float64 `json:"designated_amount,omitempty"`
   694  	CreditAmount     float64 `json:"credit_amount,omitempty"`
   695  	MeasureUnit      int64   `json:"measure_unit"`
   696  }
   697  
   698  // 这里的余额指的是所有租户的总余额
   699  func (self *SHuaweiClient) QueryAccountBalance() (*SAccountBalance, error) {
   700  	domains, err := self.getEnabledDomains()
   701  	if err != nil {
   702  		return nil, err
   703  	}
   704  
   705  	result := &SAccountBalance{}
   706  	for _, domain := range domains {
   707  		balances, err := self.queryDomainBalances(domain.ID)
   708  		if err != nil {
   709  			return nil, err
   710  		}
   711  		for _, balance := range balances {
   712  			result.AvailableAmount += balance.Amount
   713  			result.CreditAmount += balance.CreditAmount
   714  			result.DesignatedAmount += balance.DesignatedAmount
   715  		}
   716  	}
   717  
   718  	return result, nil
   719  }
   720  
   721  // https://support.huaweicloud.com/api-bpconsole/zh-cn_topic_0075213309.html
   722  func (self *SHuaweiClient) queryDomainBalances(domainId string) ([]SBalance, error) {
   723  	huawei, _ := self.newGeneralAPIClient()
   724  	huawei.Balances.SetDomainId(domainId)
   725  	balances := make([]SBalance, 0)
   726  	err := doListAll(huawei.Balances.List, nil, &balances)
   727  	if err != nil {
   728  		return nil, err
   729  	}
   730  
   731  	return balances, nil
   732  }
   733  
   734  func (self *SHuaweiClient) GetVersion() string {
   735  	return HUAWEI_API_VERSION
   736  }
   737  
   738  func (self *SHuaweiClient) GetAccessEnv() string {
   739  	switch self.cloudEnv {
   740  	case HUAWEI_INTERNATIONAL_CLOUDENV:
   741  		return api.CLOUD_ACCESS_ENV_HUAWEI_GLOBAL
   742  	case HUAWEI_CHINA_CLOUDENV:
   743  		return api.CLOUD_ACCESS_ENV_HUAWEI_CHINA
   744  	default:
   745  		return api.CLOUD_ACCESS_ENV_HUAWEI_GLOBAL
   746  	}
   747  }
   748  
   749  func (self *SHuaweiClient) GetCapabilities() []string {
   750  	caps := []string{
   751  		cloudprovider.CLOUD_CAPABILITY_PROJECT,
   752  		cloudprovider.CLOUD_CAPABILITY_COMPUTE,
   753  		cloudprovider.CLOUD_CAPABILITY_NETWORK,
   754  		cloudprovider.CLOUD_CAPABILITY_EIP,
   755  		cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
   756  		// cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
   757  		cloudprovider.CLOUD_CAPABILITY_RDS,
   758  		cloudprovider.CLOUD_CAPABILITY_CACHE,
   759  		cloudprovider.CLOUD_CAPABILITY_EVENT,
   760  		cloudprovider.CLOUD_CAPABILITY_CLOUDID,
   761  		cloudprovider.CLOUD_CAPABILITY_SAML_AUTH,
   762  		cloudprovider.CLOUD_CAPABILITY_NAT,
   763  		cloudprovider.CLOUD_CAPABILITY_NAS,
   764  		cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX,
   765  		cloudprovider.CLOUD_CAPABILITY_MODELARTES,
   766  	}
   767  	// huawei objectstore is shared across projects(subscriptions)
   768  	// to avoid multiple project access the same bucket
   769  	// only main project is allow to access objectstore bucket
   770  	if self.isMainProject {
   771  		caps = append(caps, cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE)
   772  	}
   773  	return caps
   774  }
   775  
   776  func (self *SHuaweiClient) GetUserId() (string, error) {
   777  	client, err := self.newGeneralAPIClient()
   778  	if err != nil {
   779  		return "", errors.Wrap(err, "SHuaweiClient.GetUserId.newGeneralAPIClient")
   780  	}
   781  
   782  	type cred struct {
   783  		UserId string `json:"user_id"`
   784  	}
   785  
   786  	ret := &cred{}
   787  	err = DoGet(client.Credentials.Get, self.accessKey, nil, ret)
   788  	if err != nil {
   789  		return "", errors.Wrap(err, "SHuaweiClient.GetUserId.DoGet")
   790  	}
   791  
   792  	return ret.UserId, nil
   793  }
   794  
   795  // owner id == domain_id == account id
   796  func (self *SHuaweiClient) GetOwnerId() (string, error) {
   797  	userId, err := self.GetUserId()
   798  	if err != nil {
   799  		return "", errors.Wrap(err, "SHuaweiClient.GetOwnerId.GetUserId")
   800  	}
   801  
   802  	client, err := self.newGeneralAPIClient()
   803  	if err != nil {
   804  		return "", errors.Wrap(err, "SHuaweiClient.GetOwnerId.newGeneralAPIClient")
   805  	}
   806  
   807  	type user struct {
   808  		DomainId   string `json:"domain_id"`
   809  		Name       string `json:"name"`
   810  		CreateTime string
   811  	}
   812  
   813  	ret := &user{}
   814  	err = DoGet(client.Users.Get, userId, nil, ret)
   815  	if err != nil {
   816  		return "", errors.Wrap(err, "SHuaweiClient.GetOwnerId.DoGet")
   817  	}
   818  	self.ownerName = ret.Name
   819  	// 2021-02-02 02:43:28.0
   820  	self.ownerCreateTime, _ = timeutils.ParseTimeStr(strings.TrimSuffix(ret.CreateTime, ".0"))
   821  	return ret.DomainId, nil
   822  }
   823  
   824  func (self *SHuaweiClient) initOwner() error {
   825  	ownerId, err := self.GetOwnerId()
   826  	if err != nil {
   827  		return errors.Wrap(err, "SHuaweiClient.initOwner")
   828  	}
   829  
   830  	self.ownerId = ownerId
   831  	return nil
   832  }
   833  
   834  func (self *SHuaweiClient) patchRequest(method httputils.THttpMethod, url string, query url.Values, params map[string]interface{}) (jsonutils.JSONObject, error) {
   835  	client := self.getAkClient()
   836  	if len(query) > 0 {
   837  		url = fmt.Sprintf("%s?%s", url, query.Encode())
   838  	}
   839  	var body jsonutils.JSONObject = nil
   840  	if len(params) > 0 {
   841  		body = jsonutils.Marshal(params)
   842  	}
   843  	header := http.Header{}
   844  	if len(self.projectId) > 0 {
   845  		header.Set("X-Project-Id", self.projectId)
   846  	}
   847  	var bodystr string
   848  	if !gotypes.IsNil(body) {
   849  		bodystr = body.String()
   850  	}
   851  	jbody := strings.NewReader(bodystr)
   852  	header.Set("Content-Length", strconv.FormatInt(int64(len(bodystr)), 10))
   853  	header.Set("Content-Type", "application/merge-patch+json")
   854  	resp, err := httputils.Request(client, context.Background(), method, url, header, jbody, self.debug)
   855  	_, respValue, err := httputils.ParseJSONResponse(bodystr, resp, err, self.debug)
   856  	if err != nil {
   857  		if e, ok := err.(*httputils.JSONClientError); ok && e.Code == 404 {
   858  			return nil, errors.Wrapf(cloudprovider.ErrNotFound, err.Error())
   859  		}
   860  		return nil, err
   861  	}
   862  	return respValue, err
   863  }