huawei.go 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821
  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 huawei
  15. import (
  16. "context"
  17. "fmt"
  18. "net/http"
  19. "net/url"
  20. "strings"
  21. "time"
  22. "yunion.io/x/jsonutils"
  23. "yunion.io/x/log"
  24. "yunion.io/x/pkg/errors"
  25. "yunion.io/x/pkg/gotypes"
  26. "yunion.io/x/pkg/util/httputils"
  27. "yunion.io/x/pkg/util/timeutils"
  28. "yunion.io/x/pkg/utils"
  29. api "yunion.io/x/cloudmux/pkg/apis/compute"
  30. "yunion.io/x/cloudmux/pkg/cloudprovider"
  31. "yunion.io/x/cloudmux/pkg/multicloud/huawei/obs"
  32. )
  33. const (
  34. CLOUD_PROVIDER_HUAWEI = api.CLOUD_PROVIDER_HUAWEI
  35. CLOUD_PROVIDER_HUAWEI_CN = "华为云"
  36. CLOUD_PROVIDER_HUAWEI_EN = "Huawei"
  37. HUAWEI_DEFAULT_REGION = "cn-north-4"
  38. HUAWEI_API_VERSION = "2018-12-25"
  39. SERVICE_IAM = "iam"
  40. SERVICE_IAM_V3 = "iam_v3"
  41. SERVICE_IAM_V3_EXT = "iam_v3_ext"
  42. SERVICE_ELB = "elb"
  43. SERVICE_VPC = "vpc"
  44. SERVICE_VPC_V2_0 = "vpc_v2.0"
  45. SERVICE_VPC_V3 = "vpc_v3"
  46. SERVICE_VPN = "vpn"
  47. SERVICE_CES = "ces"
  48. SERVICE_RDS = "rds"
  49. SERVICE_ECS = "ecs"
  50. SERVICE_ECS_V1_1 = "ecs_v1.1"
  51. SERVICE_ECS_V2_1 = "ecs_v2.1"
  52. SERVICE_EPS = "eps"
  53. SERVICE_EVS = "evs"
  54. SERVICE_EVS_V1 = "evs_v1"
  55. SERVICE_EVS_V2_1 = "evs_v2.1"
  56. SERVICE_BSS = "bss"
  57. SERVICE_BSS_INTL = "bss-intl"
  58. SERVICE_SFS = "sfs-turbo"
  59. SERVICE_CTS = "cts"
  60. SERVICE_NAT = "nat"
  61. SERVICE_NAT_V2 = "nat_v2"
  62. SERVICE_BMS = "bms"
  63. SERVICE_CCI = "cci"
  64. SERVICE_CSBS = "csbs"
  65. SERVICE_IMS = "ims"
  66. SERVICE_IMS_V1 = "ims_v1"
  67. SERVICE_AS = "as"
  68. SERVICE_CCE = "cce"
  69. SERVICE_DCS = "dcs"
  70. SERVICE_MODELARTS = "modelarts"
  71. SERVICE_MODELARTS_V1 = "modelarts_v1"
  72. SERVICE_SCM = "scm"
  73. SERVICE_CDN = "cdn"
  74. SERVICE_GAUSSDB = "gaussdb"
  75. SERVICE_GAUSSDB_V3_1 = "gaussdb_v3.1"
  76. SERVICE_GAUSSDB_NOSQL = "gaussdb-nosql"
  77. SERVICE_GAUSSDB_NOSQL_V3_1 = "gaussdb-nosql_v3.1"
  78. SERVICE_GAUSSDB_OPENGAUSS = "gaussdb-opengauss"
  79. SERVICE_FUNCTIONGRAPH = "functiongraph"
  80. SERVICE_APIG = "apig"
  81. SERVICE_APIG_V1_0 = "apig_v1.0"
  82. SERVICE_MRS = "mrs"
  83. SERVICE_DIS = "dis"
  84. SERVICE_LTS = "lts"
  85. )
  86. type HuaweiClientConfig struct {
  87. cpcfg cloudprovider.ProviderConfig
  88. accessKey string
  89. accessSecret string
  90. debug bool
  91. }
  92. func NewHuaweiClientConfig(accessKey, accessSecret string) *HuaweiClientConfig {
  93. cfg := &HuaweiClientConfig{
  94. accessKey: accessKey,
  95. accessSecret: accessSecret,
  96. }
  97. return cfg
  98. }
  99. func (cfg *HuaweiClientConfig) CloudproviderConfig(cpcfg cloudprovider.ProviderConfig) *HuaweiClientConfig {
  100. cfg.cpcfg = cpcfg
  101. return cfg
  102. }
  103. func (cfg *HuaweiClientConfig) Debug(debug bool) *HuaweiClientConfig {
  104. cfg.debug = debug
  105. return cfg
  106. }
  107. type SHuaweiClient struct {
  108. *HuaweiClientConfig
  109. userId string
  110. ownerId string
  111. ownerName string
  112. ownerCreateTime time.Time
  113. iBuckets []cloudprovider.ICloudBucket
  114. projects map[string]SProject
  115. regions map[string]SRegion
  116. httpClient *http.Client
  117. orders map[string]SOrderResource
  118. }
  119. func NewHuaweiClient(cfg *HuaweiClientConfig) (*SHuaweiClient, error) {
  120. client := SHuaweiClient{
  121. HuaweiClientConfig: cfg,
  122. regions: map[string]SRegion{},
  123. projects: map[string]SProject{},
  124. }
  125. err := client.init()
  126. if err != nil {
  127. return nil, err
  128. }
  129. return &client, nil
  130. }
  131. func (self *SHuaweiClient) init() error {
  132. _, err := self.getRegions()
  133. if err != nil {
  134. return errors.Wrapf(err, "GetRegions")
  135. }
  136. _, err = self.GetProjects()
  137. if err != nil {
  138. return errors.Wrapf(err, "GetProjects")
  139. }
  140. _, err = self.GetOwnerId()
  141. return err
  142. }
  143. func (self *SHuaweiClient) getDefaultClient() *http.Client {
  144. if self.httpClient != nil {
  145. return self.httpClient
  146. }
  147. self.httpClient = self.cpcfg.AdaptiveTimeoutHttpClient()
  148. ts, _ := self.httpClient.Transport.(*http.Transport)
  149. self.httpClient.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response) error, error) {
  150. service, method, path := strings.Split(req.URL.Host, ".")[0], req.Method, req.URL.Path
  151. respCheck := func(resp *http.Response) error {
  152. if resp.StatusCode == 403 {
  153. if self.cpcfg.UpdatePermission != nil {
  154. self.cpcfg.UpdatePermission(service, fmt.Sprintf("%s %s", method, path))
  155. }
  156. }
  157. return nil
  158. }
  159. if self.cpcfg.ReadOnly {
  160. // get or metric skip read only check
  161. if req.Method == "GET" || strings.HasPrefix(req.URL.Host, "ces") {
  162. return respCheck, nil
  163. }
  164. return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
  165. }
  166. return respCheck, nil
  167. })
  168. return self.httpClient
  169. }
  170. type sPageInfo struct {
  171. NextMarker string
  172. }
  173. type akClient struct {
  174. client *http.Client
  175. aksk Signer
  176. }
  177. func (self *akClient) Do(req *http.Request) (*http.Response, error) {
  178. req.Header.Del("Accept")
  179. length := req.Header.Get("Content-Length")
  180. if length == "0" {
  181. req.Header.Del("Content-Length")
  182. }
  183. if strings.HasPrefix(req.Host, "modelarts") && req.Method == string(httputils.PATCH) {
  184. req.Header.Set("Content-Type", "application/merge-patch+json")
  185. }
  186. self.aksk.Sign(req)
  187. return self.client.Do(req)
  188. }
  189. func (self *SHuaweiClient) getAkClient() *akClient {
  190. return &akClient{
  191. client: self.getDefaultClient(),
  192. aksk: Signer{
  193. Key: self.accessKey,
  194. Secret: self.accessSecret,
  195. },
  196. }
  197. }
  198. type sHuaweiError struct {
  199. RequestId string `json:"request_id"`
  200. ErrorMsg string `json:"error_msg"`
  201. ErrorCode string `json:"error_code"`
  202. Code string
  203. Message string
  204. ErrorInfo struct {
  205. Message string
  206. Code string
  207. Title string
  208. } `json:"error"`
  209. Errorcode []string `json:"errorcode"`
  210. ConvertMsg string
  211. RawMsg jsonutils.JSONObject
  212. }
  213. func (self *sHuaweiError) Error() string {
  214. return jsonutils.Marshal(self).String()
  215. }
  216. // https://support.huaweicloud.com/api-iam/iam_02_0006.html
  217. var convertMsg = map[string]string{
  218. "1101": "用户名校验失败,请检查用户名",
  219. "1103": "密码校验失败,请检查密码",
  220. "1104": "手机号校验失败,请检查手机号",
  221. "1108": "新密码不能与原密码相同,请修改新密码",
  222. "1109": "用户名已存在,请修改用户名",
  223. "1118": "密码是弱密码,重新选择密码",
  224. }
  225. func (self *sHuaweiError) ParseErrorFromJsonResponse(statusCode int, status string, body jsonutils.JSONObject) error {
  226. if body != nil {
  227. body.Unmarshal(self)
  228. // 特殊错误将返回原始错误信息
  229. if self.Error() == jsonutils.Marshal(sHuaweiError{}).String() {
  230. self.RawMsg = body
  231. }
  232. }
  233. for _, code := range self.Errorcode {
  234. self.ConvertMsg = convertMsg[code]
  235. }
  236. if statusCode == 404 {
  237. return errors.Wrapf(cloudprovider.ErrNotFound, "%s", self.Error())
  238. }
  239. return self
  240. }
  241. func (self *SHuaweiClient) request(method httputils.THttpMethod, regionId, service, url string, query url.Values, params map[string]interface{}) (jsonutils.JSONObject, error) {
  242. client := self.getAkClient()
  243. if len(query) > 0 {
  244. url = fmt.Sprintf("%s?%s", url, query.Encode())
  245. }
  246. var body jsonutils.JSONObject = nil
  247. if len(params) > 0 {
  248. body = jsonutils.Marshal(params)
  249. }
  250. header := http.Header{}
  251. if project, ok := self.projects[regionId]; ok {
  252. if len(project.Id) > 0 && service != SERVICE_EPS {
  253. header.Set("X-Project-Id", project.Id)
  254. }
  255. }
  256. if ((strings.Contains(url, "/OS-CREDENTIAL/") ||
  257. strings.Contains(url, "/users") ||
  258. strings.Contains(url, "/roles") ||
  259. strings.Contains(url, "/mappings") ||
  260. strings.Contains(url, "/identity_providers") ||
  261. strings.Contains(url, "/groups") && (utils.IsInStringArray(service, []string{SERVICE_IAM, SERVICE_IAM_V3, SERVICE_IAM_V3_EXT}))) ||
  262. service == SERVICE_EPS) && len(self.ownerId) > 0 {
  263. header.Set("X-Domain-Id", self.ownerId)
  264. }
  265. req := httputils.NewJsonRequest(method, url, body)
  266. req.SetHeader(header)
  267. hwErr := &sHuaweiError{}
  268. cli := httputils.NewJsonClient(client)
  269. _, resp, err := cli.Send(context.Background(), req, hwErr, self.debug)
  270. if err != nil {
  271. return nil, err
  272. }
  273. if gotypes.IsNil(resp) {
  274. return jsonutils.NewDict(), nil
  275. }
  276. return resp, nil
  277. }
  278. // https://console.huaweicloud.com/apiexplorer/#/openapi/IAM/doc?api=KeystoneListRegions
  279. func (self *SHuaweiClient) getRegions() ([]SRegion, error) {
  280. if len(self.regions) > 0 {
  281. ret := []SRegion{}
  282. for _, region := range self.regions {
  283. ret = append(ret, region)
  284. }
  285. return ret, nil
  286. }
  287. resp, err := self.list(SERVICE_IAM_V3, "", "regions", nil)
  288. if err != nil {
  289. return nil, errors.Wrapf(err, "list regions")
  290. }
  291. self.regions = map[string]SRegion{}
  292. regions := make([]SRegion, 0)
  293. err = resp.Unmarshal(&regions, "regions")
  294. if err != nil {
  295. return nil, errors.Wrapf(err, "Unmarshal")
  296. }
  297. for _, region := range regions {
  298. region.client = self
  299. self.regions[region.Id] = region
  300. }
  301. return regions, nil
  302. }
  303. func (self *SHuaweiClient) invalidateIBuckets() {
  304. self.iBuckets = nil
  305. }
  306. func (self *SHuaweiClient) getIBuckets() ([]cloudprovider.ICloudBucket, error) {
  307. if self.iBuckets == nil {
  308. err := self.fetchBuckets()
  309. if err != nil {
  310. return nil, errors.Wrap(err, "fetchBuckets")
  311. }
  312. }
  313. return self.iBuckets, nil
  314. }
  315. func getOBSEndpoint(regionId string) string {
  316. return fmt.Sprintf("obs.%s.myhuaweicloud.com", regionId)
  317. }
  318. func (self *SHuaweiClient) getOBSClient(regionId string, signType obs.SignatureType) (*obs.ObsClient, error) {
  319. endpoint := getOBSEndpoint(regionId)
  320. cli, err := obs.New(self.accessKey, self.accessSecret, endpoint, obs.WithSignature(signType))
  321. if err != nil {
  322. return nil, err
  323. }
  324. client := cli.GetClient()
  325. ts, _ := client.Transport.(*http.Transport)
  326. client.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response) error, error) {
  327. method, path := req.Method, req.URL.Path
  328. respCheck := func(resp *http.Response) error {
  329. if resp.StatusCode == 403 {
  330. if self.cpcfg.UpdatePermission != nil {
  331. self.cpcfg.UpdatePermission("obs", fmt.Sprintf("%s %s", method, path))
  332. }
  333. }
  334. return nil
  335. }
  336. if self.cpcfg.ReadOnly {
  337. if req.Method == "GET" || req.Method == "HEAD" {
  338. return respCheck, nil
  339. }
  340. return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
  341. }
  342. return respCheck, nil
  343. })
  344. return cli, nil
  345. }
  346. func (self *SHuaweiClient) fetchBuckets() error {
  347. obscli, err := self.getOBSClient(HUAWEI_DEFAULT_REGION, "")
  348. if err != nil {
  349. return errors.Wrap(err, "getOBSClient")
  350. }
  351. input := &obs.ListBucketsInput{QueryLocation: true}
  352. output, err := obscli.ListBuckets(input)
  353. if err != nil {
  354. return errors.Wrap(err, "obscli.ListBuckets")
  355. }
  356. self.ownerId = output.Owner.ID
  357. ret := make([]cloudprovider.ICloudBucket, 0)
  358. for i := range output.Buckets {
  359. bInfo := output.Buckets[i]
  360. region := self.GetRegion(bInfo.Location)
  361. if gotypes.IsNil(region) {
  362. log.Errorf("fail to find region %s", bInfo.Location)
  363. continue
  364. }
  365. b := SBucket{
  366. region: region,
  367. Name: bInfo.Name,
  368. Location: bInfo.Location,
  369. CreationDate: bInfo.CreationDate,
  370. }
  371. ret = append(ret, &b)
  372. }
  373. self.iBuckets = ret
  374. return nil
  375. }
  376. func (self *SHuaweiClient) GetSubAccounts() ([]cloudprovider.SSubAccount, error) {
  377. subAccount := cloudprovider.SSubAccount{}
  378. subAccount.Id = self.GetAccountId()
  379. subAccount.Name = self.cpcfg.Name
  380. subAccount.Account = self.accessKey
  381. subAccount.HealthStatus = api.CLOUD_PROVIDER_HEALTH_NORMAL
  382. subAccount.DefaultProjectId = "0"
  383. return []cloudprovider.SSubAccount{subAccount}, nil
  384. }
  385. func (client *SHuaweiClient) GetAccountId() string {
  386. ownerId, _ := client.GetOwnerId()
  387. return ownerId
  388. }
  389. func (client *SHuaweiClient) GetIamLoginUrl() string {
  390. return fmt.Sprintf("https://auth.huaweicloud.com/authui/login.html?account=%s#/login", client.ownerName)
  391. }
  392. func (self *SHuaweiClient) GetRegions() []SRegion {
  393. ret := []SRegion{}
  394. for id := range self.regions {
  395. if _, ok := self.projects[id]; !ok {
  396. continue
  397. }
  398. region := self.regions[id]
  399. region.client = self
  400. ret = append(ret, region)
  401. }
  402. for id := range self.projects {
  403. project := self.projects[id]
  404. if strings.Contains(project.Name, "_") {
  405. regionId := project.GetRegionId()
  406. if region, ok := self.regions[regionId]; ok {
  407. region.Id = project.Name
  408. region.client = self
  409. ret = append(ret, region)
  410. }
  411. }
  412. }
  413. return ret
  414. }
  415. func (self *SHuaweiClient) GetIRegions() ([]cloudprovider.ICloudRegion, error) {
  416. regions := self.GetRegions()
  417. ret := []cloudprovider.ICloudRegion{}
  418. for i := range regions {
  419. ret = append(ret, &regions[i])
  420. }
  421. return ret, nil
  422. }
  423. func (self *SHuaweiClient) GetIRegionById(id string) (cloudprovider.ICloudRegion, error) {
  424. regions, err := self.GetIRegions()
  425. if err != nil {
  426. return nil, err
  427. }
  428. for i := range regions {
  429. if regions[i].GetId() == id || regions[i].GetGlobalId() == id {
  430. return regions[i], nil
  431. }
  432. }
  433. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s", id)
  434. }
  435. func (self *SHuaweiClient) GetRegion(regionId string) *SRegion {
  436. if len(regionId) == 0 {
  437. regionId = HUAWEI_DEFAULT_REGION
  438. }
  439. regions := self.GetRegions()
  440. for i := range regions {
  441. if regions[i].Id == regionId {
  442. return &regions[i]
  443. }
  444. }
  445. return nil
  446. }
  447. type SBalance struct {
  448. Amount float64 `json:"amount"`
  449. Currency string `json:"currency"`
  450. AccountId string `json:"account_id"`
  451. AccountType int64 `json:"account_type"`
  452. DesignatedAmount float64 `json:"designated_amount,omitempty"`
  453. CreditAmount float64 `json:"credit_amount,omitempty"`
  454. MeasureUnit int64 `json:"measure_unit"`
  455. }
  456. // https://console.huaweicloud.com/apiexplorer/#/openapi/BSS/doc?api=ShowCustomerAccountBalances
  457. func (self *SHuaweiClient) QueryAccountBalance() (*SBalance, error) {
  458. ret := struct {
  459. AccountBalances []SBalance
  460. Currency string
  461. }{}
  462. for _, service := range []string{SERVICE_BSS, SERVICE_BSS_INTL} {
  463. resp, err := self.list(service, "", "accounts/customer-accounts/balances", nil)
  464. if err != nil {
  465. // 国际区账号会报错: {"error_code":"CBC.0150","error_msg":"Access denied. The customer does not belong to the website you are now at."}
  466. if e, ok := err.(*sHuaweiError); ok && (e.ErrorCode == "CBC.0150" || e.ErrorCode == "CBC.0156") {
  467. continue
  468. }
  469. return nil, err
  470. }
  471. err = resp.Unmarshal(&ret)
  472. if err != nil {
  473. return nil, err
  474. }
  475. break
  476. }
  477. for i := range ret.AccountBalances {
  478. if ret.AccountBalances[i].AccountType == 1 {
  479. return &ret.AccountBalances[i], nil
  480. }
  481. }
  482. return &SBalance{Currency: ret.Currency}, nil
  483. }
  484. func (self *SHuaweiClient) GetISSLCertificates() ([]cloudprovider.ICloudSSLCertificate, error) {
  485. ret, err := self.GetSSLCertificates()
  486. if err != nil {
  487. return nil, errors.Wrapf(err, "GetSSLCertificates")
  488. }
  489. result := make([]cloudprovider.ICloudSSLCertificate, 0)
  490. for i := range ret {
  491. ret[i].client = self
  492. result = append(result, &ret[i])
  493. }
  494. return result, nil
  495. }
  496. func (self *SHuaweiClient) GetISSLCertificate(certId string) (cloudprovider.ICloudSSLCertificate, error) {
  497. var res cloudprovider.ICloudSSLCertificate
  498. res, err := self.GetSSLCertificate(certId)
  499. if err != nil {
  500. return nil, errors.Wrapf(err, "GetSSLCertificate")
  501. }
  502. return res, nil
  503. }
  504. func (self *SHuaweiClient) GetVersion() string {
  505. return HUAWEI_API_VERSION
  506. }
  507. func (self *SHuaweiClient) GetCapabilities() []string {
  508. caps := []string{
  509. cloudprovider.CLOUD_CAPABILITY_PROJECT,
  510. cloudprovider.CLOUD_CAPABILITY_COMPUTE,
  511. cloudprovider.CLOUD_CAPABILITY_NETWORK,
  512. cloudprovider.CLOUD_CAPABILITY_SECURITY_GROUP,
  513. cloudprovider.CLOUD_CAPABILITY_EIP,
  514. cloudprovider.CLOUD_CAPABILITY_LOADBALANCER,
  515. cloudprovider.CLOUD_CAPABILITY_OBJECTSTORE,
  516. cloudprovider.CLOUD_CAPABILITY_RDS,
  517. cloudprovider.CLOUD_CAPABILITY_CACHE,
  518. cloudprovider.CLOUD_CAPABILITY_EVENT,
  519. cloudprovider.CLOUD_CAPABILITY_CLOUDID,
  520. cloudprovider.CLOUD_CAPABILITY_SAML_AUTH,
  521. cloudprovider.CLOUD_CAPABILITY_NAT,
  522. cloudprovider.CLOUD_CAPABILITY_NAS,
  523. cloudprovider.CLOUD_CAPABILITY_QUOTA + cloudprovider.READ_ONLY_SUFFIX,
  524. cloudprovider.CLOUD_CAPABILITY_MODELARTES,
  525. cloudprovider.CLOUD_CAPABILITY_VPC_PEER,
  526. cloudprovider.CLOUD_CAPABILITY_CERT,
  527. cloudprovider.CLOUD_CAPABILITY_CDN + cloudprovider.READ_ONLY_SUFFIX,
  528. }
  529. return caps
  530. }
  531. // https://console.huaweicloud.com/apiexplorer/#/openapi/IAM/doc?api=ShowPermanentAccessKey
  532. func (self *SHuaweiClient) GetUserId() (string, error) {
  533. if len(self.userId) > 0 {
  534. return self.userId, nil
  535. }
  536. type cred struct {
  537. UserId string `json:"user_id"`
  538. }
  539. ret := &cred{}
  540. resp, err := self.list(SERVICE_IAM, "", "OS-CREDENTIAL/credentials/"+self.accessKey, nil)
  541. if err != nil {
  542. return "", errors.Wrapf(err, "show credential")
  543. }
  544. err = resp.Unmarshal(ret, "credential")
  545. if err != nil {
  546. return "", errors.Wrapf(err, "Unmarshal")
  547. }
  548. self.userId = ret.UserId
  549. return self.userId, nil
  550. }
  551. // owner id == domain_id == account id
  552. // https://console.huaweicloud.com/apiexplorer/#/openapi/IAM/doc?api=ShowUser
  553. func (self *SHuaweiClient) GetOwnerId() (string, error) {
  554. if len(self.ownerId) > 0 {
  555. return self.ownerId, nil
  556. }
  557. userId, err := self.GetUserId()
  558. if err != nil {
  559. return "", errors.Wrap(err, "SHuaweiClient.GetOwnerId.GetUserId")
  560. }
  561. type user struct {
  562. DomainId string `json:"domain_id"`
  563. Name string `json:"name"`
  564. CreateTime string
  565. }
  566. resp, err := self.list(SERVICE_IAM, "", "OS-USER/users/"+userId, nil)
  567. if err != nil {
  568. return "", errors.Wrapf(err, "show user")
  569. }
  570. ret := &user{}
  571. err = resp.Unmarshal(ret, "user")
  572. if err != nil {
  573. return "", errors.Wrapf(err, "Unmarshal")
  574. }
  575. self.ownerName = ret.Name
  576. // 2021-02-02 02:43:28.0
  577. self.ownerCreateTime, _ = timeutils.ParseTimeStr(strings.TrimSuffix(ret.CreateTime, ".0"))
  578. self.ownerId = ret.DomainId
  579. return self.ownerId, nil
  580. }
  581. func (self *SHuaweiClient) list(service, regionId, resource string, query url.Values) (jsonutils.JSONObject, error) {
  582. url, err := self.getUrl(service, regionId, resource, httputils.GET, nil)
  583. if err != nil {
  584. return nil, err
  585. }
  586. return self.request(httputils.GET, regionId, service, url, query, nil)
  587. }
  588. func (self *SHuaweiClient) delete(service, regionId, resource string) (jsonutils.JSONObject, error) {
  589. url, err := self.getUrl(service, regionId, resource, httputils.DELETE, nil)
  590. if err != nil {
  591. return nil, err
  592. }
  593. return self.request(httputils.DELETE, regionId, service, url, nil, nil)
  594. }
  595. func (self *SHuaweiClient) getUrl(service, regionId, resource string, method httputils.THttpMethod, params map[string]interface{}) (string, error) {
  596. url := ""
  597. resource = strings.TrimPrefix(resource, "/")
  598. if len(regionId) == 0 {
  599. regionId = HUAWEI_DEFAULT_REGION
  600. }
  601. projectId := ""
  602. project, ok := self.projects[regionId]
  603. if ok {
  604. regionId = project.GetRegionId()
  605. projectId = project.Id
  606. }
  607. switch service {
  608. case SERVICE_IAM:
  609. url = fmt.Sprintf("https://iam.myhuaweicloud.com/v3.0/%s", resource)
  610. case SERVICE_IAM_V3:
  611. url = fmt.Sprintf("https://iam.myhuaweicloud.com/v3/%s", resource)
  612. case SERVICE_IAM_V3_EXT:
  613. url = fmt.Sprintf("https://iam.myhuaweicloud.com/v3-ext/%s", resource)
  614. case SERVICE_ELB:
  615. url = fmt.Sprintf("https://elb.%s.myhuaweicloud.com/v3/%s/%s", regionId, projectId, resource)
  616. case SERVICE_VPC:
  617. url = fmt.Sprintf("https://vpc.%s.myhuaweicloud.com/v1/%s/%s", regionId, projectId, resource)
  618. case SERVICE_VPC_V2_0:
  619. url = fmt.Sprintf("https://vpc.%s.myhuaweicloud.com/v2.0/%s/%s", regionId, projectId, resource)
  620. if strings.Contains(resource, "/peerings") {
  621. url = fmt.Sprintf("https://vpc.%s.myhuaweicloud.com/v2.0/%s", regionId, resource)
  622. }
  623. case SERVICE_VPC_V3:
  624. url = fmt.Sprintf("https://vpc.%s.myhuaweicloud.com/v3/%s/%s", regionId, projectId, resource)
  625. case SERVICE_VPN:
  626. url = fmt.Sprintf("https://vpn.%s.myhuaweicloud.com/v5/%s/%s", regionId, projectId, resource)
  627. case SERVICE_CES:
  628. url = fmt.Sprintf("https://ces.%s.myhuaweicloud.com/V1.0/%s/%s", regionId, projectId, resource)
  629. case SERVICE_MODELARTS:
  630. url = fmt.Sprintf("https://modelarts.%s.myhuaweicloud.com/v2/%s/%s", regionId, projectId, resource)
  631. case SERVICE_MODELARTS_V1:
  632. url = fmt.Sprintf("https://modelarts.%s.myhuaweicloud.com/v1/%s/%s", regionId, projectId, resource)
  633. case SERVICE_RDS:
  634. url = fmt.Sprintf("https://rds.%s.myhuaweicloud.com/v3/%s/%s", regionId, projectId, resource)
  635. case SERVICE_ECS:
  636. url = fmt.Sprintf("https://ecs.%s.myhuaweicloud.com/v1/%s/%s", regionId, projectId, resource)
  637. case SERVICE_ECS_V1_1:
  638. url = fmt.Sprintf("https://ecs.%s.myhuaweicloud.com/v1.1/%s/%s", regionId, projectId, resource)
  639. case SERVICE_ECS_V2_1:
  640. url = fmt.Sprintf("https://ecs.%s.myhuaweicloud.com/v2.1/%s/%s", regionId, projectId, resource)
  641. case SERVICE_EPS:
  642. url = fmt.Sprintf("https://eps.myhuaweicloud.com/v1.0/%s", resource)
  643. case SERVICE_EVS_V1:
  644. url = fmt.Sprintf("https://evs.%s.myhuaweicloud.com/v1/%s/%s", regionId, projectId, resource)
  645. case SERVICE_EVS:
  646. url = fmt.Sprintf("https://evs.%s.myhuaweicloud.com/v2/%s/%s", regionId, projectId, resource)
  647. case SERVICE_EVS_V2_1:
  648. url = fmt.Sprintf("https://evs.%s.myhuaweicloud.com/v2.1/%s/%s", regionId, projectId, resource)
  649. case SERVICE_BSS, SERVICE_BSS_INTL:
  650. url = fmt.Sprintf("https://%s.myhuaweicloud.com/v2/%s", service, resource)
  651. case SERVICE_SFS:
  652. url = fmt.Sprintf("https://sfs-turbo.%s.myhuaweicloud.com/v1/%s/%s", regionId, projectId, resource)
  653. case SERVICE_IMS:
  654. url = fmt.Sprintf("https://ims.%s.myhuaweicloud.com/v2/%s", regionId, resource)
  655. case SERVICE_IMS_V1:
  656. url = fmt.Sprintf("https://ims.%s.myhuaweicloud.com/v1/%s", regionId, resource)
  657. case SERVICE_DCS:
  658. url = fmt.Sprintf("https://dcs.%s.myhuaweicloud.com/v2/%s/%s", regionId, projectId, resource)
  659. case SERVICE_CTS:
  660. url = fmt.Sprintf("https://cts.%s.myhuaweicloud.com/v3/%s/%s", regionId, projectId, resource)
  661. case SERVICE_NAT:
  662. url = fmt.Sprintf("https://nat.%s.myhuaweicloud.com/v3/%s/%s", regionId, projectId, resource)
  663. case SERVICE_NAT_V2:
  664. url = fmt.Sprintf("https://nat.%s.myhuaweicloud.com/v2/%s/%s", regionId, projectId, resource)
  665. case SERVICE_SCM:
  666. url = fmt.Sprintf("https://scm.cn-north-4.myhuaweicloud.com/v3/%s", resource)
  667. case SERVICE_CDN:
  668. url = fmt.Sprintf("https://cdn.myhuaweicloud.com/v1.0/%s", resource)
  669. case SERVICE_GAUSSDB, SERVICE_GAUSSDB_NOSQL:
  670. url = fmt.Sprintf("https://%s.%s.myhuaweicloud.com/v3/%s/%s", service, regionId, projectId, resource)
  671. case SERVICE_GAUSSDB_V3_1:
  672. url = fmt.Sprintf("https://%s.%s.myhuaweicloud.com/v3.1/%s/%s", SERVICE_GAUSSDB, regionId, projectId, resource)
  673. case SERVICE_GAUSSDB_NOSQL_V3_1:
  674. url = fmt.Sprintf("https://gaussdb-nosql.%s.myhuaweicloud.com/v3.1/%s/%s", regionId, projectId, resource)
  675. case SERVICE_GAUSSDB_OPENGAUSS:
  676. url = fmt.Sprintf("https://%s.%s.myhuaweicloud.com/v3.1/%s/%s", service, regionId, projectId, resource)
  677. case SERVICE_FUNCTIONGRAPH:
  678. url = fmt.Sprintf("https://%s.%s.myhuaweicloud.com/v2/%s/%s", service, regionId, projectId, resource)
  679. case SERVICE_APIG:
  680. url = fmt.Sprintf("https://%s.%s.myhuaweicloud.com/v2/%s/%s", service, regionId, projectId, resource)
  681. case SERVICE_APIG_V1_0:
  682. url = fmt.Sprintf("https://%s.%s.myhuaweicloud.com/v1.0/%s", SERVICE_APIG, regionId, resource)
  683. case SERVICE_MRS:
  684. url = fmt.Sprintf("https://%s.%s.myhuaweicloud.com/v1.1/%s/%s", service, regionId, projectId, resource)
  685. case SERVICE_DIS:
  686. url = fmt.Sprintf("https://%s.%s.myhuaweicloud.com/v2/%s/%s", service, regionId, projectId, resource)
  687. case SERVICE_LTS:
  688. url = fmt.Sprintf("https://%s.%s.myhuaweicloud.com/v2/%s/%s", service, regionId, projectId, resource)
  689. case SERVICE_CCE:
  690. url = fmt.Sprintf("https://%s.%s.myhuaweicloud.com/api/v3/projects/%s/%s", service, regionId, projectId, resource)
  691. case SERVICE_AS:
  692. url = fmt.Sprintf("https://%s.%s.myhuaweicloud.com/autoscaling-api/v1/%s/%s", service, regionId, projectId, resource)
  693. default:
  694. return "", fmt.Errorf("invalid service %s", service)
  695. }
  696. return url, nil
  697. }
  698. func (self *SHuaweiClient) post(service, regionId, resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
  699. url, err := self.getUrl(service, regionId, resource, httputils.POST, params)
  700. if err != nil {
  701. return nil, err
  702. }
  703. return self.request(httputils.POST, regionId, service, url, nil, params)
  704. }
  705. func (self *SHuaweiClient) patch(service, regionId, resource string, query url.Values, params map[string]interface{}) (jsonutils.JSONObject, error) {
  706. url, err := self.getUrl(service, regionId, resource, httputils.PATCH, params)
  707. if err != nil {
  708. return nil, err
  709. }
  710. return self.request(httputils.PATCH, regionId, service, url, query, params)
  711. }
  712. func (self *SHuaweiClient) put(service, regionId, resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
  713. url, err := self.getUrl(service, regionId, resource, httputils.PUT, params)
  714. if err != nil {
  715. return nil, err
  716. }
  717. return self.request(httputils.PUT, regionId, service, url, nil, params)
  718. }
  719. type SMonthBill struct {
  720. Currency string
  721. ConsumeAmount float64
  722. CreditAmount float64
  723. CashAmount float64
  724. BillSums []struct {
  725. CustomerId string
  726. ConsumeAmount float64
  727. }
  728. }
  729. // https://console.huaweicloud.com/apiexplorer/#/openapi/BSS/doc?api=ShowCustomerMonthlySum
  730. func (self *SHuaweiClient) QueryAccountMonthBill(month int) (*SMonthBill, error) {
  731. ret := SMonthBill{}
  732. query := url.Values{}
  733. query.Set("bill_cycle", fmt.Sprintf("%d-%02d", month/100, month%100))
  734. for _, service := range []string{SERVICE_BSS, SERVICE_BSS_INTL} {
  735. resp, err := self.list(service, "", "bills/customer-bills/monthly-sum", query)
  736. if err != nil {
  737. // 国际区账号会报错: {"error_code":"CBC.0150","error_msg":"Access denied. The customer does not belong to the website you are now at."}
  738. if e, ok := err.(*sHuaweiError); ok && (e.ErrorCode == "CBC.0150" || e.ErrorCode == "CBC.0156") {
  739. continue
  740. }
  741. return nil, err
  742. }
  743. err = resp.Unmarshal(&ret)
  744. if err != nil {
  745. return nil, err
  746. }
  747. break
  748. }
  749. return &ret, nil
  750. }