From 6da9785667ed53f3f5f8289fa68ebade105c06a9 Mon Sep 17 00:00:00 2001 From: bhatipradeep Date: Sat, 23 Apr 2022 12:41:53 +0530 Subject: [PATCH] Fix and add tests for v3 and karbon client Add image nodes service func unit test Image nodes test fixes 1. Add networking and file management foundation service unit tests. 2. Add progress of image nodes unit test. 3. Fix v3 image upload test. Resolve lint erros Fix upload unit test after rebase Minor fix Resolve lint errors resolve lint errors fix lint errors Fix image node unit test Fix tests 1. Add integration tests for foundation. 2. Update docs for imageing of nodes. 3. Update Existing tests. 4. Add new computed fields for printing cluster urls Add foundation config json structure Resolve lint errors Acceptance tests workflow changes. Update karbon tests names Minor fix in image upload test in foundation Fix image upload tests for foundation test_files is no longer need Add build tags to upload image tests to remove it from compile tests unit testcase for fc lint fixes adding testcases lint fix changing comments test config Change test names Change some test names Minor test filx Conifg changes for regression --- GNUmakefile | 4 +- client/client_test.go | 137 ++-- client/fc/fc_service_test.go | 618 ++++++++++++++++++ client/fc/fc_test.go | 44 ++ client/foundation/foundation_api_test.go | 7 +- ...foundation_file_management_service_test.go | 194 ++++++ .../foundation_networking_service_test.go | 281 ++++++++ .../foundation_node_imaging_service_test.go | 222 +++++++ client/foundation/foundation_structs.go | 2 +- client/karbon/karbon_api_test.go | 43 ++ client/v3/v3_service_test.go | 6 +- client/v3/v3_test.go | 65 +- nutanix/config_test.go | 48 +- .../data_source_nutanix_assert_helper_test.go | 37 ++ ...utanix_foundation_central_api_keys_test.go | 62 ++ ...foundation_central_cluster_details_test.go | 55 ++ ...ation_central_imaged_clusters_list_test.go | 28 + ...dation_central_imaged_node_details_test.go | 55 ++ ...undation_central_imaged_nodes_list_test.go | 28 + ...x_foundation_central_list_api_keys_test.go | 28 + ..._nutanix_foundation_discover_nodes_test.go | 31 + ...nutanix_foundation_hypervisor_isos_test.go | 30 + ...ix_foundation_node_network_details_test.go | 34 + ...ce_nutanix_foundation_nos_packages_test.go | 29 + ..._nutanix_karbon_cluster_kubeconfig_test.go | 4 +- ..._source_nutanix_karbon_cluster_ssh_test.go | 4 +- ...data_source_nutanix_karbon_cluster_test.go | 4 +- ...ata_source_nutanix_karbon_clusters_test.go | 2 +- nutanix/data_source_recovery_plan_test.go | 4 +- nutanix/internal/data_source_assert_helper.go | 2 + nutanix/main_test.go | 63 +- nutanix/provider_test.go | 7 + ...utanix_foundation_central_api_keys_test.go | 34 + ...nix_foundation_central_image_nodes_test.go | 101 +++ ...resource_nutanix_foundation_image_nodes.go | 34 +- ...rce_nutanix_foundation_image_nodes_test.go | 199 ++++++ .../resource_nutanix_foundation_image_test.go | 148 +++++ ...rce_nutanix_foundation_ipmi_config_test.go | 79 +++ .../resource_nutanix_karbon_cluster_test.go | 6 +- nutanix/resource_nutanix_project_test.go | 2 +- .../resource_nutanix_protection_rule_test.go | 2 +- .../resource_nutanix_recovery_plan_test.go | 2 +- test_config.json | 12 +- test_foundation_config.json | 136 ++++ .../r/foundation_image_nodes.html.markdown | 8 +- 45 files changed, 2778 insertions(+), 163 deletions(-) create mode 100644 client/fc/fc_service_test.go create mode 100644 client/fc/fc_test.go create mode 100644 client/foundation/foundation_file_management_service_test.go create mode 100644 client/foundation/foundation_networking_service_test.go create mode 100644 client/foundation/foundation_node_imaging_service_test.go create mode 100644 client/karbon/karbon_api_test.go create mode 100644 nutanix/data_source_nutanix_assert_helper_test.go create mode 100644 nutanix/data_source_nutanix_foundation_central_api_keys_test.go create mode 100644 nutanix/data_source_nutanix_foundation_central_cluster_details_test.go create mode 100644 nutanix/data_source_nutanix_foundation_central_imaged_clusters_list_test.go create mode 100644 nutanix/data_source_nutanix_foundation_central_imaged_node_details_test.go create mode 100644 nutanix/data_source_nutanix_foundation_central_imaged_nodes_list_test.go create mode 100644 nutanix/data_source_nutanix_foundation_central_list_api_keys_test.go create mode 100644 nutanix/data_source_nutanix_foundation_discover_nodes_test.go create mode 100644 nutanix/data_source_nutanix_foundation_hypervisor_isos_test.go create mode 100644 nutanix/data_source_nutanix_foundation_node_network_details_test.go create mode 100644 nutanix/data_source_nutanix_foundation_nos_packages_test.go create mode 100644 nutanix/resource_nutanix_foundation_central_api_keys_test.go create mode 100644 nutanix/resource_nutanix_foundation_central_image_nodes_test.go create mode 100644 nutanix/resource_nutanix_foundation_image_nodes_test.go create mode 100644 nutanix/resource_nutanix_foundation_image_test.go create mode 100644 nutanix/resource_nutanix_foundation_ipmi_config_test.go create mode 100644 test_foundation_config.json diff --git a/GNUmakefile b/GNUmakefile index 482421df7..3a77fc5e0 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -9,10 +9,10 @@ build: fmtcheck go install test: fmtcheck - go test $(TEST) -timeout=30s -parallel=4 + go test --tags=unit $(TEST) -timeout=30s -parallel=4 testacc: fmtcheck - TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 200m -coverprofile c.out -covermode=count + TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 500m -coverprofile c.out -covermode=count fmt: @echo "==> Fixing source code with gofmt..." diff --git a/client/client_test.go b/client/client_test.go index 95d247a9b..f15692cc6 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -1,6 +1,7 @@ package client import ( + "bytes" "context" "fmt" "io" @@ -19,6 +20,7 @@ const ( testLibraryVersion = "v3" testAbsolutePath = "api/nutanix/" + testLibraryVersion testUserAgent = "nutanix/" + testLibraryVersion + fileName = "v3/v3.go" ) func setup() (*http.ServeMux, *Client, *httptest.Server) { @@ -90,6 +92,50 @@ func TestNewRequest(t *testing.T) { } } +func TestNewUploadRequest(t *testing.T) { + c, err := NewClient(&Credentials{"foo.com", "username", "password", "", "", true, false, "", "", "", nil}, testUserAgent, testAbsolutePath, true) + + if err != nil { + t.Errorf("Unexpected Error: %v", err) + } + + inURL, outURL := "/foo", fmt.Sprintf(defaultBaseURL+testAbsolutePath+"/foo", httpPrefix, "foo.com") + inBody, _ := os.Open(fileName) + if err != nil { + t.Fatalf("Error opening file %v, error : %v", fileName, err) + } + + // expected body + out, _ := os.Open(fileName) + outBody, _ := ioutil.ReadAll(out) + + req, err := c.NewUploadRequest(context.TODO(), http.MethodPost, inURL, inBody) + if err != nil { + t.Fatalf("NewUploadRequest() errored out with error : %v", err.Error()) + } + // test relative URL was expanded + if req.URL.String() != outURL { + t.Errorf("NewUploadRequest(%v) URL = %v, expected %v", inURL, req.URL, outURL) + } + + //test body contents + got, _ := ioutil.ReadAll(req.Body) + if !bytes.Equal(got, outBody) { + t.Errorf("NewUploadRequest(%v) Body = %v, expected %v", inBody, string(got), string(outBody)) + } + + // test headers. + inHeaders := map[string]string{ + "Content-Type": octetStreamType, + "Accept": mediaType, + } + for k, v := range inHeaders { + if v != req.Header[k][0] { + t.Errorf("NewUploadRequest() Header value for %v = %v, expected %v", k, req.Header[k][0], v) + } + } +} + func TestNewUnAuthRequest(t *testing.T) { c, err := NewClient(&Credentials{"foo.com", "username", "password", "", "", true, false, "", "", "", nil}, testUserAgent, testAbsolutePath, true) @@ -137,7 +183,8 @@ func TestNewUnAuthFormEncodedRequest(t *testing.T) { } inURL, outURL := "/foo", fmt.Sprintf(defaultBaseURL+testAbsolutePath+"/foo", httpPrefix, "foo.com") - inBody, outBody := map[string]string{"name": "bar", "fullname": "foobar"}, "name=bar&fullname=foobar"+"\n" + inBody := map[string]string{"name": "bar", "fullname": "foobar"} + outBody := map[string][]string{"name": {"bar"}, "fullname": {"foobar"}} req, _ := c.NewUnAuthFormEncodedRequest(context.TODO(), http.MethodPost, inURL, inBody) @@ -146,10 +193,13 @@ func TestNewUnAuthFormEncodedRequest(t *testing.T) { t.Errorf("NewUnAuthFormEncodedRequest(%v) URL = %v, expected %v", inURL, req.URL, outURL) } - // test body was JSON encoded - body, _ := ioutil.ReadAll(req.Body) - if string(body) != outBody { - t.Errorf("NewUnAuthFormEncodedRequest(%v) Body = %v, expected %v", inBody, string(body), outBody) + // test body + // Parse the body form data to a map structure which can be accessed by req.PostForm + req.ParseForm() + + // check form encoded key-values as compared to input values + if !reflect.DeepEqual(outBody, (map[string][]string)(req.PostForm)) { + t.Errorf("NewUnAuthFormEncodedRequest(%v) Form encoded k-v, got = %v, expected %v", inBody, req.PostForm, outBody) } // test headers. Authorization header shouldn't exist @@ -176,18 +226,28 @@ func TestNewUnAuthUploadRequest(t *testing.T) { } inURL, outURL := "/foo", fmt.Sprintf(defaultBaseURL+testAbsolutePath+"/foo", httpPrefix, "foo.com") - inBody, outBody := []byte("Yeah I am genius!"), "Yeah I am genius!" - req, _ := c.NewUnAuthUploadRequest(context.TODO(), http.MethodPost, inURL, inBody) + inBody, _ := os.Open(fileName) + if err != nil { + t.Fatalf("Error opening fiele %v, error : %v", fileName, err) + } + + // expected body + out, _ := os.Open(fileName) + outBody, _ := ioutil.ReadAll(out) + req, err := c.NewUnAuthUploadRequest(context.TODO(), http.MethodPost, inURL, inBody) + if err != nil { + t.Fatalf("NewUnAuthUploadRequest() errored out with error : %v", err.Error()) + } // test relative URL was expanded if req.URL.String() != outURL { t.Errorf("NewUnAuthUploadRequest(%v) URL = %v, expected %v", inURL, req.URL, outURL) } - //test body was JSON encoded - body, _ := ioutil.ReadAll(req.Body) - if string(body) != outBody { - t.Errorf("NewUnAuthUploadRequest(%v) Body = %v, expected %v", inBody, string(body), outBody) + //test body contents + got, _ := ioutil.ReadAll(req.Body) + if !bytes.Equal(got, outBody) { + t.Errorf("NewUnAuthUploadRequest(%v) Body = %v, expected %v", inBody, string(got), string(outBody)) } // test headers. Authorization header shouldn't exist @@ -197,11 +257,10 @@ func TestNewUnAuthUploadRequest(t *testing.T) { inHeaders := map[string]string{ "Content-Type": octetStreamType, "Accept": mediaType, - "User-Agent": testUserAgent, } - for k, v := range req.Header { - if v[0] != inHeaders[k] { - t.Errorf("NewUnAuthUploadRequest() Header value for %v = %v, expected %v", k, v[0], inHeaders[k]) + for k, v := range inHeaders { + if v != req.Header[k][0] { + t.Errorf("NewUploadRequest() Header value for %v = %v, expected %v", k, req.Header[k][0], v) } } } @@ -484,54 +543,6 @@ func TestClient_NewRequest(t *testing.T) { } } -func TestClient_NewUploadRequest(t *testing.T) { - type fields struct { - Credentials *Credentials - client *http.Client - BaseURL *url.URL - UserAgent string - onRequestCompleted RequestCompletionCallback - } - type args struct { - ctx context.Context - method string - urlStr string - file *os.File - } - - tests := []struct { - name string - fields fields - args args - want *http.Request - wantErr bool - }{ - // TODO: Add test cases. - } - - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - c := &Client{ - Credentials: tt.fields.Credentials, - client: tt.fields.client, - BaseURL: tt.fields.BaseURL, - UserAgent: tt.fields.UserAgent, - onRequestCompleted: tt.fields.onRequestCompleted, - } - got, err := c.NewUploadRequest(tt.args.ctx, tt.args.method, tt.args.urlStr, tt.args.file) - if (err != nil) != tt.wantErr { - t.Errorf("Client.NewUploadRequest() error = %v, wantErr %v", err, tt.wantErr) - - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("Client.NewUploadRequest() = %v, want %v", got, tt.want) - } - }) - } -} - func TestClient_OnRequestCompleted(t *testing.T) { type fields struct { Credentials *Credentials diff --git a/client/fc/fc_service_test.go b/client/fc/fc_service_test.go new file mode 100644 index 000000000..1146c09fc --- /dev/null +++ b/client/fc/fc_service_test.go @@ -0,0 +1,618 @@ +package foundationcentral + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "testing" + + "github.com/terraform-providers/terraform-provider-nutanix/client" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func setup() (*http.ServeMux, *client.Client, *httptest.Server) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + c, _ := client.NewClient(&client.Credentials{ + URL: "https://10.2.242.13:9440", + Username: "admin", + Password: "Nutanix.123", + Port: "9440", + Endpoint: "10.2.242.13", + Insecure: true}, + userAgent, + absolutePath, + false) + c.BaseURL, _ = url.Parse(server.URL) + + return mux, c, server +} + +func testHTTPMethod(t *testing.T, r *http.Request, expected string) { + if expected != r.Method { + t.Errorf("Request method = %v, expected %v", r.Method, expected) + } +} + +func TestOperations_ListImagedNodes(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/fc/v1/imaged_nodes/list", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPost) + fmt.Fprint(w, `{"imaged_nodes":[{"node_state": "STATE_AVAILABLE"}]}`) + }) + + list := &ImagedNodesListResponse{} + list.ImagedNodes = make([]*ImagedNodeDetails, 1) + list.ImagedNodes[0] = &ImagedNodeDetails{} + list.ImagedNodes[0].NodeState = utils.StringPtr("STATE_AVAILABLE") + + input := &ImagedNodesListInput{ + Length: utils.IntPtr(1), + } + + type fields struct { + client *client.Client + } + + type args struct { + getEntitiesRequest *ImagedNodesListInput + } + ctx := context.TODO() + tests := []struct { + name string + fields fields + args args + want *ImagedNodesListResponse + wantErr bool + }{ + { + "Test Imaged Nodes", + fields{c}, + args{input}, + list, + false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + got, err := op.ListImagedNodes(ctx, tt.args.getEntitiesRequest) + if (err != nil) != tt.wantErr { + t.Errorf("Operations.ListImagedNodes() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Operations.ListImagedNodes() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOperations_ListImagedClusters(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/fc/v1/imaged_clusters/list", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPost) + fmt.Fprint(w, `{"imaged_clusters":[{"cluster_name": "Test-Cluster"}]}`) + }) + + list := &ImagedClustersListResponse{} + list.ImagedClusters = make([]*ImagedClusterDetails, 1) + list.ImagedClusters[0] = &ImagedClusterDetails{} + list.ImagedClusters[0].ClusterName = utils.StringPtr("Test-Cluster") + + input := &ImagedClustersListInput{ + Length: utils.IntPtr(1), + } + + type fields struct { + client *client.Client + } + + type args struct { + getEntitiesRequest *ImagedClustersListInput + } + ctx := context.TODO() + tests := []struct { + name string + fields fields + args args + want *ImagedClustersListResponse + wantErr bool + }{ + { + "Test Imaged Clusters", + fields{c}, + args{input}, + list, + false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + got, err := op.ListImagedClusters(ctx, tt.args.getEntitiesRequest) + if (err != nil) != tt.wantErr { + t.Errorf("Operations.ListImagedClusters() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Operations.ListImagedClusters() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOperations_ListAPIKeys(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/fc/v1/api_keys/list", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPost) + fmt.Fprint(w, `{"api_keys":[{"api_key": "00a9-23h9", "alias":"test-1"}]}`) + }) + + list := &ListAPIKeysResponse{} + list.APIKeys = make([]*CreateAPIKeysResponse, 1) + list.APIKeys[0] = &CreateAPIKeysResponse{} + list.APIKeys[0].APIKey = "00a9-23h9" + list.APIKeys[0].Alias = "test-1" + + input := &ListMetadataInput{ + Length: utils.IntPtr(1), + } + + type fields struct { + client *client.Client + } + + type args struct { + getEntitiesRequest *ListMetadataInput + } + ctx := context.TODO() + tests := []struct { + name string + fields fields + args args + want *ListAPIKeysResponse + wantErr bool + }{ + { + "Test List API Keys", + fields{c}, + args{input}, + list, + false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + got, err := op.ListAPIKeys(ctx, tt.args.getEntitiesRequest) + if (err != nil) != tt.wantErr { + t.Errorf("Operations.ListAPIKeys() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Operations.ListAPIKeys() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOperations_GetImagedNode(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/fc/v1/imaged_nodes/0a8x-23d8", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodGet) + fmt.Fprint(w, `{"api_key_uuid": "234d-876f", "available": true, "cvm_ip": "10.0.0.0"}`) + }) + + node := &ImagedNodeDetails{} + node.APIKeyUUID = utils.StringPtr("234d-876f") + node.Available = utils.BoolPtr(true) + node.CvmIP = utils.StringPtr("10.0.0.0") + + type fields struct { + client *client.Client + } + + type args struct { + KeyUUID string + } + ctx := context.TODO() + tests := []struct { + name string + fields fields + args args + want *ImagedNodeDetails + wantErr bool + }{ + { + "Get Imaged Node Details", + fields{c}, + args{"0a8x-23d8"}, + node, + false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + got, err := op.GetImagedNode(ctx, tt.args.KeyUUID) + if (err != nil) != tt.wantErr { + t.Errorf("Operations.GetImagedNode() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Operations.GetImagedNode() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOperations_GetImagedCluster(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/fc/v1/imaged_clusters/0a8x-23d8", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodGet) + fmt.Fprint(w, `{"cluster_name": "test-cluster", "archived": true, "cluster_external_ip": "10.0.0.0"}`) + }) + + cluster := &ImagedClusterDetails{} + cluster.ClusterName = utils.StringPtr("test-cluster") + cluster.Archived = utils.BoolPtr(true) + cluster.ClusterExternalIP = utils.StringPtr("10.0.0.0") + + type fields struct { + client *client.Client + } + + type args struct { + KeyUUID string + } + ctx := context.TODO() + tests := []struct { + name string + fields fields + args args + want *ImagedClusterDetails + wantErr bool + }{ + { + "Get Imaged Cluster Details", + fields{c}, + args{"0a8x-23d8"}, + cluster, + false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + got, err := op.GetImagedCluster(ctx, tt.args.KeyUUID) + if (err != nil) != tt.wantErr { + t.Errorf("Operations.GetImagedCluster() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Operations.GetImagedCluster() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOperations_GetAPIKey(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/fc/v1/api_keys/20ca-4d4c-61fd", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodGet) + + fmt.Fprint(w, `{ + "api_key": "1243-7645", + "alias": "test-key", + "created_timestamp": "2022-04-27T05:15:59.000-07:00", + "current_time": "2022-04-27T09:33:25.000-07:00", + "key_uuid": "20ca-4d4c-61fd" + }`) + }) + + apiKey := &CreateAPIKeysResponse{} + apiKey.APIKey = "1243-7645" + apiKey.Alias = "test-key" + apiKey.CreatedTimestamp = "2022-04-27T05:15:59.000-07:00" + apiKey.CurrentTime = "2022-04-27T09:33:25.000-07:00" + apiKey.KeyUUID = "20ca-4d4c-61fd" + + type fields struct { + client *client.Client + } + + type args struct { + KeyUUID string + } + ctx := context.TODO() + tests := []struct { + name string + fields fields + args args + want *CreateAPIKeysResponse + wantErr bool + }{ + { + "Get API Key Details", + fields{c}, + args{"20ca-4d4c-61fd"}, + apiKey, + false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + got, err := op.GetAPIKey(ctx, tt.args.KeyUUID) + if (err != nil) != tt.wantErr { + t.Errorf("Operations.GetAPIKey() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Operations.GetAPIKey() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOperations_CreateAPIKey(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/fc/v1/api_keys", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPost) + + fmt.Fprint(w, `{ + "api_key": "1243-7645", + "alias": "test-key", + "created_timestamp": "2022-04-27T05:15:59.000-07:00", + "current_time": "2022-04-27T09:33:25.000-07:00", + "key_uuid": "20ca-4d4c-61fd" + }`) + }) + + apiKey := &CreateAPIKeysResponse{} + apiKey.APIKey = "1243-7645" + apiKey.Alias = "test-key" + apiKey.CreatedTimestamp = "2022-04-27T05:15:59.000-07:00" + apiKey.CurrentTime = "2022-04-27T09:33:25.000-07:00" + apiKey.KeyUUID = "20ca-4d4c-61fd" + + input := &CreateAPIKeysInput{ + Alias: "test-key", + } + + type fields struct { + client *client.Client + } + + type args struct { + alias *CreateAPIKeysInput + } + + ctx := context.TODO() + tests := []struct { + name string + fields fields + args args + want *CreateAPIKeysResponse + wantErr bool + }{ + { + "Create API Key", + fields{c}, + args{input}, + apiKey, + false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + got, err := op.CreateAPIKey(ctx, tt.args.alias) + if (err != nil) != tt.wantErr { + t.Errorf("Operations.CreateAPIKey() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Operations.CreateAPIKey() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOperations_CreateCluster(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/fc/v1/imaged_clusters", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPost) + + fmt.Fprint(w, `{ + "imaged_cluster_uuid": "123-654-678" + }`) + }) + + clusterUUID := &CreateClusterResponse{ + ImagedClusterUUID: utils.StringPtr("123-654-678"), + } + + input := &CreateClusterInput{ + CommonNetworkSettings: &CommonNetworkSettings{ + CvmDNSServers: []string{"10.0.0.0"}, + HypervisorDNSServers: []string{"10.0.0.0"}, + CvmNtpServers: []string{"0.0.0.0"}, + HypervisorNtpServers: []string{"0.0.0.0"}, + }, + RedundancyFactor: utils.IntPtr(2), + AosPackageURL: utils.StringPtr("test_aos.tar.gz"), + ClusterName: utils.StringPtr("test-cluster"), + NodesList: []*Node{ + { + CvmGateway: utils.StringPtr("0.0.0.0"), + IpmiNetmask: utils.StringPtr("255.255.255.0"), + ImagedNodeUUID: utils.StringPtr("12n0-vh87"), + HypervisorType: utils.StringPtr("kvm"), + ImageNow: utils.BoolPtr(true), + HypervisorHostname: utils.StringPtr("HOST-1"), + HypervisorNetmask: utils.StringPtr("255.255.255.0"), + HypervisorGateway: utils.StringPtr("0.0.0.0"), + CvmIP: utils.StringPtr("10.0.0.0"), + CvmNetmask: utils.StringPtr("255.255.255.0"), + IpmiIP: utils.StringPtr("10.0.0.0"), + HypervisorIP: utils.StringPtr("10.0.0.0"), + IpmiGateway: utils.StringPtr("0.0.0.0"), + UseExistingNetworkSettings: utils.BoolPtr(false), + }, + }, + HypervisorIsoDetails: &HypervisorIsoDetails{ + URL: utils.StringPtr("hypervisor.iso.tar.gz"), + }, + } + + type fields struct { + client *client.Client + } + + type args struct { + spec *CreateClusterInput + } + + ctx := context.TODO() + tests := []struct { + name string + fields fields + args args + want *CreateClusterResponse + wantErr bool + }{ + { + "Imaged Nodes and create Cluster ", + fields{c}, + args{input}, + clusterUUID, + false, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + got, err := op.CreateCluster(ctx, tt.args.spec) + if (err != nil) != tt.wantErr { + t.Errorf("Operations.CreateCluster() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Operations.CreateCluster() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestOperations_DeleteCluster(t *testing.T) { + mux, c, server := setup() + + defer server.Close() + + mux.HandleFunc("/api/fc/v1/imaged_clusters/4e87-4a75-960f", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodDelete) + }) + + type fields struct { + client *client.Client + } + + type args struct { + UUID string + } + ctx := context.TODO() + + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + "Test Delete Cluster OK", + fields{c}, + args{"4e87-4a75-960f"}, + false, + }, + + { + "Test Delete Cluster Errored", + fields{c}, + args{}, + true, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + op := Operations{ + client: tt.fields.client, + } + if err := op.DeleteCluster(ctx, tt.args.UUID); (err != nil) != tt.wantErr { + t.Errorf("Operations.DeleteCluster() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/client/fc/fc_test.go b/client/fc/fc_test.go new file mode 100644 index 000000000..f2718a2e3 --- /dev/null +++ b/client/fc/fc_test.go @@ -0,0 +1,44 @@ +package foundationcentral + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-nutanix/client" +) + +func TestNewFoundationCentralClient(t *testing.T) { + // verifies positive client creation + cred := client.Credentials{ + URL: "foo.com", + Username: "username", + Password: "password", + Port: "", + Endpoint: "0.0.0.0", + Insecure: true, + FoundationEndpoint: "10.0.0.0", + FoundationPort: "8000", + RequiredFields: nil, + } + _, err := NewFoundationCentralClient(cred) + if err != nil { + t.Errorf(err.Error()) + } + + // verify missing client scenario + cred2 := client.Credentials{ + URL: "foo.com", + Insecure: true, + RequiredFields: map[string][]string{ + "prism_central": {"username", "password", "endpoint"}, + "foundation_central": {"username", "password", "endpoint"}, + }, + } + FcClient2, err2 := NewFoundationCentralClient(cred2) + if err2 != nil { + t.Errorf(err2.Error()) + } + + if FcClient2.client.ErrorMsg == "" { + t.Errorf("NewFoundationCentralClient(%v) expected the base client in v3 client to have some error message", cred2) + } +} diff --git a/client/foundation/foundation_api_test.go b/client/foundation/foundation_api_test.go index fbfc45ec2..dd4c073f9 100644 --- a/client/foundation/foundation_api_test.go +++ b/client/foundation/foundation_api_test.go @@ -8,7 +8,6 @@ import ( ) func TestNewFoundationAPIClient(t *testing.T) { - // verifies positive client creation cred := client.Credentials{ URL: "foo.com", @@ -25,9 +24,9 @@ func TestNewFoundationAPIClient(t *testing.T) { if err != nil { t.Errorf(err.Error()) } - outUrl := fmt.Sprintf("http://%s:%s/", cred.FoundationEndpoint, cred.FoundationPort) - if foundationClient.client.BaseURL.String() != outUrl { - t.Errorf("NewFoundationAPIClient(%v) BaseUrl in base client of foundation client = %v, expected %v", cred, foundationClient.client.BaseURL.String(), outUrl) + outURL := fmt.Sprintf("http://%s:%s/", cred.FoundationEndpoint, cred.FoundationPort) + if foundationClient.client.BaseURL.String() != outURL { + t.Errorf("NewFoundationAPIClient(%v) BaseUrl in base client of foundation client = %v, expected %v", cred, foundationClient.client.BaseURL.String(), outURL) } // verify missing client scenario diff --git a/client/foundation/foundation_file_management_service_test.go b/client/foundation/foundation_file_management_service_test.go new file mode 100644 index 000000000..f601ae35d --- /dev/null +++ b/client/foundation/foundation_file_management_service_test.go @@ -0,0 +1,194 @@ +package foundation + +import ( + "context" + "fmt" + "io/ioutil" + "net/http" + "reflect" + "testing" + + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func TestFMOperations_ListNOSPackages(t *testing.T) { + mux, c, server := setup() + defer server.Close() + mux.HandleFunc("/foundation/enumerate_nos_packages", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodGet) + + // mock response + fmt.Fprintf(w, `[ + "package1", + "package2" + ]`) + }) + ctx := context.TODO() + + out := &ListNOSPackagesResponse{ + "package1", + "package2", + } + + op := FileManagementOperations{ + client: c, + } + + // checks + got, err := op.ListNOSPackages(ctx) + if err != nil { + t.Fatalf("FileManagementOperations.ListNOSPackages() error = %v", err) + } + if !reflect.DeepEqual(got, out) { + t.Errorf("FileManagementOperations.ListNOSPackages() got = %#v, want = %#v", got, out) + } +} + +func TestFMOperations_ListHypervisorISOs(t *testing.T) { + mux, c, server := setup() + defer server.Close() + mux.HandleFunc("/foundation/enumerate_hypervisor_isos", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodGet) + + // mock response + fmt.Fprintf(w, `{ + "hyperv": [{ + "filename": "hyperv1.iso", + "supported": true + }, + { + "filename": "hyperv2.iso", + "supported": false + } + ], + "kvm": [{ + "filename": "kvm1.iso", + "supported": true + }, + { + "filename": "kvm2.iso", + "supported": false + } + ] + }`) + }) + ctx := context.TODO() + + out := &ListHypervisorISOsResponse{ + Hyperv: []*HypervisorISOReference{ + { + Supported: utils.BoolPtr(true), + Filename: "hyperv1.iso", + }, + { + Supported: utils.BoolPtr(false), + Filename: "hyperv2.iso", + }, + }, + Kvm: []*HypervisorISOReference{ + { + Supported: utils.BoolPtr(true), + Filename: "kvm1.iso", + }, + { + Supported: utils.BoolPtr(false), + Filename: "kvm2.iso", + }, + }, + } + + op := FileManagementOperations{ + client: c, + } + + // checks + got, err := op.ListHypervisorISOs(ctx) + if err != nil { + t.Fatalf("FileManagementOperations.ListHypervisorISOs() error = %v", err) + } + if !reflect.DeepEqual(got, out) { + t.Errorf("FileManagementOperations.ListHypervisorISOs() got = %#v, want = %#v", got, out) + } +} + +func TestFMOperations_UploadImage(t *testing.T) { + mux, c, server := setup() + defer server.Close() + installerType := "kvm" + filename := "test_ahv.iso" + source := "foundation_api.go" + mux.HandleFunc("/foundation/upload", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPost) + + expectedURL := fmt.Sprintf("/foundation/upload?installer_type=%v&filename=%v", installerType, filename) + if expectedURL != r.URL.String() { + t.Errorf("FileManagementOperations.UploadImage() expected URL %v, got %v", expectedURL, r.URL.String()) + } + + body, _ := ioutil.ReadAll(r.Body) + file, _ := ioutil.ReadFile(source) + + if !reflect.DeepEqual(body, file) { + t.Errorf("FileManagementOperations.UploadImage() error: different uploaded files") + } + + // mock response + fmt.Fprintf(w, `{ + "md5sum": "1234QAA", + "name": "/home/foundation/kvm/%v", + "in_whitelist": false + }`, filename) + }) + ctx := context.TODO() + + out := &UploadImageResponse{ + Md5Sum: "1234QAA", + Name: "/home/foundation/kvm/" + filename, + InWhitelist: false, + } + + op := FileManagementOperations{ + client: c, + } + + // checks + got, err := op.UploadImage(ctx, installerType, filename, source) + if err != nil { + t.Fatalf("FileManagementOperations.UploadImage() error = %v", err) + } + if !reflect.DeepEqual(got, out) { + t.Errorf("FileManagementOperations.UploadImage() got = %#v, want = %#v", got, out) + } +} + +func TestFMOperations_DeleteImage(t *testing.T) { + mux, c, server := setup() + defer server.Close() + installerType := "kvm" + filename := "test_ahv.iso" + mux.HandleFunc("/foundation/delete/", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPost) + + body, err := ioutil.ReadAll(r.Body) + if err != nil { + t.Fatalf("FileManagementOperations.DeleteImage() error reading request body = %v", err) + } + + // check form encoded body + expected := fmt.Sprintf("filename=%v&installer_type=%v", filename, installerType) + if string(body) != expected { + t.Errorf("FileManagementOperations.DeleteImage() request body expected = %v, got = %v", expected, string(body)) + } + }) + ctx := context.TODO() + + op := FileManagementOperations{ + client: c, + } + + // checks + err := op.DeleteImage(ctx, installerType, filename) + if err != nil { + t.Fatalf("FileManagementOperations.DeleteImage() error = %v", err) + } +} diff --git a/client/foundation/foundation_networking_service_test.go b/client/foundation/foundation_networking_service_test.go new file mode 100644 index 000000000..c4ccee992 --- /dev/null +++ b/client/foundation/foundation_networking_service_test.go @@ -0,0 +1,281 @@ +package foundation + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "reflect" + "testing" + + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func TestNtwOperations_DiscoverNodes(t *testing.T) { + mux, c, server := setup() + defer server.Close() + mux.HandleFunc("/foundation/discover_nodes", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodGet) + + // mock response + fmt.Fprintf(w, `[{ + "model": "XCV10", + "nodes": [{ + "node_position": "A", + "hypervisor": "kvm", + "svm_ip": "0.0.0.0", + "configured": true + }], + "block_id": "GMD1" + }, { + "model": "XCV10", + "nodes": [{ + "node_position": "A", + "hypervisor": "kvm", + "svm_ip": "0.0.0.0", + "configured": false + } + ], + "block_id": "GMD2" + }]`) + }) + ctx := context.TODO() + + out := &DiscoverNodesAPIResponse{ + { + Model: "XCV10", + Nodes: []DiscoveredNode{ + { + NodePosition: "A", + Hypervisor: "kvm", + SvmIP: "0.0.0.0", + Configured: utils.BoolPtr(true), + }, + }, + BlockID: "GMD1", + }, + { + Model: "XCV10", + Nodes: []DiscoveredNode{ + { + NodePosition: "A", + Hypervisor: "kvm", + SvmIP: "0.0.0.0", + Configured: utils.BoolPtr(false), + }, + }, + BlockID: "GMD2", + }, + } + + op := NetworkingOperations{ + client: c, + } + + // checks + got, err := op.DiscoverNodes(ctx) + if err != nil { + t.Fatalf("NetworkingOperations.DiscoverNodes() error = %v", err) + } + if !reflect.DeepEqual(got, out) { + t.Errorf("NetworkingOperations.DiscoverNodes() got = %#v, want = %#v", got, out) + } +} + +func TestNtwOperations_NodeNetworkDetails(t *testing.T) { + mux, c, server := setup() + defer server.Close() + mux.HandleFunc("/foundation/node_network_details", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPost) + + expected := map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "ipv6_address": "ffff::ffff:fffff:ffff", + }, + map[string]interface{}{ + "ipv6_address": "ec12::ec12:ec12:ec12", + }, + }, + "timeout": "30", + } + + // checks + var v map[string]interface{} + err := json.NewDecoder(r.Body).Decode(&v) + if err != nil { + t.Fatalf("decode json: %v", err) + } + + if !reflect.DeepEqual(v, expected) { + t.Errorf("Request body\n got=%#v\nwant=%#v", v, expected) + } + + // mock response + fmt.Fprintf(w, `{ + "nodes" : [ + { + "cvm_ip" : "0.0.0.0", + "node_serial" : "NX1234", + "ipmi_ip" : "0.0.0.0" + }, + { + "cvm_ip" : "0.0.0.0", + "node_serial" : "NX1235", + "ipmi_ip" : "0.0.0.0" + } + ] + }`) + }) + ctx := context.TODO() + inp := &NodeNetworkDetailsInput{ + Nodes: []NodeIpv6Input{ + { + Ipv6Address: "ffff::ffff:fffff:ffff", + }, + { + Ipv6Address: "ec12::ec12:ec12:ec12", + }, + }, + Timeout: "30", + } + out := &NodeNetworkDetailsResponse{ + Nodes: []NodeNetworkDetail{ + { + CvmIP: "0.0.0.0", + NodeSerial: "NX1234", + IpmiIP: "0.0.0.0", + }, + { + CvmIP: "0.0.0.0", + NodeSerial: "NX1235", + IpmiIP: "0.0.0.0", + }, + }, + } + + op := NetworkingOperations{ + client: c, + } + + // checks + got, err := op.NodeNetworkDetails(ctx, inp) + if err != nil { + t.Fatalf("NetworkingOperations.NodeNetworkDetails() error = %v", err) + } + if !reflect.DeepEqual(got, out) { + t.Errorf("NetworkingOperations.NodeNetworkDetails() got = %#v, want = %#v", got, out) + } +} + +func TestNtwOperations_ConfigureIPMI(t *testing.T) { + mux, c, server := setup() + defer server.Close() + mux.HandleFunc("/foundation/ipmi_config", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPost) + + expected := map[string]interface{}{ + "blocks": []interface{}{ + map[string]interface{}{ + "nodes": []interface{}{ + map[string]interface{}{ + "ipmi_ip": "0.0.0.0", + "ipmi_mac": "ac:da:af:fa:af:fa", + "ipmi_configure_now": true, + }, + }, + "block_id": "GMD10", + }, + }, + "ipmi_netmask": "255.255.255.0", + "ipmi_gateway": "0.0.0.0", + "ipmi_user": "username", + "ipmi_password": "password", + } + + // checks + var v map[string]interface{} + err := json.NewDecoder(r.Body).Decode(&v) + if err != nil { + t.Fatalf("decode json: %v", err) + } + + if !reflect.DeepEqual(v, expected) { + t.Errorf("Request body\n got=%#v\nwant=%#v", v, expected) + } + + // mock response + fmt.Fprintf(w, `{ + "blocks": [ + { + "nodes":[ + { + "ipmi_ip": "0.0.0.0", + "ipmi_mac": "ac:da:af:fa:af:fa", + "ipmi_configure_now": true, + "ipmi_configure_successful": true, + "ipmi_message" : "success" + } + ], + "block_id": "GMD10" + } + ], + "ipmi_netmask": "255.255.255.0", + "ipmi_gateway": "0.0.0.0", + "ipmi_user": "username", + "ipmi_password": "password" + }`) + }) + ctx := context.TODO() + inp := &IPMIConfigAPIInput{ + IpmiUser: "username", + IpmiPassword: "password", + IpmiNetmask: "255.255.255.0", + IpmiGateway: "0.0.0.0", + Blocks: []IPMIConfigBlockInput{ + { + Nodes: []IPMIConfigNodeInput{ + { + IpmiIP: "0.0.0.0", + IpmiMac: "ac:da:af:fa:af:fa", + IpmiConfigureNow: true, + }, + }, + BlockID: "GMD10", + }, + }, + } + out := &IPMIConfigAPIResponse{ + IpmiUser: "username", + IpmiPassword: "password", + IpmiNetmask: "255.255.255.0", + IpmiGateway: "0.0.0.0", + Blocks: []IPMIConfigBlockResponse{ + { + Nodes: []IPMIConfigNodeResponse{ + { + IpmiIP: "0.0.0.0", + IpmiMac: "ac:da:af:fa:af:fa", + IpmiConfigureNow: true, + IpmiConfigureSuccessful: true, + IpmiMessage: "success", + }, + }, + BlockID: "GMD10", + }, + }, + } + + op := NetworkingOperations{ + client: c, + } + + // checks + got, err := op.ConfigureIPMI(ctx, inp) + if err != nil { + t.Fatalf("NetworkingOperations.ConfigureIPMI() error = %v", err) + } + if !reflect.DeepEqual(got, out) { + t.Errorf("NetworkingOperations.ConfigureIPMI() got = %#v, want = %#v", got, out) + } +} diff --git a/client/foundation/foundation_node_imaging_service_test.go b/client/foundation/foundation_node_imaging_service_test.go new file mode 100644 index 000000000..e2b9296b5 --- /dev/null +++ b/client/foundation/foundation_node_imaging_service_test.go @@ -0,0 +1,222 @@ +package foundation + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "testing" + + "github.com/terraform-providers/terraform-provider-nutanix/client" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func setup() (*http.ServeMux, *client.Client, *httptest.Server) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + c, _ := client.NewBaseClient(&client.Credentials{ + URL: "", + Username: "username", + Password: "password", + Port: "", + Endpoint: "0.0.0.0", + Insecure: true}, + absolutePath, + true) + c.UserAgent = userAgent + c.BaseURL, _ = url.Parse(server.URL) + + return mux, c, server +} + +func testHTTPMethod(t *testing.T, r *http.Request, expected string) { + if expected != r.Method { + t.Errorf("Request method = %v, expected %v", r.Method, expected) + } +} + +func TestNodeImagingOperations_ImageNodes(t *testing.T) { + mux, c, server := setup() + defer server.Close() + + mux.HandleFunc("/foundation/image_nodes", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodPost) + + expected := map[string]interface{}{ + "ipmi_password": "test_password", + "ipmi_user": "test_user", + "cvm_gateway": "0.0.0.0", + "cvm_netmask": "255.255.255.0", + "hypervisor_gateway": "0.0.0.0", + "hypervisor_netmask": "255.255.255.0", + "nos_package": "test_nos.tar.gz", + "hypervisor_iso": map[string]interface{}{}, + "blocks": []interface{}{ + map[string]interface{}{ + "block_id": "N123", + "nodes": []interface{}{ + map[string]interface{}{ + "ipmi_configure_now": true, + "ipmi_ip": "0.0.0.0", + "cvm_ip": "0.0.0.0", + "hypervisor_ip": "0.0.0.0", + "image_now": true, + "ipmi_password": "test_password", + "ipmi_user": "test_user", + "hypervisor_hostname": "test_hostname", + "hypervisor": "kvm", + "node_position": "A", + }, + }, + }, + }, + "clusters": []interface{}{ + map[string]interface{}{ + "redundancy_factor": float64(1), + "cluster_init_now": true, + "cluster_external_ip": nil, + "cluster_name": "test_cluster", + "cluster_members": []interface{}{"0.0.0.0"}, + }, + }, + } + + // checks + var v map[string]interface{} + err := json.NewDecoder(r.Body).Decode(&v) + if err != nil { + t.Fatalf("decode json: %v", err) + } + if !reflect.DeepEqual(v, expected) { + t.Errorf("Request body\n got=%#v\nwant=%#v", v, expected) + } + + // mock response + fmt.Fprintf(w, `{ + "session_id" : "123456-1234-123456" + }`) + }) + ctx := context.TODO() + inp := &ImageNodesInput{ + IpmiPassword: "test_password", + IpmiUser: "test_user", + CvmGateway: "0.0.0.0", + CvmNetmask: "255.255.255.0", + HypervisorGateway: "0.0.0.0", + HypervisorNetmask: "255.255.255.0", + NosPackage: "test_nos.tar.gz", + Blocks: []*Block{ + { + BlockID: "N123", + Nodes: []*Node{ + { + IpmiConfigureNow: utils.BoolPtr(true), + IpmiIP: "0.0.0.0", + IpmiUser: "test_user", + IpmiPassword: "test_password", + CvmIP: "0.0.0.0", + ImageNow: utils.BoolPtr(true), + HypervisorIP: "0.0.0.0", + HypervisorHostname: "test_hostname", + Hypervisor: "kvm", + NodePosition: "A", + }, + }, + }, + }, + Clusters: []*Clusters{ + { + RedundancyFactor: utils.Int64Ptr(1), + ClusterInitNow: utils.BoolPtr(true), + ClusterName: "test_cluster", + ClusterMembers: []string{"0.0.0.0"}, + }, + }, + } + + out := &ImageNodesAPIResponse{ + SessionID: "123456-1234-123456", + } + + op := NodeImagingOperations{ + client: c, + } + + // checks + got, err := op.ImageNodes(ctx, inp) + if err != nil { + t.Fatalf("NodeImagingOperations.ImageNodes() error = %v", err) + } + if !reflect.DeepEqual(got, out) { + t.Errorf("NodeImagingOperations.ImageNodes() got = %#v, want = %#v", got, out) + } +} + +func TestNodeImagingOperations_ImageNodesProgress(t *testing.T) { + mux, c, server := setup() + defer server.Close() + sessionID := "123456-1234-123456" + mux.HandleFunc("/foundation/progress", func(w http.ResponseWriter, r *http.Request) { + testHTTPMethod(t, r, http.MethodGet) + + // mock response + fmt.Fprintf(w, `{ + "session_id": "%v", + "imaging_stopped": true, + "aggregate_percent_complete": 100.00, + "clusters": [{ + "cluster_name": "test_cluster", + "time_elapsed": 102.33, + "cluster_members": [ + "0.0.0.0" + ], + "percent_complete": 100.00 + }], + "nodes": [{ + "cvm_ip": "0.0.0.0", + "hypervisor_ip": "0.0.0.0", + "time_elapsed": 102.33, + "percent_complete": 100.00 + }] + }`, sessionID) + }) + ctx := context.TODO() + + out := &ImageNodesProgressResponse{ + SessionID: "123456-1234-123456", + ImagingStopped: utils.BoolPtr(true), + AggregatePercentComplete: utils.Float64Ptr(100.00), + Clusters: []*ClusterProgress{ + { + ClusterName: "test_cluster", + TimeElapsed: utils.Float64Ptr(102.33), + ClusterMembers: []string{"0.0.0.0"}, + PercentComplete: utils.Float64Ptr(100.00), + }, + }, + Nodes: []*NodeProgress{ + { + CvmIP: "0.0.0.0", + HypervisorIP: "0.0.0.0", + TimeElapsed: utils.Float64Ptr(102.33), + PercentComplete: utils.Float64Ptr(100.00), + }, + }, + } + + op := NodeImagingOperations{ + client: c, + } + + // checks + got, err := op.ImageNodesProgress(ctx, sessionID) + if err != nil { + t.Fatalf("NodeImagingOperations.ImageNodesProgress() error = %v", err) + } + if !reflect.DeepEqual(got, out) { + t.Errorf("NodeImagingOperations.ImageNodesProgress() got = %#v, want = %#v", got, out) + } +} diff --git a/client/foundation/foundation_structs.go b/client/foundation/foundation_structs.go index 6b863024f..a51b2e77e 100644 --- a/client/foundation/foundation_structs.go +++ b/client/foundation/foundation_structs.go @@ -70,7 +70,7 @@ type FcSettings struct { type Clusters struct { EnableNs *bool `json:"enable_ns,omitempty"` BackplaneSubnet string `json:"backplane_subnet,omitempty"` - ClusterInitSuccessful *bool `json:"cluster_init_successful"` + ClusterInitSuccessful *bool `json:"cluster_init_successful,omitempty"` BackplaneNetmask string `json:"backplane_netmask,omitempty"` RedundancyFactor *int64 `json:"redundancy_factor"` BackplaneVlan string `json:"backplane_vlan,omitempty"` diff --git a/client/karbon/karbon_api_test.go b/client/karbon/karbon_api_test.go new file mode 100644 index 000000000..7d299b140 --- /dev/null +++ b/client/karbon/karbon_api_test.go @@ -0,0 +1,43 @@ +package karbon + +import ( + "testing" + + "github.com/terraform-providers/terraform-provider-nutanix/client" +) + +func TestNewKarbonAPIClient(t *testing.T) { + // verifies positive client creation + cred := client.Credentials{ + URL: "foo.com", + Username: "username", + Password: "password", + Port: "", + Endpoint: "0.0.0.0", + Insecure: true, + FoundationEndpoint: "10.0.0.0", + FoundationPort: "8000", + RequiredFields: nil, + } + _, err := NewKarbonAPIClient(cred) + if err != nil { + t.Errorf(err.Error()) + } + + // verify missing client scenario + cred2 := client.Credentials{ + URL: "foo.com", + Insecure: true, + RequiredFields: map[string][]string{ + "karbon": {"username", "password", "endpoint"}, + }, + } + v3Client2, err2 := NewKarbonAPIClient(cred2) + if err2 != nil { + t.Errorf(err2.Error()) + } + + if v3Client2.client.ErrorMsg == "" { + t.Errorf("NewKarbonAPIClient(%v) expected the base client in karbon client to have some error message", cred2) + } +} diff --git a/client/v3/v3_service_test.go b/client/v3/v3_service_test.go index b8c915e34..d7350f64a 100644 --- a/client/v3/v3_service_test.go +++ b/client/v3/v3_service_test.go @@ -1003,9 +1003,9 @@ func TestOperations_UploadImage(t *testing.T) { testHTTPMethod(t, r, http.MethodPut) bodyBytes, _ := ioutil.ReadAll(r.Body) - file, _ := ioutil.ReadFile("/v3.go") + file, _ := ioutil.ReadFile("v3.go") - if reflect.DeepEqual(bodyBytes, file) { + if !reflect.DeepEqual(bodyBytes, file) { t.Errorf("Operations.UploadImage() error: different uploaded files") } }) @@ -1027,7 +1027,7 @@ func TestOperations_UploadImage(t *testing.T) { { "TestOperations_UploadImage Upload Image", fields{c}, - args{"cfde831a-4e87-4a75-960f-89b0148aa2cc", "./v3.go"}, + args{"cfde831a-4e87-4a75-960f-89b0148aa2cc", "v3.go"}, }, } diff --git a/client/v3/v3_test.go b/client/v3/v3_test.go index dca654a9a..fd2b58862 100644 --- a/client/v3/v3_test.go +++ b/client/v3/v3_test.go @@ -1,54 +1,43 @@ package v3 import ( - "reflect" "testing" "github.com/terraform-providers/terraform-provider-nutanix/client" ) func TestNewV3Client(t *testing.T) { - cred := client.Credentials{URL: "foo.com", Username: "username", Password: "password", Port: "", Endpoint: "0.0.0.0", Insecure: true} - c, _ := NewV3Client(cred) - - cred2 := client.Credentials{URL: "^^^", Username: "username", Password: "password", Port: "", Endpoint: "0.0.0.0", Insecure: true} - c2, _ := NewV3Client(cred2) - - type args struct { - credentials client.Credentials + // verifies positive client creation + cred := client.Credentials{ + URL: "foo.com", + Username: "username", + Password: "password", + Port: "", + Endpoint: "0.0.0.0", + Insecure: true, + FoundationEndpoint: "10.0.0.0", + FoundationPort: "8000", + RequiredFields: nil, + } + _, err := NewV3Client(cred) + if err != nil { + t.Errorf(err.Error()) } - tests := []struct { - name string - args args - want *Client - wantErr bool - }{ - { - "test one", - args{cred}, - c, - false, - }, - { - "test one", - args{cred2}, - c2, - true, + // verify missing client scenario + cred2 := client.Credentials{ + URL: "foo.com", + Insecure: true, + RequiredFields: map[string][]string{ + "prism_central": {"username", "password", "endpoint"}, }, } + v3Client2, err2 := NewV3Client(cred2) + if err2 != nil { + t.Errorf(err2.Error()) + } - for _, tt := range tests { - tt := tt - t.Run(tt.name, func(t *testing.T) { - got, err := NewV3Client(tt.args.credentials) - if (err != nil) != tt.wantErr { - t.Errorf("NewV3Client() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("NewV3Client() = %v, want %v", got, tt.want) - } - }) + if v3Client2.client.ErrorMsg == "" { + t.Errorf("NewV3Client(%v) expected the base client in v3 client to have some error message", cred2) } } diff --git a/nutanix/config_test.go b/nutanix/config_test.go index 29c48f368..fdc6cebfb 100644 --- a/nutanix/config_test.go +++ b/nutanix/config_test.go @@ -7,18 +7,22 @@ import ( func TestConfig_Client(t *testing.T) { type fields struct { - Endpoint string - Username string - Password string - Port string - Insecure bool + Endpoint string + Username string + Password string + Port string + Insecure bool + FoundationPort string + FoundationEndpoint string } config := &Config{ - Endpoint: "http://localhost", - Username: "test", - Password: "test", - Port: "8080", - Insecure: true, + Endpoint: "http://localhost", + Username: "test", + Password: "test", + Port: "8080", + Insecure: true, + FoundationPort: "8000", + FoundationEndpoint: "0.0.0.0", } client, err := config.Client() @@ -35,11 +39,13 @@ func TestConfig_Client(t *testing.T) { { name: "new client", fields: fields{ - Endpoint: "http://localhost", - Username: "test", - Password: "test", - Port: "8080", - Insecure: true, + Endpoint: "http://localhost", + Username: "test", + Password: "test", + Port: "8080", + Insecure: true, + FoundationPort: "8000", + FoundationEndpoint: "0.0.0.0", }, want: client, wantErr: false, @@ -50,11 +56,13 @@ func TestConfig_Client(t *testing.T) { tt := tt t.Run(tt.name, func(t *testing.T) { c := &Config{ - Endpoint: tt.fields.Endpoint, - Username: tt.fields.Username, - Password: tt.fields.Password, - Port: tt.fields.Port, - Insecure: tt.fields.Insecure, + Endpoint: tt.fields.Endpoint, + Username: tt.fields.Username, + Password: tt.fields.Password, + Port: tt.fields.Port, + Insecure: tt.fields.Insecure, + FoundationEndpoint: tt.fields.FoundationEndpoint, + FoundationPort: tt.fields.FoundationPort, } got, err := c.Client() if (err != nil) != tt.wantErr { diff --git a/nutanix/data_source_nutanix_assert_helper_test.go b/nutanix/data_source_nutanix_assert_helper_test.go new file mode 100644 index 000000000..913e7296e --- /dev/null +++ b/nutanix/data_source_nutanix_assert_helper_test.go @@ -0,0 +1,37 @@ +package nutanix + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccNutanixAssertHelperDS(t *testing.T) { + name := "checks" + errorMsg := "Error message for nutanix assert helper" + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccNutanixAssertHelperDS(name, "false", errorMsg), + ExpectError: regexp.MustCompile(errorMsg), + }, + { + Config: testAccNutanixAssertHelperDS(name, "true", errorMsg), + }, + }, + }) +} + +func testAccNutanixAssertHelperDS(name, condition, errMsg string) string { + return fmt.Sprintf(` + data "nutanix_assert_helper" "%s" { + checks { + condition = %s + error_message = "%s" + } + } + `, name, condition, errMsg) +} diff --git a/nutanix/data_source_nutanix_foundation_central_api_keys_test.go b/nutanix/data_source_nutanix_foundation_central_api_keys_test.go new file mode 100644 index 000000000..8db86e6b4 --- /dev/null +++ b/nutanix/data_source_nutanix_foundation_central_api_keys_test.go @@ -0,0 +1,62 @@ +package nutanix + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFCAPIKeysDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAPIKeysDataSourceConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_list_api_keys.test", "api_keys.#"), + ), + }, + }, + }) +} + +func TestAccFCAPIKeysDataSource_KeyUUID(t *testing.T) { + apiKeyName := acctest.RandomWithPrefix("test-key") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAPIKeysDataSourceConfigWithKeyUUID(apiKeyName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.nutanix_foundation_central_api_keys.k1", "alias", apiKeyName), + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_api_keys.k1", "alias"), + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_api_keys.k1", "created_timestamp"), + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_api_keys.k1", "current_time"), + ), + }, + }, + }) +} + +func testAccAPIKeysDataSourceConfig() string { + return ` + data "nutanix_foundation_central_list_api_keys" "test"{} + ` +} + +func testAccAPIKeysDataSourceConfigWithKeyUUID(apiKeyName string) string { + return fmt.Sprintf(` + resource "nutanix_foundation_central_api_keys" "apk"{ + alias = "%s" + } + + data "nutanix_foundation_central_api_keys" "k1"{ + key_uuid = "${nutanix_foundation_central_api_keys.apk.key_uuid}" + } + + `, apiKeyName) +} diff --git a/nutanix/data_source_nutanix_foundation_central_cluster_details_test.go b/nutanix/data_source_nutanix_foundation_central_cluster_details_test.go new file mode 100644 index 000000000..f4033d796 --- /dev/null +++ b/nutanix/data_source_nutanix_foundation_central_cluster_details_test.go @@ -0,0 +1,55 @@ +package nutanix + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFCClusterDetailsDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFCClusterDetailsDataSourceConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_imaged_clusters_list.cls", "imaged_clusters.#"), + ), + }, + }, + }) +} + +func TestAccFCClusterDetailsDataSource_ClusterUUID(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFCClusterDetailsDataSourceConfigWithUUID(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_imaged_clusters_list.cls", "imaged_clusters.#"), + resource.TestCheckResourceAttr("data.nutanix_foundation_central_cluster_details.k1", "storage_node_count", "0"), + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_cluster_details.k1", "imaged_cluster_uuid"), + ), + }, + }, + }) +} + +func testAccFCClusterDetailsDataSourceConfig() string { + return ` + data "nutanix_foundation_central_imaged_clusters_list" "cls" {} + ` +} + +func testAccFCClusterDetailsDataSourceConfigWithUUID() string { + return ` + data "nutanix_foundation_central_imaged_clusters_list" "cls" {} + + data "nutanix_foundation_central_cluster_details" "k1"{ + imaged_cluster_uuid = "${data.nutanix_foundation_central_imaged_clusters_list.cls.imaged_clusters[0].imaged_cluster_uuid}" + } + ` +} diff --git a/nutanix/data_source_nutanix_foundation_central_imaged_clusters_list_test.go b/nutanix/data_source_nutanix_foundation_central_imaged_clusters_list_test.go new file mode 100644 index 000000000..3644bf33d --- /dev/null +++ b/nutanix/data_source_nutanix_foundation_central_imaged_clusters_list_test.go @@ -0,0 +1,28 @@ +package nutanix + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFCClusterListDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFCClusterListDataSourceConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_imaged_clusters_list.cls", "imaged_clusters.#"), + ), + }, + }, + }) +} + +func testAccFCClusterListDataSourceConfig() string { + return ` + data "nutanix_foundation_central_imaged_clusters_list" "cls" {} + ` +} diff --git a/nutanix/data_source_nutanix_foundation_central_imaged_node_details_test.go b/nutanix/data_source_nutanix_foundation_central_imaged_node_details_test.go new file mode 100644 index 000000000..fdd6b295a --- /dev/null +++ b/nutanix/data_source_nutanix_foundation_central_imaged_node_details_test.go @@ -0,0 +1,55 @@ +package nutanix + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFCNodeDetailsDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFCNodeDetailsDataSourceConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_imaged_nodes_list.cls", "imaged_nodes.#"), + ), + }, + }, + }) +} + +func TestAccFCNodeDetailsDataSource_NodeUUID(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFCNodeDetailsDataSourceConfigWithUUID(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_imaged_nodes_list.cls", "imaged_nodes.#"), + resource.TestCheckResourceAttr("data.nutanix_foundation_central_imaged_node_details.k1", "cvm_vlan_id", "0"), + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_imaged_node_details.k1", "imaged_node_uuid"), + ), + }, + }, + }) +} + +func testAccFCNodeDetailsDataSourceConfig() string { + return ` + data "nutanix_foundation_central_imaged_nodes_list" "cls" {} + ` +} + +func testAccFCNodeDetailsDataSourceConfigWithUUID() string { + return ` + data "nutanix_foundation_central_imaged_nodes_list" "cls" {} + + data "nutanix_foundation_central_imaged_node_details" "k1"{ + imaged_node_uuid = "${data.nutanix_foundation_central_imaged_nodes_list.cls.imaged_nodes[0].imaged_node_uuid}" + } + ` +} diff --git a/nutanix/data_source_nutanix_foundation_central_imaged_nodes_list_test.go b/nutanix/data_source_nutanix_foundation_central_imaged_nodes_list_test.go new file mode 100644 index 000000000..78301d276 --- /dev/null +++ b/nutanix/data_source_nutanix_foundation_central_imaged_nodes_list_test.go @@ -0,0 +1,28 @@ +package nutanix + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFCNodesListDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFCNodeListDataSourceConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_imaged_nodes_list.cls", "imaged_nodes.#"), + ), + }, + }, + }) +} + +func testAccFCNodeListDataSourceConfig() string { + return ` + data "nutanix_foundation_central_imaged_nodes_list" "cls" {} + ` +} diff --git a/nutanix/data_source_nutanix_foundation_central_list_api_keys_test.go b/nutanix/data_source_nutanix_foundation_central_list_api_keys_test.go new file mode 100644 index 000000000..3cf99a9a6 --- /dev/null +++ b/nutanix/data_source_nutanix_foundation_central_list_api_keys_test.go @@ -0,0 +1,28 @@ +package nutanix + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFCAPIKeysListDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccAPIKeysListDataSourceConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.nutanix_foundation_central_list_api_keys.test", "api_keys.#"), + ), + }, + }, + }) +} + +func testAccAPIKeysListDataSourceConfig() string { + return ` + data "nutanix_foundation_central_list_api_keys" "test"{} + ` +} diff --git a/nutanix/data_source_nutanix_foundation_discover_nodes_test.go b/nutanix/data_source_nutanix_foundation_discover_nodes_test.go new file mode 100644 index 000000000..e38fc72c3 --- /dev/null +++ b/nutanix/data_source_nutanix_foundation_discover_nodes_test.go @@ -0,0 +1,31 @@ +package nutanix + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFoundationDiscoverNodesDataSource(t *testing.T) { + name := "nodes" + resourcePath := "data.nutanix_foundation_discover_nodes." + name + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccFoundationPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testDiscoverNodesConfig(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourcePath, "entities.0.nodes.0.ipv6_address"), + resource.TestCheckResourceAttrSet(resourcePath, "entities.0.nodes.0.hypervisor"), + resource.TestCheckResourceAttrSet(resourcePath, "entities.0.block_id"), + ), + }, + }, + }) +} + +func testDiscoverNodesConfig(name string) string { + return fmt.Sprintf(`data "nutanix_foundation_discover_nodes" "%s" {}`, name) +} diff --git a/nutanix/data_source_nutanix_foundation_hypervisor_isos_test.go b/nutanix/data_source_nutanix_foundation_hypervisor_isos_test.go new file mode 100644 index 000000000..ac580c18b --- /dev/null +++ b/nutanix/data_source_nutanix_foundation_hypervisor_isos_test.go @@ -0,0 +1,30 @@ +package nutanix + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFoundationHypervisorISOSDataSource(t *testing.T) { + name := "hypervisor_isos" + resourcePath := "data.nutanix_foundation_hypervisor_isos." + name + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccFoundationPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testHypervisorISOSDSConfig(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourcePath, "kvm.0.filename"), + resource.TestCheckResourceAttrSet(resourcePath, "esx.0.filename"), + ), + }, + }, + }) +} + +func testHypervisorISOSDSConfig(name string) string { + return fmt.Sprintf(`data "nutanix_foundation_hypervisor_isos" "%s" {}`, name) +} diff --git a/nutanix/data_source_nutanix_foundation_node_network_details_test.go b/nutanix/data_source_nutanix_foundation_node_network_details_test.go new file mode 100644 index 000000000..41db9290a --- /dev/null +++ b/nutanix/data_source_nutanix_foundation_node_network_details_test.go @@ -0,0 +1,34 @@ +package nutanix + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFoundationNodeNetworkDetailsDataSource(t *testing.T) { + name := "nodes" + resourcePath := "data.nutanix_foundation_node_network_details." + name + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccFoundationPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testNodeNetworkDetailsConfig(name, foundationVars.IPv6Addresses), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourcePath, "nodes.#", "2"), + resource.TestCheckResourceAttrSet(resourcePath, "nodes.0.ipmi_ip"), + resource.TestCheckResourceAttrSet(resourcePath, "nodes.1.ipmi_ip"), + ), + }, + }, + }) +} + +func testNodeNetworkDetailsConfig(name string, ipv6Addr []string) string { + return fmt.Sprintf(` + data "nutanix_foundation_node_network_details" "%s" { + ipv6_addresses = ["%s", "%s"] + }`, name, ipv6Addr[0], ipv6Addr[1]) +} diff --git a/nutanix/data_source_nutanix_foundation_nos_packages_test.go b/nutanix/data_source_nutanix_foundation_nos_packages_test.go new file mode 100644 index 000000000..46d57398b --- /dev/null +++ b/nutanix/data_source_nutanix_foundation_nos_packages_test.go @@ -0,0 +1,29 @@ +package nutanix + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFoundationNosPackagesDataSource(t *testing.T) { + name := "nos_packages" + resourcePath := "data.nutanix_foundation_nos_packages." + name + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccFoundationPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testNosPackagesDSConfig(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourcePath, "entities.0"), + ), + }, + }, + }) +} + +func testNosPackagesDSConfig(name string) string { + return fmt.Sprintf(`data "nutanix_foundation_nos_packages" "%s" {}`, name) +} diff --git a/nutanix/data_source_nutanix_karbon_cluster_kubeconfig_test.go b/nutanix/data_source_nutanix_karbon_cluster_kubeconfig_test.go index c51afbe3c..04bf2855f 100644 --- a/nutanix/data_source_nutanix_karbon_cluster_kubeconfig_test.go +++ b/nutanix/data_source_nutanix_karbon_cluster_kubeconfig_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccNutanixKarbonClusterKubeConfigDataSource_basic(t *testing.T) { +func TestAccKarbonClusterKubeConfigDataSource_basic(t *testing.T) { t.Skip() r := acctest.RandInt() subnetName := testVars.SubnetName @@ -30,7 +30,7 @@ func TestAccNutanixKarbonClusterKubeConfigDataSource_basic(t *testing.T) { }) } -func TestAccNutanixKarbonClusterKubeConfigDataSource_basicByName(t *testing.T) { +func TestAccKarbonClusterKubeConfigDataSource_basicByName(t *testing.T) { r := acctest.RandInt() subnetName := testVars.SubnetName defaultContainter := testVars.DefaultContainerName diff --git a/nutanix/data_source_nutanix_karbon_cluster_ssh_test.go b/nutanix/data_source_nutanix_karbon_cluster_ssh_test.go index e4cf52997..2c144a120 100644 --- a/nutanix/data_source_nutanix_karbon_cluster_ssh_test.go +++ b/nutanix/data_source_nutanix_karbon_cluster_ssh_test.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccNutanixKarbonClusterSSHDataSource_basicx(t *testing.T) { +func TestAccKarbonClusterSSHDataSource_basicx(t *testing.T) { t.Skip() r := acctest.RandInt() //resourceName := "nutanix_karbon_cluster.cluster" @@ -30,7 +30,7 @@ func TestAccNutanixKarbonClusterSSHDataSource_basicx(t *testing.T) { }) } -func TestAccNutanixKarbonClusterSSHDataSource_basicByName(t *testing.T) { +func TestAccKarbonClusterSSHDataSource_basicByName(t *testing.T) { r := acctest.RandInt() //resourceName := "nutanix_karbon_cluster.cluster" subnetName := testVars.SubnetName diff --git a/nutanix/data_source_nutanix_karbon_cluster_test.go b/nutanix/data_source_nutanix_karbon_cluster_test.go index bc0299731..107c47340 100644 --- a/nutanix/data_source_nutanix_karbon_cluster_test.go +++ b/nutanix/data_source_nutanix_karbon_cluster_test.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccNutanixKarbonClusterDataSource_basic(t *testing.T) { +func TestAccKarbonClusterDataSource_basic(t *testing.T) { t.Skip() r := acctest.RandInt() dataSourceName := "data.nutanix_karbon_cluster.kcluster" @@ -29,7 +29,7 @@ func TestAccNutanixKarbonClusterDataSource_basic(t *testing.T) { }) } -func TestAccNutanixKarbonClusterDataSource_basicByName(t *testing.T) { +func TestAccKarbonClusterDataSource_basicByName(t *testing.T) { r := acctest.RandInt() //resourceName := "nutanix_karbon_cluster.cluster" subnetName := testVars.SubnetName diff --git a/nutanix/data_source_nutanix_karbon_clusters_test.go b/nutanix/data_source_nutanix_karbon_clusters_test.go index a986771ed..54c529a42 100644 --- a/nutanix/data_source_nutanix_karbon_clusters_test.go +++ b/nutanix/data_source_nutanix_karbon_clusters_test.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccNutanixKarbonClustersDataSource_basic(t *testing.T) { +func TestAccKarbonClustersDataSource_basic(t *testing.T) { r := acctest.RandInt() //resourceName := "nutanix_karbon_cluster.cluster" subnetName := testVars.SubnetName diff --git a/nutanix/data_source_recovery_plan_test.go b/nutanix/data_source_recovery_plan_test.go index f1af3703e..7709c9594 100644 --- a/nutanix/data_source_recovery_plan_test.go +++ b/nutanix/data_source_recovery_plan_test.go @@ -8,7 +8,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestAccRecoveryPlanDataSourceConfig_WithID(t *testing.T) { +func TestAccNutanixRecoveryPlanDataSourceConfig_WithID(t *testing.T) { resourceName := "nutanix_recovery_plan.test" name := acctest.RandomWithPrefix("test-recovery-name-dou") @@ -42,7 +42,7 @@ func TestAccRecoveryPlanDataSourceConfig_WithID(t *testing.T) { }) } -func TestAccRecoveryPlanDataSourceConfig_WithName(t *testing.T) { +func TestAccNutanixRecoveryPlanDataSourceConfig_WithName(t *testing.T) { name := acctest.RandomWithPrefix("test-recovery-name") nameUpdated := acctest.RandomWithPrefix("test-recovery-name") description := acctest.RandomWithPrefix("test-recovery-desc") diff --git a/nutanix/internal/data_source_assert_helper.go b/nutanix/internal/data_source_assert_helper.go index 887e43762..49d7080b9 100644 --- a/nutanix/internal/data_source_assert_helper.go +++ b/nutanix/internal/data_source_assert_helper.go @@ -4,6 +4,7 @@ import ( "context" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -51,5 +52,6 @@ func dataSourceAssertLogic(ctx context.Context, d *schema.ResourceData, meta int if len(diags) > 0 { return diags } + d.SetId(resource.UniqueId()) return nil } diff --git a/nutanix/main_test.go b/nutanix/main_test.go index 36d60295e..edf7f0a23 100644 --- a/nutanix/main_test.go +++ b/nutanix/main_test.go @@ -31,25 +31,74 @@ type TestConfig struct { } `json:"ad_rule_target"` } -var testVars TestConfig +type IPMIConfig struct { + IpmiGateway string `json:"ipmi_gateway"` + IpmiNetmask string `json:"ipmi_netmask"` + IpmiUser string `json:"ipmi_user"` + IpmiPassword string `json:"ipmi_password"` + IpmiIP string `json:"ipmi_ip"` + IpmiMac string `json:"ipmi_mac"` +} -func TestMain(m *testing.M) { - log.Println("Do some crazy stuff before tests!") +type FoundationVars struct { + IPv6Addresses []string `json:"ipv6_addresses"` + IpmiConfig IPMIConfig `json:"ipmi_config"` + Blocks []struct { + Nodes []struct { + IpmiIP string `json:"ipmi_ip"` + IpmiPassword string `json:"ipmi_password"` + IpmiUser string `json:"ipmi_user"` + IpmiNetmask string `json:"ipmi_netmask"` + IpmiGateway string `json:"ipmi_gateway"` + CvmIP string `json:"cvm_ip"` + HypervisorIP string `json:"hypervisor_ip"` + Hypervisor string `json:"hypervisor"` + HypervisorHostname string `json:"hypervisor_hostname"` + NodePosition string `json:"node_position"` + IPv6Address string `json:"ipv6_address"` + CurrentNetworkInterface string `json:"current_network_interface"` + ImagedNodeUUID string `json:"imaged_node_uuid"` + HypervisorType string `json:"hypervisor_type"` + } `json:"nodes"` + BlockID string `json:"block_id"` + CvmGateway string `json:"cvm_gateway"` + HypervisorGateway string `json:"hypervisor_gateway"` + CvmNetmask string `json:"cvm_netmask"` + HypervisorNetmask string `json:"hypervisor_netmask"` + IpmiUser string `json:"ipmi_user"` + AosPackageURL string `json:"aos_package_url"` + UseExistingNetworkSettings bool `json:"use_existing_network_settings"` + ImageNow bool `json:"image_now"` + CommonNetworkSettings struct { + CvmDNSServers []string `json:"cvm_dns_servers"` + HypervisorDNSServers []string `json:"hypervisor_dns_servers"` + CvmNtpServers []string `json:"cvm_ntp_servers"` + HypervisorNtpServers []string `json:"hypervisor_ntp_servers"` + } `json:"common_network_settings"` + } `json:"blocks"` +} +var testVars TestConfig +var foundationVars FoundationVars + +func loadVars(filepath string, varStuct interface{}) { // Read config.json from home current path - configData, err := os.ReadFile("../test_config.json") + configData, err := os.ReadFile(filepath) if err != nil { log.Printf("Got this error while reading config.json: %s", err.Error()) os.Exit(1) } - err = json.Unmarshal(configData, &testVars) + err = json.Unmarshal(configData, varStuct) if err != nil { log.Printf("Got this error while unmarshalling config.json: %s", err.Error()) os.Exit(1) } - - log.Println(testVars) +} +func TestMain(m *testing.M) { + log.Println("Do some crazy stuff before tests!") + loadVars("../test_config.json", &testVars) + loadVars("../test_foundation_config.json", &foundationVars) os.Exit(m.Run()) } diff --git a/nutanix/provider_test.go b/nutanix/provider_test.go index 4068838d8..d86f59079 100644 --- a/nutanix/provider_test.go +++ b/nutanix/provider_test.go @@ -42,6 +42,13 @@ func testAccPreCheck(t *testing.T) { } } +func testAccFoundationPreCheck(t *testing.T) { + if os.Getenv("FOUNDATION_ENDPOINT") == "" || + os.Getenv("FOUNDATION_PORT") == "" { + t.Fatal("`FOUNDATION_ENDPOINT` and `FOUNDATION_PORT` must be set for foundation acceptance testing") + } +} + func randIntBetween(min, max int) int { rand.Seed(time.Now().UnixNano()) return rand.Intn(max-min) + min diff --git a/nutanix/resource_nutanix_foundation_central_api_keys_test.go b/nutanix/resource_nutanix_foundation_central_api_keys_test.go new file mode 100644 index 000000000..50a124f5f --- /dev/null +++ b/nutanix/resource_nutanix_foundation_central_api_keys_test.go @@ -0,0 +1,34 @@ +package nutanix + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFCAPIKey_basic(t *testing.T) { + name := acctest.RandomWithPrefix("test-key") + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckNutanixAddressGroupDestroy, + Steps: []resource.TestStep{ + { + Config: testAccFCAPIKeyConfig(name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("nutanix_foundation_central_api_keys.test", "alias", name), + ), + }, + }, + }) +} + +func testAccFCAPIKeyConfig(name string) string { + return fmt.Sprintf(` + resource "nutanix_foundation_central_api_keys" "test"{ + alias = "%s" + } +`, name) +} diff --git a/nutanix/resource_nutanix_foundation_central_image_nodes_test.go b/nutanix/resource_nutanix_foundation_central_image_nodes_test.go new file mode 100644 index 000000000..f8b59cc3e --- /dev/null +++ b/nutanix/resource_nutanix_foundation_central_image_nodes_test.go @@ -0,0 +1,101 @@ +package nutanix + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFCImageNodesResource(t *testing.T) { + name := "batch2" + resourcePath := "nutanix_foundation_central_image_cluster." + name + clusterName := "test_cluster" + // get file file path to config having nodes info + path, _ := os.Getwd() + filepath := path + "/../test_foundation_config.json" + + // using block 1 in the test_foundation_config.json for this testcase + blockNum := 1 + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testFCImageNodesResource(filepath, blockNum, name, clusterName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourcePath, "cluster_name", clusterName), + ), + }, + }, + }) +} + +func testFCImageNodesResource(filepath string, blockNum int, name, clusterName string) string { + return fmt.Sprintf(` + locals{ + config = (jsondecode(file("%[1]s"))).blocks[%[2]v] + } + + resource "nutanix_foundation_central_image_cluster" "%[3]s" { + aos_package_url = local.config.aos_package_url + + node_list{ + cvm_gateway = local.config.cvm_gateway + cvm_netmask = local.config.cvm_netmask + cvm_ip = local.config.nodes[0].cvm_ip + hypervisor_gateway = local.config.hypervisor_gateway + hypervisor_netmask = local.config.hypervisor_netmask + hypervisor_ip = local.config.nodes[0].hypervisor_ip + hypervisor_hostname = local.config.nodes[0].hypervisor_hostname + imaged_node_uuid = local.config.nodes[0].imaged_node_uuid + use_existing_network_settings = local.config.use_existing_network_settings + ipmi_ip = local.config.nodes[0].ipmi_ip + ipmi_netmask = local.config.nodes[0].ipmi_netmask + ipmi_gateway = local.config.nodes[0].ipmi_gateway + image_now = local.config.image_now + hypervisor_type = local.config.nodes[0].hypervisor_type + } + node_list{ + cvm_gateway = local.config.cvm_gateway + cvm_netmask = local.config.cvm_netmask + cvm_ip = local.config.nodes[1].cvm_ip + hypervisor_gateway = local.config.hypervisor_gateway + hypervisor_netmask = local.config.hypervisor_netmask + hypervisor_ip = local.config.nodes[1].hypervisor_ip + hypervisor_hostname = local.config.nodes[1].hypervisor_hostname + imaged_node_uuid = local.config.nodes[1].imaged_node_uuid + use_existing_network_settings = local.config.use_existing_network_settings + ipmi_ip = local.config.nodes[1].ipmi_ip + ipmi_netmask = local.config.nodes[1].ipmi_netmask + ipmi_gateway = local.config.nodes[1].ipmi_gateway + image_now = local.config.image_now + hypervisor_type = local.config.nodes[1].hypervisor_type + } + node_list{ + cvm_gateway = local.config.cvm_gateway + cvm_netmask = local.config.cvm_netmask + cvm_ip = local.config.nodes[2].cvm_ip + hypervisor_gateway = local.config.hypervisor_gateway + hypervisor_netmask = local.config.hypervisor_netmask + hypervisor_ip = local.config.nodes[2].hypervisor_ip + hypervisor_hostname = local.config.nodes[2].hypervisor_hostname + imaged_node_uuid = local.config.nodes[2].imaged_node_uuid + use_existing_network_settings = local.config.use_existing_network_settings + ipmi_ip = local.config.nodes[2].ipmi_ip + ipmi_netmask = local.config.nodes[2].ipmi_netmask + ipmi_gateway = local.config.nodes[2].ipmi_gateway + image_now = local.config.image_now + hypervisor_type = local.config.nodes[2].hypervisor_type + } + common_network_settings{ + cvm_dns_servers = [local.config.common_network_settings.cvm_dns_servers[0]] + hypervisor_dns_servers = [local.config.common_network_settings.hypervisor_dns_servers[0]] + cvm_ntp_servers = [local.config.common_network_settings.cvm_ntp_servers[0]] + hypervisor_ntp_servers = [local.config.common_network_settings.hypervisor_ntp_servers[0]] + } + redundancy_factor = 2 + cluster_name = "%[4]s" + }`, filepath, blockNum, name, clusterName) +} diff --git a/nutanix/resource_nutanix_foundation_image_nodes.go b/nutanix/resource_nutanix_foundation_image_nodes.go index 44edd7c21..84b176a56 100644 --- a/nutanix/resource_nutanix_foundation_image_nodes.go +++ b/nutanix/resource_nutanix_foundation_image_nodes.go @@ -15,6 +15,7 @@ import ( var ( ImageMinTimeout = 60 * time.Minute AggregatePercentComplete = 100 + ClusterURL = "https://%s:9440/" ) func resourceFoundationImageNodes() *schema.Resource { @@ -303,7 +304,8 @@ func resourceFoundationImageNodes() *schema.Resource { }, "cluster_init_now": { Type: schema.TypeBool, - Required: true, + Optional: true, + Default: true, ForceNew: true, }, "hypervisor_ntp_servers": { @@ -591,6 +593,7 @@ func resourceFoundationImageNodes() *schema.Resource { "image_now": { Type: schema.TypeBool, Optional: true, + Default: true, ForceNew: true, }, "ucsm_managed_mode": { @@ -708,6 +711,22 @@ func resourceFoundationImageNodes() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "cluster_urls": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cluster_url": { + Type: schema.TypeString, + Computed: true, + }, + "cluster_name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, }, } } @@ -915,7 +934,18 @@ func resourceFoundationImageNodesCreate(ctx context.Context, d *schema.ResourceD } d.SetId(resp.SessionID) - + d.Set("session_id", resp.SessionID) + + // set cluster urls in state file + clusterURLs := make([]map[string]interface{}, len(request.Clusters)) + for k, v := range request.Clusters { + c := map[string]interface{}{ + "cluster_url": fmt.Sprintf(ClusterURL, v.ClusterMembers[0]), + "cluster_name": v.ClusterName, + } + clusterURLs[k] = c + } + d.Set("cluster_urls", clusterURLs) return nil } diff --git a/nutanix/resource_nutanix_foundation_image_nodes_test.go b/nutanix/resource_nutanix_foundation_image_nodes_test.go new file mode 100644 index 000000000..082bdbf36 --- /dev/null +++ b/nutanix/resource_nutanix_foundation_image_nodes_test.go @@ -0,0 +1,199 @@ +package nutanix + +import ( + "fmt" + "os" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFoundationImageNodesResource(t *testing.T) { + name := "batch1" + resourcePath := "nutanix_foundation_image_nodes." + name + + // get file file path to config having nodes info + path, _ := os.Getwd() + filepath := path + "/../test_foundation_config.json" + + // using block 0 in the test_foundation_config.json for this testcase + blockNum := 0 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccFoundationPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testImageNodesResource(filepath, blockNum, name), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourcePath, "session_id"), + resource.TestCheckResourceAttr(resourcePath, "cluster_urls.0.cluster_name", foundationVars.Blocks[0].Nodes[0].HypervisorHostname), + ), + }, + }, + }) +} + +// Checks negative scenario for a given invalid nos file name +func TestAccFoundationImageNodesResource_InvalidNosError(t *testing.T) { + name := "batch1" + + // get file file path to config having nodes info + path, _ := os.Getwd() + filepath := path + "/../test_foundation_config.json" + + // using block 0 in the test_foundation_config.json for this testcase + blockNum := 0 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccFoundationPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testImageNodesResourceInvalidNosError(filepath, blockNum, name), + ExpectError: regexp.MustCompile("Node imaging process failed due to error: Couldn't find nos_package at"), + }, + }, + }) +} + +func testImageNodesResource(filepath string, blockNum int, name string) string { + return fmt.Sprintf(` + data "nutanix_foundation_nos_packages" "nos"{} + + locals{ + config = (jsondecode(file("%s"))).blocks[%v] + } + + resource "nutanix_foundation_image_nodes" "%s" { + timeouts { + create = "80m" + } + cvm_gateway = local.config.cvm_gateway + hypervisor_gateway = local.config.hypervisor_gateway + cvm_netmask = local.config.cvm_netmask + hypervisor_netmask = local.config.cvm_netmask + ipmi_user = local.config.ipmi_user + nos_package = data.nutanix_foundation_nos_packages.nos.entities[0] + blocks { + // manual mode using ipmi (Bare Metal, AOS or DOS nodes) + nodes{ + cvm_ip = local.config.nodes[0].cvm_ip + hypervisor_ip = local.config.nodes[0].hypervisor_ip + hypervisor = local.config.nodes[0].hypervisor + hypervisor_hostname = local.config.nodes[0].hypervisor_hostname + ipmi_ip = local.config.nodes[0].ipmi_ip + ipmi_netmask = local.config.nodes[0].ipmi_netmask + ipmi_gateway = local.config.nodes[0].ipmi_gateway + ipmi_user = local.config.nodes[0].ipmi_user + ipmi_password = local.config.nodes[0].ipmi_password + node_position = local.config.nodes[0].node_position + } + // using cvm (AOS or DOS nodes) + nodes{ + cvm_ip = local.config.nodes[1].cvm_ip + hypervisor_ip = local.config.nodes[1].hypervisor_ip + hypervisor = local.config.nodes[1].hypervisor + hypervisor_hostname = local.config.nodes[1].hypervisor_hostname + ipmi_ip = local.config.nodes[1].ipmi_ip + ipv6_address = local.config.nodes[1].ipv6_address + current_network_interface = local.config.nodes[1].current_network_interface + node_position = local.config.nodes[1].node_position + device_hint = "vm_installer" + } + // using cvm (AOS or DOS nodes) + nodes{ + cvm_ip = local.config.nodes[2].cvm_ip + hypervisor_ip = local.config.nodes[2].hypervisor_ip + hypervisor = local.config.nodes[2].hypervisor + hypervisor_hostname = local.config.nodes[2].hypervisor_hostname + ipmi_ip = local.config.nodes[2].ipmi_ip + ipv6_address = local.config.nodes[2].ipv6_address + current_network_interface = local.config.nodes[2].current_network_interface + node_position = local.config.nodes[2].node_position + device_hint = "vm_installer" + ipmi_netmask = local.config.nodes[2].ipmi_netmask + ipmi_gateway = local.config.nodes[2].ipmi_gateway + } + block_id = local.config.block_id + } + clusters { + cluster_members = [ + local.config.nodes[0].cvm_ip, + local.config.nodes[1].cvm_ip, + local.config.nodes[2].cvm_ip + ] + redundancy_factor = 2 + cluster_name = local.config.nodes[0].hypervisor_hostname + } + }`, filepath, blockNum, name) +} + +func testImageNodesResourceInvalidNosError(filepath string, blockNum int, name string) string { + return fmt.Sprintf(` + locals{ + config = (jsondecode(file("%s"))).blocks[%v] + } + + resource "nutanix_foundation_image_nodes" "%s" { + timeouts { + create = "80m" + } + cvm_gateway = local.config.cvm_gateway + hypervisor_gateway = local.config.hypervisor_gateway + cvm_netmask = local.config.cvm_netmask + hypervisor_netmask = local.config.cvm_netmask + ipmi_user = local.config.ipmi_user + nos_package = "ironman" + blocks { + // manual mode using ipmi (Bare Metal, AOS or DOS nodes) + nodes{ + cvm_ip = local.config.nodes[0].cvm_ip + hypervisor_ip = local.config.nodes[0].hypervisor_ip + hypervisor = local.config.nodes[0].hypervisor + hypervisor_hostname = local.config.nodes[0].hypervisor_hostname + ipmi_ip = local.config.nodes[0].ipmi_ip + ipmi_netmask = local.config.nodes[0].ipmi_netmask + ipmi_gateway = local.config.nodes[0].ipmi_gateway + ipmi_user = local.config.nodes[0].ipmi_user + ipmi_password = local.config.nodes[0].ipmi_password + node_position = local.config.nodes[0].node_position + } + // using cvm (AOS or DOS nodes) + nodes{ + cvm_ip = local.config.nodes[1].cvm_ip + hypervisor_ip = local.config.nodes[1].hypervisor_ip + hypervisor = local.config.nodes[1].hypervisor + hypervisor_hostname = local.config.nodes[1].hypervisor_hostname + ipmi_ip = local.config.nodes[1].ipmi_ip + ipv6_address = local.config.nodes[1].ipv6_address + current_network_interface = local.config.nodes[1].current_network_interface + node_position = local.config.nodes[1].node_position + device_hint = "vm_installer" + } + // using cvm (AOS or DOS nodes) + nodes{ + cvm_ip = local.config.nodes[2].cvm_ip + hypervisor_ip = local.config.nodes[2].hypervisor_ip + hypervisor = local.config.nodes[2].hypervisor + hypervisor_hostname = local.config.nodes[2].hypervisor_hostname + ipmi_ip = local.config.nodes[2].ipmi_ip + ipv6_address = local.config.nodes[2].ipv6_address + current_network_interface = local.config.nodes[2].current_network_interface + node_position = local.config.nodes[2].node_position + device_hint = "vm_installer" + } + block_id = local.config.block_id + } + clusters { + cluster_members = [ + local.config.nodes[0].cvm_ip, + local.config.nodes[1].cvm_ip, + local.config.nodes[2].cvm_ip + ] + redundancy_factor = 2 + cluster_name = local.config.nodes[0].hypervisor_hostname + } + }`, filepath, blockNum, name) +} diff --git a/nutanix/resource_nutanix_foundation_image_test.go b/nutanix/resource_nutanix_foundation_image_test.go new file mode 100644 index 000000000..271dc0c34 --- /dev/null +++ b/nutanix/resource_nutanix_foundation_image_test.go @@ -0,0 +1,148 @@ +//go:build !unit +// +build !unit + +package nutanix + +import ( + "context" + "fmt" + "os" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func GetResourceState(s *terraform.State, key string) map[string]string { + moduleState := s.RootModule() + return moduleState.Resources[key].Primary.Attributes +} + +func testAccCheckNosImageExists(filename string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*Client).FoundationClientAPI + ctx := context.TODO() + resp, err := conn.FileManagement.ListNOSPackages(ctx) + if err != nil { + return fmt.Errorf("failed to fetch nos packages from FVM") + } + + for _, v := range *resp { + if v == filename { + return nil + } + } + return fmt.Errorf("upload for nos package %s failed. Image not found in FVM", filename) + } +} + +func testAccCheckNosImageDestroy(filename string) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := testAccProvider.Meta().(*Client).FoundationClientAPI + ctx := context.TODO() + resp, err := conn.FileManagement.ListNOSPackages(ctx) + if err != nil { + return fmt.Errorf("failed to fetch nos packages from FVM") + } + + for _, v := range *resp { + if v == filename { + return fmt.Errorf("teraform destroy for nos package %s failed. It still exists in FVM", filename) + } + } + return nil + } +} + +func TestAccFoundationImageResource_NOSUpload(t *testing.T) { + nameForUpload := "nos_upload" + resourcePathForUpload := "nutanix_foundation_image." + nameForUpload + r := acctest.RandIntRange(0, 500) + filename := fmt.Sprintf("test_nos_image-%d.tar.gz", r) + nosFile := "test_nos_image.tar.gz" + + // Get the Working directory + dir, err := os.Getwd() + if err != nil { + t.Errorf("TestAccFoundationImageResource_NOSUpload failed to get working directory %s", err) + } + + filepath := fmt.Sprintf("%s/%s", dir, nosFile) + + defer os.Remove(filepath) + + // get image url from env variables + image := os.Getenv("NOS_IMAGE_TEST_URL") + if image == "" { + t.Fatal("NOS_IMAGE_TEST_URL is empty. Please set env variable NOS_IMAGE_TEST_URL") + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if err := downloadFile(filepath, image); err != nil { + t.Errorf("TestAccFoundationImageResource_NOSUpload failed to download image %s", err) + } + testAccFoundationPreCheck(t) + }, + CheckDestroy: testAccCheckNosImageDestroy(filename), + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testImageResourceUpload(nameForUpload, filename, "nos", filepath), + Check: resource.ComposeTestCheckFunc( + testAccCheckNosImageExists(filename), + resource.TestCheckResourceAttrSet(resourcePathForUpload, "in_whitelist"), + resource.TestCheckResourceAttrSet(resourcePathForUpload, "name"), + ), + }, + }, + }) +} + +// Check negative scenario incase the resource errors out for incorrect installer type +func TestAccFoundationImageResource_Error(t *testing.T) { + nameForUpload := "iso_upload" + r := acctest.RandIntRange(0, 500) + filename := fmt.Sprintf("test_alpine-%d.iso", r) + file := "alpine.iso" + + // Get the Working directory + dir, err := os.Getwd() + if err != nil { + t.Errorf("TestAccFoundationImageResource_NOSUpload failed to get working directory %s", err) + } + + filepath := fmt.Sprintf("%s/%s", dir, file) + + defer os.Remove(filepath) + + // get image url from env variables + image := "http://dl-cdn.alpinelinux.org/alpine/v3.8/releases/x86_64/alpine-virt-3.8.1-x86_64.iso" + resource.Test(t, resource.TestCase{ + PreCheck: func() { + if err := downloadFile(filepath, image); err != nil { + t.Errorf("TestAccFoundationImageResource_Error failed to download image %s", err) + } + testAccFoundationPreCheck(t) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testImageResourceUpload(nameForUpload, filename, "check", filepath), + ExpectError: regexp.MustCompile("installer_type check should be one of "), + }, + }, + }) +} + +func testImageResourceUpload(name, filename, instType, filepath string) string { + return fmt.Sprintf(` + resource "nutanix_foundation_image" "%s"{ + filename = "%s" + installer_type = "%s" + source = "%s" + } + `, name, filename, instType, filepath) +} diff --git a/nutanix/resource_nutanix_foundation_ipmi_config_test.go b/nutanix/resource_nutanix_foundation_ipmi_config_test.go new file mode 100644 index 000000000..e76badc90 --- /dev/null +++ b/nutanix/resource_nutanix_foundation_ipmi_config_test.go @@ -0,0 +1,79 @@ +package nutanix + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccFoundationIPMIConfigResource(t *testing.T) { + name := "ipmi_configure" + resourcePath := "nutanix_foundation_ipmi_config." + name + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccFoundationPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testIPMIConfigResource(name, foundationVars.IpmiConfig), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourcePath, "blocks.0.nodes.0.ipmi_configure_successful", "true"), + // verify that again apply would again do create due to "ipmi_configure_now" = true + resource.TestCheckResourceAttr(resourcePath, "blocks.0.nodes.0.ipmi_configure_now", "true"), + resource.TestCheckResourceAttr(resourcePath, "blocks.0.nodes.#", "1"), + ), + }, + }, + }) +} + +func TestAccFoundationIPMIConfigResource_Error(t *testing.T) { + name := "ipmi_configure" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccFoundationPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testIPMIConfigResourceWithWrongPassword(name, foundationVars.IpmiConfig), + ExpectError: regexp.MustCompile("IPMI config failed for IPMI IP"), + }, + }, + }) +} + +func testIPMIConfigResource(name string, i IPMIConfig) string { + return fmt.Sprintf(` + resource "nutanix_foundation_ipmi_config" "%[1]s" { + ipmi_gateway = "%[2]s" + ipmi_netmask = "%[3]s" + ipmi_user = "%[4]s" + ipmi_password = "%[5]s" + blocks{ + nodes { + ipmi_mac = "%[6]s" + ipmi_configure_now = true + ipmi_ip = "%[7]s" + } + } + + }`, name, i.IpmiGateway, i.IpmiNetmask, i.IpmiUser, i.IpmiPassword, i.IpmiMac, i.IpmiIP) +} + +func testIPMIConfigResourceWithWrongPassword(name string, i IPMIConfig) string { + return fmt.Sprintf(` + resource "nutanix_foundation_ipmi_config" "%[1]s" { + ipmi_gateway = "%[2]s" + ipmi_netmask = "%[3]s" + ipmi_user = "%[4]s" + ipmi_password = "%[5]s" + blocks{ + nodes { + ipmi_mac = "%[6]s" + ipmi_configure_now = true + ipmi_ip = "%[7]s" + } + } + + }`, name, i.IpmiGateway, i.IpmiNetmask, i.IpmiUser, "ironman", i.IpmiMac, i.IpmiIP) +} diff --git a/nutanix/resource_nutanix_karbon_cluster_test.go b/nutanix/resource_nutanix_karbon_cluster_test.go index f317ce50f..aeea48b1e 100644 --- a/nutanix/resource_nutanix_karbon_cluster_test.go +++ b/nutanix/resource_nutanix_karbon_cluster_test.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) -func TestAccNutanixKarbonCluster_basic(t *testing.T) { +func TestAccKarbonCluster_basic(t *testing.T) { t.Skip() r := acctest.RandInt() resourceName := "nutanix_karbon_cluster.cluster" @@ -56,7 +56,7 @@ func TestAccNutanixKarbonCluster_basic(t *testing.T) { }) } -func TestAccNutanixKarbonCluster_scaleDown(t *testing.T) { +func TestAccKarbonCluster_scaleDown(t *testing.T) { r := acctest.RandInt() t.Skip() resourceName := "nutanix_karbon_cluster.cluster" @@ -101,7 +101,7 @@ func TestAccNutanixKarbonCluster_scaleDown(t *testing.T) { }) } -func TestAccNutanixKarbonCluster_updateCNI(t *testing.T) { +func TestAccKarbonCluster_updateCNI(t *testing.T) { r := acctest.RandInt() t.Skip() resourceName := "nutanix_karbon_cluster.cluster" diff --git a/nutanix/resource_nutanix_project_test.go b/nutanix/resource_nutanix_project_test.go index 8b92a2356..777d812da 100644 --- a/nutanix/resource_nutanix_project_test.go +++ b/nutanix/resource_nutanix_project_test.go @@ -68,7 +68,7 @@ func TestAccNutanixProject_basic(t *testing.T) { }) } -func TestAccResourceNutanixProject_importBasic(t *testing.T) { +func TestAccNutanixProject_importBasic(t *testing.T) { resourceName := "nutanix_project.project_test" subnetName := acctest.RandomWithPrefix("test-subnateName") diff --git a/nutanix/resource_nutanix_protection_rule_test.go b/nutanix/resource_nutanix_protection_rule_test.go index 094b3052b..f803ea19a 100644 --- a/nutanix/resource_nutanix_protection_rule_test.go +++ b/nutanix/resource_nutanix_protection_rule_test.go @@ -51,7 +51,7 @@ func TestAccNutanixProtectionRule_basic(t *testing.T) { }) } -func TestAccResourceNutanixProtectionRule_importBasic(t *testing.T) { +func TestAccNutanixProtectionRule_importBasic(t *testing.T) { t.Skip() resourceName := "nutanix_protection_rule.test" diff --git a/nutanix/resource_nutanix_recovery_plan_test.go b/nutanix/resource_nutanix_recovery_plan_test.go index 18addede5..7b048d3f8 100644 --- a/nutanix/resource_nutanix_recovery_plan_test.go +++ b/nutanix/resource_nutanix_recovery_plan_test.go @@ -146,7 +146,7 @@ func TestAccNutanixRecoveryPlanWithNetwork_basic(t *testing.T) { }) } -func TestAccResourceNutanixRecoveryPlanWithStageList_importBasic(t *testing.T) { +func TestAccNutanixRecoveryPlanWithStageList_importBasic(t *testing.T) { resourceName := "nutanix_recovery_plan.test" name := acctest.RandomWithPrefix("test-protection-name-dou") diff --git a/test_config.json b/test_config.json index ebb26e960..c82ac2558 100644 --- a/test_config.json +++ b/test_config.json @@ -4,33 +4,33 @@ "user_group_with_distinguished_name": { "distinguished_name": "cn=sspadmins,cn=users,dc=qa,dc=nucalm,dc=io", "display_name": "sspadmins", - "uuid": "7b68e9ac-f9a5-4e13-935a-a4d8a5379577" + "uuid": "a4f29c09-2552-4c01-992f-b96061796c98" }, "permissions": [ { "name": "", - "uuid": "f19406d0-7037-4323-8e77-16aab8860aef" + "uuid": "57f9b783-2a85-4684-b543-1976855027d4" }, { "name": "Delete_ACP", - "uuid": "466c25f1-a185-47f1-b334-b31b5ea178ae" + "uuid": "0696cac3-516b-460e-9bc1-d6a5d1b761d3" } ], "users": [ { "principal_name": "user4@qa.nucalm.io", "expected_display_name": "user4", - "directory_service_uuid": "725b051e-91d2-468f-b33d-038165dc7cc7" + "directory_service_uuid": "0791faca-1a48-499e-8171-60801dd41637" }, { "principal_name": "user6@qa.nucalm.io", "expected_display_name": "user6", - "directory_service_uuid": "725b051e-91d2-468f-b33d-038165dc7cc7" + "directory_service_uuid": "0791faca-1a48-499e-8171-60801dd41637" }, { "principal_name": "ssptest3@qa.nucalm.io", "expected_display_name": "ssptest3", - "directory_service_uuid": "725b051e-91d2-468f-b33d-038165dc7cc7" + "directory_service_uuid": "0791faca-1a48-499e-8171-60801dd41637" } ], "node_os_version": "ntnx-1.0", diff --git a/test_foundation_config.json b/test_foundation_config.json new file mode 100644 index 000000000..850923cb5 --- /dev/null +++ b/test_foundation_config.json @@ -0,0 +1,136 @@ +{ + "ipv6_addresses" : ["",""], + "ipmi_config": { + "ipmi_netmask":"", + "ipmi_gateway":"", + "ipmi_user":"", + "ipmi_password":"", + "ipmi_mac":"", + "ipmi_ip":"" + }, + "blocks":[ + { + "cvm_gateway": "", + "hypervisor_gateway":"", + "cvm_netmask":"", + "hypervisor_netmask":"", + "ipmi_user": "", + "nodes": [ + { + "ipmi_ip":"", + "ipmi_user":"", + "ipmi_password":"", + "ipmi_netmask":"", + "ipmi_gateway":"", + "cvm_ip":"", + "hypervisor":"", + "hypervisor_hostname":"", + "hypervisor_ip":"", + "node_position":"", + "ipv6_address":"", + "current_network_interface":"" + }, + { + "ipmi_ip":"", + "ipmi_user":"", + "ipmi_password":"", + "ipmi_netmask":"", + "ipmi_gateway":"", + "cvm_ip":"", + "hypervisor":"", + "hypervisor_hostname":"", + "hypervisor_ip":"", + "node_position":"", + "ipv6_address":"", + "current_network_interface":"" + }, + { + "ipmi_ip":"", + "ipmi_user":"", + "ipmi_password":"", + "ipmi_netmask":"", + "ipmi_gateway":"", + "cvm_ip":"", + "hypervisor":"", + "hypervisor_hostname":"", + "hypervisor_ip":"", + "node_position":"", + "ipv6_address":"", + "current_network_interface":"" + }, + { + "ipmi_ip":"", + "ipmi_user":"", + "ipmi_password":"", + "ipmi_netmask":"", + "ipmi_gateway":"", + "cvm_ip":"", + "hypervisor":"", + "hypervisor_hostname":"", + "hypervisor_ip":"", + "node_position":"", + "ipv6_address":"", + "current_network_interface":"" + } + ], + "block_id": "" + }, + { + "common_network_settings":{ + "cvm_dns_servers":[ + "" + ], + "hypervisor_dns_servers":[ + "" + ], + "cvm_ntp_servers":[ + "" + ], + "hypervisor_ntp_servers":[ + "" + ] + }, + "cvm_gateway":"", + "cvm_netmask":"", + "hypervisor_gateway":"", + "hypervisor_netmask":"", + "use_existing_network_settings":false, + "image_now":true, + + "nodes": [ + { + "cvm_ip":"", + "hypervisor_ip":"", + "hypervisor_hostname":"", + "imaged_node_uuid":"", + "ipmi_gateway":"", + "ipmi_ip":"", + "ipmi_netmask":"", + "hypervisor_type":"" + }, + { + "cvm_ip":"", + "hypervisor_ip":"", + "hypervisor_hostname":"", + "imaged_node_uuid":"", + "ipmi_gateway":"", + "ipmi_ip":"", + "ipmi_netmask":"", + "hypervisor_type":"" + }, + { + "cvm_ip":"", + "hypervisor_ip":"", + "hypervisor_hostname":"", + "imaged_node_uuid":"", + "ipmi_gateway":"", + "ipmi_ip":"", + "ipmi_netmask":"", + "hypervisor_type":"" + } + + ], + "aos_package_url":"" + } + ] +} diff --git a/website/docs/r/foundation_image_nodes.html.markdown b/website/docs/r/foundation_image_nodes.html.markdown index f2f5ebec9..9173857b1 100644 --- a/website/docs/r/foundation_image_nodes.html.markdown +++ b/website/docs/r/foundation_image_nodes.html.markdown @@ -174,7 +174,7 @@ The following arguments are supported for each node: * `hypervisor_hostname` :- (Required) Hypervisor Hostname. * `hypervisor_ip` :- (Required) Hypervisor IP address. * `node_position` :- (Required) Position of the node in the block. -* `image_now` :- (Required) If the node should be imaged now. +* `image_now` :- (Optional, Default = true) If the node should be imaged now. * `bond_mode` :- (Required if node is capable) dynamic if using LACP, static for LAG * `rdma_passthrough` :- (Required if node is capable) passthru RDMA nic to CVM if possible, default to false * `bond_lacp_rate` :- (Required if node is lacp configured) slow or fast if lacp if being used at the switch @@ -228,7 +228,7 @@ The following arguments are supported for each cluster: * `single_node_cluster` : - If it is a single node cluster. * `cluster_members` : - (Required) Members in the cluster. * `cvm_dns_servers` : - DNS servers of CVM. -* `cluster_init_now` : - (Required) If cluster should be created. +* `cluster_init_now` : - (Optional, Default = true) If cluster should be created. * `hypervisor_ntp_servers` : - NTP servers of hypervisor. ### hypervisor_iso @@ -278,6 +278,10 @@ The following arguments are supported: The following attributes are exported: * `id` : - unique id of terraform resouce is set to session_id of the imaging session +* `session_id` : - session_id of the imaging session +* `cluster_urls` :- list containing cluster name and cluster urls for created clusters in current session +* `cluster_urls.#.cluster_name` :- cluster_name +* `cluster_urls.#.cluster_url` :- url to access the cluster login ## Defaults