yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcso/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 hcso 16 17 import ( 18 "fmt" 19 "net/http" 20 "strings" 21 "time" 22 23 "yunion.io/x/log" 24 "yunion.io/x/pkg/errors" 25 "yunion.io/x/pkg/util/timeutils" 26 27 api "yunion.io/x/cloudmux/pkg/apis/compute" 28 "yunion.io/x/cloudmux/pkg/cloudprovider" 29 "yunion.io/x/cloudmux/pkg/multicloud/hcso/client" 30 "yunion.io/x/cloudmux/pkg/multicloud/hcso/client/auth" 31 "yunion.io/x/cloudmux/pkg/multicloud/hcso/client/auth/credentials" 32 "yunion.io/x/cloudmux/pkg/multicloud/huawei/obs" 33 ) 34 35 /* 36 待解决问题: 37 2.VM密码登录不成功(ubuntu不行,centos可以) 38 3.实例绑定eip 查不出来eip? 39 */ 40 41 const ( 42 CLOUD_PROVIDER_HUAWEI = api.CLOUD_PROVIDER_HCSO 43 CLOUD_PROVIDER_HUAWEI_CN = "华为云Stack" 44 CLOUD_PROVIDER_HUAWEI_EN = "HCSO" 45 46 HUAWEI_API_VERSION = "" 47 ) 48 49 var HUAWEI_REGION_CACHES = map[string]userRegionsCache{} 50 51 type userRegionsCache struct { 52 UserId string 53 ExpireAt time.Time 54 Regions []SRegion 55 } 56 57 type HuaweiClientConfig struct { 58 cpcfg cloudprovider.ProviderConfig 59 endpoints *cloudprovider.SHCSOEndpoints 60 61 projectId string // 华为云项目ID. 62 accessKey string 63 accessSecret string 64 65 debug bool 66 } 67 68 func NewHuaweiClientConfig(accessKey, accessSecret, projectId string, endpoints *cloudprovider.SHCSOEndpoints) *HuaweiClientConfig { 69 cfg := &HuaweiClientConfig{ 70 projectId: projectId, 71 accessKey: accessKey, 72 accessSecret: accessSecret, 73 endpoints: endpoints, 74 } 75 76 return cfg 77 } 78 79 func (cfg *HuaweiClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *HuaweiClientConfig { 80 cfg.cpcfg = cpcfg 81 return cfg 82 } 83 84 func (cfg *HuaweiClientConfig) Debug(debug bool) *HuaweiClientConfig { 85 cfg.debug = debug 86 return cfg 87 } 88 89 type SHuaweiClient struct { 90 *HuaweiClientConfig 91 92 signer auth.Signer 93 94 isMainProject bool // whether the project is the main project in the region 95 96 ownerId string 97 ownerName string 98 ownerCreateTime time.Time 99 100 iregions []cloudprovider.ICloudRegion 101 iBuckets []cloudprovider.ICloudBucket 102 103 projects []SProject 104 regions []SRegion 105 106 httpClient *http.Client 107 } 108 109 // 进行资源操作时参数account 对应数据库cloudprovider表中的account字段,由accessKey和projectID两部分组成,通过"/"分割。 110 // 初次导入Subaccount时,参数account对应cloudaccounts表中的account字段,即accesskey。此时projectID为空, 111 // 只能进行同步子账号、查询region列表等projectId无关的操作。 112 func NewHuaweiClient(cfg *HuaweiClientConfig) (*SHuaweiClient, error) { 113 client := SHuaweiClient{ 114 HuaweiClientConfig: cfg, 115 } 116 117 err := client.init() 118 if err != nil { 119 return nil, err 120 } 121 return &client, nil 122 } 123 124 func (self *SHuaweiClient) init() error { 125 err := self.fetchRegions() 126 if err != nil { 127 return err 128 } 129 err = self.initSigner() 130 if err != nil { 131 return errors.Wrap(err, "initSigner") 132 } 133 err = self.initOwner() 134 if err != nil { 135 return errors.Wrap(err, "fetchOwner") 136 } 137 if self.debug { 138 log.Debugf("OwnerId: %s name: %s", self.ownerId, self.ownerName) 139 } 140 return nil 141 } 142 143 func (self *SHuaweiClient) initSigner() error { 144 var err error 145 cred := credentials.NewAccessKeyCredential(self.accessKey, self.accessKey) 146 self.signer, err = auth.NewSignerWithCredential(cred) 147 if err != nil { 148 return err 149 } 150 return nil 151 } 152 153 func (self *SHuaweiClient) newRegionAPIClient(regionId string) (*client.Client, error) { 154 cli, err := client.NewClientWithAccessKey(regionId, self.ownerId, self.projectId, self.accessKey, self.accessSecret, self.debug, self.cpcfg.DefaultRegion, self.endpoints) 155 if err != nil { 156 return nil, err 157 } 158 159 httpClient := self.cpcfg.AdaptiveTimeoutHttpClient() 160 ts, _ := httpClient.Transport.(*http.Transport) 161 httpClient.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response), error) { 162 if self.cpcfg.ReadOnly { 163 if req.Method == "GET" { 164 return nil, nil 165 } 166 return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path) 167 } 168 return nil, nil 169 }) 170 cli.SetHttpClient(httpClient) 171 172 return cli, nil 173 } 174 175 func (self *SHuaweiClient) newGeneralAPIClient() (*client.Client, error) { 176 cli, err := client.NewClientWithAccessKey(self.cpcfg.DefaultRegion, self.ownerId, "", self.accessKey, self.accessSecret, self.debug, self.cpcfg.DefaultRegion, self.endpoints) 177 if err != nil { 178 return nil, err 179 } 180 181 httpClient := self.cpcfg.AdaptiveTimeoutHttpClient() 182 ts, _ := httpClient.Transport.(*http.Transport) 183 httpClient.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response), error) { 184 if self.cpcfg.ReadOnly { 185 if req.Method == "GET" { 186 return nil, nil 187 } 188 return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path) 189 } 190 return nil, nil 191 }) 192 cli.SetHttpClient(httpClient) 193 194 return cli, nil 195 } 196 197 func (self *SHuaweiClient) fetchRegions() error { 198 huawei, _ := self.newGeneralAPIClient() 199 if self.regions == nil { 200 userId, err := self.GetUserId() 201 if err != nil { 202 return errors.Wrap(err, "GetUserId") 203 } 204 205 if regionsCache, ok := HUAWEI_REGION_CACHES[userId]; !ok || regionsCache.ExpireAt.Sub(time.Now()).Seconds() > 0 { 206 regions := make([]SRegion, 0) 207 err := doListAll(huawei.Regions.List, nil, ®ions) 208 if err != nil { 209 return errors.Wrap(err, "Regions.List") 210 } 211 212 HUAWEI_REGION_CACHES[userId] = userRegionsCache{ExpireAt: time.Now().Add(24 * time.Hour), UserId: userId, Regions: regions} 213 } 214 215 self.regions = HUAWEI_REGION_CACHES[userId].Regions 216 } 217 218 filtedRegions := make([]SRegion, 0) 219 if len(self.projectId) > 0 { 220 project, err := self.GetProjectById(self.projectId) 221 if err != nil { 222 return err 223 } 224 225 regionId := strings.Split(project.Name, "_")[0] 226 for _, region := range self.regions { 227 if region.ID == regionId { 228 filtedRegions = append(filtedRegions, region) 229 } 230 } 231 if regionId == project.Name { 232 self.isMainProject = true 233 } 234 } else { 235 filtedRegions = self.regions 236 } 237 238 self.iregions = make([]cloudprovider.ICloudRegion, len(filtedRegions)) 239 for i := 0; i < len(filtedRegions); i += 1 { 240 filtedRegions[i].client = self 241 _, err := filtedRegions[i].getECSClient() 242 if err != nil { 243 return err 244 } 245 self.iregions[i] = &filtedRegions[i] 246 } 247 return nil 248 } 249 250 func (self *SHuaweiClient) invalidateIBuckets() { 251 self.iBuckets = nil 252 } 253 254 func (self *SHuaweiClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) { 255 if self.iBuckets == nil { 256 err := self.fetchBuckets() 257 if err != nil { 258 return nil, errors.Wrap(err, "fetchBuckets") 259 } 260 } 261 return self.iBuckets, nil 262 } 263 264 func getOBSEndpoint(regionId string) string { 265 return fmt.Sprintf("obs.%s.myhuaweicloud.com", regionId) 266 } 267 268 func (client *SHuaweiClient) getOBSClient(regionId string) (*obs.ObsClient, error) { 269 endpoint := client.endpoints.GetEndpoint(client.cpcfg.DefaultRegion, "obs", regionId) 270 return obs.New(client.accessKey, client.accessSecret, endpoint) 271 } 272 273 func (self *SHuaweiClient) fetchBuckets() error { 274 obscli, err := self.getOBSClient(self.cpcfg.DefaultRegion) 275 if err != nil { 276 return errors.Wrap(err, "getOBSClient") 277 } 278 input := &obs.ListBucketsInput{QueryLocation: true} 279 output, err := obscli.ListBuckets(input) 280 if err != nil { 281 return errors.Wrap(err, "obscli.ListBuckets") 282 } 283 self.ownerId = output.Owner.ID 284 285 ret := make([]cloudprovider.ICloudBucket, 0) 286 for i := range output.Buckets { 287 bInfo := output.Buckets[i] 288 region, err := self.getIRegionByRegionId(bInfo.Location) 289 if err != nil { 290 log.Errorf("fail to find region %s", bInfo.Location) 291 continue 292 } 293 b := SBucket{ 294 region: region.(*SRegion), 295 296 Name: bInfo.Name, 297 Location: bInfo.Location, 298 CreationDate: bInfo.CreationDate, 299 } 300 ret = append(ret, &b) 301 } 302 self.iBuckets = ret 303 return nil 304 } 305 306 func (self *SHuaweiClient) GetCloudRegionExternalIdPrefix() string { 307 if len(self.projectId) > 0 { 308 return self.iregions[0].GetGlobalId() 309 } else { 310 return CLOUD_PROVIDER_HUAWEI 311 } 312 } 313 314 func (self *SHuaweiClient) UpdateAccount(accessKey, secret string) error { 315 if self.accessKey != accessKey || self.accessSecret != secret { 316 self.accessKey = accessKey 317 self.accessSecret = secret 318 return self.fetchRegions() 319 } else { 320 return nil 321 } 322 } 323 324 func (self *SHuaweiClient) GetRegions() []SRegion { 325 regions := make([]SRegion, len(self.iregions)) 326 for i := 0; i < len(regions); i += 1 { 327 region := self.iregions[i].(*SRegion) 328 regions[i] = *region 329 } 330 return regions 331 } 332 333 func (self *SHuaweiClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) { 334 projects, err := self.fetchProjects() 335 if err != nil { 336 return nil, err 337 } 338 339 // https://support.huaweicloud.com/api-iam/zh-cn_topic_0074171149.html 340 subAccounts := make([]cloudprovider.SSubAccount, 0) 341 for i := range projects { 342 project := projects[i] 343 // name 为MOS的project是华为云内部的一个特殊project。不需要同步到本地 344 if strings.ToLower(project.Name) == "mos" { 345 continue 346 } 347 348 s := cloudprovider.SSubAccount{ 349 Name: fmt.Sprintf("%s-%s", self.cpcfg.Name, project.Name), 350 Account: fmt.Sprintf("%s/%s", self.accessKey, project.ID), 351 HealthStatus: project.GetHealthStatus(), 352 } 353 354 subAccounts = append(subAccounts, s) 355 } 356 357 return subAccounts, nil 358 } 359 360 func (client *SHuaweiClient) GetAccountId() string { 361 return client.ownerId 362 } 363 364 func (client *SHuaweiClient) GetIamLoginUrl() string { 365 return fmt.Sprintf("https://auth.huaweicloud.com/authui/login.html?account=%s#/login", client.ownerName) 366 } 367 368 func (self *SHuaweiClient) GetIRegions() []cloudprovider.ICloudRegion { 369 return self.iregions 370 } 371 372 func (self *SHuaweiClient) getIRegionByRegionId(id string) (cloudprovider.ICloudRegion, error) { 373 for i := 0; i < len(self.iregions); i += 1 { 374 log.Debugf("%d ID: %s", i, self.iregions[i].GetId()) 375 if self.iregions[i].GetId() == id { 376 return self.iregions[i], nil 377 } 378 } 379 return nil, cloudprovider.ErrNotFound 380 } 381 382 func (self *SHuaweiClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) { 383 for i := 0; i < len(self.iregions); i += 1 { 384 if self.iregions[i].GetGlobalId() == id { 385 return self.iregions[i], nil 386 } 387 } 388 return nil, cloudprovider.ErrNotFound 389 } 390 391 func (self *SHuaweiClient) GetRegion(regionId string) *SRegion { 392 if len(regionId) == 0 { 393 regionId = self.cpcfg.DefaultRegion 394 } 395 396 for i := 0; i < len(self.iregions); i += 1 { 397 if self.iregions[i].GetId() == regionId { 398 return self.iregions[i].(*SRegion) 399 } 400 } 401 return nil 402 } 403 404 func (self *SHuaweiClient) GetIHostById(id string) (cloudprovider.ICloudHost, error) { 405 for i := 0; i < len(self.iregions); i += 1 { 406 ihost, err := self.iregions[i].GetIHostById(id) 407 if err == nil { 408 return ihost, nil 409 } else if errors.Cause(err) != cloudprovider.ErrNotFound { 410 return nil, err 411 } 412 } 413 return nil, cloudprovider.ErrNotFound 414 } 415 416 func (self *SHuaweiClient) GetIVpcById(id string) (cloudprovider.ICloudVpc, error) { 417 for i := 0; i < len(self.iregions); i += 1 { 418 ivpc, err := self.iregions[i].GetIVpcById(id) 419 if err == nil { 420 return ivpc, nil 421 } else if errors.Cause(err) != cloudprovider.ErrNotFound { 422 return nil, err 423 } 424 } 425 return nil, cloudprovider.ErrNotFound 426 } 427 428 func (self *SHuaweiClient) GetIStorageById(id string) (cloudprovider.ICloudStorage, error) { 429 for i := 0; i < len(self.iregions); i += 1 { 430 istorage, err := self.iregions[i].GetIStorageById(id) 431 if err == nil { 432 return istorage, nil 433 } else if errors.Cause(err) != cloudprovider.ErrNotFound { 434 return nil, err 435 } 436 } 437 return nil, cloudprovider.ErrNotFound 438 } 439 440 // 总账户余额 441 type SAccountBalance struct { 442 AvailableAmount float64 443 CreditAmount float64 444 DesignatedAmount float64 445 } 446 447 // 账户余额 448 // https://support.huaweicloud.com/api-oce/zh-cn_topic_0109685133.html 449 type SBalance struct { 450 Amount float64 `json:"amount"` 451 Currency string `json:"currency"` 452 AccountID string `json:"account_id"` 453 AccountType int64 `json:"account_type"` 454 DesignatedAmount float64 `json:"designated_amount,omitempty"` 455 CreditAmount float64 `json:"credit_amount,omitempty"` 456 MeasureUnit int64 `json:"measure_unit"` 457 } 458 459 func (self *SHuaweiClient) GetVersion() string { 460 return HUAWEI_API_VERSION 461 } 462 463 func (self *SHuaweiClient) GetAccessEnv() string { 464 return "" 465 } 466 467 func (self *SHuaweiClient) GetCapabilities() []string { 468 caps := []string{ 469 cloudprovider.CLOUD_CAPABILITY_PROJECT, 470 cloudprovider.CLOUD_CAPABILITY_COMPUTE, 471 cloudprovider.CLOUD_CAPABILITY_NETWORK, 472 cloudprovider.CLOUD_CAPABILITY_EIP, 473 cloudprovider.CLOUD_CAPABILITY_LOADBALANCER, 474 // cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE, 475 cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX, 476 cloudprovider.CLOUD_CAPABILITY_RDS, 477 cloudprovider.CLOUD_CAPABILITY_CACHE, 478 cloudprovider.CLOUD_CAPABILITY_EVENT, 479 cloudprovider.CLOUD_CAPABILITY_CLOUDID, 480 cloudprovider.CLOUD_CAPABILITY_SAML_AUTH, 481 cloudprovider.CLOUD_CAPABILITY_NAT, 482 cloudprovider.CLOUD_CAPABILITY_NAS, 483 cloudprovider.CLOUD_CAPABILITY_MODELARTES, 484 } 485 // huawei objectstore is shared across projects(subscriptions) 486 // to avoid multiple project access the same bucket 487 // only main project is allow to access objectstore bucket 488 if self.isMainProject { 489 caps = append(caps, cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE) 490 } 491 return caps 492 } 493 494 func (self *SHuaweiClient) GetUserId() (string, error) { 495 client, err := self.newGeneralAPIClient() 496 if err != nil { 497 return "", errors.Wrap(err, "SHuaweiClient.GetUserId.newGeneralAPIClient") 498 } 499 500 type cred struct { 501 UserId string `json:"user_id"` 502 } 503 504 ret := &cred{} 505 err = DoGet(client.Credentials.Get, self.accessKey, nil, ret) 506 if err != nil { 507 return "", errors.Wrap(err, "SHuaweiClient.GetUserId.DoGet") 508 } 509 510 return ret.UserId, nil 511 } 512 513 // owner id == domain_id == account id 514 func (self *SHuaweiClient) GetOwnerId() (string, error) { 515 userId, err := self.GetUserId() 516 if err != nil { 517 return "", errors.Wrap(err, "SHuaweiClient.GetOwnerId.GetUserId") 518 } 519 520 client, err := self.newGeneralAPIClient() 521 if err != nil { 522 return "", errors.Wrap(err, "SHuaweiClient.GetOwnerId.newGeneralAPIClient") 523 } 524 525 type user struct { 526 DomainId string `json:"domain_id"` 527 Name string `json:"name"` 528 CreateTime string 529 } 530 531 ret := &user{} 532 err = DoGet(client.Users.Get, userId, nil, ret) 533 if err != nil { 534 return "", errors.Wrap(err, "SHuaweiClient.GetOwnerId.DoGet") 535 } 536 self.ownerName = ret.Name 537 // 2021-02-02 02:43:28.0 538 self.ownerCreateTime, _ = timeutils.ParseTimeStr(strings.TrimSuffix(ret.CreateTime, ".0")) 539 return ret.DomainId, nil 540 } 541 542 func (self *SHuaweiClient) GetSamlEntityId() string { 543 return fmt.Sprintf("auth.%s", self.endpoints.EndpointDomain) 544 } 545 546 func (self *SHuaweiClient) initOwner() error { 547 ownerId, err := self.GetOwnerId() 548 if err != nil { 549 return errors.Wrap(err, "SHuaweiClient.initOwner") 550 } 551 552 self.ownerId = ownerId 553 return nil 554 }