diff --git a/examples/retry/README.md b/examples/retry/README.md index 7e960ce9..e4b26111 100644 --- a/examples/retry/README.md +++ b/examples/retry/README.md @@ -9,7 +9,6 @@ To enable retry for client or request. ```go export UCLOUD_PUBLIC_KEY="your public key" export UCLOUD_PRIVATE_KEY="your private key" -export UCLOUD_REGION="cn-bj2" export UCLOUD_PROJECT_ID="your project id" ``` diff --git a/examples/retry/main.go b/examples/retry/main.go index 0a728864..4c4299a8 100644 --- a/examples/retry/main.go +++ b/examples/retry/main.go @@ -3,16 +3,19 @@ package main import ( "os" - "github.com/ucloud/ucloud-sdk-go/services/ulb" "github.com/ucloud/ucloud-sdk-go/ucloud" "github.com/ucloud/ucloud-sdk-go/ucloud/auth" "github.com/ucloud/ucloud-sdk-go/ucloud/log" + + "github.com/ucloud/ucloud-sdk-go/services/ulb" ) +const region = "cn-bj2" + func main() { cfg := ucloud.NewConfig() cfg.LogLevel = log.DebugLevel - cfg.Region = os.Getenv("UCLOUD_REGION") + cfg.Region = region cfg.ProjectId = os.Getenv("UCLOUD_PROJECT_ID") credential := auth.NewCredential() diff --git a/examples/uhost/main.go b/examples/uhost/main.go index 9b567107..e75e4461 100644 --- a/examples/uhost/main.go +++ b/examples/uhost/main.go @@ -1,9 +1,10 @@ package main import ( + "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/services/uhost" "github.com/ucloud/ucloud-sdk-go/services/unet" - "github.com/ucloud/ucloud-sdk-go/ucloud" ) var uhostClient *uhost.UHostClient diff --git a/examples/wait/README.md b/examples/wait/README.md new file mode 100644 index 00000000..ab57599b --- /dev/null +++ b/examples/wait/README.md @@ -0,0 +1,19 @@ +# UCloud SDK Wait Example + +## What is the goal + +To enable state waiter to wait remote resource is completed. + +## Setup Environment + +```go +export UCLOUD_PUBLIC_KEY="your public key" +export UCLOUD_PRIVATE_KEY="your private key" +export UCLOUD_PROJECT_ID="your project id" +``` + +## How to run + +```sh +go run main.go +``` diff --git a/examples/wait/main.go b/examples/wait/main.go new file mode 100644 index 00000000..4cade3d8 --- /dev/null +++ b/examples/wait/main.go @@ -0,0 +1,101 @@ +package main + +import ( + "os" + "time" + + "github.com/ucloud/ucloud-sdk-go/ucloud" + "github.com/ucloud/ucloud-sdk-go/ucloud/auth" + "github.com/ucloud/ucloud-sdk-go/ucloud/log" + + "github.com/ucloud/ucloud-sdk-go/ucloud/helpers/waiter" + + "github.com/ucloud/ucloud-sdk-go/services/uhost" +) + +const region = "cn-bj2" +const zone = "cn-bj2-05" +const imageID = "uimage-kg0w4u" + +var uhostClient *uhost.UHostClient + +func init() { + cfg := ucloud.NewConfig() + cfg.LogLevel = log.DebugLevel + cfg.Region = region + cfg.ProjectId = os.Getenv("UCLOUD_PROJECT_ID") + + credential := auth.NewCredential() + credential.PrivateKey = os.Getenv("UCLOUD_PRIVATE_KEY") + credential.PublicKey = os.Getenv("UCLOUD_PUBLIC_KEY") + + uhostClient = uhost.NewClient(&cfg, &credential) + + log.Info("setup clients ...") +} + +func main() { + uhostID, err := createUHost("sdk-example-wait") + if err != nil { + log.Error(err) + } + + w := waiter.StateWaiter{ + Pending: []string{"pending"}, + Target: []string{"avaliable"}, + Refresh: func() (interface{}, string, error) { + inst, err := describeUHostByID(uhostID) + if err != nil { + return nil, "", err + } + + if inst == nil || inst.State != "Running" { + return nil, "pending", nil + } + + return inst, "avaliable", nil + }, + Timeout: 5 * time.Minute, + } + + if resp, err := w.Wait(); err != nil { + log.Error(err) + } else { + log.Infof("%#v", resp) + } +} + +func describeUHostByID(uhostID string) (*uhost.UHostInstanceSet, error) { + req := uhostClient.NewDescribeUHostInstanceRequest() + req.UHostIds = []string{uhostID} + + resp, err := uhostClient.DescribeUHostInstance(req) + if err != nil { + return nil, err + } + if len(resp.UHostSet) < 1 { + return nil, nil + } + + return &resp.UHostSet[0], nil +} + +func createUHost(name string) (string, error) { + req := uhostClient.NewCreateUHostInstanceRequest() + req.Name = ucloud.String(name) + req.Zone = ucloud.String(zone) // TODO: use random zone + req.ImageId = ucloud.String(imageID) // TODO: use random image + req.LoginMode = ucloud.String("Password") + req.Password = ucloud.String("somePassword_") + req.ChargeType = ucloud.String("Dynamic") + req.CPU = ucloud.Int(1) + req.Memory = ucloud.Int(1024) + req.Tag = ucloud.String("sdk-example") + + resp, err := uhostClient.CreateUHostInstance(req) + if err != nil { + return "", err + } + + return resp.UHostIds[0], nil +} diff --git a/internal/services/umon/describe_resource_metric.go b/internal/services/umon/describe_resource_metric.go index b6c76714..19ed7598 100644 --- a/internal/services/umon/describe_resource_metric.go +++ b/internal/services/umon/describe_resource_metric.go @@ -27,7 +27,12 @@ type DescribeResourceMetricResponse struct { // NewDescribeResourceMetricRequest will create request of DescribeResourceMetric action. func (c *UMonClient) NewDescribeResourceMetricRequest() *DescribeResourceMetricRequest { req := &DescribeResourceMetricRequest{} + + // setup request with client config c.client.SetupRequest(req) + + // setup retryable with default retry policy (retry for non-create action and common error) + req.SetRetryable(true) return req } diff --git a/internal/services/umon/types_metric_info.go b/internal/services/umon/types_metric_info.go index 28d8d717..b2809ff2 100644 --- a/internal/services/umon/types_metric_info.go +++ b/internal/services/umon/types_metric_info.go @@ -27,10 +27,10 @@ type MetricInfo struct { SupportAlarm string // 告警设置范围 - AlarmRange string + AlarmRange interface{} // Backend Bug: backend use dynamic type, object or string - // 数据上报频率 - Frequency string + // 仅限内部使用 + Frequency int // 比较参数,可选GE,LE CompareOption []string diff --git a/internal/services/unet/describe_security_group.go b/internal/services/unet/describe_security_group.go index 14e87672..3f6cd2fc 100644 --- a/internal/services/unet/describe_security_group.go +++ b/internal/services/unet/describe_security_group.go @@ -33,7 +33,12 @@ type DescribeSecurityGroupResponse struct { // NewDescribeSecurityGroupRequest will create request of DescribeSecurityGroup action. func (c *UNetClient) NewDescribeSecurityGroupRequest() *DescribeSecurityGroupRequest { req := &DescribeSecurityGroupRequest{} + + // setup request with client config c.client.SetupRequest(req) + + // setup retryable with default retry policy (retry for non-create action and common error) + req.SetRetryable(true) return req } diff --git a/internal/services/unet/types_security_group_rule_set.go b/internal/services/unet/types_security_group_rule_set.go index 4bb9ad90..e695c976 100644 --- a/internal/services/unet/types_security_group_rule_set.go +++ b/internal/services/unet/types_security_group_rule_set.go @@ -12,7 +12,7 @@ type SecurityGroupRuleSet struct { SrcIP string // 优先级 - Priority int + Priority string // 协议类型 ProtocolType string diff --git a/internal/services/unet/types_unet_security_group_set.go b/internal/services/unet/types_unet_security_group_set.go index 77787bd4..682b06df 100644 --- a/internal/services/unet/types_unet_security_group_set.go +++ b/internal/services/unet/types_unet_security_group_set.go @@ -15,10 +15,10 @@ type UnetSecurityGroupSet struct { GroupName string // 防火墙组创建时间,格式为Unix Timestamp - CreateTime string + CreateTime int // 防火墙组类型,枚举值为: 0:用户自定义防火墙; 1:默认Web防火墙; 2:默认非Web防火墙 - Type string + Type int // 防火墙组中的规则表,参见 SecurityGroupRuleSet Rule []SecurityGroupRuleSet diff --git a/internal/utest/utils.go b/internal/utest/utils.go index 24e6dd99..66e012d3 100644 --- a/internal/utest/utils.go +++ b/internal/utest/utils.go @@ -24,7 +24,11 @@ func GetValue(obj interface{}, path string) (interface{}, error) { } // SetReqValue will set value into pointer referenced or slice -func SetReqValue(addr interface{}, field string, value interface{}) error { +func SetReqValue(addr interface{}, field string, values ...interface{}) error { + if len(values) == 0 { + return errors.Errorf("no values to be set") + } + rv := reflect.ValueOf(addr) if rv.IsValid() == false { return errors.Errorf("struct is invalid") @@ -54,16 +58,23 @@ func SetReqValue(addr interface{}, field string, value interface{}) error { return errors.Errorf("cannot set %s, field cannot be set", field) } - s, _ := toString(value) - v, err := convertValueWithType(rv.Type().Elem(), s) - if err != nil { - return err + rValues := []reflect.Value{} + for _, value := range values { + s, _ := toString(value) + v, err := convertValueWithType(rv.Type().Elem(), s) + if err != nil { + return err + } + rValues = append(rValues, v) } if rv.Kind() == reflect.Ptr { - rv.Set(v) + rv.Set(rValues[0]) } else if rv.Kind() == reflect.Slice { - rv.Set(reflect.Append(rv, v.Elem())) + rv.Set(reflect.MakeSlice(rv.Type(), 0, 0)) + for _, willSet := range rValues { + rv.Set(reflect.Append(rv, willSet.Elem())) + } } return nil diff --git a/internal/utest/utils_test.go b/internal/utest/utils_test.go index 66c02556..9ea5e178 100644 --- a/internal/utest/utils_test.go +++ b/internal/utest/utils_test.go @@ -87,4 +87,9 @@ func TestSetReqValue(t *testing.T) { if testObj.IPs[0] != "192.168.0.1" { t.Errorf("SetReqValue() = %#v, want %v", testObj.IPs[0], "192.168.0.1") } + + SetReqValue(&testObj, "IPs", "192.168.0.1", "192.168.0.2") + if testObj.IPs[0] != "192.168.0.1" || testObj.IPs[1] != "192.168.0.2" { + t.Errorf("SetReqValue() = %#v, want %v", testObj.IPs, []string{"192.168.0.1", "192.168.0.2"}) + } } diff --git a/services/unet/types_resource_set.go b/services/unet/types_resource_set.go index 6a5e16f2..9e7b5ee9 100644 --- a/services/unet/types_resource_set.go +++ b/services/unet/types_resource_set.go @@ -23,8 +23,8 @@ type ResourceSet struct { // 绑定资源的资源类型 ResourceType string - // 状态 - Status string + // 资源状态 + Status int // 业务组 Tag string diff --git a/services/vpc/types_resource_info.go b/services/vpc/types_resource_info.go index bd295d2a..a29cf9e9 100644 --- a/services/vpc/types_resource_info.go +++ b/services/vpc/types_resource_info.go @@ -14,7 +14,7 @@ type ResourceInfo struct { // 资源id ResourceId string - // 创建时间 + // 资源类型 ResourceType string // ip地址 diff --git a/tests/set_1840_test.go b/tests/set_1840_test.go index ac963ea7..c2e62a05 100644 --- a/tests/set_1840_test.go +++ b/tests/set_1840_test.go @@ -58,6 +58,7 @@ func testSet1840CreateUHostInstance00(ctx *utest.TestContext) { ctx.NoError(utest.SetReqValue(req, "TimemachineFeature", "no")) ctx.NoError(utest.SetReqValue(req, "HotplugFeature", "false")) + ctx.NoError(utest.SetReqValue(req, "DiskSpace", 20)) ctx.NoError(utest.SetReqValue(req, "GPU", 0)) // TODO: check diff --git a/tests/set_333_test.go b/tests/set_333_test.go index f3bb95a8..3987ab72 100644 --- a/tests/set_333_test.go +++ b/tests/set_333_test.go @@ -587,7 +587,7 @@ func testSet333DescribeUHostInstance17(ctx *utest.TestContext) { ctx.NoError(utest.SetReqValue(req, "Region", ctx.GetVar("Region"))) - ctx.NoError(utest.SetReqValue(req, "UHostIds", []string{ctx.GetVar("hostId").(string), ctx.GetVar("hostId2").(string)})) // TODO: check + ctx.NoError(utest.SetReqValue(req, "UHostIds", ctx.GetVar("hostId"), ctx.GetVar("hostId2"))) testCase := utest.TestCase{ Invoker: func() (interface{}, error) { diff --git a/tests/set_687_test.go b/tests/set_687_test.go index 4b1a9a47..44ce861c 100644 --- a/tests/set_687_test.go +++ b/tests/set_687_test.go @@ -177,6 +177,7 @@ func testSet687CreateVPC04(ctx *utest.TestContext) { ctx.NoError(utest.SetReqValue(req, "Region", ctx.GetVar("Region"))) ctx.NoError(utest.SetReqValue(req, "Name", "vpc_2")) ctx.NoError(utest.SetReqValue(req, "Network", "192.168.16.0/20")) + ctx.NoError(utest.SetReqValue(req, "ProjectId", ctx.Must(utest.SearchValue(ctx.GetVar("project_list"), "IsDefault", "true", "ProjectId")))) testCase := utest.TestCase{ Invoker: func() (interface{}, error) { @@ -202,6 +203,7 @@ func testSet687CreateSubnet05(ctx *utest.TestContext) { req := vpcClient.NewCreateSubnetRequest() ctx.NoError(utest.SetReqValue(req, "Region", ctx.GetVar("Region"))) + ctx.NoError(utest.SetReqValue(req, "ProjectId", ctx.Must(utest.SearchValue(ctx.GetVar("project_list"), "IsDefault", "true", "ProjectId")))) ctx.NoError(utest.SetReqValue(req, "VPCId", ctx.GetVar("VPCId_2"))) ctx.NoError(utest.SetReqValue(req, "Subnet", "192.168.17.0")) @@ -234,6 +236,7 @@ func testSet687CreateSubnet06(ctx *utest.TestContext) { req := vpcClient.NewCreateSubnetRequest() ctx.NoError(utest.SetReqValue(req, "Region", ctx.GetVar("Region"))) + ctx.NoError(utest.SetReqValue(req, "ProjectId", ctx.Must(utest.SearchValue(ctx.GetVar("project_list"), "IsDefault", "true", "ProjectId")))) ctx.NoError(utest.SetReqValue(req, "VPCId", ctx.GetVar("VPCId_2"))) ctx.NoError(utest.SetReqValue(req, "Subnet", "192.168.18.0")) @@ -465,6 +468,7 @@ func testSet687DeleteSubnet13(ctx *utest.TestContext) { req := vpcClient.NewDeleteSubnetRequest() ctx.NoError(utest.SetReqValue(req, "Region", ctx.GetVar("Region"))) + ctx.NoError(utest.SetReqValue(req, "ProjectId", ctx.Must(utest.SearchValue(ctx.GetVar("project_list"), "IsDefault", "true", "ProjectId")))) ctx.NoError(utest.SetReqValue(req, "SubnetId", ctx.GetVar("SubnetId_2_1"))) @@ -493,6 +497,7 @@ func testSet687DeleteSubnet14(ctx *utest.TestContext) { req := vpcClient.NewDeleteSubnetRequest() ctx.NoError(utest.SetReqValue(req, "Region", ctx.GetVar("Region"))) + ctx.NoError(utest.SetReqValue(req, "ProjectId", ctx.Must(utest.SearchValue(ctx.GetVar("project_list"), "IsDefault", "true", "ProjectId")))) ctx.NoError(utest.SetReqValue(req, "SubnetId", ctx.GetVar("SubnetId_2_2"))) @@ -695,6 +700,7 @@ func testSet687DeleteVPC21(ctx *utest.TestContext) { req := vpcClient.NewDeleteVPCRequest() ctx.NoError(utest.SetReqValue(req, "Region", ctx.GetVar("Region"))) + ctx.NoError(utest.SetReqValue(req, "ProjectId", ctx.Must(utest.SearchValue(ctx.GetVar("project_list"), "IsDefault", "true", "ProjectId")))) ctx.NoError(utest.SetReqValue(req, "VPCId", ctx.GetVar("VPCId_2"))) diff --git a/tests/setup_fixture.go b/tests/setup_test.go similarity index 100% rename from tests/setup_fixture.go rename to tests/setup_test.go diff --git a/ucloud/client.go b/ucloud/client.go index cb42a629..c1176763 100644 --- a/ucloud/client.go +++ b/ucloud/client.go @@ -65,7 +65,7 @@ func (c *Client) InvokeAction(action string, req request.Common, resp response.C httpResp, err = handler(c, httpReq, httpResp, err) } - err = c.UnmarshalHTTPReponse(httpResp, resp) + err = c.unmarshalHTTPReponse(httpResp, resp) if err != nil { return err } diff --git a/ucloud/client_test.go b/ucloud/client_test.go index 20dce4b7..ef493d80 100644 --- a/ucloud/client_test.go +++ b/ucloud/client_test.go @@ -3,6 +3,7 @@ package ucloud import ( "net/http" "os" + "reflect" "testing" "time" @@ -139,3 +140,24 @@ func Test_errorHandler(t *testing.T) { }) } } + +func Test_patchForRetCodeString(t *testing.T) { + type args struct { + body []byte + } + tests := []struct { + name string + args args + want []byte + }{ + {"int_code", args{[]byte(`"RetCode": 100,`)}, []byte(`"RetCode": 100,`)}, + {"str_code", args{[]byte(`"RetCode": "100",`)}, []byte(`"RetCode": 100,`)}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := patchForRetCodeString(tt.args.body); !reflect.DeepEqual(got, tt.want) { + t.Errorf("patchForRetCodeString() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/ucloud/helpers/waiter/error.go b/ucloud/helpers/waiter/error.go new file mode 100644 index 00000000..2bbbcfbb --- /dev/null +++ b/ucloud/helpers/waiter/error.go @@ -0,0 +1,37 @@ +package waiter + +import ( + "fmt" + "strings" + "time" +) + +// TimeoutError is returned when WaitForState times out +type TimeoutError struct { + LastError error + LastState string + Timeout time.Duration + ExpectedStates []string +} + +func (e *TimeoutError) Error() string { + errors := []string{"cannot waiting for resource is completed"} + + if e.Timeout > 0 { + errors = append(errors, fmt.Sprintf("timeout: %s", e.Timeout)) + } + + if e.LastState != "" { + errors = append(errors, fmt.Sprintf("last state: %q", e.LastState)) + } + + if len(e.ExpectedStates) > 0 { + errors = append(errors, fmt.Sprintf("want: %q", strings.Join(e.ExpectedStates, ","))) + } + + if e.LastError != nil { + errors = append(errors, fmt.Sprintf("err: %s", e.LastError)) + } + + return strings.Join(errors, ", ") +} diff --git a/ucloud/helpers/waiter/waiter.go b/ucloud/helpers/waiter/waiter.go new file mode 100644 index 00000000..fa217447 --- /dev/null +++ b/ucloud/helpers/waiter/waiter.go @@ -0,0 +1,175 @@ +/* +Package waiter is a helper package use for waiting remote state is transformed into target state. +*/ +package waiter + +import ( + "time" + + "github.com/ucloud/ucloud-sdk-go/ucloud/log" +) + +const graceRefreshTimeout = 30 * time.Second + +// RefreshFunc is the function to query remote resource state +type RefreshFunc func() (result interface{}, state string, err error) + +// StateWaiter is the waiter that waiting for remote state achieve to target state +type StateWaiter struct { + Pending []string + Target []string + Refresh RefreshFunc + Delay time.Duration + Timeout time.Duration + MinTimeout time.Duration + PollInterval time.Duration +} + +// Wait watches an object and waits for it to achieve the state +func (waiter *StateWaiter) Wait() (interface{}, error) { + // it is a re-implementation of terraform StateChangeConf + log.Debugf("Waiting for state: %s", waiter.Target) + + resCh := make(chan refreshResult, 1) + cancelCh := make(chan struct{}) + + result := refreshResult{} + + go func() { + defer close(resCh) + + // delay before refresh + time.Sleep(waiter.Delay) + + var wait time.Duration + + for { + // send an empty result to active channel + resCh <- result + + select { + case <-cancelCh: + return + case <-time.After(wait): + // first round should not wait + // initial wait with 100ms + if wait == 0 { + wait = 100 * time.Millisecond + } + } + + // refresh newest state + res, currentState, err := waiter.Refresh() + result = refreshResult{ + Result: res, + State: currentState, + Error: err, + } + + if err != nil { + resCh <- result + return + } + + // if target state is achieved, done + for _, allowed := range waiter.Target { + if currentState == allowed { + result.Done = true + resCh <- result + return + } + } + + isPending := false + for _, allowed := range waiter.Pending { + if currentState == allowed { + isPending = true + break + } + } + + if !isPending && len(waiter.Pending) > 0 { + resCh <- result + return + } + + // wait interval using exponential backoff, policy as follow: + // + // * 0 100ms 200ms 400ms 800ms 1.6s 3.2s 6.4s 10s 10s ... (0 <= MinTimeout <= 100ms) + // * 0 3s 6s 10s 10s ... (100ms < MinTimeout <= 10s, eg. 3s) + // * 0 11s 10s 10s 10s ... (10s < MinTimeout, eg. 11s) + wait *= 2 + + if waiter.PollInterval > 0 && waiter.PollInterval < 180*time.Second { + wait = waiter.PollInterval + } else { + if wait < waiter.MinTimeout { + wait = waiter.MinTimeout + } else if wait > 10*time.Second { + wait = 10 * time.Second + } + } + } + }() + + // store the last value result from the refresh loop + var lastRes refreshResult + + timeout := time.After(waiter.Timeout) + for { + select { + case r, ok := <-resCh: + if !ok { + return lastRes.Result, lastRes.Error + } + + if r.Done { + return r.Result, r.Error + } + + lastRes = r + case <-timeout: + log.Printf("[WARN] WaitForState timeout after %s", waiter.Timeout) + + // cancel the goroutine and start our grace period timer + close(cancelCh) + + return waiter.waitForTimeout(resCh, lastRes) + } + } +} + +type refreshResult struct { + Result interface{} + State string + Error error + Done bool +} + +func (waiter *StateWaiter) waitForTimeout(resCh <-chan refreshResult, last refreshResult) (interface{}, error) { + timeout := time.After(graceRefreshTimeout) + + timoutError := &TimeoutError{ + LastError: last.Error, + LastState: last.State, + Timeout: waiter.Timeout, + ExpectedStates: waiter.Target, + } + + // we need wait until at lease once refresh is completed, + // and close the cancel channel + select { + case r, ok := <-resCh: + if r.Done { + return r.Result, r.Error + } + + if !ok { + return nil, timoutError + } + case <-timeout: + return nil, timoutError + } + + return nil, nil +} diff --git a/ucloud/helpers/waiter/waiter_test.go b/ucloud/helpers/waiter/waiter_test.go new file mode 100644 index 00000000..286dabb6 --- /dev/null +++ b/ucloud/helpers/waiter/waiter_test.go @@ -0,0 +1,54 @@ +package waiter + +import ( + "reflect" + "testing" + "time" +) + +func TestWaitForState(t *testing.T) { + tests := []struct { + name string + conf *StateWaiter + want interface{} + wantErr bool + }{ + { + "ok", + &StateWaiter{ + Pending: []string{"pending"}, + Target: []string{"ok"}, + Refresh: func() (interface{}, string, error) { + return true, "ok", nil + }, + }, + true, + false, + }, + { + "timeout", + &StateWaiter{ + Pending: []string{"pending"}, + Target: []string{"ok"}, + Refresh: func() (interface{}, string, error) { + return true, "pending", nil + }, + Timeout: 2 * time.Second, + }, + nil, + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := tt.conf.Wait() + if (err != nil) != tt.wantErr { + t.Errorf("StateChangeConf.WaitForState() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("StateChangeConf.WaitForState() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/ucloud/marshaler.go b/ucloud/marshaler.go index 659e69c5..04a2afdc 100644 --- a/ucloud/marshaler.go +++ b/ucloud/marshaler.go @@ -3,6 +3,7 @@ package ucloud import ( "encoding/json" "fmt" + "regexp" "runtime" "github.com/pkg/errors" @@ -69,12 +70,19 @@ func (c *Client) buildHTTPRequest(req request.Common) (*http.HttpRequest, error) return &httpReq, nil } -// UnmarshalHTTPReponse will get body from http response and unmarshal it's data into response struct -func (c *Client) UnmarshalHTTPReponse(httpResp *http.HttpResponse, resp response.Common) error { +// unmarshalHTTPReponse will get body from http response and unmarshal it's data into response struct +func (c *Client) unmarshalHTTPReponse(httpResp *http.HttpResponse, resp response.Common) error { body := httpResp.GetBody() if len(body) < 0 { return nil } + body = patchForRetCodeString(body) return json.Unmarshal([]byte(body), &resp) } + +var patchForCodePattern = regexp.MustCompile(`"RetCode":\s*"(\d+)"`) + +func patchForRetCodeString(body []byte) []byte { + return patchForCodePattern.ReplaceAll(body, []byte(`"RetCode": $1`)) +} diff --git a/ucloud/version/version.go b/ucloud/version/version.go index 81e0c50e..cfecb44f 100644 --- a/ucloud/version/version.go +++ b/ucloud/version/version.go @@ -2,4 +2,4 @@ package version // Version is the version of sdk // See also semantic version: https://semver.org/ -const Version = "0.5.1" +const Version = "0.5.2"