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  }