yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/hcso/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/hcso/client/auth" 32 "yunion.io/x/cloudmux/pkg/multicloud/hcso/client/manager" 33 "yunion.io/x/cloudmux/pkg/multicloud/hcso/client/requests" 34 "yunion.io/x/cloudmux/pkg/multicloud/hcso/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) GetColumns() []string { 92 return self.columns 93 } 94 95 func (self *SBaseManager) SetHttpClient(httpClient *http.Client) { 96 self.httpClient = httpClient 97 } 98 99 func (self *SBaseManager) _list(request requests.IRequest, responseKey string) (*responses.ListResult, error) { 100 _, body, err := self.jsonRequest(request) 101 if err != nil { 102 return nil, err 103 } 104 if body == nil { 105 log.Warningf("empty response") 106 return &responses.ListResult{}, nil 107 } 108 109 rets, err := body.GetArray(responseKey) 110 if err != nil { 111 return nil, err 112 } 113 total, _ := body.Int("count") 114 // if err != nil { 115 // total = int64(len(rets)) 116 //} 117 118 //if total == 0 { 119 // total = int64(len(rets)) 120 //} 121 122 limit := 0 123 if v, exists := request.GetQueryParams()["limit"]; exists { 124 limit, _ = strconv.Atoi(v) 125 } 126 127 offset := 0 128 if v, exists := request.GetQueryParams()["offset"]; exists { 129 offset, _ = strconv.Atoi(v) 130 } 131 132 return &responses.ListResult{ 133 Data: rets, 134 Total: int(total), 135 Limit: limit, 136 Offset: offset, 137 }, nil 138 } 139 140 func (self *SBaseManager) _do(request requests.IRequest, responseKey string) (jsonutils.JSONObject, error) { 141 _, resp, e := self.jsonRequest(request) 142 if e != nil { 143 return nil, e 144 } 145 146 if resp == nil { // no reslt 147 return jsonutils.NewDict(), nil 148 } 149 150 if len(responseKey) == 0 { 151 return resp, nil 152 } 153 154 ret, e := resp.Get(responseKey) 155 if e != nil { 156 return nil, e 157 } 158 159 return ret, nil 160 } 161 162 func (self *SBaseManager) _get(request requests.IRequest, responseKey string) (jsonutils.JSONObject, error) { 163 return self._do(request, responseKey) 164 } 165 166 type HuaweiClientError struct { 167 Code int 168 Errorcode []string 169 err error 170 Details string 171 ErrorCode string 172 } 173 174 func (ce *HuaweiClientError) Error() string { 175 return jsonutils.Marshal(ce).String() 176 } 177 178 func (ce *HuaweiClientError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error { 179 if body != nil { 180 body.Unmarshal(ce) 181 } 182 if ce.Code == 0 { 183 ce.Code = statusCode 184 } 185 if len(ce.Details) == 0 && body != nil { 186 ce.Details = body.String() 187 } 188 return ce 189 } 190 191 func (self *SBaseManager) jsonRequest(request requests.IRequest) (http.Header, jsonutils.JSONObject, error) { 192 ThrottlingLock.CheckingLock() 193 ctx := context.Background() 194 // hook request 195 if self.requestHook != nil { 196 self.requestHook.Process(request) 197 } 198 // 拼接、编译、签名 requests here。 199 err := self.buildRequestWithSigner(request, self.cfg.GetSigner()) 200 if err != nil { 201 return nil, nil, err 202 } 203 header := http.Header{} 204 for k, v := range request.GetHeaders() { 205 header.Set(k, v) 206 } 207 208 var jsonBody jsonutils.JSONObject 209 content := request.GetContent() 210 if len(content) > 0 { 211 jsonBody, err = jsonutils.Parse(content) 212 if err != nil { 213 return nil, nil, fmt.Errorf("not a json body") 214 } 215 } 216 217 client := httputils.NewJsonClient(self.httpClient) 218 req := httputils.NewJsonRequest(httputils.THttpMethod(request.GetMethod()), request.BuildUrl(), jsonBody) 219 req.SetHeader(header) 220 resp := &HuaweiClientError{} 221 const MAX_RETRY = 3 222 retry := MAX_RETRY 223 for { 224 h, b, e := client.Send(ctx, req, resp, self.debug) 225 if e == nil { 226 return h, b, nil 227 } 228 229 log.Errorf("[%s] %s body: %v error: %v", req.GetHttpMethod(), req.GetUrl(), jsonBody, e) 230 231 switch err := e.(type) { 232 case *HuaweiClientError: 233 if err.ErrorCode == "APIGW.0301" { 234 return h, b, errors.Wrapf(httperrors.ErrInvalidAccessKey, e.Error()) 235 } else if err.Code == 499 && retry > 0 && request.GetMethod() == "GET" { 236 retry -= 1 237 time.Sleep(3 * time.Second * time.Duration(MAX_RETRY-retry)) 238 } else if (err.Code == 404 || strings.Contains(err.Details, "could not be found") || 239 strings.Contains(err.Error(), "Not Found") || 240 strings.Contains(err.Details, "does not exist")) && request.GetMethod() != "POST" { 241 return h, b, errors.Wrap(cloudprovider.ErrNotFound, err.Error()) 242 } else if err.Code == 429 && retry > 0 { 243 // 当前请求过多。 244 ThrottlingLock.Lock() 245 retry -= 1 246 time.Sleep(15 * time.Second) 247 } else { 248 return h, b, e 249 } 250 default: 251 return h, b, e 252 } 253 } 254 } 255 256 func (self *SBaseManager) rawRequest(request requests.IRequest) (*http.Response, error) { 257 ctx := context.Background() 258 // 拼接、编译requests here。 259 header := http.Header{} 260 for k, v := range request.GetHeaders() { 261 header.Set(k, v) 262 } 263 return httputils.Request(self.httpClient, ctx, httputils.THttpMethod(request.GetMethod()), request.BuildUrl(), header, request.GetBodyReader(), self.debug) 264 } 265 266 func (self *SBaseManager) buildRequestWithSigner(request requests.IRequest, signer auth.Signer) error { 267 return auth.Sign(request, signer) 268 }