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  }