diff --git a/pkg/multicloud/huawei/client/modules/manager_base.go b/pkg/multicloud/huawei/client/modules/manager_base.go index 6828fb7a05..763168b1a5 100644 --- a/pkg/multicloud/huawei/client/modules/manager_base.go +++ b/pkg/multicloud/huawei/client/modules/manager_base.go @@ -24,6 +24,7 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/cloudprovider" "yunion.io/x/onecloud/pkg/multicloud/huawei/client/auth" @@ -133,6 +134,34 @@ func (self *SBaseManager) _get(request requests.IRequest, responseKey string) (j return self._do(request, responseKey) } +type HuaweiClientError struct { + Code int + Errorcode []string + err error + Details string +} + +func (ce *HuaweiClientError) Error() string { + return jsonutils.Marshal(ce).String() +} + +func (ce *HuaweiClientError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error { + err := body.Unmarshal(ce) + if err != nil { + ce.err = errors.Wrapf(err, "body.Unmarshal(%s)", body.String()) + ce.Code = statusCode + ce.Details = body.String() + return ce + } + if ce.Code == 0 { + ce.Code = statusCode + } + if len(ce.Details) == 0 { + ce.Details = body.String() + } + return ce +} + func (self *SBaseManager) jsonRequest(request requests.IRequest) (http.Header, jsonutils.JSONObject, error) { ctx := context.Background() // hook request @@ -162,11 +191,15 @@ func (self *SBaseManager) jsonRequest(request requests.IRequest) (http.Header, j log.Debugf("url: %s", request.BuildUrl()) } + client := httputils.NewJsonClient(self.httpClient) + req := httputils.NewJsonRequest(httputils.THttpMethod(request.GetMethod()), request.BuildUrl(), jsonBody) + req.SetHeader(header) + resp := &HuaweiClientError{} // 发送 request。todo: 支持debug const MAX_RETRY = 3 retry := MAX_RETRY for { - h, b, e := httputils.JSONRequest(self.httpClient, ctx, httputils.THttpMethod(request.GetMethod()), request.BuildUrl(), header, jsonBody, self.debug) + h, b, e := client.Send(ctx, req, resp, self.debug) if e == nil { if self.debug { log.Debugf("response: %s body: %s", h, b) @@ -175,12 +208,12 @@ func (self *SBaseManager) jsonRequest(request requests.IRequest) (http.Header, j } switch err := e.(type) { - case *httputils.JSONClientError: + case *HuaweiClientError: if err.Code == 499 && retry > 0 && request.GetMethod() == "GET" { retry -= 1 time.Sleep(time.Second * time.Duration(MAX_RETRY-retry)) } else if (err.Code == 404 || strings.Contains(err.Details, "could not be found") || strings.Contains(err.Details, "does not exist")) && request.GetMethod() != "POST" { - return h, b, cloudprovider.ErrNotFound + return h, b, errors.Wrap(cloudprovider.ErrNotFound, err.Error()) } else { return h, b, e } diff --git a/pkg/multicloud/huawei/clouduser.go b/pkg/multicloud/huawei/clouduser.go index befcbd5b5f..7da5591fd5 100644 --- a/pkg/multicloud/huawei/clouduser.go +++ b/pkg/multicloud/huawei/clouduser.go @@ -19,6 +19,7 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/pkg/cloudprovider" + "yunion.io/x/onecloud/pkg/multicloud/huawei/client/modules" ) type SLink struct { @@ -182,6 +183,10 @@ func (self *SHuaweiClient) CreateClouduser(name, password, desc string) (*SCloud user := SClouduser{client: self} err = DoCreate(client.Users.Create, jsonutils.Marshal(map[string]interface{}{"user": params}), &user) if err != nil { + ce, ok := err.(*modules.HuaweiClientError) + if ok && len(ce.Errorcode) > 0 && ce.Errorcode[0] == "1101" { + return nil, errors.Wrap(err, `IAM user name. The length is between 5 and 32. The first digit is not a number. Special characters can only contain the '_' '-' or ' '`) //https://support.huaweicloud.com/api-iam/iam_08_0015.html + } return nil, errors.Wrap(err, "DoCreate") } return &user, nil diff --git a/pkg/multicloud/huawei/natgateway.go b/pkg/multicloud/huawei/natgateway.go index 42bf056968..7fdf215d47 100644 --- a/pkg/multicloud/huawei/natgateway.go +++ b/pkg/multicloud/huawei/natgateway.go @@ -239,7 +239,7 @@ func (region *SRegion) GetNatDEntryByID(id string) (SNatDEntry, error) { err := DoGet(region.ecsClient.DNatRules.Get, id, map[string]string{}, &dnat) if err != nil { - return SNatDEntry{}, cloudprovider.ErrNotFound + return SNatDEntry{}, err } return dnat, nil } diff --git a/pkg/util/httputils/httputils.go b/pkg/util/httputils/httputils.go index 9d70e958fd..beb6c8ec3d 100644 --- a/pkg/util/httputils/httputils.go +++ b/pkg/util/httputils/httputils.go @@ -81,6 +81,89 @@ type JSONClientErrorMsg struct { Error *JSONClientError } +type JsonClient struct { + client *http.Client +} + +type JsonReuest interface { + GetHttpMethod() THttpMethod + GetRequestBody() jsonutils.JSONObject + GetUrl() string + SetHttpMethod(method THttpMethod) + GetHeader() http.Header + SetHeader(header http.Header) +} + +type JsonBaseRequest struct { + httpMethod THttpMethod + url string + params interface{} + header http.Header +} + +func (req *JsonBaseRequest) GetHttpMethod() THttpMethod { + return req.httpMethod +} + +func (req *JsonBaseRequest) GetRequestBody() jsonutils.JSONObject { + if req.params != nil { + return jsonutils.Marshal(req.params) + } + return nil +} + +func (req *JsonBaseRequest) GetUrl() string { + return req.url +} + +func (req *JsonBaseRequest) SetHttpMethod(method THttpMethod) { + req.httpMethod = method +} + +func (req *JsonBaseRequest) GetHeader() http.Header { + return req.header +} + +func (req *JsonBaseRequest) SetHeader(header http.Header) { + for k, values := range header { + req.header.Del(k) + for _, v := range values { + req.header.Add(k, v) + } + } +} + +func NewJsonRequest(method THttpMethod, url string, params interface{}) *JsonBaseRequest { + return &JsonBaseRequest{ + httpMethod: method, + url: url, + params: params, + header: http.Header{"Content-Type": []string{"application/json"}}, + } +} + +type JsonResponse interface { + ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error +} + +func (ce *JSONClientError) ParseErrorFromJsonResponse(statusCode int, body jsonutils.JSONObject) error { + err := body.Unmarshal(ce) + if err != nil { + return errors.Wrapf(err, "body.Unmarshal(%s)", body.String()) + } + if ce.Code != 0 || len(ce.Class) > 0 || len(ce.Class) > 0 || len(ce.Details) > 0 { + if ce.Code == 0 { + ce.Code = statusCode + } + return ce + } + return nil +} + +func NewJsonClient(client *http.Client) *JsonClient { + return &JsonClient{client: client} +} + func (e *JSONClientError) Error() string { errMsg := JSONClientErrorMsg{Error: e} return jsonutils.Marshal(errMsg).String() @@ -383,6 +466,71 @@ func CloseResponse(resp *http.Response) { } } +func (client *JsonClient) Send(ctx context.Context, req JsonReuest, response JsonResponse, debug bool) (http.Header, jsonutils.JSONObject, error) { + var bodystr string + body := req.GetRequestBody() + if !gotypes.IsNil(body) { + bodystr = body.String() + } + jbody := strings.NewReader(bodystr) + resp, err := Request(client.client, ctx, req.GetHttpMethod(), req.GetUrl(), req.GetHeader(), jbody, debug) + if err != nil { + ce := &JSONClientError{} + ce.Code = 499 + ce.Details = err.Error() + return nil, nil, ce + } + defer CloseResponse(resp) + if debug { + dump, _ := httputil.DumpResponse(resp, false) + if resp.StatusCode < 300 { + green(string(dump)) + } else if resp.StatusCode < 400 { + yellow(string(dump)) + } else { + red(string(dump)) + } + } + rbody, err := ioutil.ReadAll(resp.Body) + if debug { + fmt.Fprintf(os.Stderr, "Response body: %s\n", string(rbody)) + } + if err != nil { + ce := &JSONClientError{} + ce.Code = resp.StatusCode + ce.Details = fmt.Sprintf("Fail to read body: %v", err) + return resp.Header, nil, ce + } + + var jrbody jsonutils.JSONObject = nil + if len(rbody) > 0 && string(rbody[0]) == "{" { + var err error + jrbody, err = jsonutils.Parse(rbody) + if err != nil { + if debug { + fmt.Fprintf(os.Stderr, "parsing json %s failed: %v", string(rbody), err) + } + ce := &JSONClientError{} + ce.Code = resp.StatusCode + ce.Details = fmt.Sprintf("jsonutils.Parse(%s) error: %v", string(rbody), err) + return resp.Header, nil, ce + } + } else { + jrbody = jsonutils.NewDict() + } + + if resp.StatusCode < 300 { + return resp.Header, jrbody, nil + } else if resp.StatusCode >= 300 && resp.StatusCode < 400 { + ce := JSONClientError{} + ce.Code = resp.StatusCode + ce.Details = resp.Header.Get("Location") + ce.Class = "redirect" + return resp.Header, nil, &ce + } + return resp.Header, jrbody, response.ParseErrorFromJsonResponse(resp.StatusCode, jrbody) +} + func ParseResponse(resp *http.Response, err error, debug bool) (http.Header, []byte, error) { if err != nil { ce := JSONClientError{}