yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/ucloud/client.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 ucloud
    16  
    17  import (
    18  	"context"
    19  	"crypto/sha1"
    20  	"fmt"
    21  	"strings"
    22  	"time"
    23  
    24  	"yunion.io/x/jsonutils"
    25  	"yunion.io/x/log"
    26  	"yunion.io/x/pkg/errors"
    27  
    28  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    29  	"yunion.io/x/onecloud/pkg/httperrors"
    30  	"yunion.io/x/onecloud/pkg/util/httputils"
    31  )
    32  
    33  const UCLOUD_API_HOST = "https://api.ucloud.cn"
    34  
    35  // API返回结果对应的字段名
    36  var UCLOUD_API_RESULT_KEYS = map[string]string{
    37  	"AllocateEIP":            "EIPSet",
    38  	"GetProjectList":         "ProjectSet",
    39  	"GetRegion":              "Regions",
    40  	"DescribeVPC":            "DataSet",
    41  	"DescribeImage":          "ImageSet",
    42  	"DescribeIsolationGroup": "IsolationGroupSet",
    43  	"DescribeUHostInstance":  "UHostSet",
    44  	"DescribeUHostTags":      "TagSet",
    45  	"DescribeUDSet":          "UDSet",
    46  	"DescribeUDisk":          "DataSet",
    47  	"DescribeUDiskSnapshot":  "DataSet",
    48  	"DescribeEIP":            "EIPSet",
    49  	"DescribeFirewall":       "DataSet",
    50  	"DescribeSubnet":         "DataSet",
    51  	"DescribeBucket":         "DataSet",
    52  	"CreateUDisk":            "UDiskId",
    53  	"CreateVPC":              "VPCId",
    54  	"CreateUDiskSnapshot":    "SnapshotId",
    55  	"DescribeVIP":            "VIPSet",
    56  }
    57  
    58  type SParams struct {
    59  	data jsonutils.JSONDict
    60  }
    61  
    62  type SUcloudError struct {
    63  	Action  string `json:"Action"`
    64  	Message string `json:"Message"`
    65  	RetCode int64  `json:"RetCode"`
    66  }
    67  
    68  func (self *SUcloudError) Error() string {
    69  	return fmt.Sprintf("Do %s failed, code: %d, %s", self.Action, self.RetCode, self.Message)
    70  }
    71  
    72  func NewUcloudParams() SParams {
    73  	data := jsonutils.NewDict()
    74  	return SParams{data: *data}
    75  }
    76  
    77  func (self *SParams) Set(key string, value interface{}) {
    78  	switch v := value.(type) {
    79  	case string:
    80  		self.data.Set(key, jsonutils.NewString(v))
    81  	case int64:
    82  		self.data.Set(key, jsonutils.NewInt(v))
    83  	case int:
    84  		self.data.Set(key, jsonutils.NewInt(int64(v)))
    85  	case bool:
    86  		self.data.Set(key, jsonutils.NewBool(v))
    87  	case float64:
    88  		self.data.Set(key, jsonutils.NewFloat64(v))
    89  	case float32:
    90  		self.data.Set(key, jsonutils.NewFloat32(v))
    91  	case []string:
    92  		self.data.Set(key, jsonutils.NewStringArray(v))
    93  	default:
    94  		log.Debugf("unsuported params type %T", value)
    95  	}
    96  }
    97  
    98  func (self *SParams) SetAction(action string) {
    99  	self.data.Set("Action", jsonutils.NewString(action))
   100  }
   101  
   102  func (self *SParams) SetPagination(limit, offset int) {
   103  	if limit == 0 {
   104  		limit = 20
   105  	}
   106  
   107  	self.data.Set("Limit", jsonutils.NewInt(int64(limit)))
   108  	self.data.Set("Offset", jsonutils.NewInt(int64(offset)))
   109  }
   110  
   111  func (self *SParams) String() string {
   112  	return self.data.String()
   113  }
   114  
   115  func (self *SParams) PrettyString() string {
   116  	return self.data.PrettyString()
   117  }
   118  
   119  func (self *SParams) GetParams() jsonutils.JSONDict {
   120  	return self.data
   121  }
   122  
   123  // https://docs.ucloud.cn/api/summary/signature
   124  func BuildParams(params SParams, privateKey string) jsonutils.JSONObject {
   125  	data := params.GetParams()
   126  	// remove old Signature
   127  	data.Remove("Signature")
   128  
   129  	// 	排序并计算signture
   130  	keys := data.SortedKeys()
   131  	lst := []string{}
   132  	for _, k := range keys {
   133  		lst = append(lst, k)
   134  		v, _ := data.GetString(k)
   135  		lst = append(lst, v)
   136  	}
   137  
   138  	raw := strings.Join(lst, "") + privateKey
   139  	signture := fmt.Sprintf("%x", sha1.Sum([]byte(raw)))
   140  
   141  	data.Set("Signature", jsonutils.NewString(signture))
   142  	return &data
   143  }
   144  
   145  func GetSignature(params SParams, privateKey string) string {
   146  	sign, _ := BuildParams(params, privateKey).GetString("Signature")
   147  	return sign
   148  }
   149  
   150  func parseUcloudResponse(params SParams, resp jsonutils.JSONObject) (jsonutils.JSONObject, error) {
   151  	err := &SUcloudError{}
   152  	e := resp.Unmarshal(err)
   153  	if e != nil {
   154  		return nil, e
   155  	}
   156  
   157  	err.Action, _ = params.data.GetString("Action")
   158  
   159  	if err.RetCode > 0 {
   160  		if err.RetCode == 171 {
   161  			return nil, errors.Wrapf(httperrors.ErrInvalidAccessKey, err.Error())
   162  		}
   163  		return nil, err
   164  	}
   165  
   166  	return resp, nil
   167  }
   168  
   169  func jsonRequest(client *SUcloudClient, params SParams) (jsonutils.JSONObject, error) {
   170  	ctx := context.Background()
   171  	MAX_RETRY := 3
   172  	retry := 0
   173  	var err error
   174  	var resp jsonutils.JSONObject
   175  	for retry < MAX_RETRY {
   176  		_, resp, err = httputils.JSONRequest(
   177  			client.httpClient,
   178  			ctx,
   179  			httputils.POST,
   180  			UCLOUD_API_HOST,
   181  			nil,
   182  			BuildParams(params, client.accessKeySecret),
   183  			client.debug)
   184  
   185  		if err == nil {
   186  			return parseUcloudResponse(params, resp)
   187  		}
   188  
   189  		switch e := err.(type) {
   190  		case *httputils.JSONClientError:
   191  			if e.Code >= 499 && !strings.Contains(err.Error(), cloudprovider.ErrAccountReadOnly.Error()) {
   192  				time.Sleep(3 * time.Second)
   193  				retry += 1
   194  				continue
   195  			} else {
   196  				return nil, err
   197  			}
   198  		default:
   199  			return nil, err
   200  		}
   201  	}
   202  
   203  	return resp, err
   204  }