From 620b34f286411407fdc9c6dd697a6896da04dcec Mon Sep 17 00:00:00 2001 From: Domen Dobnikar <113340617+domendobnikar@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:59:15 +0000 Subject: [PATCH 1/4] CI/CD configuration - added Unit tests to workflow - added template unit test - bumped Terraform test version to v1.11 - added necessary mods to mod.go - added testing environment prepare/cleanup/check script - altered workflow file to run check and cleanup script with acceptance tests --- .github/workflows/test.yml | 35 ++- DEVELOPMENT.md | 9 + go.mod | 2 + .../hypercore_vm_resource_acc_test.go} | 2 +- .../acceptance/provider_acc_test.go} | 18 +- .../setup/acceptance_test_env_prepare.go | 284 ++++++++++++++++++ .../provider/tests/acceptance/setup/env.txt | 5 + .../tests/unit/hypercore_iso_resource_test.go | 55 ++++ 8 files changed, 401 insertions(+), 9 deletions(-) rename internal/provider/{hypercore_vm_resource_test.go => tests/acceptance/hypercore_vm_resource_acc_test.go} (99%) rename internal/provider/{provider_test.go => tests/acceptance/provider_acc_test.go} (54%) create mode 100644 internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go create mode 100644 internal/provider/tests/acceptance/setup/env.txt create mode 100644 internal/provider/tests/unit/hypercore_iso_resource_test.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8906805..f57d63e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -53,7 +53,7 @@ jobs: (echo; echo "Unexpected difference in directories after code generation. Run 'make generate' command and commit."; exit 1) # Run acceptance tests in a matrix with Terraform CLI versions - test: + acceptance-test: name: Terraform Provider Acceptance Tests needs: build runs-on: self-hosted @@ -82,7 +82,7 @@ jobs: # - '1.1.*' # - '1.2.*' # - '1.3.*' - - '1.4.*' + - '1.11.*' steps: - name: Debug info run: | @@ -107,11 +107,40 @@ jobs: terraform_version: ${{ matrix.terraform }} terraform_wrapper: false - run: go mod download + - name: Check Acceptance Test Environment + run: | + set -o allexport; eval "$(echo "$CI_CONFIG_HC_IP205_dos" | tr -s '\r\n' '\n')"; set +o allexport; + set -a + . ./internal/provider/tests/acceptance/setup/env.txt + go run ./internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go - env: TF_ACC: "1" run: | set -o allexport; eval "$(echo "$CI_CONFIG_HC_IP205_dos" | tr -s '\r\n' '\n')"; set +o allexport; echo cur-shell HC_HOST=$HC_HOST sh -c 'echo sub-shell HC_HOST=$HC_HOST' - go test -v -cover ./internal/provider/ + set -a + . ./internal/provider/tests/acceptance/setup/env.txt + go test -v -cover -coverpkg=github.com/hashicorp/terraform-provider-hypercore/internal/provider ./internal/provider/tests/acceptance/ timeout-minutes: 10 + - name: Cleanup Acceptance Test Environment + if: always() + run: | + set -o allexport; eval "$(echo "$CI_CONFIG_HC_IP205_dos" | tr -s '\r\n' '\n')"; set +o allexport; + set -a + . ./internal/provider/tests/acceptance/setup/env.txt + go run ./internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go "cleanup" + + unit-test: + name: Go Unit Tests + needs: build + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - uses: actions/setup-go@41dfa10bad2bb2ae585af6ee5bb4d7d973ad74ed + with: + go-version-file: 'go.mod' + cache: true + - name: Run Unit Tests + run: go test -v -cover -coverpkg=github.com/hashicorp/terraform-provider-hypercore/internal/provider ./internal/provider/tests/unit/ diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index 00bff46..85ee62c 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -23,3 +23,12 @@ Add needed variables/secrets to github project: ``` TEMP: create VM named `testtf-src`. + +Prior to running acceptance tests we need to setup: + 1. Virtual machine + a. has one disk + b. has one nic + c. boot order is configured as [disk, nic] + 2. Virtual disk (as standalone not attached to the testing VM) + 3. Add names and UUIDs to the env.txt file in /tests/acceptance/setup directory + 4. Virtual machine needs to be powered off diff --git a/go.mod b/go.mod index 8d6a989..dd2ba0e 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/hashicorp/terraform-plugin-go v0.25.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.11.0 + github.com/stretchr/testify v1.9.0 ) require ( @@ -67,4 +68,5 @@ require ( google.golang.org/grpc v1.67.1 // indirect google.golang.org/protobuf v1.35.1 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/internal/provider/hypercore_vm_resource_test.go b/internal/provider/tests/acceptance/hypercore_vm_resource_acc_test.go similarity index 99% rename from internal/provider/hypercore_vm_resource_test.go rename to internal/provider/tests/acceptance/hypercore_vm_resource_acc_test.go index b365242..b776ae9 100644 --- a/internal/provider/hypercore_vm_resource_test.go +++ b/internal/provider/tests/acceptance/hypercore_vm_resource_acc_test.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package provider +package acceptance import ( "fmt" diff --git a/internal/provider/provider_test.go b/internal/provider/tests/acceptance/provider_acc_test.go similarity index 54% rename from internal/provider/provider_test.go rename to internal/provider/tests/acceptance/provider_acc_test.go index c204235..71d2b36 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/tests/acceptance/provider_acc_test.go @@ -1,25 +1,33 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package provider +package acceptance import ( "testing" "github.com/hashicorp/terraform-plugin-framework/providerserver" "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-provider-hypercore/internal/provider" ) +/* +var source_vm_name = os.Getenv("SOURCE_VM_NAME") +var existing_vdisk_uuid = os.Getenv("EXISTING_VDISK_UUID") +var source_nic_uuid = os.Getenv("SOURCE_NIC_UUID") +var source_disk_uuid = os.Getenv("SOURCE_DISK_UUID") +*/ + // testAccProtoV6ProviderFactories are used to instantiate a provider during // acceptance testing. The factory function will be invoked for every Terraform // CLI command executed to create a provider server to which the CLI can // reattach. var testAccProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ - "hypercore": providerserver.NewProtocol6WithError(New("test")()), + "hypercore": providerserver.NewProtocol6WithError(provider.New("test")()), } func testAccPreCheck(t *testing.T) { - // You can add code here to run prior to any test case execution, for example assertions - // about the appropriate environment variables being set are common to see in a pre-check - // function. + // Prechecks + // Don't use terraform CRUD operations here, this is ran prior to the test and will not cleanup + } diff --git a/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go b/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go new file mode 100644 index 0000000..49496fa --- /dev/null +++ b/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go @@ -0,0 +1,284 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package main + +import ( + "bytes" + "crypto/tls" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "log" + "net/http" + "os" + "reflect" + "time" +) + +var source_vm_uuid = os.Getenv("SOURCE_VM_UUID") +var existing_vdisk_uuid = os.Getenv("EXISTING_VDISK_UUID") +var source_vm_name = os.Getenv("SOURCE_VM_NAME") +var source_disk_uuid = os.Getenv("SOURCE_DISK_UUID") +var source_nic_uuid = os.Getenv("SOURCE_NIC_UUID") + +func SetHTTPHeader(req *http.Request) *http.Request { + user := os.Getenv("HC_USERNAME") + pass := os.Getenv("HC_PASSWORD") + + // Create the Basic Authentication string + auth := user + ":" + pass + authHeader := "Basic " + base64.StdEncoding.EncodeToString([]byte(auth)) + + // Set the Content-Type header + req.Header.Set("Content-Type", "application/json") + + // Set the Content-Length header (not required, it's usually set automatically) + // req.Header.Set("Content-Length", fmt.Sprintf("%d", len(data))) + + // Set Basic Authentication header + req.Header.Set("Authorization", authHeader) + return req +} +func SetHTTPClient() *http.Client { + // Create a custom HTTP client with insecure transport + tr := &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, // Disable certificate verification + }, + } + client := &http.Client{Transport: tr} + + return client +} + +func AreEnvVariablesLoaded() bool { + if source_vm_uuid == "" || existing_vdisk_uuid == "" || source_vm_name == "" { + return false + } + return true +} +func DoesTestVMExist(host string) bool { + client := SetHTTPClient() + req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/v1/VirDomain/%s", host, source_vm_uuid), bytes.NewBuffer(nil)) + if err != nil { + log.Fatal(err) + } + req = SetHTTPHeader(req) + + // Execute the request + resp, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + // Read and print the response + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + + fmt.Println("Response Status:", resp.Status) + fmt.Println("Response Body:", string(body)) + + return resp.StatusCode == http.StatusOK +} +func IsTestVMRunning(host string) bool { + client := SetHTTPClient() + req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/v1/VirDomain/%s", host, source_vm_uuid), bytes.NewBuffer(nil)) + if err != nil { + log.Fatal(err) + } + req = SetHTTPHeader(req) + + // Execute the request + resp, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + // Read and print the response + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + + fmt.Println("Response Status:", resp.Status) + fmt.Println("Response Body:", string(body)) + + var result []map[string]interface{} + errr := json.Unmarshal(body, &result) + if errr != nil { + log.Fatal(errr) + } + return result[0]["state"] != "SHUTOFF" +} +func DoesVirtualDiskExist(host string) bool { + client := SetHTTPClient() + req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/v1/VirtualDisk/%s", host, existing_vdisk_uuid), bytes.NewBuffer(nil)) + if err != nil { + log.Fatal(err) + } + req = SetHTTPHeader(req) + + // Execute the request + resp, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + // Read and print the response + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + + fmt.Println("Response Status:", resp.Status) + fmt.Println("Response Body:", string(body)) + return resp.StatusCode == http.StatusOK +} +func IsBootOrderCorrect(host string) bool { + expectedBootOrder := []string{source_disk_uuid, source_nic_uuid} + client := SetHTTPClient() + req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/v1/VirDomain/%s", host, source_vm_uuid), bytes.NewBuffer(nil)) + if err != nil { + log.Fatal(err) + } + req = SetHTTPHeader(req) + + // Execute the request + resp, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + // Read and print the response + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + + fmt.Println("Response Status:", resp.Status) + fmt.Println("Response Body:", string(body)) + + var result []map[string]interface{} + errr := json.Unmarshal(body, &result) + if errr != nil { + log.Fatal(errr) + } + return reflect.DeepEqual(result[0]["bootDevices"], expectedBootOrder) +} + +func CleanUpPowerState(host string, client *http.Client) { + data := []byte(fmt.Sprintf(`[{"virDomainUUID": "%s", "actionType": "STOP", "cause": "INTERNAL"}]`, source_vm_uuid)) + req, err := http.NewRequest("POST", fmt.Sprintf("%s/rest/v1/VirDomain/action", host), bytes.NewBuffer(data)) + if err != nil { + log.Fatal(err) + } + req = SetHTTPHeader(req) + + // Execute the request + resp, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + // Read and print the response + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + + fmt.Println("Response Status:", resp.Status) + fmt.Println("Response Body:", string(body)) + + // wait 30 seconds for VM to shutdown and then proceed with other cleanup tasks + time.Sleep(30 * time.Second) +} +func CleanUpBootOrder(host string, client *http.Client) { + bootOrder := []string{source_disk_uuid, source_nic_uuid} + payload := map[string]interface{}{ + "bootDevices": bootOrder, + } + data, err := json.Marshal(payload) + if err != nil { + log.Fatalf("Failed to marshal JSON: %v", err) + } + req, err := http.NewRequest("POST", fmt.Sprintf("%s/rest/v1/VirDomain/%s", host, source_vm_uuid), bytes.NewBuffer(data)) + if err != nil { + log.Fatal(err) + } + + req = SetHTTPHeader(req) + + // Execute the request + resp, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + + // Read and print the response + body, err := io.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + + fmt.Println("Response Status:", resp.Status) + fmt.Println("Response Body:", string(body)) +} + +func CleanupEnv(host string) { + client := SetHTTPClient() + CleanUpPowerState(host, client) + CleanUpBootOrder(host, client) +} + +func main() { + /* + We are running env setup here based on the arguments passed into GO program it's either going to: + 1. Prepare environment + 2. Cleanup environment + Argument we are looking to pass is "cleanup" see test.yml workflow file for more information + */ + host := os.Getenv("HC_HOST") + isCleanup := len(os.Args) > 1 && os.Args[1] == "cleanup" + fmt.Println("Are we doing Cleanup:", isCleanup) + + if isCleanup { + CleanupEnv(host) + } else { + // We are doing env prepare here, make sure all the necessary entities are setup and present + if !AreEnvVariablesLoaded() { + log.Fatal("Environment variables aren't loaded, check env file in /acceptance/setup directory") + } else { + fmt.Println("Environment variables are loaded correctly") + } + if !DoesTestVMExist(host) { + log.Fatal("Acceptance test VM is missing in your testing environment") + } else { + fmt.Println("Acceptance test VM is present in the testing environment") + } + if IsTestVMRunning(host) { + log.Fatal("Acceptance test VM is RUNNING and should be turned off before the testing begins") + } else { + fmt.Println("Acceptance test VM is in the correct SHUTOFF state") + } + if !DoesVirtualDiskExist(host) { + log.Fatal("Acceptance test Virtual disk is missing in your testing environment") + } else { + fmt.Println("Acceptance test Virtual disk is present in your testing environment") + } + if IsBootOrderCorrect(host) { + log.Fatal("Acceptance test Boot order is incorrect on the test VM, should be disk followed by network interface") + } else { + fmt.Println("Acceptance test Boot order is in correct order") + } + } +} diff --git a/internal/provider/tests/acceptance/setup/env.txt b/internal/provider/tests/acceptance/setup/env.txt new file mode 100644 index 0000000..9f645d5 --- /dev/null +++ b/internal/provider/tests/acceptance/setup/env.txt @@ -0,0 +1,5 @@ +SOURCE_VM_UUID="97904009-1878-4881-b6df-83c85ab7dc1a" +EXISTING_VDISK_UUID="33c78baf-c3c6-4600-8432-9c7a2a3008ab" +SOURCE_VM_NAME="integration-test-vm" +SOURCE_NIC_UUID="7a79893f-129f-4d35-8800-35a947ff4a51" +SOURCE_DISK_UUID="46dac49c-9726-4e4f-950f-f2b06fcf2e24" diff --git a/internal/provider/tests/unit/hypercore_iso_resource_test.go b/internal/provider/tests/unit/hypercore_iso_resource_test.go new file mode 100644 index 0000000..3b53fe0 --- /dev/null +++ b/internal/provider/tests/unit/hypercore_iso_resource_test.go @@ -0,0 +1,55 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package unit + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-provider-hypercore/internal/provider" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/stretchr/testify/assert" +) + +func TestHypercoreISOResource_Schema(t *testing.T) { + // Create an instance of the resource + r := &provider.HypercoreISOResource{} + + // Prepare request and response objects + req := resource.SchemaRequest{} + resp := &resource.SchemaResponse{} + + // Call the Schema function + r.Schema(context.Background(), req, resp) + + // Validate the schema is set + assert.NotNil(t, resp.Schema) + assert.NotNil(t, resp.Schema.Attributes) + + // Check the description + assert.Contains(t, resp.Schema.MarkdownDescription, "Hypercore ISO resource to manage ISO images") + + // Check individual attributes + attributes := resp.Schema.Attributes + + // Check ID attribute + idAttr, ok := attributes["id"].(schema.StringAttribute) + assert.True(t, ok) + assert.True(t, idAttr.Computed) + assert.Contains(t, idAttr.MarkdownDescription, "ISO identifier") + + // Check Name attribute + nameAttr, ok := attributes["name"].(schema.StringAttribute) + assert.True(t, ok) + assert.True(t, nameAttr.Required) + assert.Contains(t, nameAttr.MarkdownDescription, "Desired name of the ISO to upload") + + // Check Source URL attribute + sourceURLAttr, ok := attributes["source_url"].(schema.StringAttribute) + assert.True(t, ok) + assert.True(t, sourceURLAttr.Optional) + assert.Contains(t, sourceURLAttr.MarkdownDescription, "Source URL from where to fetch") +} From c724674f1813e98d9b2e3d35c8d91d815ceb6d5c Mon Sep 17 00:00:00 2001 From: Domen Dobnikar <113340617+domendobnikar@users.noreply.github.com> Date: Wed, 9 Apr 2025 18:35:41 +0000 Subject: [PATCH 2/4] Code cleanup - added more GO convention --- .../setup/acceptance_test_env_prepare.go | 195 +++++++----------- 1 file changed, 74 insertions(+), 121 deletions(-) diff --git a/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go b/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go index 49496fa..58a03b5 100644 --- a/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go +++ b/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go @@ -17,11 +17,29 @@ import ( "time" ) -var source_vm_uuid = os.Getenv("SOURCE_VM_UUID") -var existing_vdisk_uuid = os.Getenv("EXISTING_VDISK_UUID") -var source_vm_name = os.Getenv("SOURCE_VM_NAME") -var source_disk_uuid = os.Getenv("SOURCE_DISK_UUID") -var source_nic_uuid = os.Getenv("SOURCE_NIC_UUID") +type EnvConfig struct { + SourceVmUUID string + ExistingVdiskUUID string + SourceVmName string + SourceDiskUUID string + SourceNicUUID string +} + +const ( + VirDomainEndpoint = "/rest/v1/VirDomain/" + VirtualDiskEndpoint = "/rest/v1/VirtualDisk/" + VirDomainActionEndpoint = "/rest/v1/VirDomain/action" +) + +func LoadEnv() EnvConfig { + return EnvConfig{ + SourceVmUUID: os.Getenv("SOURCE_VM_UUID"), + ExistingVdiskUUID: os.Getenv("EXISTING_VDISK_UUID"), + SourceVmName: os.Getenv("SOURCE_VM_NAME"), + SourceDiskUUID: os.Getenv("SOURCE_DISK_UUID"), + SourceNicUUID: os.Getenv("SOURCE_NIC_UUID"), + } +} func SetHTTPHeader(req *http.Request) *http.Request { user := os.Getenv("HC_USERNAME") @@ -52,23 +70,8 @@ func SetHTTPClient() *http.Client { return client } - -func AreEnvVariablesLoaded() bool { - if source_vm_uuid == "" || existing_vdisk_uuid == "" || source_vm_name == "" { - return false - } - return true -} -func DoesTestVMExist(host string) bool { - client := SetHTTPClient() - req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/v1/VirDomain/%s", host, source_vm_uuid), bytes.NewBuffer(nil)) - if err != nil { - log.Fatal(err) - } - req = SetHTTPHeader(req) - - // Execute the request - resp, err := client.Do(req) +func SendHTTPRequest(request *http.Request, client *http.Client) (*http.Response, []byte) { + resp, err := client.Do(request) if err != nil { log.Fatal(err) } @@ -83,31 +86,36 @@ func DoesTestVMExist(host string) bool { fmt.Println("Response Status:", resp.Status) fmt.Println("Response Body:", string(body)) - return resp.StatusCode == http.StatusOK + return resp, body } -func IsTestVMRunning(host string) bool { - client := SetHTTPClient() - req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/v1/VirDomain/%s", host, source_vm_uuid), bytes.NewBuffer(nil)) + +func AreEnvVariablesLoaded(env EnvConfig) bool { + if env.SourceVmUUID == "" || env.ExistingVdiskUUID == "" || env.SourceVmName == "" || env.SourceDiskUUID == "" || env.SourceNicUUID == "" { + return false + } + return true +} +func DoesTestVMExist(host string, client *http.Client, env EnvConfig) bool { + url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID) + req, err := http.NewRequest("GET", url, bytes.NewBuffer(nil)) if err != nil { log.Fatal(err) } req = SetHTTPHeader(req) - // Execute the request - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() + resp, _ := SendHTTPRequest(req, client) - // Read and print the response - body, err := io.ReadAll(resp.Body) + return resp.StatusCode == http.StatusOK +} +func IsTestVMRunning(host string, client *http.Client, env EnvConfig) bool { + url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID) + req, err := http.NewRequest("GET", url, bytes.NewBuffer(nil)) if err != nil { log.Fatal(err) } + req = SetHTTPHeader(req) - fmt.Println("Response Status:", resp.Status) - fmt.Println("Response Body:", string(body)) + _, body := SendHTTPRequest(req, client) var result []map[string]interface{} errr := json.Unmarshal(body, &result) @@ -116,55 +124,28 @@ func IsTestVMRunning(host string) bool { } return result[0]["state"] != "SHUTOFF" } -func DoesVirtualDiskExist(host string) bool { - client := SetHTTPClient() - req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/v1/VirtualDisk/%s", host, existing_vdisk_uuid), bytes.NewBuffer(nil)) +func DoesVirtualDiskExist(host string, client *http.Client, env EnvConfig) bool { + url := fmt.Sprintf("%s%s%s", host, VirtualDiskEndpoint, env.ExistingVdiskUUID) + req, err := http.NewRequest("GET", url, bytes.NewBuffer(nil)) if err != nil { log.Fatal(err) } req = SetHTTPHeader(req) - // Execute the request - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() + resp, _ := SendHTTPRequest(req, client) - // Read and print the response - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) - } - - fmt.Println("Response Status:", resp.Status) - fmt.Println("Response Body:", string(body)) return resp.StatusCode == http.StatusOK } -func IsBootOrderCorrect(host string) bool { - expectedBootOrder := []string{source_disk_uuid, source_nic_uuid} - client := SetHTTPClient() - req, err := http.NewRequest("GET", fmt.Sprintf("%s/rest/v1/VirDomain/%s", host, source_vm_uuid), bytes.NewBuffer(nil)) +func IsBootOrderCorrect(host string, client *http.Client, env EnvConfig) bool { + expectedBootOrder := []string{env.SourceDiskUUID, env.SourceNicUUID} + url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID) + req, err := http.NewRequest("GET", url, bytes.NewBuffer(nil)) if err != nil { log.Fatal(err) } req = SetHTTPHeader(req) - // Execute the request - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() - - // Read and print the response - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) - } - - fmt.Println("Response Status:", resp.Status) - fmt.Println("Response Body:", string(body)) + _, body := SendHTTPRequest(req, client) var result []map[string]interface{} errr := json.Unmarshal(body, &result) @@ -174,35 +155,20 @@ func IsBootOrderCorrect(host string) bool { return reflect.DeepEqual(result[0]["bootDevices"], expectedBootOrder) } -func CleanUpPowerState(host string, client *http.Client) { - data := []byte(fmt.Sprintf(`[{"virDomainUUID": "%s", "actionType": "STOP", "cause": "INTERNAL"}]`, source_vm_uuid)) - req, err := http.NewRequest("POST", fmt.Sprintf("%s/rest/v1/VirDomain/action", host), bytes.NewBuffer(data)) +func CleanUpPowerState(host string, client *http.Client, env EnvConfig) { + data := []byte(fmt.Sprintf(`[{"virDomainUUID": "%s", "actionType": "STOP", "cause": "INTERNAL"}]`, env.SourceVmUUID)) + url := fmt.Sprintf("%s%s", host, VirDomainActionEndpoint) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) if err != nil { log.Fatal(err) } req = SetHTTPHeader(req) - - // Execute the request - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() - - // Read and print the response - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) - } - - fmt.Println("Response Status:", resp.Status) - fmt.Println("Response Body:", string(body)) - + SendHTTPRequest(req, client) // wait 30 seconds for VM to shutdown and then proceed with other cleanup tasks time.Sleep(30 * time.Second) } -func CleanUpBootOrder(host string, client *http.Client) { - bootOrder := []string{source_disk_uuid, source_nic_uuid} +func CleanUpBootOrder(host string, client *http.Client, env EnvConfig) { + bootOrder := []string{env.SourceDiskUUID, env.SourceNicUUID} payload := map[string]interface{}{ "bootDevices": bootOrder, } @@ -210,34 +176,19 @@ func CleanUpBootOrder(host string, client *http.Client) { if err != nil { log.Fatalf("Failed to marshal JSON: %v", err) } - req, err := http.NewRequest("POST", fmt.Sprintf("%s/rest/v1/VirDomain/%s", host, source_vm_uuid), bytes.NewBuffer(data)) + url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID) + req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) if err != nil { log.Fatal(err) } req = SetHTTPHeader(req) - - // Execute the request - resp, err := client.Do(req) - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() - - // Read and print the response - body, err := io.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) - } - - fmt.Println("Response Status:", resp.Status) - fmt.Println("Response Body:", string(body)) + SendHTTPRequest(req, client) } -func CleanupEnv(host string) { - client := SetHTTPClient() - CleanUpPowerState(host, client) - CleanUpBootOrder(host, client) +func CleanupEnv(host string, client *http.Client, env EnvConfig) { + CleanUpPowerState(host, client, env) + CleanUpBootOrder(host, client, env) } func main() { @@ -247,35 +198,37 @@ func main() { 2. Cleanup environment Argument we are looking to pass is "cleanup" see test.yml workflow file for more information */ + env := LoadEnv() host := os.Getenv("HC_HOST") + client := SetHTTPClient() isCleanup := len(os.Args) > 1 && os.Args[1] == "cleanup" fmt.Println("Are we doing Cleanup:", isCleanup) if isCleanup { - CleanupEnv(host) + CleanupEnv(host, client, env) } else { // We are doing env prepare here, make sure all the necessary entities are setup and present - if !AreEnvVariablesLoaded() { + if !AreEnvVariablesLoaded(env) { log.Fatal("Environment variables aren't loaded, check env file in /acceptance/setup directory") } else { fmt.Println("Environment variables are loaded correctly") } - if !DoesTestVMExist(host) { + if !DoesTestVMExist(host, client, env) { log.Fatal("Acceptance test VM is missing in your testing environment") } else { fmt.Println("Acceptance test VM is present in the testing environment") } - if IsTestVMRunning(host) { + if IsTestVMRunning(host, client, env) { log.Fatal("Acceptance test VM is RUNNING and should be turned off before the testing begins") } else { fmt.Println("Acceptance test VM is in the correct SHUTOFF state") } - if !DoesVirtualDiskExist(host) { + if !DoesVirtualDiskExist(host, client, env) { log.Fatal("Acceptance test Virtual disk is missing in your testing environment") } else { fmt.Println("Acceptance test Virtual disk is present in your testing environment") } - if IsBootOrderCorrect(host) { + if IsBootOrderCorrect(host, client, env) { log.Fatal("Acceptance test Boot order is incorrect on the test VM, should be disk followed by network interface") } else { fmt.Println("Acceptance test Boot order is in correct order") From 03b11a5f253ec382358c8e770b4734232b54a21a Mon Sep 17 00:00:00 2001 From: Domen Dobnikar <113340617+domendobnikar@users.noreply.github.com> Date: Wed, 9 Apr 2025 19:17:48 +0000 Subject: [PATCH 3/4] Code cleanup --- .../setup/acceptance_test_env_prepare.go | 125 +++++++++--------- 1 file changed, 65 insertions(+), 60 deletions(-) diff --git a/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go b/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go index 58a03b5..e2500d9 100644 --- a/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go +++ b/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go @@ -73,14 +73,14 @@ func SetHTTPClient() *http.Client { func SendHTTPRequest(request *http.Request, client *http.Client) (*http.Response, []byte) { resp, err := client.Do(request) if err != nil { - log.Fatal(err) + log.Fatalf("Sending request failed with %v", err) } defer resp.Body.Close() // Read and print the response body, err := io.ReadAll(resp.Body) if err != nil { - log.Fatal(err) + log.Fatalf("Reading request response body failed with %v", err) } fmt.Println("Response Status:", resp.Status) @@ -88,6 +88,24 @@ func SendHTTPRequest(request *http.Request, client *http.Client) (*http.Response return resp, body } +func SetHTTPRequest(method string, url string, data []byte) *http.Request { + var req *http.Request + var err error + + // Set request method and body + if method == "GET" { + req, err = http.NewRequest("GET", url, bytes.NewBuffer(nil)) + } else { + req, err = http.NewRequest("POST", url, bytes.NewBuffer(data)) + } + + // Handle any errors that occur + if err != nil { + log.Fatalf("%s request to url: %s failed with error: %v", method, url, err) + } + + return req +} func AreEnvVariablesLoaded(env EnvConfig) bool { if env.SourceVmUUID == "" || env.ExistingVdiskUUID == "" || env.SourceVmName == "" || env.SourceDiskUUID == "" || env.SourceNicUUID == "" { @@ -97,10 +115,8 @@ func AreEnvVariablesLoaded(env EnvConfig) bool { } func DoesTestVMExist(host string, client *http.Client, env EnvConfig) bool { url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID) - req, err := http.NewRequest("GET", url, bytes.NewBuffer(nil)) - if err != nil { - log.Fatal(err) - } + + req := SetHTTPRequest("GET", url, nil) req = SetHTTPHeader(req) resp, _ := SendHTTPRequest(req, client) @@ -109,27 +125,23 @@ func DoesTestVMExist(host string, client *http.Client, env EnvConfig) bool { } func IsTestVMRunning(host string, client *http.Client, env EnvConfig) bool { url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID) - req, err := http.NewRequest("GET", url, bytes.NewBuffer(nil)) - if err != nil { - log.Fatal(err) - } + + req := SetHTTPRequest("GET", url, nil) req = SetHTTPHeader(req) _, body := SendHTTPRequest(req, client) var result []map[string]interface{} - errr := json.Unmarshal(body, &result) - if errr != nil { - log.Fatal(errr) + err := json.Unmarshal(body, &result) + if err != nil { + log.Fatal(err) } return result[0]["state"] != "SHUTOFF" } func DoesVirtualDiskExist(host string, client *http.Client, env EnvConfig) bool { url := fmt.Sprintf("%s%s%s", host, VirtualDiskEndpoint, env.ExistingVdiskUUID) - req, err := http.NewRequest("GET", url, bytes.NewBuffer(nil)) - if err != nil { - log.Fatal(err) - } + + req := SetHTTPRequest("GET", url, nil) req = SetHTTPHeader(req) resp, _ := SendHTTPRequest(req, client) @@ -139,29 +151,52 @@ func DoesVirtualDiskExist(host string, client *http.Client, env EnvConfig) bool func IsBootOrderCorrect(host string, client *http.Client, env EnvConfig) bool { expectedBootOrder := []string{env.SourceDiskUUID, env.SourceNicUUID} url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID) - req, err := http.NewRequest("GET", url, bytes.NewBuffer(nil)) - if err != nil { - log.Fatal(err) - } + + req := SetHTTPRequest("GET", url, nil) req = SetHTTPHeader(req) _, body := SendHTTPRequest(req, client) var result []map[string]interface{} - errr := json.Unmarshal(body, &result) - if errr != nil { - log.Fatal(errr) + err := json.Unmarshal(body, &result) + if err != nil { + log.Fatal(err) } return reflect.DeepEqual(result[0]["bootDevices"], expectedBootOrder) } +func PrepareEnv(host string, client *http.Client, env EnvConfig) { + // We are doing env prepare here, make sure all the necessary entities are setup and present + if !AreEnvVariablesLoaded(env) { + log.Fatal("Environment variables aren't loaded, check env file in /acceptance/setup directory") + } else { + fmt.Println("Environment variables are loaded correctly") + } + if !DoesTestVMExist(host, client, env) { + log.Fatal("Acceptance test VM is missing in your testing environment") + } else { + fmt.Println("Acceptance test VM is present in the testing environment") + } + if IsTestVMRunning(host, client, env) { + log.Fatal("Acceptance test VM is RUNNING and should be turned off before the testing begins") + } else { + fmt.Println("Acceptance test VM is in the correct SHUTOFF state") + } + if !DoesVirtualDiskExist(host, client, env) { + log.Fatal("Acceptance test Virtual disk is missing in your testing environment") + } else { + fmt.Println("Acceptance test Virtual disk is present in your testing environment") + } + if IsBootOrderCorrect(host, client, env) { + log.Fatal("Acceptance test Boot order is incorrect on the test VM, should be disk followed by network interface") + } else { + fmt.Println("Acceptance test Boot order is in correct order") + } +} func CleanUpPowerState(host string, client *http.Client, env EnvConfig) { data := []byte(fmt.Sprintf(`[{"virDomainUUID": "%s", "actionType": "STOP", "cause": "INTERNAL"}]`, env.SourceVmUUID)) url := fmt.Sprintf("%s%s", host, VirDomainActionEndpoint) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) - if err != nil { - log.Fatal(err) - } + req := SetHTTPRequest("POST", url, data) req = SetHTTPHeader(req) SendHTTPRequest(req, client) // wait 30 seconds for VM to shutdown and then proceed with other cleanup tasks @@ -177,15 +212,10 @@ func CleanUpBootOrder(host string, client *http.Client, env EnvConfig) { log.Fatalf("Failed to marshal JSON: %v", err) } url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID) - req, err := http.NewRequest("POST", url, bytes.NewBuffer(data)) - if err != nil { - log.Fatal(err) - } - + req := SetHTTPRequest("POST", url, data) req = SetHTTPHeader(req) SendHTTPRequest(req, client) } - func CleanupEnv(host string, client *http.Client, env EnvConfig) { CleanUpPowerState(host, client, env) CleanUpBootOrder(host, client, env) @@ -207,31 +237,6 @@ func main() { if isCleanup { CleanupEnv(host, client, env) } else { - // We are doing env prepare here, make sure all the necessary entities are setup and present - if !AreEnvVariablesLoaded(env) { - log.Fatal("Environment variables aren't loaded, check env file in /acceptance/setup directory") - } else { - fmt.Println("Environment variables are loaded correctly") - } - if !DoesTestVMExist(host, client, env) { - log.Fatal("Acceptance test VM is missing in your testing environment") - } else { - fmt.Println("Acceptance test VM is present in the testing environment") - } - if IsTestVMRunning(host, client, env) { - log.Fatal("Acceptance test VM is RUNNING and should be turned off before the testing begins") - } else { - fmt.Println("Acceptance test VM is in the correct SHUTOFF state") - } - if !DoesVirtualDiskExist(host, client, env) { - log.Fatal("Acceptance test Virtual disk is missing in your testing environment") - } else { - fmt.Println("Acceptance test Virtual disk is present in your testing environment") - } - if IsBootOrderCorrect(host, client, env) { - log.Fatal("Acceptance test Boot order is incorrect on the test VM, should be disk followed by network interface") - } else { - fmt.Println("Acceptance test Boot order is in correct order") - } + PrepareEnv(host, client, env) } } From b55ef594db70dabbdc2f99cc859939e3f943963d Mon Sep 17 00:00:00 2001 From: Domen Dobnikar <113340617+domendobnikar@users.noreply.github.com> Date: Wed, 9 Apr 2025 22:43:34 +0000 Subject: [PATCH 4/4] Code cleanup --- .../setup/acceptance_test_env_prepare.go | 36 ++++++------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go b/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go index e2500d9..9d4894d 100644 --- a/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go +++ b/internal/provider/tests/acceptance/setup/acceptance_test_env_prepare.go @@ -70,8 +70,10 @@ func SetHTTPClient() *http.Client { return client } -func SendHTTPRequest(request *http.Request, client *http.Client) (*http.Response, []byte) { - resp, err := client.Do(request) +func SendHTTPRequest(client *http.Client, method string, url string, data []byte) (*http.Response, []byte) { + req := SetHTTPMethod(method, url, data) + req = SetHTTPHeader(req) + resp, err := client.Do(req) if err != nil { log.Fatalf("Sending request failed with %v", err) } @@ -88,7 +90,7 @@ func SendHTTPRequest(request *http.Request, client *http.Client) (*http.Response return resp, body } -func SetHTTPRequest(method string, url string, data []byte) *http.Request { +func SetHTTPMethod(method string, url string, data []byte) *http.Request { var req *http.Request var err error @@ -116,20 +118,14 @@ func AreEnvVariablesLoaded(env EnvConfig) bool { func DoesTestVMExist(host string, client *http.Client, env EnvConfig) bool { url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID) - req := SetHTTPRequest("GET", url, nil) - req = SetHTTPHeader(req) - - resp, _ := SendHTTPRequest(req, client) + resp, _ := SendHTTPRequest(client, "GET", url, nil) return resp.StatusCode == http.StatusOK } func IsTestVMRunning(host string, client *http.Client, env EnvConfig) bool { url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID) - req := SetHTTPRequest("GET", url, nil) - req = SetHTTPHeader(req) - - _, body := SendHTTPRequest(req, client) + _, body := SendHTTPRequest(client, "GET", url, nil) var result []map[string]interface{} err := json.Unmarshal(body, &result) @@ -141,10 +137,7 @@ func IsTestVMRunning(host string, client *http.Client, env EnvConfig) bool { func DoesVirtualDiskExist(host string, client *http.Client, env EnvConfig) bool { url := fmt.Sprintf("%s%s%s", host, VirtualDiskEndpoint, env.ExistingVdiskUUID) - req := SetHTTPRequest("GET", url, nil) - req = SetHTTPHeader(req) - - resp, _ := SendHTTPRequest(req, client) + resp, _ := SendHTTPRequest(client, "GET", url, nil) return resp.StatusCode == http.StatusOK } @@ -152,10 +145,7 @@ func IsBootOrderCorrect(host string, client *http.Client, env EnvConfig) bool { expectedBootOrder := []string{env.SourceDiskUUID, env.SourceNicUUID} url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID) - req := SetHTTPRequest("GET", url, nil) - req = SetHTTPHeader(req) - - _, body := SendHTTPRequest(req, client) + _, body := SendHTTPRequest(client, "GET", url, nil) var result []map[string]interface{} err := json.Unmarshal(body, &result) @@ -196,9 +186,7 @@ func PrepareEnv(host string, client *http.Client, env EnvConfig) { func CleanUpPowerState(host string, client *http.Client, env EnvConfig) { data := []byte(fmt.Sprintf(`[{"virDomainUUID": "%s", "actionType": "STOP", "cause": "INTERNAL"}]`, env.SourceVmUUID)) url := fmt.Sprintf("%s%s", host, VirDomainActionEndpoint) - req := SetHTTPRequest("POST", url, data) - req = SetHTTPHeader(req) - SendHTTPRequest(req, client) + SendHTTPRequest(client, "POST", url, data) // wait 30 seconds for VM to shutdown and then proceed with other cleanup tasks time.Sleep(30 * time.Second) } @@ -212,9 +200,7 @@ func CleanUpBootOrder(host string, client *http.Client, env EnvConfig) { log.Fatalf("Failed to marshal JSON: %v", err) } url := fmt.Sprintf("%s%s%s", host, VirDomainEndpoint, env.SourceVmUUID) - req := SetHTTPRequest("POST", url, data) - req = SetHTTPHeader(req) - SendHTTPRequest(req, client) + SendHTTPRequest(client, "POST", url, data) } func CleanupEnv(host string, client *http.Client, env EnvConfig) { CleanUpPowerState(host, client, env)