yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/huawei/client/modules/manager_base.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 modules 16 17 import ( 18 "context" 19 "fmt" 20 "net/http" 21 "strconv" 22 "strings" 23 "time" 24 25 "yunion.io/x/jsonutils" 26 "yunion.io/x/log" 27 "yunion.io/x/pkg/errors" 28 29 "yunion.io/x/cloudmux/pkg/cloudprovider" 30 "yunion.io/x/onecloud/pkg/httperrors" 31 "yunion.io/x/cloudmux/pkg/multicloud/huawei/client/auth" 32 "yunion.io/x/cloudmux/pkg/multicloud/huawei/client/manager" 33 "yunion.io/x/cloudmux/pkg/multicloud/huawei/client/requests" 34 "yunion.io/x/cloudmux/pkg/multicloud/huawei/client/responses" 35 "yunion.io/x/onecloud/pkg/util/httputils" 36 ) 37 38 type IRequestHook interface { 39 Process(r requests.IRequest) 40 } 41 42 type SBaseManager struct { 43 cfg manager.IManagerConfig 44 httpClient *http.Client 45 requestHook IRequestHook // 用于对request做特殊处理。非必要请不要使用!!!。目前只有port接口用到。 46 47 columns []string 48 debug bool 49 } 50 51 type sThrottlingThreshold struct { 52 locked bool 53 lockTime time.Time 54 } 55 56 func (t *sThrottlingThreshold) CheckingLock() { 57 if !t.locked { 58 return 59 } 60 61 for { 62 if t.lockTime.Sub(time.Now()).Seconds() < 0 { 63 return 64 } 65 log.Debugf("throttling threshold has been reached. release at %s", t.lockTime) 66 time.Sleep(5 * time.Second) 67 } 68 } 69 70 func (t *sThrottlingThreshold) Lock() { 71 // 锁定至少15秒 72 t.locked = true 73 t.lockTime = time.Now().Add(15 * time.Second) 74 } 75 76 var ThrottlingLock = sThrottlingThreshold{locked: false, lockTime: time.Time{}} 77 78 func NewBaseManager2(cfg manager.IManagerConfig, requesthk IRequestHook) SBaseManager { 79 return SBaseManager{ 80 cfg: cfg, 81 httpClient: httputils.GetDefaultClient(), 82 debug: cfg.GetDebug(), 83 requestHook: requesthk, 84 } 85 } 86 87 func NewBaseManager(cfg manager.IManagerConfig) SBaseManager { 88 return NewBaseManager2(cfg, nil) 89 } 90 91 func (self *SBaseManager) GetEndpoint() string { 92 return self.cfg.GetEndpoint() 93 } 94 95 func (self *SBaseManager) GetColumns() []string { 96 return self.columns 97 } 98 99 func (self *SBaseManager) SetHttpClient(httpClient *http.Client) { 100 self.httpClient = httpClient 101 } 102 103 func (self *SBaseManager) _list(request requests.IRequest, responseKey string) (*responses.ListResult, error) { 104 _, body, err := self.jsonRequest(request) 105 if err != nil { 106 return nil, err 107 } 108 if body == nil { 109 log.Warningf("empty response") 110 return &responses.ListResult{}, nil 111 } 112 113 rets, err := body.GetArray(responseKey) 114 if err != nil { 115 return nil, errors.Wrapf(err, "body.GetArray %s", responseKey) 116 } 117 total, _ := body.Int("count") 118 // if err != nil { 119 // total = int64(len(rets)) 120 //} 121 122 //if total == 0 { 123 // total = int64(len(rets)) 124 //} 125 126 limit := 0 127 if v, exists := request.GetQueryParams()["limit"]; exists { 128 limit, _ = strconv.Atoi(v) 129 } 130 131 offset := 0 132 if v, exists := request.GetQueryParams()["offset"]; exists { 133 offset, _ = strconv.Atoi(v) 134 } 135 136 return &responses.ListResult{ 137 Data: rets, 138 Total: int(total), 139 Limit: limit, 140 Offset: offset, 141 }, nil 142 } 143 144 func (self *SBaseManager) _do(request requests.IRequest, responseKey string) (jsonutils.JSONObject, error) { 145 _, resp, e := self.jsonRequest(request) 146 if e != nil { 147 return nil, e 148 } 149 150 if resp == nil { // no reslt 151 return jsonutils.NewDict(), nil 152 } 153 154 if len(responseKey) == 0 { 155 return resp, nil 156 } 157 158 ret, e := resp.Get(responseKey) 159 if e != nil { 160 return nil, e 161 } 162 163 return ret, nil 164 } 165 166 func (self *SBaseManager) _get(request requests.IRequest, responseKey string) (jsonutils.JSONObject, error) { 167 return self._do(request, responseKey) 168 } 169 170 type HuaweiClientError struct { 171 Code int 172 Errorcode []string 173 err error 174 Details string 175 ErrorCode string 176 } 177 178 func (ce *HuaweiClientError) Error() string { 179 return jsonutils.Marshal(ce).String() 180 } 181 182 func (ce *HuaweiClientError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error { 183 if body != nil { 184 body.Unmarshal(ce) 185 } 186 if ce.Code == 0 { 187 ce.Code = statusCode 188 } 189 if len(ce.Details) == 0 && body != nil { 190 ce.Details = body.String() 191 } 192 return ce 193 } 194 195 func (self *SBaseManager) jsonRequest(request requests.IRequest) (http.Header, jsonutils.JSONObject, error) { 196 ThrottlingLock.CheckingLock() 197 ctx := context.Background() 198 // hook request 199 if self.requestHook != nil { 200 self.requestHook.Process(request) 201 } 202 // 拼接、编译、签名 requests here。 203 err := self.buildRequestWithSigner(request, self.cfg.GetSigner()) 204 if err != nil { 205 return nil, nil, err 206 } 207 header := http.Header{} 208 for k, v := range request.GetHeaders() { 209 header.Set(k, v) 210 } 211 212 var jsonBody jsonutils.JSONObject 213 content := request.GetContent() 214 if len(content) > 0 { 215 jsonBody, err = jsonutils.Parse(content) 216 if err != nil { 217 return nil, nil, fmt.Errorf("not a json body") 218 } 219 } 220 221 client := httputils.NewJsonClient(self.httpClient) 222 req := httputils.NewJsonRequest(httputils.THttpMethod(request.GetMethod()), request.BuildUrl(), jsonBody) 223 req.SetHeader(header) 224 resp := &HuaweiClientError{} 225 const MAX_RETRY = 3 226 retry := MAX_RETRY 227 for { 228 h, b, e := client.Send(ctx, req, resp, self.debug) 229 if e == nil { 230 return h, b, nil 231 } 232 233 log.Errorf("[%s] %s body: %v error: %v", req.GetHttpMethod(), req.GetUrl(), jsonBody, e) 234 235 switch err := e.(type) { 236 case *HuaweiClientError: 237 if err.ErrorCode == "APIGW.0301" { 238 return h, b, errors.Wrapf(httperrors.ErrInvalidAccessKey, e.Error()) 239 } else if err.Code == 499 && retry > 0 && request.GetMethod() == "GET" { 240 retry -= 1 241 time.Sleep(3 * time.Second * time.Duration(MAX_RETRY-retry)) 242 } else if (err.Code == 404 || strings.Contains(err.Details, "could not be found") || 243 strings.Contains(err.Error(), "Not Found") || 244 strings.Contains(err.Details, "does not exist")) && request.GetMethod() != "POST" { 245 return h, b, errors.Wrap(cloudprovider.ErrNotFound, err.Error()) 246 } else if err.Code == 429 && retry > 0 { 247 // 当前请求过多。 248 ThrottlingLock.Lock() 249 retry -= 1 250 time.Sleep(15 * time.Second) 251 } else { 252 return h, b, e 253 } 254 default: 255 return h, b, e 256 } 257 } 258 } 259 260 func (self *SBaseManager) rawRequest(request requests.IRequest) (*http.Response, error) { 261 ctx := context.Background() 262 // 拼接、编译requests here。 263 header := http.Header{} 264 for k, v := range request.GetHeaders() { 265 header.Set(k, v) 266 } 267 return httputils.Request(self.httpClient, ctx, httputils.THttpMethod(request.GetMethod()), request.BuildUrl(), header, request.GetBodyReader(), self.debug) 268 } 269 270 func (self *SBaseManager) buildRequestWithSigner(request requests.IRequest, signer auth.Signer) error { 271 return auth.Sign(request, signer) 272 }