blob: f7a7d6586284b7e7c2b54013a5a14ae41742adc2 [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
/*
Package citest contains comprehensive tests for the CloudStack Go SDK.
This file is used in the CI pipeline to test the CloudStack API client
and ensure that parameter handling works correctly across all supported
data types.
The tests cover all CloudStack API parameter types:
- BOOLEAN: Boolean values (true/false)
- DATE: Date strings in CloudStack format
- INTEGER: Regular integers (int)
- SHORT: Short integers (handled as int)
- LIST: Array/slice parameters ([]string)
- LONG: Long integers (int64)
- MAP: Key-value pairs (map[string]string)
- STRING: Text strings and UUIDs
- UUID: Universally unique identifiers (string format)
These tests validate that:
1. Parameters can be set and retrieved correctly
2. Type conversions work as expected
3. Parameter validation functions properly
4. The SDK handles all CloudStack API data types
*/
package citest
import (
"os"
"slices"
"strings"
"testing"
"github.com/apache/cloudstack-go/v2/cloudstack"
)
// testResources holds IDs of created test resources for cleanup
type testResources struct {
zoneID string
vmID1 string
vmID2 string
serviceOfferingID1 string
serviceOfferingID2 string
networkID string
templateID string
networkOfferingID string
}
func TestCloudstackAPI(t *testing.T) {
// Check if CloudStack credentials are provided, otherwise use localhost defaults
apiURL := os.Getenv("CLOUDSTACK_API_URL")
if apiURL == "" {
apiURL = "http://localhost:8080/client/api"
}
apiKey := os.Getenv("CLOUDSTACK_API_KEY")
if apiKey == "" {
t.Fatalf("CLOUDSTACK_API_KEY is not set")
}
secretKey := os.Getenv("CLOUDSTACK_SECRET_KEY")
if secretKey == "" {
t.Fatalf("CLOUDSTACK_SECRET_KEY is not set")
}
client := cloudstack.NewClient(apiURL, apiKey, secretKey, false) // Don't verify SSL for testing
// Initialize test resources
resources := &testResources{}
// Set up test environment
setupTestEnvironment(t, client, resources)
// Run the actual tests
t.Run("BasicTypes", func(t *testing.T) {
testBasicTypes(t, client, resources)
})
// Clean up test resources we created
t.Cleanup(func() {
cleanupTestResources(t, client, resources)
})
}
func setupTestEnvironment(t *testing.T, client *cloudstack.CloudStackClient, resources *testResources) {
t.Helper()
// Get zone ID
resources.zoneID = getTestZone(t, client)
// Get service offering IDs
resources.serviceOfferingID1, resources.serviceOfferingID2 = getTestServiceOfferings(t, client)
// Get network offerings to create a network
resources.networkOfferingID = getTestNetworkOffering(t, client)
// Create test network
resources.networkID = createTestNetwork(t, client, "ci-test-network", resources.networkOfferingID, resources.zoneID)
// Get template ID
resources.templateID = getTestTemplate(t, client, resources.zoneID)
// Create VMs for testing
resources.vmID1 = createTestVM(t, client, "ci-test-vm-1", resources.serviceOfferingID1, resources.zoneID, resources.networkID, resources.templateID)
resources.vmID2 = createTestVM(t, client, "ci-test-vm-2", resources.serviceOfferingID1, resources.zoneID, resources.networkID, resources.templateID)
}
func testBasicTypes(t *testing.T, client *cloudstack.CloudStackClient, resources *testResources) {
// Test STRING parameters with actual API call
t.Run("StringParameters", func(t *testing.T) {
// Test with ListZones API which uses string parameters
params := client.Zone.NewListZonesParams()
params.SetKeyword("zone") // string parameter
// Verify parameter was set correctly
if keyword, ok := params.GetKeyword(); !ok || keyword != "zone" {
t.Errorf("Expected keyword to be 'zone', got %s", keyword)
}
// Make actual API call with the parameters we set (not new params)
resp, err := client.Zone.ListZones(params)
if err != nil {
t.Fatalf("ListZones API call with keyword='zone' failed: %v", err)
}
t.Logf("ListZones with keyword='zone' successful, found %d zones", resp.Count)
// Test parameter reset functionality
params.ResetKeyword()
if _, ok := params.GetKeyword(); ok {
t.Error("Expected keyword to be reset")
}
})
// Test BOOLEAN parameters with actual API call
t.Run("BooleanParameters", func(t *testing.T) {
// Test with ListVirtualMachines API which has boolean parameters
params := client.VirtualMachine.NewListVirtualMachinesParams()
params.SetListall(true) // boolean parameter
// Verify parameter was set correctly
if listall, ok := params.GetListall(); !ok || !listall {
t.Errorf("Expected listall to be true, got %v", listall)
}
// Make actual API call with the boolean parameter we set
resp, err := client.VirtualMachine.ListVirtualMachines(params)
if err != nil {
t.Fatalf("ListVirtualMachines API call with listall=true failed: %v", err)
}
t.Logf("ListVirtualMachines with listall=true successful, found %d VMs", resp.Count)
// Test setting to false and making another call
params.SetListall(false)
if listall, ok := params.GetListall(); !ok || listall {
t.Errorf("Expected listall to be false, got %v", listall)
}
// Make API call with listall=false
respFalse, err := client.VirtualMachine.ListVirtualMachines(params)
if err != nil {
t.Fatalf("ListVirtualMachines API call with listall=false failed: %v", err)
}
t.Logf("ListVirtualMachines with listall=false successful, found %d VMs", respFalse.Count)
})
// Test INTEGER parameters with actual API call
t.Run("IntegerParameters", func(t *testing.T) {
// Test with ListVirtualMachines API which has integer parameters
params := client.VirtualMachine.NewListVirtualMachinesParams()
params.SetPagesize(5) // integer parameter
params.SetPage(1) // required when pagesize is specified
// Verify parameters were set correctly
if pagesize, ok := params.GetPagesize(); !ok || pagesize != 5 {
t.Errorf("Expected pagesize to be 5, got %d", pagesize)
}
if page, ok := params.GetPage(); !ok || page != 1 {
t.Errorf("Expected page to be 1, got %d", page)
}
// Make actual API call with the integer parameters we set
resp, err := client.VirtualMachine.ListVirtualMachines(params)
if err != nil {
t.Fatalf("ListVirtualMachines with pagesize=5 API call failed: %v", err)
}
t.Logf("ListVirtualMachines with pagesize=5, page=1 successful, found %d VMs", resp.Count)
if resp.Count > 5 {
t.Logf("Note: Response has more than 5 items, pagesize parameter might not be enforced by test server")
}
})
// Test LONG (int64) parameters with actual API call
t.Run("LongParameters", func(t *testing.T) {
// Test with ChangeServiceForVirtualMachine API which has int64 parameters
params := client.VirtualMachine.NewChangeServiceForVirtualMachineParams(resources.vmID1, resources.serviceOfferingID2)
params.SetMaxiops(1000) // int64 parameter
// Verify parameter was set correctly
if maxiops, ok := params.GetMaxiops(); !ok || maxiops != 1000 {
t.Errorf("Expected maxiops to be 1000, got %d", maxiops)
}
// Make actual API call with the int64 parameter we set
// Note: This might fail if the VM is not in the right state, but that's okay for parameter testing
resp, err := client.VirtualMachine.ChangeServiceForVirtualMachine(params)
if err != nil {
t.Fatalf("ChangeServiceForVirtualMachine with maxiops=1000 API call failed: %v", err)
} else {
t.Logf("ChangeServiceForVirtualMachine with maxiops=1000 successful, job ID: %s", resp.JobID)
}
})
// Test DATE parameters with actual API call
t.Run("DateParameters", func(t *testing.T) {
// Test with ListUsageRecords API which has date parameters
params := client.Usage.NewListUsageRecordsParams("2024-01-31", "2024-01-01")
// Verify date parameters were set correctly during construction
if startdate, ok := params.GetStartdate(); !ok || startdate != "2024-01-01" {
t.Errorf("Expected startdate to be '2024-01-01', got %s", startdate)
}
if enddate, ok := params.GetEnddate(); !ok || enddate != "2024-01-31" {
t.Errorf("Expected enddate to be '2024-01-31', got %s", enddate)
}
// Set additional date parameters
params.SetStartdate("2024-01-01")
params.SetEnddate("2024-01-31")
// Verify parameters can be modified
if startdate, ok := params.GetStartdate(); !ok || startdate != "2024-01-01" {
t.Errorf("Expected modified startdate to be '2024-01-01', got %s", startdate)
}
// Make actual API call with the date parameters we set
resp, err := client.Usage.ListUsageRecords(params)
if err != nil {
t.Fatalf("ListUsageRecords API call with date parameters failed: %v", err)
}
t.Logf("ListUsageRecords with startdate='2024-01-01' and enddate='2024-01-31' successful, found %d usage records", resp.Count)
})
// Test LIST parameters with actual API call
t.Run("ListParameters", func(t *testing.T) {
// Test with ListVirtualMachines API which has list parameters
params := client.VirtualMachine.NewListVirtualMachinesParams()
vmIDList := []string{resources.vmID1, resources.vmID2}
params.SetIds(vmIDList) // []string parameter
// Verify parameter was set correctly
if ids, ok := params.GetIds(); !ok || len(ids) != 2 || ids[0] != resources.vmID1 || ids[1] != resources.vmID2 {
t.Errorf("Expected ids to be %v, got %v", vmIDList, ids)
}
// Make actual API call with the list parameter we set
resp, err := client.VirtualMachine.ListVirtualMachines(params)
if err != nil {
t.Fatalf("ListVirtualMachines API call with ids=%v failed: %v", vmIDList, err)
}
t.Logf("ListVirtualMachines with ids=%v successful, found %d VMs", vmIDList, resp.Count)
if resp.Count != 2 {
t.Errorf("Expected 2 VMs, got %d", resp.Count)
}
// Verify returned VMs are in our expected list
if resp.Count >= 2 {
foundIDs := make([]string, resp.Count)
for i, vm := range resp.VirtualMachines {
foundIDs[i] = vm.Id
}
for _, expectedID := range vmIDList {
if !slices.Contains(foundIDs, expectedID) {
t.Errorf("Expected VM ID %s not found in response", expectedID)
}
}
}
})
// Test MAP parameters with actual API call
t.Run("MapParameters", func(t *testing.T) {
// Test with AddNicToVirtualMachine API which has map parameters
params := client.VirtualMachine.NewAddNicToVirtualMachineParams(resources.networkID, resources.vmID1)
dhcpOptions := map[string]string{
"option1": "value1",
"option2": "value2",
}
params.SetDhcpoptions(dhcpOptions) // map[string]string parameter
// Verify parameter was set correctly
if options, ok := params.GetDhcpoptions(); !ok || len(options) != 2 || options["option1"] != "value1" {
t.Errorf("Expected dhcpoptions to be %v, got %v", dhcpOptions, options)
}
// Make actual API call with the map parameter we set
resp, err := client.VirtualMachine.AddNicToVirtualMachine(params)
if err != nil {
t.Fatalf("AddNicToVirtualMachine with dhcpoptions=%v API call failed: %v", dhcpOptions, err)
} else {
t.Logf("AddNicToVirtualMachine with dhcpoptions=%v successful, job ID: %s", dhcpOptions, resp.JobID)
}
})
// Test UUID parameters with actual API call
t.Run("UUIDParameters", func(t *testing.T) {
// Test with ListServiceOfferings API using zoneid UUID parameter
params := client.ServiceOffering.NewListServiceOfferingsParams()
params.SetZoneid(resources.zoneID) // UUID parameter
// Verify UUID parameter was set correctly
if zoneid, ok := params.GetZoneid(); !ok || zoneid != resources.zoneID {
t.Errorf("Expected zoneid to be %s, got %s", resources.zoneID, zoneid)
}
// Make actual API call with the UUID parameter we set
resp, err := client.ServiceOffering.ListServiceOfferings(params)
if err != nil {
t.Fatalf("ListServiceOfferings with zoneid=%s API call failed: %v", resources.zoneID, err)
}
t.Logf("ListServiceOfferings with zoneid=%s successful, found %d service offerings", resources.zoneID, resp.Count)
if resp.Count > 0 {
// Verify that returned IDs are valid UUIDs
firstOffering := resp.ServiceOfferings[0]
if !cloudstack.IsID(firstOffering.Id) {
t.Errorf("Service offering ID %s is not a valid UUID format", firstOffering.Id)
} else {
t.Logf("Service offering ID %s is valid UUID format", firstOffering.Id)
}
}
})
// Test SHORT parameters with actual API call
t.Run("ShortParameters", func(t *testing.T) {
// Test with RemoveRawUsageRecords API which has interval parameter
params := client.Usage.NewRemoveRawUsageRecordsParams(30)
// Verify parameter was set correctly during construction
if interval, ok := params.GetInterval(); !ok || interval != 30 {
t.Errorf("Expected interval to be 30, got %d", interval)
}
// Modify the parameter to a safer value
params.SetInterval(365) // Remove usage records older than 1 year
// Verify parameter was modified correctly
if interval, ok := params.GetInterval(); !ok || interval != 365 {
t.Errorf("Expected modified interval to be 365, got %d", interval)
}
// Make actual API call with the short parameter we set
resp, err := client.Usage.RemoveRawUsageRecords(params)
if err != nil {
// Log the error but don't fail the test - might not have permission
t.Logf("RemoveRawUsageRecords with interval=365 API call failed (might not have permission): %v", err)
// Verify it's a CloudStack API error, not a parameter handling error
if !strings.Contains(err.Error(), "CloudStack API error") {
t.Fatalf("Unexpected error type - might be parameter handling issue: %v", err)
}
} else {
t.Logf("RemoveRawUsageRecords with interval=365 successful: %s", resp.Displaytext)
}
})
}
/*
Helper functions to get test resources
*/
func getTestZone(t *testing.T, client *cloudstack.CloudStackClient) string {
t.Helper()
zoneParams := client.Zone.NewListZonesParams()
zones, err := client.Zone.ListZones(zoneParams)
if err != nil || zones.Count == 0 {
t.Fatalf("Failed to get zones for testing: %v", err)
}
zoneID := zones.Zones[0].Id
t.Logf("Using zone ID: %s", zoneID)
return zoneID
}
func getTestServiceOfferings(t *testing.T, client *cloudstack.CloudStackClient) (string, string) {
t.Helper()
serviceParams := client.ServiceOffering.NewListServiceOfferingsParams()
services, err := client.ServiceOffering.ListServiceOfferings(serviceParams)
if err != nil || services.Count < 2 {
t.Fatalf("Failed to get at least 2 service offerings for testing: %v (found %d)", err, services.Count)
}
serviceOfferingID1 := services.ServiceOfferings[0].Id
serviceOfferingID2 := services.ServiceOfferings[1].Id
t.Logf("Using service offering IDs: %s, %s", serviceOfferingID1, serviceOfferingID2)
return serviceOfferingID1, serviceOfferingID2
}
func getTestNetworkOffering(t *testing.T, client *cloudstack.CloudStackClient) string {
t.Helper()
netOfferingParams := client.NetworkOffering.NewListNetworkOfferingsParams()
netOfferingParams.SetState("Enabled")
netOfferings, err := client.NetworkOffering.ListNetworkOfferings(netOfferingParams)
if err != nil || netOfferings.Count == 0 {
t.Fatalf("Failed to get network offerings for network creation: %v", err)
}
// Find a suitable network offering (preferably isolated)
var networkOfferingID string
for _, offering := range netOfferings.NetworkOfferings {
if offering.Guestiptype == "Isolated" && !offering.Specifyvlan {
networkOfferingID = offering.Id
break
}
}
if networkOfferingID == "" {
// Use first available offering if no isolated found
networkOfferingID = netOfferings.NetworkOfferings[0].Id
}
t.Logf("Using network offering ID: %s", networkOfferingID)
return networkOfferingID
}
func getTestTemplate(t *testing.T, client *cloudstack.CloudStackClient, zoneID string) string {
t.Helper()
templateParams := client.Template.NewListTemplatesParams("executable")
templateParams.SetZoneid(zoneID)
templates, err := client.Template.ListTemplates(templateParams)
if err != nil || templates.Count == 0 {
t.Fatalf("Failed to get templates for VM creation: %v", err)
}
templateID := templates.Templates[0].Id
t.Logf("Using template ID: %s", templateID)
return templateID
}
/*
Helper functions to create test resources
*/
func createTestNetwork(t *testing.T, client *cloudstack.CloudStackClient, name, offeringID, zoneID string) string {
t.Helper()
createNetParams := client.Network.NewCreateNetworkParams(name, offeringID, zoneID)
createNetParams.SetDisplaytext(name)
createNetResp, err := client.Network.CreateNetwork(createNetParams)
if err != nil {
t.Fatalf("Failed to create test network: %v", err)
}
t.Logf("Created test network with ID: %s", createNetResp.Id)
return createNetResp.Id
}
func createTestVM(t *testing.T, client *cloudstack.CloudStackClient, name, offeringID, zoneID, networkID, templateID string) string {
t.Helper()
deployParams := client.VirtualMachine.NewDeployVirtualMachineParams(offeringID, templateID, zoneID)
deployParams.SetName(name)
deployParams.SetDisplayname(name)
deployParams.SetNetworkids([]string{networkID})
deployParams.SetStartvm(false) // Don't start VM to avoid resource consumption
deployResp, err := client.VirtualMachine.DeployVirtualMachine(deployParams)
if err != nil {
t.Fatalf("Failed to create test VM %s: %v", name, err)
}
t.Logf("Created test VM %s with ID: %s", name, deployResp.Id)
return deployResp.Id
}
/*
Helper functions to delete test resources
*/
func cleanupTestResources(t *testing.T, client *cloudstack.CloudStackClient, resources *testResources) {
t.Helper()
// Clean up VMs first (order matters for dependencies)
if resources.vmID1 != "" {
expungeVM(t, client, resources.vmID1)
}
if resources.vmID2 != "" {
expungeVM(t, client, resources.vmID2)
}
// Clean up network after VMs are destroyed
if resources.networkID != "" {
deleteNetwork(t, client, resources.networkID)
}
}
func deleteNetwork(t *testing.T, client *cloudstack.CloudStackClient, networkID string) {
t.Helper()
params := client.Network.NewDeleteNetworkParams(networkID)
params.SetForced(true)
_, err := client.Network.DeleteNetwork(params)
if err != nil {
t.Logf("Failed to delete test network %s: %v", networkID, err)
} else {
t.Logf("Successfully deleted test network %s", networkID)
}
}
func expungeVM(t *testing.T, client *cloudstack.CloudStackClient, vmID string) {
t.Helper()
params := client.VirtualMachine.NewDestroyVirtualMachineParams(vmID)
params.SetExpunge(true)
_, err := client.VirtualMachine.DestroyVirtualMachine(params)
if err != nil {
t.Logf("Failed to destroy test VM %s: %v", vmID, err)
} else {
t.Logf("Successfully destroyed test VM %s", vmID)
}
}