Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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