client.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  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 ucloud
  15. import (
  16. "context"
  17. "crypto/sha1"
  18. "fmt"
  19. "strings"
  20. "time"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/errors"
  24. "yunion.io/x/pkg/util/httputils"
  25. "yunion.io/x/cloudmux/pkg/cloudprovider"
  26. )
  27. const UCLOUD_API_HOST = "https://api.ucloud.cn"
  28. // API返回结果对应的字段名
  29. var UCLOUD_API_RESULT_KEYS = map[string]string{
  30. "AllocateEIP": "EIPSet",
  31. "GetProjectList": "ProjectSet",
  32. "GetRegion": "Regions",
  33. "DescribeVPC": "DataSet",
  34. "DescribeImage": "ImageSet",
  35. "DescribeIsolationGroup": "IsolationGroupSet",
  36. "DescribeUHostInstance": "UHostSet",
  37. "DescribeUHostTags": "TagSet",
  38. "DescribeUDSet": "UDSet",
  39. "DescribeUDisk": "DataSet",
  40. "DescribeUDiskSnapshot": "DataSet",
  41. "DescribeEIP": "EIPSet",
  42. "DescribeFirewall": "DataSet",
  43. "DescribeSubnet": "DataSet",
  44. "DescribeBucket": "DataSet",
  45. "CreateUDisk": "UDiskId",
  46. "CreateVPC": "VPCId",
  47. "CreateUDiskSnapshot": "SnapshotId",
  48. "DescribeVIP": "VIPSet",
  49. }
  50. type SParams struct {
  51. data jsonutils.JSONDict
  52. }
  53. type SUcloudError struct {
  54. Action string `json:"Action"`
  55. Message string `json:"Message"`
  56. RetCode int64 `json:"RetCode"`
  57. }
  58. func (self *SUcloudError) Error() string {
  59. return fmt.Sprintf("Do %s failed, code: %d, %s", self.Action, self.RetCode, self.Message)
  60. }
  61. func NewUcloudParams() SParams {
  62. data := jsonutils.NewDict()
  63. return SParams{data: *data}
  64. }
  65. func (self *SParams) Set(key string, value interface{}) {
  66. switch v := value.(type) {
  67. case string:
  68. self.data.Set(key, jsonutils.NewString(v))
  69. case int64:
  70. self.data.Set(key, jsonutils.NewInt(v))
  71. case int:
  72. self.data.Set(key, jsonutils.NewInt(int64(v)))
  73. case bool:
  74. self.data.Set(key, jsonutils.NewBool(v))
  75. case float64:
  76. self.data.Set(key, jsonutils.NewFloat64(v))
  77. case float32:
  78. self.data.Set(key, jsonutils.NewFloat32(v))
  79. case []string:
  80. self.data.Set(key, jsonutils.NewStringArray(v))
  81. default:
  82. log.Debugf("unsuported params type %T", value)
  83. }
  84. }
  85. func (self *SParams) SetAction(action string) {
  86. self.data.Set("Action", jsonutils.NewString(action))
  87. }
  88. func (self *SParams) SetPagination(limit, offset int) {
  89. if limit == 0 {
  90. limit = 20
  91. }
  92. self.data.Set("Limit", jsonutils.NewInt(int64(limit)))
  93. self.data.Set("Offset", jsonutils.NewInt(int64(offset)))
  94. }
  95. func (self *SParams) String() string {
  96. return self.data.String()
  97. }
  98. func (self *SParams) PrettyString() string {
  99. return self.data.PrettyString()
  100. }
  101. func (self *SParams) GetParams() jsonutils.JSONDict {
  102. return self.data
  103. }
  104. // https://docs.ucloud.cn/api/summary/signature
  105. func BuildParams(params SParams, privateKey string) jsonutils.JSONObject {
  106. data := params.GetParams()
  107. // remove old Signature
  108. data.Remove("Signature")
  109. // 排序并计算signture
  110. keys := data.SortedKeys()
  111. lst := []string{}
  112. for _, k := range keys {
  113. lst = append(lst, k)
  114. v, _ := data.GetString(k)
  115. lst = append(lst, v)
  116. }
  117. raw := strings.Join(lst, "") + privateKey
  118. signture := fmt.Sprintf("%x", sha1.Sum([]byte(raw)))
  119. data.Set("Signature", jsonutils.NewString(signture))
  120. return &data
  121. }
  122. func GetSignature(params SParams, privateKey string) string {
  123. sign, _ := BuildParams(params, privateKey).GetString("Signature")
  124. return sign
  125. }
  126. func parseUcloudResponse(params SParams, resp jsonutils.JSONObject) (jsonutils.JSONObject, error) {
  127. err := &SUcloudError{}
  128. e := resp.Unmarshal(err)
  129. if e != nil {
  130. return nil, e
  131. }
  132. err.Action, _ = params.data.GetString("Action")
  133. if err.RetCode > 0 {
  134. if err.RetCode == 171 {
  135. return nil, errors.Wrapf(cloudprovider.ErrInvalidAccessKey, "%s", err.Error())
  136. }
  137. return nil, err
  138. }
  139. return resp, nil
  140. }
  141. func jsonRequest(client *SUcloudClient, params SParams) (jsonutils.JSONObject, error) {
  142. ctx := context.Background()
  143. MAX_RETRY := 3
  144. retry := 0
  145. var err error
  146. var resp jsonutils.JSONObject
  147. for retry < MAX_RETRY {
  148. _, resp, err = httputils.JSONRequest(
  149. client.httpClient,
  150. ctx,
  151. httputils.POST,
  152. UCLOUD_API_HOST,
  153. nil,
  154. BuildParams(params, client.accessKeySecret),
  155. client.debug)
  156. if err == nil {
  157. return parseUcloudResponse(params, resp)
  158. }
  159. switch e := err.(type) {
  160. case *httputils.JSONClientError:
  161. if e.Code >= 499 && !strings.Contains(err.Error(), cloudprovider.ErrAccountReadOnly.Error()) {
  162. time.Sleep(3 * time.Second)
  163. retry += 1
  164. continue
  165. } else {
  166. return nil, err
  167. }
  168. default:
  169. return nil, err
  170. }
  171. }
  172. return resp, err
  173. }