diff --git a/cli/azure/client/compute.go b/cli/azure/client/compute.go index e6157197f..c82d0656d 100644 --- a/cli/azure/client/compute.go +++ b/cli/azure/client/compute.go @@ -15,7 +15,7 @@ func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput createNodesInput := CreateScaleSetInput{ Name: "constellation-scale-set-nodes-" + c.uid, NamePrefix: c.name + "-worker-" + c.uid + "-", - Count: input.Count - 1, + Count: input.CountNodes, InstanceType: input.InstanceType, StateDiskSizeGB: int32(input.StateDiskSizeGB), Image: input.Image, @@ -32,7 +32,7 @@ func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput createCoordinatorsInput := CreateScaleSetInput{ Name: "constellation-scale-set-coordinators-" + c.uid, NamePrefix: c.name + "-control-plane-" + c.uid + "-", - Count: 1, + Count: input.CountCoordinators, InstanceType: input.InstanceType, StateDiskSizeGB: int32(input.StateDiskSizeGB), Image: input.Image, @@ -63,7 +63,8 @@ func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput // CreateInstancesInput is the input for a CreateInstances operation. type CreateInstancesInput struct { - Count int + CountNodes int + CountCoordinators int InstanceType string StateDiskSizeGB int Image string @@ -78,23 +79,25 @@ func (c *Client) CreateInstancesVMs(ctx context.Context, input CreateInstancesIn return err } - vm := azure.VMInstance{ - Name: c.name + "-control-plane-" + c.uid, - Username: "constell", - Password: pw, - Location: c.location, - InstanceType: input.InstanceType, - Image: input.Image, - } - instance, err := c.createInstanceVM(ctx, vm) - if err != nil { - return err - } - c.coordinators = azure.Instances{"0": instance} - - for i := 0; i < input.Count-1; i++ { + for i := 0; i < input.CountCoordinators; i++ { vm := azure.VMInstance{ - Name: c.name + "-node-" + strconv.Itoa(i) + c.uid, + Name: c.name + "-control-plane-" + c.uid + "-" + strconv.Itoa(i), + Username: "constell", + Password: pw, + Location: c.location, + InstanceType: input.InstanceType, + Image: input.Image, + } + instance, err := c.createInstanceVM(ctx, vm) + if err != nil { + return err + } + c.coordinators[strconv.Itoa(i)] = instance + } + + for i := 0; i < input.CountNodes; i++ { + vm := azure.VMInstance{ + Name: c.name + "-node-" + c.uid + "-" + strconv.Itoa(i), Username: "constell", Password: pw, Location: c.location, diff --git a/cli/azure/client/compute_test.go b/cli/azure/client/compute_test.go index fdd4fac6b..17008dd00 100644 --- a/cli/azure/client/compute_test.go +++ b/cli/azure/client/compute_test.go @@ -146,7 +146,8 @@ func TestCreateInstances(t *testing.T) { resourceGroupAPI: newSuccessfulResourceGroupStub(), roleAssignmentsAPI: &stubRoleAssignmentsAPI{}, createInstancesInput: CreateInstancesInput{ - Count: 3, + CountCoordinators: 3, + CountNodes: 3, InstanceType: "type", Image: "image", UserAssingedIdentity: "identity", @@ -159,7 +160,8 @@ func TestCreateInstances(t *testing.T) { resourceGroupAPI: newSuccessfulResourceGroupStub(), roleAssignmentsAPI: &stubRoleAssignmentsAPI{}, createInstancesInput: CreateInstancesInput{ - Count: 3, + CountCoordinators: 3, + CountNodes: 3, InstanceType: "type", Image: "image", UserAssingedIdentity: "identity", @@ -173,7 +175,8 @@ func TestCreateInstances(t *testing.T) { resourceGroupAPI: newSuccessfulResourceGroupStub(), roleAssignmentsAPI: &stubRoleAssignmentsAPI{}, createInstancesInput: CreateInstancesInput{ - Count: 3, + CountCoordinators: 3, + CountNodes: 3, InstanceType: "type", Image: "image", UserAssingedIdentity: "identity", @@ -187,7 +190,7 @@ func TestCreateInstances(t *testing.T) { resourceGroupAPI: newSuccessfulResourceGroupStub(), roleAssignmentsAPI: &stubRoleAssignmentsAPI{}, createInstancesInput: CreateInstancesInput{ - Count: 3, + CountNodes: 3, InstanceType: "type", Image: "image", UserAssingedIdentity: "identity", @@ -219,8 +222,8 @@ func TestCreateInstances(t *testing.T) { assert.Error(client.CreateInstances(ctx, tc.createInstancesInput)) } else { assert.NoError(client.CreateInstances(ctx, tc.createInstancesInput)) - assert.Equal(1, len(client.coordinators)) - assert.Equal(tc.createInstancesInput.Count-1, len(client.nodes)) + assert.Equal(tc.createInstancesInput.CountCoordinators, len(client.coordinators)) + assert.Equal(tc.createInstancesInput.CountNodes, len(client.nodes)) assert.NotEmpty(client.nodes["0"].PrivateIP) assert.NotEmpty(client.nodes["0"].PublicIP) assert.NotEmpty(client.coordinators["0"].PrivateIP) @@ -257,9 +260,10 @@ func TestCreateInstancesVMs(t *testing.T) { resourceGroupAPI: newSuccessfulResourceGroupStub(), roleAssignmentsAPI: &stubRoleAssignmentsAPI{}, createInstancesInput: CreateInstancesInput{ - Count: 3, - InstanceType: "type", - Image: "image", + CountCoordinators: 3, + CountNodes: 3, + InstanceType: "type", + Image: "image", }, }, "error when creating scale set": { @@ -269,9 +273,10 @@ func TestCreateInstancesVMs(t *testing.T) { resourceGroupAPI: newSuccessfulResourceGroupStub(), roleAssignmentsAPI: &stubRoleAssignmentsAPI{}, createInstancesInput: CreateInstancesInput{ - Count: 3, - InstanceType: "type", - Image: "image", + CountCoordinators: 3, + CountNodes: 3, + InstanceType: "type", + Image: "image", }, errExpected: true, }, @@ -282,9 +287,10 @@ func TestCreateInstancesVMs(t *testing.T) { resourceGroupAPI: newSuccessfulResourceGroupStub(), roleAssignmentsAPI: &stubRoleAssignmentsAPI{}, createInstancesInput: CreateInstancesInput{ - Count: 3, - InstanceType: "type", - Image: "image", + CountCoordinators: 3, + CountNodes: 3, + InstanceType: "type", + Image: "image", }, errExpected: true, }, @@ -295,9 +301,10 @@ func TestCreateInstancesVMs(t *testing.T) { resourceGroupAPI: newSuccessfulResourceGroupStub(), roleAssignmentsAPI: &stubRoleAssignmentsAPI{}, createInstancesInput: CreateInstancesInput{ - Count: 3, - InstanceType: "type", - Image: "image", + CountCoordinators: 3, + CountNodes: 3, + InstanceType: "type", + Image: "image", }, errExpected: true, }, @@ -308,9 +315,10 @@ func TestCreateInstancesVMs(t *testing.T) { resourceGroupAPI: newSuccessfulResourceGroupStub(), roleAssignmentsAPI: &stubRoleAssignmentsAPI{}, createInstancesInput: CreateInstancesInput{ - Count: 3, - InstanceType: "type", - Image: "image", + CountCoordinators: 3, + CountNodes: 3, + InstanceType: "type", + Image: "image", }, errExpected: true, }, @@ -321,9 +329,10 @@ func TestCreateInstancesVMs(t *testing.T) { resourceGroupAPI: newSuccessfulResourceGroupStub(), roleAssignmentsAPI: &stubRoleAssignmentsAPI{}, createInstancesInput: CreateInstancesInput{ - Count: 3, - InstanceType: "type", - Image: "image", + CountCoordinators: 3, + CountNodes: 3, + InstanceType: "type", + Image: "image", }, errExpected: true, }, @@ -355,8 +364,8 @@ func TestCreateInstancesVMs(t *testing.T) { } require.NoError(client.CreateInstancesVMs(ctx, tc.createInstancesInput)) - assert.Equal(1, len(client.coordinators)) - assert.Equal(tc.createInstancesInput.Count-1, len(client.nodes)) + assert.Equal(tc.createInstancesInput.CountCoordinators, len(client.coordinators)) + assert.Equal(tc.createInstancesInput.CountNodes, len(client.nodes)) assert.NotEmpty(client.nodes["0"].PrivateIP) assert.NotEmpty(client.nodes["0"].PublicIP) assert.NotEmpty(client.coordinators["0"].PrivateIP) diff --git a/cli/cmd/azureclient_test.go b/cli/cmd/azureclient_test.go index 20d951f97..632e35654 100644 --- a/cli/cmd/azureclient_test.go +++ b/cli/cmd/azureclient_test.go @@ -83,24 +83,30 @@ func (c *fakeAzureClient) CreateInstances(ctx context.Context, input client.Crea c.coordinatorsScaleSet = "coordinators-scale-set" c.nodesScaleSet = "nodes-scale-set" c.nodes = make(azure.Instances) - for i := 0; i < input.Count-1; i++ { + for i := 0; i < input.CountNodes; i++ { id := strconv.Itoa(i) c.nodes[id] = azure.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"} } c.coordinators = make(azure.Instances) - c.coordinators["0"] = azure.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"} + for i := 0; i < input.CountCoordinators; i++ { + id := strconv.Itoa(i) + c.coordinators[id] = azure.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"} + } return nil } // TODO: deprecate as soon as scale sets are available. func (c *fakeAzureClient) CreateInstancesVMs(ctx context.Context, input client.CreateInstancesInput) error { c.nodes = make(azure.Instances) - for i := 0; i < input.Count-1; i++ { + for i := 0; i < input.CountNodes; i++ { id := strconv.Itoa(i) c.nodes[id] = azure.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"} } c.coordinators = make(azure.Instances) - c.coordinators["0"] = azure.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"} + for i := 0; i < input.CountCoordinators; i++ { + id := strconv.Itoa(i) + c.coordinators[id] = azure.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"} + } return nil } diff --git a/cli/cmd/create_aws.go b/cli/cmd/create_aws.go index 98142c2b8..e4204cf65 100644 --- a/cli/cmd/create_aws.go +++ b/cli/cmd/create_aws.go @@ -17,14 +17,15 @@ import ( func newCreateAWSCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "aws NUMBER SIZE", - Short: "Create a Constellation of NUMBER nodes of SIZE on AWS.", - Long: "Create a Constellation of NUMBER nodes of SIZE on AWS.", - Example: "aws 4 2xlarge", + Use: "aws C_COUNT N_COUNT TYPE", + Short: "Create a Constellation of C_COUNT coordinators and N_COUNT nodes of TYPE on AWS.", + Long: "Create a Constellation of C_COUNT coordinators and N_COUNT nodes of TYPE on AWS.", + Example: "aws 1 4 2xlarge", Args: cobra.MatchAll( - cobra.ExactArgs(2), - isIntGreaterArg(0, 1), - isEC2InstanceType(1), + cobra.ExactArgs(3), + isValidAWSCoordinatorCount(0), + isIntGreaterArg(1, 1), + isEC2InstanceType(2), ), ValidArgsFunction: createAWSCompletion, RunE: runCreateAWS, @@ -35,7 +36,7 @@ func newCreateAWSCmd() *cobra.Command { // runCreateAWS runs the create command. func runCreateAWS(cmd *cobra.Command, args []string) error { count, _ := strconv.Atoi(args[0]) // err already checked in args validation - size := strings.ToLower(args[1]) + size := strings.ToLower(args[2]) name, err := cmd.Flags().GetString("name") if err != nil { @@ -125,6 +126,8 @@ func createAWSCompletion(cmd *cobra.Command, args []string, toComplete string) ( case 0: return []string{}, cobra.ShellCompDirectiveNoFileComp case 1: + return []string{}, cobra.ShellCompDirectiveNoFileComp + case 2: return []string{ "4xlarge", "8xlarge", diff --git a/cli/cmd/create_aws_test.go b/cli/cmd/create_aws_test.go index f3d29310a..7eecbebc7 100644 --- a/cli/cmd/create_aws_test.go +++ b/cli/cmd/create_aws_test.go @@ -21,21 +21,23 @@ func TestCreateAWSCmdArgumentValidation(t *testing.T) { args []string expectErr bool }{ - "valid size 4XL": {[]string{"5", "4xlarge"}, false}, - "valid size 8XL": {[]string{"4", "8xlarge"}, false}, - "valid size 12XL": {[]string{"3", "12xlarge"}, false}, - "valid size 16XL": {[]string{"2", "16xlarge"}, false}, - "valid size 24XL": {[]string{"2", "24xlarge"}, false}, - "valid short 12XL": {[]string{"4", "12xl"}, false}, - "valid short 24XL": {[]string{"2", "24xl"}, false}, - "valid capitalized": {[]string{"3", "24XlARge"}, false}, - "valid short capitalized": {[]string{"4", "16XL"}, false}, - "invalid to many arguments": {[]string{"2", "4xl", "2xl"}, true}, - "invalid to many arguments 2": {[]string{"2", "4xl", "2"}, true}, - "invalidOnlyOneInstance": {[]string{"1", "4xl"}, true}, - "invalid first is no int": {[]string{"xl", "4xl"}, true}, - "invalid second is no size": {[]string{"2", "2"}, true}, - "invalid wrong order": {[]string{"4xl", "2"}, true}, + "valid size 4XL": {[]string{"1", "5", "4xlarge"}, false}, + "valid size 8XL": {[]string{"1", "4", "8xlarge"}, false}, + "valid size 12XL": {[]string{"1", "3", "12xlarge"}, false}, + "valid size 16XL": {[]string{"1", "2", "16xlarge"}, false}, + "valid size 24XL": {[]string{"1", "2", "24xlarge"}, false}, + "valid short 12XL": {[]string{"1", "4", "12xl"}, false}, + "valid short 24XL": {[]string{"1", "2", "24xl"}, false}, + "valid capitalized": {[]string{"1", "3", "24XlARge"}, false}, + "valid short capitalized": {[]string{"1", "4", "16XL"}, false}, + "invalid to many arguments": {[]string{"1", "2", "4xl", "2xl"}, true}, + "invalid to many arguments 2": {[]string{"1", "2", "4xl", "2"}, true}, + "invalidOnlyOneInstance": {[]string{"1", "1", "4xl"}, true}, + "invalid first is no int": {[]string{"xl", "2", "4xl"}, true}, + "invalid first is not 1": {[]string{"2", "2", "4xl"}, true}, + "invalid second is no int": {[]string{"1", "xl", "4xl"}, true}, + "invalid third is no size": {[]string{"2", "1", "2"}, true}, + "invalid wrong order": {[]string{"4xl", "1", "2"}, true}, } cmd := newCreateAWSCmd() @@ -173,7 +175,13 @@ func TestCreateAWSCompletion(t *testing.T) { shellCDExpected: cobra.ShellCompDirectiveNoFileComp, }, "second arg": { - args: []string{"23"}, + args: []string{"23"}, + toComplete: "21", + resultExpected: []string{}, + shellCDExpected: cobra.ShellCompDirectiveNoFileComp, + }, + "third arg": { + args: []string{"23", "24"}, toComplete: "4xl", resultExpected: []string{ "4xlarge", @@ -184,8 +192,8 @@ func TestCreateAWSCompletion(t *testing.T) { }, shellCDExpected: cobra.ShellCompDirectiveDefault, }, - "third arg": { - args: []string{"23", "4xlarge"}, + "fourth arg": { + args: []string{"23", "24", "4xlarge"}, toComplete: "xl", resultExpected: []string{}, shellCDExpected: cobra.ShellCompDirectiveError, diff --git a/cli/cmd/create_azure.go b/cli/cmd/create_azure.go index 48399b352..5bd7359da 100644 --- a/cli/cmd/create_azure.go +++ b/cli/cmd/create_azure.go @@ -16,13 +16,14 @@ import ( func newCreateAzureCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "azure", - Short: "Create a Constellation of NUMBER nodes of SIZE on Azure.", - Long: "Create a Constellation of NUMBER nodes of SIZE on Azure.", + Use: "azure C_COUNT N_COUNT TYPE", + Short: "Create a Constellation of C_COUNT coordinators and N_COUNT nodes of TYPE on Azure.", + Long: "Create a Constellation of C_COUNT coordinators and N_COUNT nodes of TYPE on Azure.", Args: cobra.MatchAll( - cobra.ExactArgs(2), - isIntGreaterArg(0, 1), - isAzureInstanceType(1), + cobra.ExactArgs(3), + isIntGreaterZeroArg(0), + isIntGreaterZeroArg(1), + isAzureInstanceType(2), ), ValidArgsFunction: createAzureCompletion, RunE: runCreateAzure, @@ -32,8 +33,9 @@ func newCreateAzureCmd() *cobra.Command { // runCreateAzure runs the create command. func runCreateAzure(cmd *cobra.Command, args []string) error { - count, _ := strconv.Atoi(args[0]) // err already checked in args validation - size := strings.ToLower(args[1]) + countNodes, _ := strconv.Atoi(args[0]) // err already checked in args validation + countCoordinators, _ := strconv.Atoi(args[1]) // err already checked in args validation + size := strings.ToLower(args[2]) subscriptionID := "0d202bbb-4fa7-4af8-8125-58c269a05435" // TODO: This will be user input tenantID := "adb650a8-5da3-4b15-b4b0-3daf65ff7626" // TODO: This will be user input location := "North Europe" // TODO: This will be user input @@ -65,10 +67,10 @@ func runCreateAzure(cmd *cobra.Command, args []string) error { return err } - return createAzure(cmd, client, fileHandler, config, size, count) + return createAzure(cmd, client, fileHandler, config, size, countCoordinators, countNodes) } -func createAzure(cmd *cobra.Command, cl azureclient, fileHandler file.Handler, config *config.Config, size string, count int) (retErr error) { +func createAzure(cmd *cobra.Command, cl azureclient, fileHandler file.Handler, config *config.Config, size string, countCoordinators, countNodes int) (retErr error) { if err := checkDirClean(fileHandler, config); err != nil { return err } @@ -80,7 +82,8 @@ func createAzure(cmd *cobra.Command, cl azureclient, fileHandler file.Handler, c if !ok { // Ask user to confirm action. cmd.Printf("The following Constellation will be created:\n") - cmd.Printf("%d nodes of size %s will be created.\n", count, size) + cmd.Printf("%d coordinators of size %s will be created.\n", countCoordinators, size) + cmd.Printf("%d nodes of size %s will be created.\n", countNodes, size) ok, err := askToConfirm(cmd, "Do you want to create this Constellation?") if err != nil { return err @@ -103,7 +106,8 @@ func createAzure(cmd *cobra.Command, cl azureclient, fileHandler file.Handler, c return err } if err := cl.CreateInstances(cmd.Context(), client.CreateInstancesInput{ - Count: count, + CountCoordinators: countCoordinators, + CountNodes: countNodes, InstanceType: size, StateDiskSizeGB: *config.StateDiskSizeGB, Image: *config.Provider.Azure.Image, @@ -131,6 +135,8 @@ func createAzureCompletion(cmd *cobra.Command, args []string, toComplete string) case 0: return []string{}, cobra.ShellCompDirectiveNoFileComp case 1: + return []string{}, cobra.ShellCompDirectiveNoFileComp + case 2: return azure.InstanceTypes, cobra.ShellCompDirectiveDefault default: return []string{}, cobra.ShellCompDirectiveError diff --git a/cli/cmd/create_azure_test.go b/cli/cmd/create_azure_test.go index 052094c8a..a558a208a 100644 --- a/cli/cmd/create_azure_test.go +++ b/cli/cmd/create_azure_test.go @@ -21,15 +21,17 @@ func TestCreateAzureCmdArgumentValidation(t *testing.T) { args []string expectErr bool }{ - "valid create 1": {[]string{"3", "Standard_DC2as_v5"}, false}, - "valid create 2": {[]string{"7", "Standard_DC4as_v5"}, false}, - "valid create 3": {[]string{"2", "Standard_DC8as_v5"}, false}, - "invalid to many arguments": {[]string{"2", "Standard_DC2as_v5", "Standard_DC2as_v5"}, true}, - "invalid to many arguments 2": {[]string{"2", "Standard_DC2as_v5", "2"}, true}, - "invalidOnlyOneInstance": {[]string{"1", "Standard_DC2as_v5"}, true}, - "invalid first is no int": {[]string{"Standard_DC2as_v5", "Standard_DC2as_v5"}, true}, - "invalid second is no size": {[]string{"2", "2"}, true}, - "invalid wrong order": {[]string{"Standard_DC2as_v5", "2"}, true}, + "valid create 1": {[]string{"3", "3", "Standard_DC2as_v5"}, false}, + "valid create 2": {[]string{"3", "7", "Standard_DC4as_v5"}, false}, + "valid create 3": {[]string{"1", "2", "Standard_DC8as_v5"}, false}, + "invalid to many arguments": {[]string{"3", "2", "Standard_DC2as_v5", "Standard_DC2as_v5"}, true}, + "invalid to many arguments 2": {[]string{"3", "2", "Standard_DC2as_v5", "2"}, true}, + "invalid no coordinators": {[]string{"0", "1", "Standard_DC2as_v5"}, true}, + "invalid no nodes": {[]string{"1", "0", "Standard_DC2as_v5"}, true}, + "invalid first is no int": {[]string{"Standard_DC2as_v5", "1", "Standard_DC2as_v5"}, true}, + "invalid second is no int": {[]string{"1", "Standard_DC2as_v5", "Standard_DC2as_v5"}, true}, + "invalid third is no size": {[]string{"2", "2", "2"}, true}, + "invalid wrong order": {[]string{"Standard_DC2as_v5", "2", "2"}, true}, } cmd := newCreateAzureCmd() @@ -65,6 +67,14 @@ func TestCreateAzure(t *testing.T) { PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1", }, + "1": { + PrivateIP: "192.0.2.1", + PublicIP: "192.0.2.1", + }, + "2": { + PrivateIP: "192.0.2.1", + PublicIP: "192.0.2.1", + }, }, AzureResourceGroup: "resource-group", AzureSubnet: "subnet", @@ -148,7 +158,7 @@ func TestCreateAzure(t *testing.T) { require.NoError(fileHandler.WriteJSON(*config.StatePath, *tc.existingState, false)) } - err := createAzure(cmd, tc.client, fileHandler, config, "Standard_D2s_v3", 3) + err := createAzure(cmd, tc.client, fileHandler, config, "Standard_D2s_v3", 3, 2) if tc.errExpected { assert.Error(err) if stubClient, ok := tc.client.(*stubAzureClient); ok { @@ -181,12 +191,18 @@ func TestCreateAzureCompletion(t *testing.T) { }, "second arg": { args: []string{"23"}, + toComplete: "21", + resultExpected: []string{}, + shellCDExpected: cobra.ShellCompDirectiveNoFileComp, + }, + "third arg": { + args: []string{"23", "24"}, toComplete: "Standard_D", resultExpected: azure.InstanceTypes, shellCDExpected: cobra.ShellCompDirectiveDefault, }, - "third arg": { - args: []string{"23", "Standard_D2s_v3"}, + "fourth arg": { + args: []string{"23", "24", "Standard_D2s_v3"}, toComplete: "Standard_D", resultExpected: []string{}, shellCDExpected: cobra.ShellCompDirectiveError, diff --git a/cli/cmd/create_gcp.go b/cli/cmd/create_gcp.go index 9f297fa6a..28bd13d8d 100644 --- a/cli/cmd/create_gcp.go +++ b/cli/cmd/create_gcp.go @@ -16,13 +16,14 @@ import ( func newCreateGCPCmd() *cobra.Command { cmd := &cobra.Command{ - Use: "gcp", - Short: "Create a Constellation of NUMBER nodes of SIZE on Google Cloud Platform.", - Long: "Create a Constellation of NUMBER nodes of SIZE on Google Cloud Platform.", + Use: "gcp C_COUNT N_COUNT TYPE", + Short: "Create a Constellation of C_COUNT coordinators and N_COUNT nodes of TYPE on Google Cloud Platform.", + Long: "Create a Constellation of C_COUNT coordinators and N_COUNT nodes of TYPE on Google Cloud Platform.", Args: cobra.MatchAll( - cobra.ExactArgs(2), - isIntGreaterArg(0, 1), - isGCPInstanceType(1), + cobra.ExactArgs(3), + isIntGreaterZeroArg(0), + isIntGreaterZeroArg(1), + isGCPInstanceType(2), ), ValidArgsFunction: createGCPCompletion, RunE: runCreateGCP, @@ -32,8 +33,9 @@ func newCreateGCPCmd() *cobra.Command { // runCreateGCP runs the create command. func runCreateGCP(cmd *cobra.Command, args []string) error { - count, _ := strconv.Atoi(args[0]) // err already checked in args validation - size := strings.ToLower(args[1]) + countNodes, _ := strconv.Atoi(args[0]) // err already checked in args validation + countCoordinators, _ := strconv.Atoi(args[1]) // err already checked in args validation + size := strings.ToLower(args[2]) project := "constellation-331613" // TODO: This will be user input zone := "europe-west3-b" // TODO: This will be user input region := "europe-west3" // TODO: This will be user input @@ -59,21 +61,22 @@ func runCreateGCP(cmd *cobra.Command, args []string) error { if err != nil { return err } - return createGCP(cmd, client, fileHandler, config, size, count) + return createGCP(cmd, client, fileHandler, config, size, countCoordinators, countNodes) } -func createGCP(cmd *cobra.Command, cl gcpclient, fileHandler file.Handler, config *config.Config, size string, count int) (retErr error) { +func createGCP(cmd *cobra.Command, cl gcpclient, fileHandler file.Handler, config *config.Config, size string, countCoordinators, countNodes int) (retErr error) { if err := checkDirClean(fileHandler, config); err != nil { return err } createInput := client.CreateInstancesInput{ - Count: count, - ImageId: *config.Provider.GCP.Image, - InstanceType: size, - StateDiskSizeGB: *config.StateDiskSizeGB, - KubeEnv: gcp.KubeEnv, - DisableCVM: *config.Provider.GCP.DisableCVM, + CountNodes: countNodes, + CountCoordinators: countCoordinators, + ImageId: *config.Provider.GCP.Image, + InstanceType: size, + StateDiskSizeGB: *config.StateDiskSizeGB, + KubeEnv: gcp.KubeEnv, + DisableCVM: *config.Provider.GCP.DisableCVM, } ok, err := cmd.Flags().GetBool("yes") @@ -83,7 +86,7 @@ func createGCP(cmd *cobra.Command, cl gcpclient, fileHandler file.Handler, confi if !ok { // Ask user to confirm action. cmd.Printf("The following Constellation will be created:\n") - cmd.Printf("%d nodes of size %s will be created.\n", count, size) + cmd.Printf("%d coordinators and %d nodes of size %s will be created.\n", countCoordinators, countNodes, size) ok, err := askToConfirm(cmd, "Do you want to create this Constellation?") if err != nil { return err @@ -126,6 +129,8 @@ func createGCPCompletion(cmd *cobra.Command, args []string, toComplete string) ( case 0: return []string{}, cobra.ShellCompDirectiveNoFileComp case 1: + return []string{}, cobra.ShellCompDirectiveNoFileComp + case 2: return gcp.InstanceTypes, cobra.ShellCompDirectiveDefault default: return []string{}, cobra.ShellCompDirectiveError diff --git a/cli/cmd/create_gcp_test.go b/cli/cmd/create_gcp_test.go index f1cdc3eb5..eaa112611 100644 --- a/cli/cmd/create_gcp_test.go +++ b/cli/cmd/create_gcp_test.go @@ -21,15 +21,17 @@ func TestCreateGCPCmdArgumentValidation(t *testing.T) { args []string expectErr bool }{ - "valid create 1": {[]string{"3", "n2d-standard-2"}, false}, - "valid create 2": {[]string{"7", "n2d-standard-16"}, false}, - "valid create 3": {[]string{"2", "n2d-standard-96"}, false}, - "invalid to many arguments": {[]string{"2", "n2d-standard-2", "n2d-standard-2"}, true}, - "invalid to many arguments 2": {[]string{"2", "n2d-standard-2", "2"}, true}, - "invalidOnlyOneInstance": {[]string{"1", "n2d-standard-2"}, true}, - "invalid first is no int": {[]string{"n2d-standard-2", "n2d-standard-2"}, true}, - "invalid second is no size": {[]string{"2", "2"}, true}, - "invalid wrong order": {[]string{"n2d-standard-2", "2"}, true}, + "valid create 1": {[]string{"3", "3", "n2d-standard-2"}, false}, + "valid create 2": {[]string{"3", "7", "n2d-standard-16"}, false}, + "valid create 3": {[]string{"1", "2", "n2d-standard-96"}, false}, + "invalid too many arguments": {[]string{"3", "2", "n2d-standard-2", "n2d-standard-2"}, true}, + "invalid too many arguments 2": {[]string{"3", "2", "n2d-standard-2", "2"}, true}, + "invalid no coordinators": {[]string{"0", "1", "n2d-standard-2"}, true}, + "invalid no nodes": {[]string{"1", "0", "n2d-standard-2"}, true}, + "invalid first is no int": {[]string{"n2d-standard-2", "1", "n2d-standard-2"}, true}, + "invalid second is no int": {[]string{"3", "n2d-standard-2", "n2d-standard-2"}, true}, + "invalid third is no size": {[]string{"2", "2", "2"}, true}, + "invalid wrong order": {[]string{"n2d-standard-2", "2", "2"}, true}, } cmd := newCreateGCPCmd() @@ -61,7 +63,15 @@ func TestCreateGCP(t *testing.T) { }, }, GCPCoordinators: gcp.Instances{ - "id-c": { + "id-0": { + PrivateIP: "192.0.2.1", + PublicIP: "192.0.2.1", + }, + "id-1": { + PrivateIP: "192.0.2.1", + PublicIP: "192.0.2.1", + }, + "id-2": { PrivateIP: "192.0.2.1", PublicIP: "192.0.2.1", }, @@ -146,7 +156,7 @@ func TestCreateGCP(t *testing.T) { require.NoError(fileHandler.WriteJSON(*config.StatePath, *tc.existingState, false)) } - err := createGCP(cmd, tc.client, fileHandler, config, "n2d-standard-2", 3) + err := createGCP(cmd, tc.client, fileHandler, config, "n2d-standard-2", 3, 2) if tc.errExpected { assert.Error(err) if stubClient, ok := tc.client.(*stubGcpClient); ok { @@ -181,12 +191,18 @@ func TestCreateGCPCompletion(t *testing.T) { }, "second arg": { args: []string{"23"}, + toComplete: "21", + resultExpected: []string{}, + shellCDExpected: cobra.ShellCompDirectiveNoFileComp, + }, + "third arg": { + args: []string{"23", "24"}, toComplete: "n2d-stan", resultExpected: gcp.InstanceTypes, shellCDExpected: cobra.ShellCompDirectiveDefault, }, - "third arg": { - args: []string{"23", "n2d-standard-2"}, + "fourth arg": { + args: []string{"23", "24", "n2d-standard-2"}, toComplete: "n2d-stan", resultExpected: []string{}, shellCDExpected: cobra.ShellCompDirectiveError, diff --git a/cli/cmd/gcpclient_test.go b/cli/cmd/gcpclient_test.go index 8a3f88410..8ced32d0e 100644 --- a/cli/cmd/gcpclient_test.go +++ b/cli/cmd/gcpclient_test.go @@ -92,12 +92,15 @@ func (c *fakeGcpClient) CreateInstances(ctx context.Context, input client.Create c.nodeTemplate = "node-template" c.coordinatorTemplate = "coordinator-template" c.nodes = make(gcp.Instances) - for i := 0; i < input.Count-1; i++ { - id := "id-" + strconv.Itoa(len(c.nodes)) + for i := 0; i < input.CountNodes; i++ { + id := "id-" + strconv.Itoa(i) c.nodes[id] = gcp.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"} } c.coordinators = make(gcp.Instances) - c.coordinators["id-c"] = gcp.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"} + for i := 0; i < input.CountCoordinators; i++ { + id := "id-" + strconv.Itoa(i) + c.coordinators[id] = gcp.Instance{PublicIP: "192.0.2.1", PrivateIP: "192.0.2.1"} + } return nil } diff --git a/cli/cmd/validargs.go b/cli/cmd/validargs.go index 2d3fe069a..660f6c229 100644 --- a/cli/cmd/validargs.go +++ b/cli/cmd/validargs.go @@ -21,7 +21,7 @@ func isIntArg(arg int) cobra.PositionalArgs { } } -// isIntGreaterArg checks if argument at position arg is and integer and greater i. +// isIntGreaterArg checks if argument at position arg is an integer and greater i. func isIntGreaterArg(arg int, i int) cobra.PositionalArgs { return cobra.MatchAll(isIntArg(arg), func(cmd *cobra.Command, args []string) error { if v, _ := strconv.Atoi(args[arg]); v <= i { @@ -31,6 +31,16 @@ func isIntGreaterArg(arg int, i int) cobra.PositionalArgs { }) } +// isValidAWSCoordinatorCount checks if argument at position arg is an integer exactly 1. +func isValidAWSCoordinatorCount(arg int) cobra.PositionalArgs { + return cobra.MatchAll(isIntArg(arg), func(cmd *cobra.Command, args []string) error { + if v, _ := strconv.Atoi(args[arg]); v != 1 { + return fmt.Errorf("argument %d is %d, that is not a valid coordinator count for AWS, currently the only supported coordinator count is 1", arg, v) + } + return nil + }) +} + // isIntGreaterZeroArg checks if argument at position arg is a positive non zero integer. func isIntGreaterZeroArg(arg int) cobra.PositionalArgs { return cobra.MatchAll(isIntGreaterArg(arg, 0)) diff --git a/cli/gcp/client/instances.go b/cli/gcp/client/instances.go index 9c7a2dd60..fc3257ec1 100644 --- a/cli/gcp/client/instances.go +++ b/cli/gcp/client/instances.go @@ -72,7 +72,7 @@ func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput ops = []Operation{} nodeGroupInput := instanceGroupManagerInput{ - Count: input.Count - 1, + Count: input.CountNodes, Name: strings.Join([]string{c.name, "worker", c.uid}, "-"), Template: c.nodeTemplate, UID: c.uid, @@ -87,7 +87,7 @@ func (c *Client) CreateInstances(ctx context.Context, input CreateInstancesInput c.nodesInstanceGroup = nodeGroupInput.Name coordinatorGroupInput := instanceGroupManagerInput{ - Count: 1, + Count: input.CountCoordinators, Name: strings.Join([]string{c.name, "control-plane", c.uid}, "-"), Template: c.coordinatorTemplate, UID: c.uid, @@ -287,12 +287,13 @@ func (i *instanceGroupManagerInput) InsertInstanceGroupManagerRequest() computep // CreateInstancesInput is the input for a CreatInstances operation. type CreateInstancesInput struct { - Count int - ImageId string - InstanceType string - StateDiskSizeGB int - KubeEnv string - DisableCVM bool + CountNodes int + CountCoordinators int + ImageId string + InstanceType string + StateDiskSizeGB int + KubeEnv string + DisableCVM bool } type insertInstanceTemplateInput struct { diff --git a/cli/gcp/client/instances_test.go b/cli/gcp/client/instances_test.go index f3b297148..356de2b2e 100644 --- a/cli/gcp/client/instances_test.go +++ b/cli/gcp/client/instances_test.go @@ -41,10 +41,11 @@ func TestCreateInstances(t *testing.T) { {CurrentAction: proto.String(computepb.ManagedInstance_NONE.String())}, } testInput := CreateInstancesInput{ - Count: 3, - ImageId: "img", - InstanceType: "n2d-standard-2", - KubeEnv: "kube-env", + CountCoordinators: 3, + CountNodes: 4, + ImageId: "img", + InstanceType: "n2d-standard-2", + KubeEnv: "kube-env", } someErr := errors.New("failed")