Skip to content

Commit 34db072

Browse files
authored
support repeating generic api call (#80)
1 parent 79927e4 commit 34db072

File tree

38 files changed

+2401
-363
lines changed

38 files changed

+2401
-363
lines changed

base/client.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
pumem "github.com/ucloud/ucloud-sdk-go/private/services/umem"
99
"github.com/ucloud/ucloud-sdk-go/services/pathx"
1010
"github.com/ucloud/ucloud-sdk-go/services/uaccount"
11+
"github.com/ucloud/ucloud-sdk-go/services/ucompshare"
1112
"github.com/ucloud/ucloud-sdk-go/services/udb"
1213
"github.com/ucloud/ucloud-sdk-go/services/udisk"
1314
"github.com/ucloud/ucloud-sdk-go/services/udpn"
@@ -51,6 +52,7 @@ type Client struct {
5152
PrivateUDBClient
5253
PrivateUMemClient PrivateUMemClient
5354
PrivatePathxClient
55+
ucompshare.UCompShareClient
5456
}
5557

5658
// NewClient will return a aggregate client
@@ -90,6 +92,7 @@ func NewClient(config *sdk.Config, credConfig *CredentialConfig) *Client {
9092
pudbClient = *pudb.NewClient(config, credential)
9193
pumemClient = *pumem.NewClient(config, credential)
9294
ppathxClient = *ppathx.NewClient(config, credential)
95+
ulhostClient = *ucompshare.NewClient(config, credential)
9396
)
9497

9598
uaccountClient.Client.AddRequestHandler(handler)
@@ -137,6 +140,9 @@ func NewClient(config *sdk.Config, credConfig *CredentialConfig) *Client {
137140
ppathxClient.Client.AddRequestHandler(handler)
138141
ppathxClient.Client.AddHttpRequestHandler(injectCredHeader)
139142

143+
ulhostClient.Client.AddRequestHandler(handler)
144+
ulhostClient.Client.AddHttpRequestHandler(injectCredHeader)
145+
140146
return &Client{
141147
uaccountClient,
142148
uhostClient,
@@ -153,5 +159,6 @@ func NewClient(config *sdk.Config, credConfig *CredentialConfig) *Client {
153159
pudbClient,
154160
pumemClient,
155161
ppathxClient,
162+
ulhostClient,
156163
}
157164
}

base/util.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
uerr "github.com/ucloud/ucloud-sdk-go/ucloud/error"
2020
"github.com/ucloud/ucloud-sdk-go/ucloud/helpers/waiter"
2121
"github.com/ucloud/ucloud-sdk-go/ucloud/log"
22+
"github.com/ucloud/ucloud-sdk-go/ucloud/request"
2223
"github.com/ucloud/ucloud-sdk-go/ucloud/response"
2324

2425
"github.com/ucloud/ucloud-cli/model"
@@ -335,11 +336,12 @@ var RegionLabel = map[string]string{
335336

336337
// Poller 轮询器
337338
type Poller struct {
338-
stateFields []string
339-
DescribeFunc func(string, string, string, string) (interface{}, error)
340-
Out io.Writer
341-
Timeout time.Duration
342-
SdescribeFunc func(string) (interface{}, error)
339+
stateFields []string
340+
DescribeFunc func(string, string, string, string) (interface{}, error)
341+
Out io.Writer
342+
Timeout time.Duration
343+
SdescribeFunc func(string, *request.CommonBase) (interface{}, error)
344+
SdescribeWithCommonConfigFunc func(string) (interface{}, error)
343345
}
344346

345347
type pollResult struct {
@@ -349,12 +351,12 @@ type pollResult struct {
349351
}
350352

351353
// Sspoll 简化版, 支持并发
352-
func (p *Poller) Sspoll(resourceID, pollText string, targetStates []string, block *ux.Block) *pollResult {
354+
func (p *Poller) Sspoll(resourceID, pollText string, targetStates []string, block *ux.Block, commonBase *request.CommonBase) *pollResult {
353355
w := waiter.StateWaiter{
354356
Pending: []string{"pending"},
355357
Target: []string{"avaliable"},
356358
Refresh: func() (interface{}, string, error) {
357-
inst, err := p.SdescribeFunc(resourceID)
359+
inst, err := p.SdescribeFunc(resourceID, commonBase)
358360
if err != nil {
359361
return nil, "", err
360362
}
@@ -423,7 +425,7 @@ func (p *Poller) Spoll(resourceID, pollText string, targetStates []string) {
423425
Pending: []string{"pending"},
424426
Target: []string{"avaliable"},
425427
Refresh: func() (interface{}, string, error) {
426-
inst, err := p.SdescribeFunc(resourceID)
428+
inst, err := p.SdescribeFunc(resourceID, nil)
427429
if err != nil {
428430
return nil, "", err
429431
}
@@ -543,7 +545,7 @@ func (p *Poller) Poll(resourceID, projectID, region, zone, pollText string, targ
543545
}
544546

545547
// NewSpoller simple
546-
func NewSpoller(describeFunc func(string) (interface{}, error), out io.Writer) *Poller {
548+
func NewSpoller(describeFunc func(string, *request.CommonBase) (interface{}, error), out io.Writer) *Poller {
547549
return &Poller{
548550
SdescribeFunc: describeFunc,
549551
Out: out,

cmd/api.go

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,54 @@ import (
66
"fmt"
77
"io"
88
"io/ioutil"
9+
"slices"
10+
"strconv"
911
"strings"
12+
"sync"
13+
"sync/atomic"
14+
"time"
1015

1116
"github.com/spf13/cobra"
17+
"github.com/ucloud/ucloud-sdk-go/ucloud"
18+
"github.com/ucloud/ucloud-sdk-go/ucloud/request"
1219

1320
"github.com/ucloud/ucloud-cli/base"
21+
"github.com/ucloud/ucloud-cli/model/status"
22+
"github.com/ucloud/ucloud-cli/ux"
1423
)
1524

25+
type RepeatsConfig struct {
26+
Poller *base.Poller
27+
IDInResp string
28+
}
29+
30+
var RepeatsSupportedAPI = map[string]RepeatsConfig{
31+
"CreateULHostInstance": {Poller: ulhostSpoller, IDInResp: "ULHostId"},
32+
}
33+
34+
const ActionField = "Action"
35+
const RepeatsField = "repeats"
36+
const ConcurrentField = "concurrent"
37+
const DefaultConcurrent = 20
38+
const HelpField = "help"
39+
const HelpInfo = `Usage: ucloud api [options] --Action actionName --param1 value1 --param2 value2 ...
40+
Options:
41+
--local-file string the path of the local file which contains the api parameters
42+
--repeats string the number of repeats
43+
--concurrent string the number of concurrent
44+
--help show help`
45+
1646
// NewCmdAPI ucloud api --xkey xvalue
1747
func NewCmdAPI(out io.Writer) *cobra.Command {
1848
return &cobra.Command{
1949
Use: "api",
2050
Short: "Call API",
2151
Long: "Call API",
2252
Run: func(c *cobra.Command, args []string) {
53+
if slices.Contains(args, "--help") {
54+
fmt.Fprintln(out, HelpInfo)
55+
return
56+
}
2357
params, err := parseParamsFromCmdLine(args)
2458
if err != nil {
2559
fmt.Fprintln(out, err)
@@ -37,7 +71,36 @@ func NewCmdAPI(out io.Writer) *cobra.Command {
3771
return
3872
}
3973
}
40-
74+
if action, actionOK := params[ActionField].(string); actionOK {
75+
if repeatsConfig, repeatsSupported := RepeatsSupportedAPI[action]; repeatsSupported {
76+
if repeats, repeatsOK := params[RepeatsField].(string); repeatsOK {
77+
var repeatsNum int
78+
var concurrentNum int
79+
repeatsNum, err = strconv.Atoi(repeats)
80+
if err != nil {
81+
fmt.Fprintf(out, "error: %v\n", err)
82+
return
83+
}
84+
if concurrent, concurrentOK := params[ConcurrentField].(string); concurrentOK {
85+
concurrentNum, err = strconv.Atoi(concurrent)
86+
if err != nil {
87+
fmt.Fprintf(out, "error: %v\n", err)
88+
return
89+
}
90+
} else {
91+
concurrentNum = DefaultConcurrent
92+
}
93+
delete(params, RepeatsField)
94+
delete(params, ConcurrentField)
95+
err = genericInvokeRepeatWrapper(&repeatsConfig, params, action, repeatsNum, concurrentNum)
96+
if err != nil {
97+
fmt.Fprintf(out, "error: %v\n", err)
98+
return
99+
}
100+
return
101+
}
102+
}
103+
}
41104
req := base.BizClient.UAccountClient.NewGenericRequest()
42105
err = req.SetPayload(params)
43106
if err != nil {
@@ -87,3 +150,104 @@ func parseParamsFromCmdLine(args []string) (map[string]interface{}, error) {
87150
}
88151
return params, nil
89152
}
153+
154+
func genericInvokeRepeatWrapper(repeatsConfig *RepeatsConfig, params map[string]interface{}, action string, repeats int, concurrent int) error {
155+
if repeatsConfig == nil {
156+
return fmt.Errorf("error: repeatsConfig is nil")
157+
}
158+
if repeats <= 0 {
159+
return fmt.Errorf("error: repeats should be a positive integer")
160+
}
161+
if concurrent <= 0 {
162+
return fmt.Errorf("error: concurrent should be a positive integer")
163+
}
164+
wg := &sync.WaitGroup{}
165+
tokens := make(chan struct{}, concurrent)
166+
retCh := make(chan bool, repeats)
167+
168+
wg.Add(repeats)
169+
//ux.Doc.Disable()
170+
refresh := ux.NewRefresh()
171+
172+
req := base.BizClient.UAccountClient.NewGenericRequest()
173+
err := req.SetPayload(params)
174+
if err != nil {
175+
return fmt.Errorf("fail to set payload: %w", err)
176+
}
177+
178+
go func(req request.GenericRequest) {
179+
for i := 0; i < repeats; i++ {
180+
go func(req request.GenericRequest, idx int) {
181+
tokens <- struct{}{}
182+
defer func() {
183+
<-tokens
184+
//设置延时,使报错能渲染出来
185+
time.Sleep(time.Second / 5)
186+
wg.Done()
187+
}()
188+
success := true
189+
resp, err := base.BizClient.UAccountClient.GenericInvoke(req)
190+
block := ux.NewBlock()
191+
ux.Doc.Append(block)
192+
logs := []string{"=================================================="}
193+
logs = append(logs, fmt.Sprintf("api:%v, request:%v", action, base.ToQueryMap(req)))
194+
if err != nil {
195+
logs = append(logs, fmt.Sprintf("err:%v", err))
196+
block.Append(base.ParseError(err))
197+
success = false
198+
} else {
199+
logs = append(logs, fmt.Sprintf("resp:%#v", resp))
200+
resourceId, ok := resp.GetPayload()[repeatsConfig.IDInResp].(string)
201+
if !ok {
202+
block.Append(fmt.Sprintf("expect %v in response, but not found", repeatsConfig.IDInResp))
203+
success = false
204+
} else {
205+
text := fmt.Sprintf("the resource[%s] is initializing", resourceId)
206+
result := repeatsConfig.Poller.Sspoll(resourceId, text, []string{status.HOST_RUNNING, status.HOST_FAIL}, block, &request.CommonBase{
207+
Region: ucloud.String(req.GetRegion()),
208+
Zone: ucloud.String(req.GetZone()),
209+
ProjectId: ucloud.String(req.GetProjectId()),
210+
})
211+
if result.Err != nil {
212+
success = false
213+
block.Append(result.Err.Error())
214+
}
215+
}
216+
retCh <- success
217+
logs = append(logs, fmt.Sprintf("index:%d, result:%t", idx, success))
218+
base.LogInfo(logs...)
219+
}
220+
}(req, i)
221+
}
222+
}(req)
223+
224+
var success, fail atomic.Int32
225+
go func() {
226+
block := ux.NewBlock()
227+
ux.Doc.Append(block)
228+
block.Append(fmt.Sprintf("creating, total:%d, success:%d, fail:%d", repeats, success.Load(), fail.Load()))
229+
blockCount := ux.Doc.GetBlockCount()
230+
for ret := range retCh {
231+
if ret {
232+
success.Add(1)
233+
} else {
234+
fail.Add(1)
235+
}
236+
text := fmt.Sprintf("creating, total:%d, success:%d, fail:%d", repeats, success.Load(), fail.Load())
237+
if blockCount != ux.Doc.GetBlockCount() {
238+
block = ux.NewBlock()
239+
ux.Doc.Append(block)
240+
block.Append(text)
241+
blockCount = ux.Doc.GetBlockCount()
242+
} else {
243+
block.Update(text, 0)
244+
}
245+
if repeats == int(success.Load())+int(fail.Load()) && fail.Load() > 0 {
246+
fmt.Printf("Check logs in %s\n", base.GetLogFilePath())
247+
}
248+
}
249+
}()
250+
wg.Wait()
251+
refresh.Do(fmt.Sprintf("finally, total:%d, success:%d, fail:%d", repeats, success.Load(), repeats-int(success.Load())))
252+
return nil
253+
}

cmd/disk.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"strings"
2121

2222
"github.com/spf13/cobra"
23+
"github.com/ucloud/ucloud-sdk-go/ucloud/request"
2324

2425
"github.com/ucloud/ucloud-sdk-go/private/services/uhost"
2526
"github.com/ucloud/ucloud-sdk-go/services/udisk"
@@ -340,7 +341,7 @@ func NewCmdDiskDetach(out io.Writer) *cobra.Command {
340341
}
341342

342343
func detachUdisk(async bool, udiskID string, out io.Writer) error {
343-
any, err := describeUdiskByID(udiskID)
344+
any, err := describeUdiskByID(udiskID, nil)
344345
if err != nil {
345346
return err
346347
}
@@ -579,7 +580,7 @@ func NewCmdDiskRestore(out io.Writer) *cobra.Command {
579580
Run: func(cmd *cobra.Command, args []string) {
580581
for _, snapshotID := range *snapshotIDs {
581582
snapshotID = base.PickResourceID(snapshotID)
582-
any, err := describeSnapshotByID(snapshotID)
583+
any, err := describeSnapshotByID(snapshotID, nil)
583584
if err != nil {
584585
base.HandleError(err)
585586
continue
@@ -739,8 +740,11 @@ func getDiskList(states []string, project, region, zone string) []string {
739740
return list
740741
}
741742

742-
func describeUdiskByID(udiskID string) (interface{}, error) {
743+
func describeUdiskByID(udiskID string, commonBase *request.CommonBase) (interface{}, error) {
743744
req := base.BizClient.NewDescribeUDiskRequest()
745+
if commonBase != nil {
746+
req.CommonBase = *commonBase
747+
}
744748
req.UDiskId = sdk.String(udiskID)
745749
req.Limit = sdk.Int(50)
746750
resp, err := base.BizClient.DescribeUDisk(req)
@@ -774,8 +778,11 @@ func getSnapshotList(states []string, project, region, zone string) []string {
774778
return list
775779
}
776780

777-
func describeSnapshotByID(snapshotID string) (interface{}, error) {
781+
func describeSnapshotByID(snapshotID string, commonBase *request.CommonBase) (interface{}, error) {
778782
req := base.BizClient.NewDescribeSnapshotRequest()
783+
if commonBase != nil {
784+
req.CommonBase = *commonBase
785+
}
779786
req.SnapshotIds = append(req.SnapshotIds, snapshotID)
780787
req.Limit = sdk.Int(50)
781788
resp, err := base.BizClient.DescribeSnapshot(req)

0 commit comments

Comments
 (0)