diff --git a/cmd/umem.go b/cmd/umem.go index 3964e9af8a..62f01f2b15 100644 --- a/cmd/umem.go +++ b/cmd/umem.go @@ -18,10 +18,12 @@ import ( "fmt" "io" "strings" + "unicode/utf8" "github.com/spf13/cobra" "github.com/ucloud/ucloud-sdk-go/services/umem" + "github.com/ucloud/ucloud-sdk-go/services/vpc" sdk "github.com/ucloud/ucloud-sdk-go/ucloud" "github.com/ucloud/ucloud-sdk-go/ucloud/request" @@ -152,51 +154,55 @@ func NewCmdRedisList(out io.Writer) *cobra.Command { return cmd } +// redisCreateParams holds the shared flag values for redis create commands. +type redisCreateParams struct { + name string + password string + size int + region string + zone string + projectID string + chargeType string + quantity int + group string + vpcID string + subnetID string + version string + blockCnt int + proxySize int +} + // NewCmdRedisCreate ucloud redis create func NewCmdRedisCreate(out io.Writer) *cobra.Command { - req := base.BizClient.NewCreateURedisGroupRequest() - req.HighAvailability = sdk.String("enable") - var redisType, password string + var redisType string + var p redisCreateParams cmd := &cobra.Command{ Use: "create", Short: "Create redis instance", Long: "Create redis instance", Run: func(c *cobra.Command, args []string) { - if l := len(*req.Name); l < 6 || l > 63 { + // Validate name,support Chinese name + if l := utf8.RuneCountInString(p.name); l < 6 || l > 63 { fmt.Fprintln(out, "length of name should be between 6 and 63") return } - if password != "" { - req.Password = &password - } - if redisType == "master-replica" { - resp, err := base.BizClient.CreateURedisGroup(req) - if err != nil { - base.HandleError(err) - return - } - fmt.Printf("redis[%s] created\n", resp.GroupId) - } else if redisType == "distributed" { - dreq := base.BizClient.NewCreateUMemSpaceRequest() - dreq.Region = req.Region - dreq.Zone = req.Zone - dreq.ProjectId = req.ProjectId - dreq.Name = req.Name - dreq.Size = req.Size - if *req.Size == 1 { - dreq.Size = sdk.Int(16) - } - dreq.ChargeType = req.ChargeType - dreq.Quantity = req.Quantity - dreq.Tag = req.Tag - dreq.Password = req.Password - resp, err := base.BizClient.CreateUMemSpace(dreq) - if err != nil { - base.HandleError(err) + // Validate password + if p.password != "" { + if l := len(p.password); l < 6 || l > 36 { + fmt.Fprintln(out, "length of password should be between 6 and 36") return } - fmt.Printf("redis[%s] created\n", resp.SpaceId) - } else { + } + if err := fillDefaultVPCAndSubnet(&p.vpcID, &p.subnetID, p.projectID, p.region, p.zone); err != nil { + fmt.Fprintln(out, err) + return + } + switch redisType { + case "master-replica": + createMasterReplicaRedis(out, &p) + case "distributed": + createDistributedRedis(out, &p) + default: fmt.Printf("unknow redis type[%s], it's should be 'master-replica' or 'distributed'\n", redisType) } }, @@ -205,28 +211,33 @@ func NewCmdRedisCreate(out io.Writer) *cobra.Command { flags := cmd.Flags() flags.SortFlags = false - req.Name = flags.String("name", "", "Required. Name of the redis to create. Range of the password length is [6,63] and the password can only contain letters and numbers") + flags.StringVar(&p.name, "name", "", "Required. Name of the redis to create. Range of the name length is [6,63]") flags.StringVar(&redisType, "type", "", "Required. Type of the redis. Accept values:'master-replica','distributed'") - req.Size = flags.Int("size-gb", 1, "Optional. Memory size. Default value 1GB(for master-replica redis type) or 16GB(for distributed redis type). Unit GB") - req.Version = flags.String("version", "3.2", "Optional. Version of redis") - req.VPCId = flags.String("vpc-id", "", "Optional. VPC ID. This field is required under VPC2.0. See 'ucloud vpc list'") - req.SubnetId = flags.String("subnet-id", "", "Optional. Subnet ID. This field is required under VPC2.0. See 'ucloud subnet list'") - flags.StringVar(&password, "password", "", "Optional. Password of redis to create") - - bindRegion(req, flags) - bindZone(req, flags) - bindProjectID(req, flags) - bindGroup(req, flags) - bindChargeType(req, flags) - bindQuantity(req, flags) - - flags.SetFlagValues("version", "3.0", "3.2", "4.0") + flags.IntVar(&p.size, "size-gb", 2, "Optional. Memory size. Default value 2GB. Unit GB") + flags.StringVar(&p.version, "version", "6.0", "Optional. Version of redis. Accept values: '4.0', '5.0', '6.0', '7.0'") + flags.StringVar(&p.vpcID, "vpc-id", "", "Optional. VPC ID. This field is required under VPC2.0. See 'ucloud vpc list'") + flags.StringVar(&p.subnetID, "subnet-id", "", "Optional. Subnet ID. This field is required under VPC2.0. See 'ucloud subnet list'") + flags.StringVar(&p.password, "password", "", "Optional. Password of redis to create. Range of the password length is [6,36] and the password can only contain letters and numbers") + + //distributed optional params + flags.IntVar(&p.blockCnt, "block-cnt", 2, "Optional. Block count. Default value 2(for distributed redis type).") + flags.IntVar(&p.proxySize, "proxy-size", 2, "Optional. Proxy size. Default value 2(for distributed redis type) Unit Core") + + bindRegionS(&p.region, flags) + bindZoneS(&p.zone, &p.region, flags) + bindProjectIDS(&p.projectID, flags) + flags.StringVar(&p.chargeType, "charge-type", "Month", "Optional. Enumeration value.'Year',pay yearly;'Month',pay monthly; 'Dynamic', pay hourly; 'Trial', free trial(need permission)") + flags.IntVar(&p.quantity, "quantity", 1, "Optional. The duration of the instance. N years/months.") + flags.StringVar(&p.group, "group", "", "Optional. Business group") + + flags.SetFlagValues("version", "4.0", "5.0", "6.0", "7.0") flags.SetFlagValues("type", "master-replica", "distributed") + flags.SetFlagValues("charge-type", "Month", "Dynamic", "Year") flags.SetFlagValuesFunc("vpc-id", func() []string { - return getAllVPCIdNames(*req.ProjectId, *req.Region) + return getAllVPCIdNames(p.projectID, p.region) }) flags.SetFlagValuesFunc("subnet-id", func() []string { - return getAllSubnetIDNames(*req.VPCId, *req.ProjectId, *req.Region) + return getAllSubnetIDNames(p.vpcID, p.projectID, p.region) }) cmd.MarkFlagRequired("name") @@ -235,10 +246,90 @@ func NewCmdRedisCreate(out io.Writer) *cobra.Command { return cmd } +func createMasterReplicaRedis(out io.Writer, p *redisCreateParams) { + req := base.BizClient.NewCreateURedisGroupRequest() + req.Region = &p.region + req.Zone = &p.zone + req.ProjectId = &p.projectID + req.Name = &p.name + req.HighAvailability = sdk.String("enable") + req.Size = &p.size + req.Version = &p.version + req.VPCId = &p.vpcID + req.SubnetId = &p.subnetID + req.ChargeType = &p.chargeType + req.Quantity = &p.quantity + req.Tag = &p.group + if p.password != "" { + req.Password = &p.password + } + + resp, err := base.BizClient.CreateURedisGroup(req) + if err != nil { + base.HandleError(err) + return + } + fmt.Fprintf(out, "redis[%s] created\n", resp.GroupId) +} + +func createDistributedRedis(out io.Writer, p *redisCreateParams) { + req := base.BizClient.NewCreateUMemSpaceRequest() + req.Region = &p.region + req.Zone = &p.zone + req.ProjectId = &p.projectID + req.Name = &p.name + req.Protocol = sdk.String("redis") + + // Validate block-cnt + if p.blockCnt <= 0 { + fmt.Fprintln(out, "block-cnt should be greater than 0") + return + } + + // Validate size is divisible by block-cnt + if p.size%p.blockCnt != 0 { + fmt.Fprintf(out, "size-gb(%d) should be divisible by block-cnt(%d)\n", p.size, p.blockCnt) + return + } + + // Validate proxy-size is a multiple of 2 + if p.proxySize%2 != 0 { + fmt.Fprintf(out, "proxy-size(%d) should be a multiple of 2\n", p.proxySize) + return + } + + req.BlockCnt = &p.blockCnt + req.ProxySize = &p.proxySize + req.Size = &p.size + req.Version = &p.version + req.VPCId = &p.vpcID + req.SubnetId = &p.subnetID + req.ChargeType = &p.chargeType + req.Quantity = &p.quantity + req.Tag = &p.group + if p.password != "" { + req.Password = &p.password + } + + resp, err := base.BizClient.CreateUMemSpace(req) + if err != nil { + base.HandleError(err) + return + } + fmt.Fprintf(out, "redis[%s] created\n", resp.SpaceId) +} + +// redisDeleteParams holds the shared flag values for redis delete commands. +type redisDeleteParams struct { + region string + zone string + projectID string +} + // NewCmdRedisDelete ucloud redis delete func NewCmdRedisDelete(out io.Writer) *cobra.Command { var idNames []string - req := base.BizClient.NewDeleteURedisGroupRequest() + var p redisDeleteParams cmd := &cobra.Command{ Use: "delete", Short: "Delete redis instances", @@ -247,26 +338,13 @@ func NewCmdRedisDelete(out io.Writer) *cobra.Command { Run: func(c *cobra.Command, args []string) { for _, idname := range idNames { id := base.PickResourceID(idname) - if strings.HasPrefix(id, "uredis") { - req.GroupId = &id - _, err := base.BizClient.DeleteURedisGroup(req) - if err != nil { - base.HandleError(err) - continue - } - } else if strings.HasPrefix(id, "umem") { - _req := base.BizClient.NewDeleteUMemSpaceRequest() - _req.Region = req.Region - _req.Zone = req.Zone - _req.ProjectId = req.ProjectId - _req.SpaceId = &id - _, err := base.BizClient.DeleteUMemSpace(_req) - if err != nil { - base.HandleError(err) - continue - } + if strings.HasPrefix(id, "uredis") || strings.HasPrefix(id, "uhredis") || strings.HasPrefix(id, "uregionredis") { + deleteMasterReplicaRedis(out, &p, id) + } else if strings.HasPrefix(id, "udredis") { + deleteDistributedRedis(out, &p, id) + } else { + fmt.Fprintf(out, "redis[%s] unknown id prefix, skip\n", idname) } - fmt.Fprintf(out, "redis[%s] deleted\n", idname) } }, } @@ -274,20 +352,48 @@ func NewCmdRedisDelete(out io.Writer) *cobra.Command { flags := cmd.Flags() flags.SortFlags = false - flags.StringSliceVar(&idNames, "umem-id", nil, "Required. Resource ID of redis intances to delete") - bindProjectID(req, flags) - bindRegion(req, flags) - bindZone(req, flags) + flags.StringSliceVar(&idNames, "umem-id", nil, "Required. Resource ID of redis instances to delete") + bindRegionS(&p.region, flags) + bindZoneS(&p.zone, &p.region, flags) + bindProjectIDS(&p.projectID, flags) cmd.MarkFlagRequired("umem-id") flags.SetFlagValuesFunc("umem-id", func() []string { - return getRedisIDList(*req.ProjectId, *req.Region) + return getRedisIDList(p.projectID, p.region) }) return cmd } +func deleteMasterReplicaRedis(out io.Writer, p *redisDeleteParams, id string) { + req := base.BizClient.NewDeleteURedisGroupRequest() + req.Region = &p.region + req.Zone = &p.zone + req.ProjectId = &p.projectID + req.GroupId = &id + _, err := base.BizClient.DeleteURedisGroup(req) + if err != nil { + base.HandleError(err) + return + } + fmt.Fprintf(out, "redis[%s] deleted\n", id) +} + +func deleteDistributedRedis(out io.Writer, p *redisDeleteParams, id string) { + req := base.BizClient.NewDeleteUMemSpaceRequest() + req.Region = &p.region + req.Zone = &p.zone + req.ProjectId = &p.projectID + req.SpaceId = &id + _, err := base.BizClient.DeleteUMemSpace(req) + if err != nil { + base.HandleError(err) + return + } + fmt.Fprintf(out, "redis[%s] deleted\n", id) +} + // NewCmdRedisRestart ucloud redis restart func NewCmdRedisRestart(out io.Writer) *cobra.Command { idNames := make([]string, 0) @@ -432,6 +538,7 @@ func NewCmdMemcacheList(out io.Writer) *cobra.Command { // NewCmdMemcacheCreate ucloud memcache create func NewCmdMemcacheCreate(out io.Writer) *cobra.Command { req := base.BizClient.NewCreateUMemcacheGroupRequest() + var region, zone, projectID string cmd := &cobra.Command{ Use: "create", Short: "Create memcache instance", @@ -441,6 +548,10 @@ func NewCmdMemcacheCreate(out io.Writer) *cobra.Command { fmt.Fprintln(out, "size-gb should be between 1 and 32") return } + if err := fillDefaultVPCAndSubnet(req.VPCId, req.SubnetId, *req.ProjectId, *req.Region, *req.Zone); err != nil { + fmt.Fprintln(out, err) + return + } resp, err := base.BizClient.CreateUMemcacheGroup(req) if err != nil { base.HandleError(err) @@ -457,19 +568,25 @@ func NewCmdMemcacheCreate(out io.Writer) *cobra.Command { req.Size = flags.Int("size-gb", 1, "Optional. Memory size of memcache instance. Unit GB. Accpet values:1,2,4,8,16,32") req.VPCId = flags.String("vpc-id", "", "Optional. VPC ID. See 'ucloud vpc list'") req.SubnetId = flags.String("subnet-id", "", "Optional. Subnet ID. See 'ucloud subnet list'") - bindProjectID(req, flags) - bindRegion(req, flags) - bindZone(req, flags) - bindChargeType(req, flags) - bindQuantity(req, flags) - bindGroup(req, flags) + bindRegionS(®ion, flags) + bindZoneS(&zone, ®ion, flags) + bindProjectIDS(&projectID, flags) + req.ChargeType = flags.String("charge-type", "Month", "Optional. Enumeration value.'Year',pay yearly;'Month',pay monthly; 'Dynamic', pay hourly; 'Trial', free trial(need permission)") + req.Quantity = flags.Int("quantity", 1, "Optional. The duration of the instance. N years/months.") + req.Tag = flags.String("group", "", "Optional. Business group") + + // Set region/zone/projectID to request after flag parsing + req.Region = ®ion + req.Zone = &zone + req.ProjectId = &projectID flags.SetFlagValues("size-gb", "1", "2", "4", "8", "16", "32") + flags.SetFlagValues("charge-type", "Month", "Dynamic", "Year") flags.SetFlagValuesFunc("vpc-id", func() []string { - return getAllVPCIdNames(*req.ProjectId, *req.Region) + return getAllVPCIdNames(projectID, region) }) flags.SetFlagValuesFunc("subnet-id", func() []string { - return getAllSubnetIDNames(*req.VPCId, *req.ProjectId, *req.Region) + return getAllSubnetIDNames(*req.VPCId, projectID, region) }) cmd.MarkFlagRequired("name") @@ -614,6 +731,59 @@ func describeRedisByID(redisID string, commonBase *request.CommonBase) (interfac return &resp.DataSet[0], nil } +func fillDefaultVPCAndSubnet(vpcID, subnetID *string, projectID, region, zone string) error { + if *vpcID != "" && *subnetID != "" { + return nil + } + vpcs, err := getAllVPCIns(projectID, region) + if err != nil { + return fmt.Errorf("failed to get vpc list: %s", err) + } + if len(vpcs) == 0 { + return fmt.Errorf("no vpc found in region[%s], please specify --vpc-id and --subnet-id", region) + } + + // Find the default VPC + var defaultVPC *vpc.VPCInfo + for i := range vpcs { + if vpcs[i].VPCType == "DefaultVPC" { + defaultVPC = &vpcs[i] + break + } + } + // Fallback to the first VPC if no DefaultVPC found + if defaultVPC == nil { + defaultVPC = &vpcs[0] + } + + if *vpcID == "" { + *vpcID = defaultVPC.VPCId + } + + if *subnetID == "" { + subnets, err := getAllSubnets(*vpcID, projectID, region) + if err != nil { + return fmt.Errorf("failed to get subnet list: %s", err) + } + if len(subnets) == 0 { + return fmt.Errorf("no subnet found in vpc[%s], please specify --subnet-id", *vpcID) + } + // Filter subnets by zone if specified + if zone != "" { + for _, sn := range subnets { + if sn.Zone == zone { + *subnetID = sn.SubnetId + return nil + } + } + } + // Fallback to the first subnet + *subnetID = subnets[0].SubnetId + } + + return nil +} + func getMemcacheIDList(project, region string) []string { req := base.BizClient.NewDescribeUMemcacheGroupRequest() req.ProjectId = &project