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, ®ions) 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 }