Skip to content

Commit

Permalink
support repeating generic api call (#80)
Browse files Browse the repository at this point in the history
  • Loading branch information
wangrzneu authored May 7, 2024
1 parent 79927e4 commit 34db072
Show file tree
Hide file tree
Showing 38 changed files with 2,401 additions and 363 deletions.
7 changes: 7 additions & 0 deletions base/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
pumem "github.com/ucloud/ucloud-sdk-go/private/services/umem"
"github.com/ucloud/ucloud-sdk-go/services/pathx"
"github.com/ucloud/ucloud-sdk-go/services/uaccount"
"github.com/ucloud/ucloud-sdk-go/services/ucompshare"
"github.com/ucloud/ucloud-sdk-go/services/udb"
"github.com/ucloud/ucloud-sdk-go/services/udisk"
"github.com/ucloud/ucloud-sdk-go/services/udpn"
Expand Down Expand Up @@ -51,6 +52,7 @@ type Client struct {
PrivateUDBClient
PrivateUMemClient PrivateUMemClient
PrivatePathxClient
ucompshare.UCompShareClient
}

// NewClient will return a aggregate client
Expand Down Expand Up @@ -90,6 +92,7 @@ func NewClient(config *sdk.Config, credConfig *CredentialConfig) *Client {
pudbClient = *pudb.NewClient(config, credential)
pumemClient = *pumem.NewClient(config, credential)
ppathxClient = *ppathx.NewClient(config, credential)
ulhostClient = *ucompshare.NewClient(config, credential)
)

uaccountClient.Client.AddRequestHandler(handler)
Expand Down Expand Up @@ -137,6 +140,9 @@ func NewClient(config *sdk.Config, credConfig *CredentialConfig) *Client {
ppathxClient.Client.AddRequestHandler(handler)
ppathxClient.Client.AddHttpRequestHandler(injectCredHeader)

ulhostClient.Client.AddRequestHandler(handler)
ulhostClient.Client.AddHttpRequestHandler(injectCredHeader)

return &Client{
uaccountClient,
uhostClient,
Expand All @@ -153,5 +159,6 @@ func NewClient(config *sdk.Config, credConfig *CredentialConfig) *Client {
pudbClient,
pumemClient,
ppathxClient,
ulhostClient,
}
}
20 changes: 11 additions & 9 deletions base/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
uerr "github.com/ucloud/ucloud-sdk-go/ucloud/error"
"github.com/ucloud/ucloud-sdk-go/ucloud/helpers/waiter"
"github.com/ucloud/ucloud-sdk-go/ucloud/log"
"github.com/ucloud/ucloud-sdk-go/ucloud/request"
"github.com/ucloud/ucloud-sdk-go/ucloud/response"

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

// Poller 轮询器
type Poller struct {
stateFields []string
DescribeFunc func(string, string, string, string) (interface{}, error)
Out io.Writer
Timeout time.Duration
SdescribeFunc func(string) (interface{}, error)
stateFields []string
DescribeFunc func(string, string, string, string) (interface{}, error)
Out io.Writer
Timeout time.Duration
SdescribeFunc func(string, *request.CommonBase) (interface{}, error)
SdescribeWithCommonConfigFunc func(string) (interface{}, error)
}

type pollResult struct {
Expand All @@ -349,12 +351,12 @@ type pollResult struct {
}

// Sspoll 简化版, 支持并发
func (p *Poller) Sspoll(resourceID, pollText string, targetStates []string, block *ux.Block) *pollResult {
func (p *Poller) Sspoll(resourceID, pollText string, targetStates []string, block *ux.Block, commonBase *request.CommonBase) *pollResult {
w := waiter.StateWaiter{
Pending: []string{"pending"},
Target: []string{"avaliable"},
Refresh: func() (interface{}, string, error) {
inst, err := p.SdescribeFunc(resourceID)
inst, err := p.SdescribeFunc(resourceID, commonBase)
if err != nil {
return nil, "", err
}
Expand Down Expand Up @@ -423,7 +425,7 @@ func (p *Poller) Spoll(resourceID, pollText string, targetStates []string) {
Pending: []string{"pending"},
Target: []string{"avaliable"},
Refresh: func() (interface{}, string, error) {
inst, err := p.SdescribeFunc(resourceID)
inst, err := p.SdescribeFunc(resourceID, nil)
if err != nil {
return nil, "", err
}
Expand Down Expand Up @@ -543,7 +545,7 @@ func (p *Poller) Poll(resourceID, projectID, region, zone, pollText string, targ
}

// NewSpoller simple
func NewSpoller(describeFunc func(string) (interface{}, error), out io.Writer) *Poller {
func NewSpoller(describeFunc func(string, *request.CommonBase) (interface{}, error), out io.Writer) *Poller {
return &Poller{
SdescribeFunc: describeFunc,
Out: out,
Expand Down
166 changes: 165 additions & 1 deletion cmd/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,54 @@ import (
"fmt"
"io"
"io/ioutil"
"slices"
"strconv"
"strings"
"sync"
"sync/atomic"
"time"

"github.com/spf13/cobra"
"github.com/ucloud/ucloud-sdk-go/ucloud"
"github.com/ucloud/ucloud-sdk-go/ucloud/request"

"github.com/ucloud/ucloud-cli/base"
"github.com/ucloud/ucloud-cli/model/status"
"github.com/ucloud/ucloud-cli/ux"
)

type RepeatsConfig struct {
Poller *base.Poller
IDInResp string
}

var RepeatsSupportedAPI = map[string]RepeatsConfig{
"CreateULHostInstance": {Poller: ulhostSpoller, IDInResp: "ULHostId"},
}

const ActionField = "Action"
const RepeatsField = "repeats"
const ConcurrentField = "concurrent"
const DefaultConcurrent = 20
const HelpField = "help"
const HelpInfo = `Usage: ucloud api [options] --Action actionName --param1 value1 --param2 value2 ...
Options:
--local-file string the path of the local file which contains the api parameters
--repeats string the number of repeats
--concurrent string the number of concurrent
--help show help`

// NewCmdAPI ucloud api --xkey xvalue
func NewCmdAPI(out io.Writer) *cobra.Command {
return &cobra.Command{
Use: "api",
Short: "Call API",
Long: "Call API",
Run: func(c *cobra.Command, args []string) {
if slices.Contains(args, "--help") {
fmt.Fprintln(out, HelpInfo)
return
}
params, err := parseParamsFromCmdLine(args)
if err != nil {
fmt.Fprintln(out, err)
Expand All @@ -37,7 +71,36 @@ func NewCmdAPI(out io.Writer) *cobra.Command {
return
}
}

if action, actionOK := params[ActionField].(string); actionOK {
if repeatsConfig, repeatsSupported := RepeatsSupportedAPI[action]; repeatsSupported {
if repeats, repeatsOK := params[RepeatsField].(string); repeatsOK {
var repeatsNum int
var concurrentNum int
repeatsNum, err = strconv.Atoi(repeats)
if err != nil {
fmt.Fprintf(out, "error: %v\n", err)
return
}
if concurrent, concurrentOK := params[ConcurrentField].(string); concurrentOK {
concurrentNum, err = strconv.Atoi(concurrent)
if err != nil {
fmt.Fprintf(out, "error: %v\n", err)
return
}
} else {
concurrentNum = DefaultConcurrent
}
delete(params, RepeatsField)
delete(params, ConcurrentField)
err = genericInvokeRepeatWrapper(&repeatsConfig, params, action, repeatsNum, concurrentNum)
if err != nil {
fmt.Fprintf(out, "error: %v\n", err)
return
}
return
}
}
}
req := base.BizClient.UAccountClient.NewGenericRequest()
err = req.SetPayload(params)
if err != nil {
Expand Down Expand Up @@ -87,3 +150,104 @@ func parseParamsFromCmdLine(args []string) (map[string]interface{}, error) {
}
return params, nil
}

func genericInvokeRepeatWrapper(repeatsConfig *RepeatsConfig, params map[string]interface{}, action string, repeats int, concurrent int) error {
if repeatsConfig == nil {
return fmt.Errorf("error: repeatsConfig is nil")
}
if repeats <= 0 {
return fmt.Errorf("error: repeats should be a positive integer")
}
if concurrent <= 0 {
return fmt.Errorf("error: concurrent should be a positive integer")
}
wg := &sync.WaitGroup{}
tokens := make(chan struct{}, concurrent)
retCh := make(chan bool, repeats)

wg.Add(repeats)
//ux.Doc.Disable()
refresh := ux.NewRefresh()

req := base.BizClient.UAccountClient.NewGenericRequest()
err := req.SetPayload(params)
if err != nil {
return fmt.Errorf("fail to set payload: %w", err)
}

go func(req request.GenericRequest) {
for i := 0; i < repeats; i++ {
go func(req request.GenericRequest, idx int) {
tokens <- struct{}{}
defer func() {
<-tokens
//设置延时,使报错能渲染出来
time.Sleep(time.Second / 5)
wg.Done()
}()
success := true
resp, err := base.BizClient.UAccountClient.GenericInvoke(req)
block := ux.NewBlock()
ux.Doc.Append(block)
logs := []string{"=================================================="}
logs = append(logs, fmt.Sprintf("api:%v, request:%v", action, base.ToQueryMap(req)))
if err != nil {
logs = append(logs, fmt.Sprintf("err:%v", err))
block.Append(base.ParseError(err))
success = false
} else {
logs = append(logs, fmt.Sprintf("resp:%#v", resp))
resourceId, ok := resp.GetPayload()[repeatsConfig.IDInResp].(string)
if !ok {
block.Append(fmt.Sprintf("expect %v in response, but not found", repeatsConfig.IDInResp))
success = false
} else {
text := fmt.Sprintf("the resource[%s] is initializing", resourceId)
result := repeatsConfig.Poller.Sspoll(resourceId, text, []string{status.HOST_RUNNING, status.HOST_FAIL}, block, &request.CommonBase{
Region: ucloud.String(req.GetRegion()),
Zone: ucloud.String(req.GetZone()),
ProjectId: ucloud.String(req.GetProjectId()),
})
if result.Err != nil {
success = false
block.Append(result.Err.Error())
}
}
retCh <- success
logs = append(logs, fmt.Sprintf("index:%d, result:%t", idx, success))
base.LogInfo(logs...)
}
}(req, i)
}
}(req)

var success, fail atomic.Int32
go func() {
block := ux.NewBlock()
ux.Doc.Append(block)
block.Append(fmt.Sprintf("creating, total:%d, success:%d, fail:%d", repeats, success.Load(), fail.Load()))
blockCount := ux.Doc.GetBlockCount()
for ret := range retCh {
if ret {
success.Add(1)
} else {
fail.Add(1)
}
text := fmt.Sprintf("creating, total:%d, success:%d, fail:%d", repeats, success.Load(), fail.Load())
if blockCount != ux.Doc.GetBlockCount() {
block = ux.NewBlock()
ux.Doc.Append(block)
block.Append(text)
blockCount = ux.Doc.GetBlockCount()
} else {
block.Update(text, 0)
}
if repeats == int(success.Load())+int(fail.Load()) && fail.Load() > 0 {
fmt.Printf("Check logs in %s\n", base.GetLogFilePath())
}
}
}()
wg.Wait()
refresh.Do(fmt.Sprintf("finally, total:%d, success:%d, fail:%d", repeats, success.Load(), repeats-int(success.Load())))
return nil
}
15 changes: 11 additions & 4 deletions cmd/disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"strings"

"github.com/spf13/cobra"
"github.com/ucloud/ucloud-sdk-go/ucloud/request"

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

func detachUdisk(async bool, udiskID string, out io.Writer) error {
any, err := describeUdiskByID(udiskID)
any, err := describeUdiskByID(udiskID, nil)
if err != nil {
return err
}
Expand Down Expand Up @@ -579,7 +580,7 @@ func NewCmdDiskRestore(out io.Writer) *cobra.Command {
Run: func(cmd *cobra.Command, args []string) {
for _, snapshotID := range *snapshotIDs {
snapshotID = base.PickResourceID(snapshotID)
any, err := describeSnapshotByID(snapshotID)
any, err := describeSnapshotByID(snapshotID, nil)
if err != nil {
base.HandleError(err)
continue
Expand Down Expand Up @@ -739,8 +740,11 @@ func getDiskList(states []string, project, region, zone string) []string {
return list
}

func describeUdiskByID(udiskID string) (interface{}, error) {
func describeUdiskByID(udiskID string, commonBase *request.CommonBase) (interface{}, error) {
req := base.BizClient.NewDescribeUDiskRequest()
if commonBase != nil {
req.CommonBase = *commonBase
}
req.UDiskId = sdk.String(udiskID)
req.Limit = sdk.Int(50)
resp, err := base.BizClient.DescribeUDisk(req)
Expand Down Expand Up @@ -774,8 +778,11 @@ func getSnapshotList(states []string, project, region, zone string) []string {
return list
}

func describeSnapshotByID(snapshotID string) (interface{}, error) {
func describeSnapshotByID(snapshotID string, commonBase *request.CommonBase) (interface{}, error) {
req := base.BizClient.NewDescribeSnapshotRequest()
if commonBase != nil {
req.CommonBase = *commonBase
}
req.SnapshotIds = append(req.SnapshotIds, snapshotID)
req.Limit = sdk.Int(50)
resp, err := base.BizClient.DescribeSnapshot(req)
Expand Down
Loading

0 comments on commit 34db072

Please sign in to comment.