hcso.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438
  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. "context"
  17. "fmt"
  18. "net"
  19. "net/http"
  20. "net/url"
  21. "strconv"
  22. "strings"
  23. "time"
  24. "github.com/huaweicloud/huaweicloud-sdk-go/auth/aksk"
  25. "yunion.io/x/jsonutils"
  26. "yunion.io/x/pkg/errors"
  27. "yunion.io/x/pkg/gotypes"
  28. "yunion.io/x/pkg/util/httputils"
  29. "yunion.io/x/cloudmux/pkg/cloudprovider"
  30. )
  31. type akClient struct {
  32. client *http.Client
  33. aksk aksk.SignOptions
  34. }
  35. func (self *akClient) Do(req *http.Request) (*http.Response, error) {
  36. req.Header.Del("Host")
  37. req.Header.Del("Authorization")
  38. req.Header.Del("X-Sdk-Date")
  39. req.Header.Del("Accept")
  40. if req.Method == string(httputils.GET) || req.Method == string(httputils.DELETE) || req.Method == string(httputils.PATCH) {
  41. req.Header.Del("Content-Length")
  42. }
  43. aksk.Sign(req, self.aksk)
  44. return self.client.Do(req)
  45. }
  46. func (self *SHuaweiClient) getAkClient() *akClient {
  47. return &akClient{
  48. client: self.getDefaultClient(),
  49. aksk: aksk.SignOptions{
  50. AccessKey: self.accessKey,
  51. SecretKey: self.accessSecret,
  52. },
  53. }
  54. }
  55. func (self *SHuaweiClient) getDefaultClient() *http.Client {
  56. if self.httpClient != nil {
  57. return self.httpClient
  58. }
  59. self.httpClient = self.cpcfg.AdaptiveTimeoutHttpClient()
  60. ts, _ := self.httpClient.Transport.(*http.Transport)
  61. self.httpClient.Transport = cloudprovider.GetCheckTransport(ts, func(req *http.Request) (func(resp *http.Response) error, error) {
  62. service, method, path := strings.Split(req.URL.Host, ".")[0], req.Method, req.URL.Path
  63. respCheck := func(resp *http.Response) error {
  64. if resp.StatusCode == 403 {
  65. if self.cpcfg.UpdatePermission != nil {
  66. self.cpcfg.UpdatePermission(service, fmt.Sprintf("%s %s", method, path))
  67. }
  68. }
  69. return nil
  70. }
  71. if self.cpcfg.ReadOnly {
  72. if req.Method == "GET" {
  73. return respCheck, nil
  74. }
  75. return nil, errors.Wrapf(cloudprovider.ErrAccountReadOnly, "%s %s", req.Method, req.URL.Path)
  76. }
  77. return respCheck, nil
  78. })
  79. return self.httpClient
  80. }
  81. func (self *SHuaweiClient) list(service, regionId, resource string, query url.Values) (jsonutils.JSONObject, error) {
  82. url, err := self.getUrl(service, regionId, resource, httputils.GET, nil)
  83. if err != nil {
  84. return nil, err
  85. }
  86. return self.request(httputils.GET, url, query, nil)
  87. }
  88. func (self *SHuaweiClient) delete(service, regionId, resource string) (jsonutils.JSONObject, error) {
  89. url, err := self.getUrl(service, regionId, resource, httputils.DELETE, nil)
  90. if err != nil {
  91. return nil, err
  92. }
  93. return self.request(httputils.DELETE, url, nil, nil)
  94. }
  95. func (self *SHuaweiClient) post(service, regionId, resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
  96. url, err := self.getUrl(service, regionId, resource, httputils.POST, params)
  97. if err != nil {
  98. return nil, err
  99. }
  100. return self.request(httputils.POST, url, nil, params)
  101. }
  102. func (self *SHuaweiClient) patch(service, regionId, resource string, query url.Values, params map[string]interface{}) (jsonutils.JSONObject, error) {
  103. url, err := self.getUrl(service, regionId, resource, httputils.PATCH, params)
  104. if err != nil {
  105. return nil, err
  106. }
  107. return self.request(httputils.PATCH, url, query, params)
  108. }
  109. func (self *SHuaweiClient) put(service, regionId, resource string, params map[string]interface{}) (jsonutils.JSONObject, error) {
  110. url, err := self.getUrl(service, regionId, resource, httputils.PUT, params)
  111. if err != nil {
  112. return nil, err
  113. }
  114. return self.request(httputils.PUT, url, nil, params)
  115. }
  116. func (self *SHuaweiClient) request(method httputils.THttpMethod, url string, query url.Values, params map[string]interface{}) (jsonutils.JSONObject, error) {
  117. client := self.getAkClient()
  118. if len(query) > 0 {
  119. url = fmt.Sprintf("%s?%s", url, query.Encode())
  120. }
  121. var body jsonutils.JSONObject = nil
  122. if len(params) > 0 {
  123. body = jsonutils.Marshal(params)
  124. }
  125. header := http.Header{}
  126. if len(self.projectId) > 0 && !strings.Contains(url, "eps") {
  127. header.Set("X-Project-Id", self.projectId)
  128. }
  129. if (strings.Contains(url, "/OS-CREDENTIAL/") ||
  130. strings.Contains(url, "/users") ||
  131. strings.Contains(url, "eps.myhuaweicloud.com")) && len(self.ownerId) > 0 {
  132. header.Set("X-Domain-Id", self.ownerId)
  133. }
  134. var resp jsonutils.JSONObject
  135. var err error
  136. for i := 0; i < 3; i++ {
  137. _, resp, err = requestWithRetry(client, context.Background(), method, url, header, body, self.debug)
  138. if method == httputils.GET && needRetry(err) {
  139. time.Sleep(time.Second * 15)
  140. continue
  141. }
  142. break
  143. }
  144. return resp, err
  145. }
  146. func (self *SHuaweiClient) getUrl(service, regionId, resource string, method httputils.THttpMethod, params map[string]interface{}) (string, error) {
  147. url := ""
  148. resource = strings.TrimPrefix(resource, "/")
  149. switch service {
  150. case SERVICE_IAM:
  151. endpoint := self.resetEndpoint(self.endpoints.Iam, "iam-pub")
  152. url = fmt.Sprintf("https://%s/v3.0/%s", resource, endpoint)
  153. if !strings.HasPrefix(resource, "OS-") {
  154. url = fmt.Sprintf("https://%s/v3/%s", endpoint, resource)
  155. }
  156. case SERVICE_ELB:
  157. endpoint := self.resetEndpoint(self.endpoints.Elb, "elb")
  158. url = fmt.Sprintf("https://%s/v2/%s/%s", endpoint, self.projectId, resource)
  159. case SERVICE_VPC:
  160. endpoint := self.resetEndpoint(self.endpoints.Vpc, "vpc")
  161. version := "v1"
  162. if strings.HasPrefix(resource, "vpc/") {
  163. version = "v3"
  164. }
  165. url = fmt.Sprintf("https://%s/%s/%s/%s", endpoint, version, self.projectId, resource)
  166. case SERVICE_CES:
  167. endpoint := self.resetEndpoint(self.endpoints.Ces, "ces")
  168. url = fmt.Sprintf("https://%s/v1.0/%s/%s", endpoint, self.projectId, resource)
  169. case SERVICE_MODELARTS:
  170. endpoint := self.resetEndpoint(self.endpoints.Modelarts, "modelarts")
  171. url = fmt.Sprintf("https://%s/v2/%s/%s", endpoint, self.projectId, resource)
  172. if strings.HasPrefix(resource, "networks") || strings.HasPrefix(resource, "resourceflavors") {
  173. url = fmt.Sprintf("https://%s/v1/%s/%s", endpoint, self.projectId, resource)
  174. }
  175. case SERVICE_RDS:
  176. endpoint := self.resetEndpoint(self.endpoints.Rds, "rds")
  177. url = fmt.Sprintf("https://%s/v3/%s/%s", endpoint, self.projectId, resource)
  178. case SERVICE_ECS:
  179. version := "v1"
  180. for _, prefix := range []string{
  181. "os-availability-zone",
  182. "servers",
  183. "os-keypairs",
  184. } {
  185. if strings.HasPrefix(resource, prefix) || strings.Contains(resource, "os-security-groups") {
  186. version = "v2.1"
  187. break
  188. }
  189. }
  190. if strings.HasSuffix(resource, "action") && !gotypes.IsNil(params) {
  191. for _, k := range []string{"addSecurityGroup", "removeSecurityGroup"} {
  192. _, ok := params[k]
  193. if ok {
  194. version = "v2.1"
  195. break
  196. }
  197. }
  198. }
  199. endpoint := self.resetEndpoint(self.endpoints.Ecs, "ecs")
  200. url = fmt.Sprintf("https://%s/%s/%s/%s", endpoint, version, self.projectId, resource)
  201. case SERVICE_EPS:
  202. endpoint := self.resetEndpoint(self.endpoints.Eps, "eps")
  203. url = fmt.Sprintf("https://%s/v1.0/%s", endpoint, resource)
  204. case SERVICE_EVS:
  205. version := "v2"
  206. endpoint := self.resetEndpoint(self.endpoints.Evs, "evs")
  207. url = fmt.Sprintf("https://%s/%s/%s/%s", endpoint, version, self.projectId, resource)
  208. case SERVICE_BSS:
  209. endpoint := self.resetEndpoint(self.endpoints.Bss, "bss")
  210. url = fmt.Sprintf("https://%s/v2/%s", endpoint, resource)
  211. case SERVICE_SFS:
  212. endpoint := self.resetEndpoint(self.endpoints.SfsTurbo, "sfs-turbo")
  213. url = fmt.Sprintf("https://%s/v1/%s/%s", endpoint, self.projectId, resource)
  214. case SERVICE_IMS:
  215. endpoint := self.resetEndpoint(self.endpoints.Ims, "ims")
  216. url = fmt.Sprintf("https://%s/v2/%s", endpoint, resource)
  217. case SERVICE_DCS:
  218. endpoint := self.resetEndpoint(self.endpoints.Dcs, "dcs")
  219. url = fmt.Sprintf("https://%s/v2/%s/%s", endpoint, self.projectId, resource)
  220. case SERVICE_CTS:
  221. endpoint := self.resetEndpoint(self.endpoints.Cts, "cts")
  222. url = fmt.Sprintf("https://%s/v3/%s/%s", endpoint, self.projectId, resource)
  223. case SERVICE_NAT:
  224. endpoint := self.resetEndpoint(self.endpoints.Nat, "nat")
  225. url = fmt.Sprintf("https://%s/v2/%s/%s", endpoint, self.projectId, resource)
  226. default:
  227. return "", fmt.Errorf("invalid service %s", service)
  228. }
  229. return url, nil
  230. }
  231. func requestWithRetry(client *akClient, ctx context.Context, method httputils.THttpMethod, urlStr string, header http.Header, body jsonutils.JSONObject, debug bool) (http.Header, jsonutils.JSONObject, error) {
  232. var bodystr string
  233. if !gotypes.IsNil(body) {
  234. bodystr = body.String()
  235. }
  236. jbody := strings.NewReader(bodystr)
  237. if header == nil {
  238. header = http.Header{}
  239. }
  240. header.Set("Content-Length", strconv.FormatInt(int64(len(bodystr)), 10))
  241. header.Set("Content-Type", "application/json")
  242. resp, err := httputils.RequestWithRetry(client, ctx, method, urlStr, header, jbody, debug)
  243. return httputils.ParseJSONResponse(bodystr, resp, err, debug)
  244. }
  245. func (self *SHuaweiClient) resetEndpoint(endpoint, serviceName string) string {
  246. if len(endpoint) == 0 {
  247. domain := self.HuaweiClientConfig.endpoints.EndpointDomain
  248. regionId := self.HuaweiClientConfig.cpcfg.RegionId
  249. if len(regionId) == 0 {
  250. regionId = self.GetRegions()[0].ID
  251. }
  252. endpoint = fmt.Sprintf("%s.%s.%s", serviceName, regionId, domain)
  253. }
  254. return endpoint
  255. }
  256. func (self *SHuaweiClient) getAKSKList(userId string) (jsonutils.JSONObject, error) {
  257. endpoint := self.resetEndpoint(self.endpoints.Iam, "iam-pub")
  258. uri := fmt.Sprintf("https://%s/v3.0/OS-CREDENTIAL/credentials", endpoint)
  259. query := url.Values{}
  260. query.Set("user_id", userId)
  261. return self.request(httputils.GET, uri, query, nil)
  262. }
  263. func (self *SHuaweiClient) createAKSK(params map[string]interface{}) (jsonutils.JSONObject, error) {
  264. endpoint := self.resetEndpoint(self.endpoints.Iam, "iam-pub")
  265. uri := fmt.Sprintf("https://%s/v3.0/OS-CREDENTIAL/credentials", endpoint)
  266. return self.request(httputils.POST, uri, nil, params)
  267. }
  268. func (self *SHuaweiClient) deleteAKSK(accessKey string) (jsonutils.JSONObject, error) {
  269. endpoint := self.resetEndpoint(self.endpoints.Iam, "iam-pub")
  270. uri := fmt.Sprintf("https://%s/v3.0/OS-CREDENTIAL/credentials/%s", endpoint, accessKey)
  271. return self.request(httputils.DELETE, uri, nil, nil)
  272. }
  273. func (self *SHuaweiClient) modelartsPoolNetworkList(params map[string]interface{}) (jsonutils.JSONObject, error) {
  274. endpoint := self.resetEndpoint(self.endpoints.Modelarts, "modelarts")
  275. uri := fmt.Sprintf("https://%s/v1/%s/networks", endpoint, self.projectId)
  276. return self.request(httputils.GET, uri, url.Values{}, params)
  277. }
  278. func (cli *SHuaweiClient) modelartsPoolNetworkDetail(networkName string) (jsonutils.JSONObject, error) {
  279. endpoint := cli.resetEndpoint(cli.endpoints.Modelarts, "modelarts")
  280. uri := fmt.Sprintf("https://%s/v1/%s/networks/%s", endpoint, cli.projectId, networkName)
  281. return cli.request(httputils.GET, uri, url.Values{}, nil)
  282. }
  283. func (cli *SHuaweiClient) modelartsPoolNetworkDelete(networkName string) (jsonutils.JSONObject, error) {
  284. endpoint := cli.resetEndpoint(cli.endpoints.Modelarts, "modelarts")
  285. uri := fmt.Sprintf("https://%s/v1/%s/networks/%s", endpoint, cli.projectId, networkName)
  286. return cli.request(httputils.DELETE, uri, url.Values{}, nil)
  287. }
  288. func (self *SHuaweiClient) modelartsPoolNetworkCreate(params map[string]interface{}) (jsonutils.JSONObject, error) {
  289. endpoint := self.resetEndpoint(self.endpoints.Modelarts, "modelarts")
  290. uri := fmt.Sprintf("https://%s/v1/%s/networks", endpoint, self.projectId)
  291. return self.request(httputils.POST, uri, url.Values{}, params)
  292. }
  293. func (self *SHuaweiClient) modelartsPoolById(poolName string) (jsonutils.JSONObject, error) {
  294. endpoint := self.resetEndpoint(self.endpoints.Modelarts, "modelarts")
  295. uri := fmt.Sprintf("https://%s/v2/%s/pools/%s", endpoint, self.projectId, poolName)
  296. res, err := self.request(httputils.GET, uri, url.Values{}, nil)
  297. if err != nil {
  298. if strings.Contains(err.Error(), "not found") {
  299. return nil, errors.ErrNotFound
  300. } else {
  301. return nil, err
  302. }
  303. }
  304. return res, nil
  305. }
  306. func (cli *SHuaweiClient) modelartsPoolListWithStatus(resource, status string, params map[string]interface{}) (jsonutils.JSONObject, error) {
  307. endpoint := cli.resetEndpoint(cli.endpoints.Modelarts, "modelarts")
  308. uri := fmt.Sprintf("https://%s/v2/%s/pools", endpoint, cli.projectId)
  309. value := url.Values{}
  310. value.Add("status", status)
  311. return cli.request(httputils.GET, uri, value, params)
  312. }
  313. func (self *SHuaweiClient) modelartsPoolList(params map[string]interface{}) (jsonutils.JSONObject, error) {
  314. endpoint := self.resetEndpoint(self.endpoints.Modelarts, "modelarts")
  315. uri := fmt.Sprintf("https://%s/v2/%s/pools", endpoint, self.projectId)
  316. return self.request(httputils.GET, uri, url.Values{}, params)
  317. }
  318. func (self *SHuaweiClient) modelartsPoolCreate(params map[string]interface{}) (jsonutils.JSONObject, error) {
  319. endpoint := self.resetEndpoint(self.endpoints.Modelarts, "modelarts")
  320. uri := fmt.Sprintf("https://%s/v2/%s/pools", endpoint, self.projectId)
  321. return self.request(httputils.POST, uri, url.Values{}, params)
  322. }
  323. func (self *SHuaweiClient) modelartsPoolDelete(poolName string, params map[string]interface{}) (jsonutils.JSONObject, error) {
  324. endpoint := self.resetEndpoint(self.endpoints.Modelarts, "modelarts")
  325. uri := fmt.Sprintf("https://%s/v2/%s/pools/%s", endpoint, self.projectId, poolName)
  326. return self.request(httputils.DELETE, uri, url.Values{}, params)
  327. }
  328. func (self *SHuaweiClient) modelartsPoolUpdate(poolName string, params map[string]interface{}) (jsonutils.JSONObject, error) {
  329. endpoint := self.resetEndpoint(self.endpoints.Modelarts, "modelarts")
  330. uri := fmt.Sprintf("https://%s/v2/%s/pools/%s", endpoint, self.projectId, poolName)
  331. urlValue := url.Values{}
  332. urlValue.Add("time_range", "")
  333. urlValue.Add("statistics", "")
  334. urlValue.Add("period", "")
  335. return self.patchRequest(httputils.PATCH, uri, urlValue, params)
  336. }
  337. func (self *SHuaweiClient) modelartsPoolMonitor(poolName string, params map[string]interface{}) (jsonutils.JSONObject, error) {
  338. endpoint := self.resetEndpoint(self.endpoints.Modelarts, "modelarts")
  339. uri := fmt.Sprintf("https://%s/v2/%s/pools/%s/monitor", endpoint, self.projectId, poolName)
  340. return self.request(httputils.GET, uri, url.Values{}, params)
  341. }
  342. func (self *SHuaweiClient) modelartsResourceflavors(params map[string]interface{}) (jsonutils.JSONObject, error) {
  343. endpoint := self.resetEndpoint(self.endpoints.Modelarts, "modelarts")
  344. uri := fmt.Sprintf("https://%s/v1/%s/resourceflavors", endpoint, self.projectId)
  345. return self.request(httputils.GET, uri, url.Values{}, params)
  346. }
  347. func (self *SHuaweiClient) commonMonitor(params map[string]string) (jsonutils.JSONObject, error) {
  348. endpoint := self.resetEndpoint(self.endpoints.Ces, "ces")
  349. uri := fmt.Sprintf("https://%s/V1.0/%s/metric-data", endpoint, self.projectId)
  350. url := url.Values{}
  351. for k, v := range params {
  352. url.Set(k, v)
  353. }
  354. return self.request(httputils.GET, uri, url, nil)
  355. }
  356. func (self *SHuaweiClient) patchRequest(method httputils.THttpMethod, url string, query url.Values, params map[string]interface{}) (jsonutils.JSONObject, error) {
  357. client := self.getAkClient()
  358. if len(query) > 0 {
  359. url = fmt.Sprintf("%s?%s", url, query.Encode())
  360. }
  361. var body jsonutils.JSONObject = nil
  362. if len(params) > 0 {
  363. body = jsonutils.Marshal(params)
  364. }
  365. header := http.Header{}
  366. if len(self.projectId) > 0 {
  367. header.Set("X-Project-Id", self.projectId)
  368. }
  369. var bodystr string
  370. if !gotypes.IsNil(body) {
  371. bodystr = body.String()
  372. }
  373. jbody := strings.NewReader(bodystr)
  374. header.Set("Content-Length", strconv.FormatInt(int64(len(bodystr)), 10))
  375. header.Set("Content-Type", "application/merge-patch+json")
  376. resp, err := httputils.Request(client, context.Background(), method, url, header, jbody, self.debug)
  377. _, respValue, err := httputils.ParseJSONResponse(bodystr, resp, err, self.debug)
  378. if err != nil {
  379. if e, ok := err.(*httputils.JSONClientError); ok && e.Code == 404 {
  380. return nil, errors.Wrapf(cloudprovider.ErrNotFound, "%s", err.Error())
  381. }
  382. return nil, err
  383. }
  384. return respValue, err
  385. }
  386. func needRetry(err error) bool {
  387. if err == nil {
  388. return false
  389. }
  390. switch e := err.(type) {
  391. case *url.Error:
  392. switch e.Err.(type) {
  393. case *net.DNSError, *net.OpError, net.UnknownNetworkError:
  394. return true
  395. }
  396. if strings.Contains(err.Error(), "The throttling threshold has been reached: policy ip over ratelimit") {
  397. return true
  398. }
  399. }
  400. return false
  401. }