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