huawei.go 16 KB

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