diff --git a/.gitignore b/.gitignore index b6b0f366..bac1d545 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ website/vendor !command/test-fixtures/**/.terraform/ .terraform.lock.hcl provider.tf +examples/ \ No newline at end of file diff --git a/cloudstack/data_source_cloudstack_service_offering.go b/cloudstack/data_source_cloudstack_service_offering.go index 8c1272a3..d0e2709e 100644 --- a/cloudstack/data_source_cloudstack_service_offering.go +++ b/cloudstack/data_source_cloudstack_service_offering.go @@ -46,6 +46,186 @@ func dataSourceCloudstackServiceOffering() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "cpu_number": { + Type: schema.TypeInt, + Computed: true, + }, + "cpu_speed": { + Type: schema.TypeInt, + Computed: true, + }, + "memory": { + Type: schema.TypeInt, + Computed: true, + }, + "created": { + Type: schema.TypeString, + Computed: true, + }, + "domain_id": { + Type: schema.TypeString, + Computed: true, + }, + "domain": { + Type: schema.TypeString, + Computed: true, + }, + "host_tags": { + Type: schema.TypeString, + Computed: true, + }, + "is_customized": { + Type: schema.TypeBool, + Computed: true, + }, + "is_system": { + Type: schema.TypeBool, + Computed: true, + }, + "is_volatile": { + Type: schema.TypeBool, + Computed: true, + }, + "limit_cpu_use": { + Type: schema.TypeBool, + Computed: true, + }, + "network_rate": { + Type: schema.TypeInt, + Computed: true, + }, + "storage_type": { + Type: schema.TypeString, + Computed: true, + }, + "system_vm_type": { + Type: schema.TypeString, + Computed: true, + }, + "deployment_planner": { + Type: schema.TypeString, + Computed: true, + }, + "offer_ha": { + Type: schema.TypeBool, + Computed: true, + }, + "tags": { + Type: schema.TypeString, + Computed: true, + }, + "provisioning_type": { + Type: schema.TypeString, + Computed: true, + }, + "min_iops": { + Type: schema.TypeInt, + Computed: true, + }, + "max_iops": { + Type: schema.TypeInt, + Computed: true, + }, + "hypervisor_snapshot_reserve": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_iops": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_bytes_read_rate": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_bytes_write_rate": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_iops_read_rate": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_iops_write_rate": { + Type: schema.TypeInt, + Computed: true, + }, + "root_disk_size": { + Type: schema.TypeInt, + Computed: true, + }, + "gpu_card_id": { + Type: schema.TypeString, + Computed: true, + }, + "gpu_card_name": { + Type: schema.TypeString, + Computed: true, + }, + "gpu_count": { + Type: schema.TypeInt, + Computed: true, + }, + "gpu_display": { + Type: schema.TypeBool, + Computed: true, + }, + "default_use": { + Type: schema.TypeBool, + Computed: true, + }, + "dynamic_scaling_enabled": { + Type: schema.TypeBool, + Computed: true, + }, + "encrypt_root": { + Type: schema.TypeBool, + Computed: true, + }, + "has_annotations": { + Type: schema.TypeBool, + Computed: true, + }, + "is_customized_iops": { + Type: schema.TypeBool, + Computed: true, + }, + "lease_duration": { + Type: schema.TypeInt, + Computed: true, + }, + "lease_expiry_action": { + Type: schema.TypeString, + Computed: true, + }, + "max_heads": { + Type: schema.TypeInt, + Computed: true, + }, + "max_resolution_x": { + Type: schema.TypeInt, + Computed: true, + }, + "max_resolution_y": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_bytes_read_rate_max": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_bytes_write_rate_max": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_iops_read_rate_max": { + Type: schema.TypeInt, + Computed: true, + }, + "disk_iops_write_rate_max": { + Type: schema.TypeInt, + Computed: true, + }, }, } } @@ -90,6 +270,59 @@ func serviceOfferingDescriptionAttributes(d *schema.ResourceData, serviceOfferin d.SetId(serviceOffering.Id) d.Set("name", serviceOffering.Name) d.Set("display_text", serviceOffering.Displaytext) + d.Set("cpu_number", serviceOffering.Cpunumber) + d.Set("cpu_speed", serviceOffering.Cpuspeed) + d.Set("memory", serviceOffering.Memory) + d.Set("created", serviceOffering.Created) + d.Set("domain_id", serviceOffering.Domainid) + d.Set("domain", serviceOffering.Domain) + d.Set("host_tags", serviceOffering.Hosttags) + d.Set("is_customized", serviceOffering.Iscustomized) + d.Set("is_system", serviceOffering.Issystem) + d.Set("is_volatile", serviceOffering.Isvolatile) + d.Set("limit_cpu_use", serviceOffering.Limitcpuuse) + d.Set("network_rate", serviceOffering.Networkrate) + d.Set("storage_type", serviceOffering.Storagetype) + d.Set("system_vm_type", serviceOffering.Systemvmtype) + d.Set("deployment_planner", serviceOffering.Deploymentplanner) + d.Set("offer_ha", serviceOffering.Offerha) + d.Set("tags", serviceOffering.Storagetags) + d.Set("provisioning_type", serviceOffering.Provisioningtype) + + // IOPS limits - only set if returned by API (> 0) + if serviceOffering.Miniops > 0 { + d.Set("min_iops", int(serviceOffering.Miniops)) + } + if serviceOffering.Maxiops > 0 { + d.Set("max_iops", int(serviceOffering.Maxiops)) + } + + d.Set("hypervisor_snapshot_reserve", serviceOffering.Hypervisorsnapshotreserve) + d.Set("disk_bytes_read_rate", serviceOffering.DiskBytesReadRate) + d.Set("disk_bytes_write_rate", serviceOffering.DiskBytesWriteRate) + d.Set("disk_iops_read_rate", serviceOffering.DiskIopsReadRate) + d.Set("disk_iops_write_rate", serviceOffering.DiskIopsWriteRate) + d.Set("root_disk_size", serviceOffering.Rootdisksize) + d.Set("gpu_card_id", serviceOffering.Gpucardid) + d.Set("gpu_card_name", serviceOffering.Gpucardname) + d.Set("gpu_count", serviceOffering.Gpucount) + d.Set("gpu_display", serviceOffering.Gpudisplay) + + // New fields + d.Set("default_use", serviceOffering.Defaultuse) + d.Set("dynamic_scaling_enabled", serviceOffering.Dynamicscalingenabled) + d.Set("encrypt_root", serviceOffering.Encryptroot) + d.Set("has_annotations", serviceOffering.Hasannotations) + d.Set("is_customized_iops", serviceOffering.Iscustomizediops) + d.Set("lease_duration", serviceOffering.Leaseduration) + d.Set("lease_expiry_action", serviceOffering.Leaseexpiryaction) + d.Set("max_heads", serviceOffering.Maxheads) + d.Set("max_resolution_x", serviceOffering.Maxresolutionx) + d.Set("max_resolution_y", serviceOffering.Maxresolutiony) + d.Set("disk_bytes_read_rate_max", serviceOffering.DiskBytesReadRateMax) + d.Set("disk_bytes_write_rate_max", serviceOffering.DiskBytesWriteRateMax) + d.Set("disk_iops_read_rate_max", serviceOffering.DiskIopsReadRateMax) + d.Set("disk_iops_write_rate_max", serviceOffering.DiskIopsWriteRateMax) return nil } diff --git a/cloudstack/data_source_cloudstack_service_offering_test.go b/cloudstack/data_source_cloudstack_service_offering_test.go index afe94d5e..048e67b5 100644 --- a/cloudstack/data_source_cloudstack_service_offering_test.go +++ b/cloudstack/data_source_cloudstack_service_offering_test.go @@ -37,6 +37,21 @@ func TestAccServiceOfferingDataSource_basic(t *testing.T) { Config: testServiceOfferingDataSourceConfig_basic, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, "name"), + resource.TestCheckResourceAttrPair(datasourceName, "cpu_number", resourceName, "cpu_number"), + resource.TestCheckResourceAttrPair(datasourceName, "cpu_speed", resourceName, "cpu_speed"), + resource.TestCheckResourceAttrPair(datasourceName, "memory", resourceName, "memory"), + resource.TestCheckResourceAttrPair(datasourceName, "gpu_count", resourceName, "gpu_count"), + resource.TestCheckResourceAttrPair(datasourceName, "gpu_display", resourceName, "gpu_display"), + resource.TestCheckResourceAttrPair(datasourceName, "offer_ha", resourceName, "offer_ha"), + resource.TestCheckResourceAttrPair(datasourceName, "storage_type", resourceName, "storage_type"), + resource.TestCheckResourceAttrPair(datasourceName, "disk_iops_read_rate", resourceName, "disk_iops_read_rate"), + resource.TestCheckResourceAttrPair(datasourceName, "disk_iops_write_rate", resourceName, "disk_iops_write_rate"), + // Skip IOPS comparison - these fields may not be supported by CloudStack API + // resource.TestCheckResourceAttrPair(datasourceName, "min_iops", resourceName, "min_iops"), + // resource.TestCheckResourceAttrPair(datasourceName, "max_iops", resourceName, "max_iops"), + resource.TestCheckResourceAttrPair(datasourceName, "dynamic_scaling_enabled", resourceName, "dynamic_scaling_enabled"), + resource.TestCheckResourceAttrPair(datasourceName, "is_volatile", resourceName, "is_volatile"), + resource.TestCheckResourceAttrPair(datasourceName, "root_disk_size", resourceName, "root_disk_size"), ), }, }, @@ -45,8 +60,22 @@ func TestAccServiceOfferingDataSource_basic(t *testing.T) { const testServiceOfferingDataSourceConfig_basic = ` resource "cloudstack_service_offering" "service-offering-resource" { - name = "TestServiceUpdate" - display_text = "DisplayService" + name = "TestServiceUpdate" + display_text = "DisplayService" + cpu_number = 4 + cpu_speed = 2400 + memory = 8192 + gpu_count = 1 + gpu_display = true + offer_ha = true + storage_type = "shared" + disk_iops_read_rate = 10000 + disk_iops_write_rate = 10000 + min_iops = 5000 + max_iops = 15000 + dynamic_scaling_enabled = true + is_volatile = false + root_disk_size = 50 } data "cloudstack_service_offering" "service-offering-data-source" { diff --git a/cloudstack/provider_v6.go b/cloudstack/provider_v6.go index 82c06c04..e6361fa8 100644 --- a/cloudstack/provider_v6.go +++ b/cloudstack/provider_v6.go @@ -165,9 +165,7 @@ func (p *CloudstackProvider) ConfigValidators(ctx context.Context) []provider.Co func (p *CloudstackProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ - NewserviceOfferingUnconstrainedResource, - NewserviceOfferingConstrainedResource, - NewserviceOfferingFixedResource, + // Service offering resources removed - using unified approach in provider.go } } diff --git a/cloudstack/resource_cloudstack_service_offering.go b/cloudstack/resource_cloudstack_service_offering.go index b2b2b92d..fca9ec4c 100644 --- a/cloudstack/resource_cloudstack_service_offering.go +++ b/cloudstack/resource_cloudstack_service_offering.go @@ -21,7 +21,6 @@ package cloudstack import ( "fmt" - "log" "strconv" "github.com/apache/cloudstack-go/v2/cloudstack" @@ -50,104 +49,394 @@ func resourceCloudStackServiceOffering() *schema.Resource { ForceNew: true, }, "cpu_speed": { - Description: "Speed of CPU in Mhz", + Description: "Speed of CPU in MHz", + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "memory": { + Description: "Amount of memory in MB", Type: schema.TypeInt, Optional: true, ForceNew: true, }, "host_tags": { - Description: "The host tag for this service offering", + Description: "The host tags for this service offering", Type: schema.TypeString, Optional: true, }, - "limit_cpu_use": { - Description: "Restrict the CPU usage to committed service offering", + "storage_type": { + Description: "The storage type of the service offering", + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "shared", + }, + "service_offering_details": { + Description: "Service offering details for custom configuration", + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "customized": { + Description: "Whether service offering allows custom CPU/memory or not. If not specified, CloudStack automatically determines based on cpu_number and memory presence: creates customizable offering (true) when cpu_number/memory are omitted, or fixed offering (false) when they are provided.", Type: schema.TypeBool, Optional: true, ForceNew: true, - Default: false, }, - "memory": { - Description: "The total memory of the service offering in MB", + "gpu_card": { + Description: "GPU card name (e.g., 'Tesla P100 Auto Created')", + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "gpu_type": { + Description: "GPU profile/type (e.g., 'passthrough', 'GRID V100-8Q')", + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + "gpu_count": { + Description: "Number of GPUs", Type: schema.TypeInt, Optional: true, ForceNew: true, }, + + // Behavior settings "offer_ha": { - Description: "The HA for the service offering", + Description: "Whether to offer HA to the VMs created with this offering", Type: schema.TypeBool, Optional: true, + Computed: true, // CloudStack returns default value ForceNew: true, - Default: false, }, - "storage_type": { - Description: "The storage type of the service offering. Values are local and shared", - Type: schema.TypeString, + + "dynamic_scaling_enabled": { + Description: "Whether to enable dynamic scaling", + Type: schema.TypeBool, Optional: true, + Computed: true, // CloudStack returns default value ForceNew: true, - Default: "shared", - ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) { - v := val.(string) - - if v == "local" || v == "shared" { - return - } + }, - errs = append(errs, fmt.Errorf("storage type should be either local or shared, got %s", v)) + // Disk configuration + "root_disk_size": { + Description: "Root disk size in GB", + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, - return - }, + "provisioning_type": { + Description: "Provisioning type: thin, sparse, or fat", + Type: schema.TypeString, + Optional: true, + Computed: true, // CloudStack returns default value + ForceNew: true, }, - "customized": { - Description: "Whether service offering allows custom CPU/memory or not", + + // Security + "encrypt_root": { + Description: "Whether to encrypt the root disk or not", Type: schema.TypeBool, Optional: true, + Computed: true, // CloudStack returns default value ForceNew: true, - Computed: true, - }, + }, // Customized Offering Limits "min_cpu_number": { - Description: "Minimum number of CPU cores allowed", + Description: "Minimum number of CPUs for customized offerings", Type: schema.TypeInt, Optional: true, ForceNew: true, }, + "max_cpu_number": { - Description: "Maximum number of CPU cores allowed", + Description: "Maximum number of CPUs for customized offerings", Type: schema.TypeInt, Optional: true, ForceNew: true, }, + "min_memory": { - Description: "Minimum memory allowed (MB)", + Description: "Minimum memory in MB for customized offerings", Type: schema.TypeInt, Optional: true, ForceNew: true, }, + "max_memory": { - Description: "Maximum memory allowed (MB)", + Description: "Maximum memory in MB for customized offerings", Type: schema.TypeInt, Optional: true, ForceNew: true, }, - "encrypt_root": { - Description: "Encrypt the root disk for VMs using this service offering", + + // IOPS Limits + "min_iops": { + Description: "Minimum IOPS", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "max_iops": { + Description: "Maximum IOPS", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + + // GPU Display + "gpu_display": { + Description: "Whether to display GPU in UI or not", Type: schema.TypeBool, Optional: true, + Computed: true, // CloudStack returns default value ForceNew: true, }, - "storage_tags": { - Description: "Storage tags to associate with the service offering", + + // High Priority Parameters + "limit_cpu_use": { + Description: "Restrict CPU usage to the service offering value", + Type: schema.TypeBool, + Optional: true, + Computed: true, // CloudStack returns default value + ForceNew: true, + }, + + "is_volatile": { + Description: "True if the virtual machine needs to be volatile (root disk destroyed on stop)", + Type: schema.TypeBool, + Optional: true, + Computed: true, // CloudStack returns default value + ForceNew: true, + }, + + "customized_iops": { + Description: "Whether compute offering iops is custom or not", + Type: schema.TypeBool, + Optional: true, + Computed: true, // CloudStack returns default value + ForceNew: true, + }, + + "tags": { + Description: "Comma-separated list of tags for the service offering", Type: schema.TypeString, Optional: true, + ForceNew: true, }, - "service_offering_details": { - Description: "Service offering details for GPU configuration and other advanced settings", + + "domain_id": { + Description: "The ID(s) of the domain(s) to which the service offering belongs", + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "zone_id": { + Description: "The ID(s) of the zone(s) this service offering belongs to", + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + // IOPS/Bandwidth parameters (Phase 2) + // Note: CloudStack API does not support updating these after creation + "disk_iops_read_rate": { + Description: "IO requests read rate of the disk offering", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, + + "disk_iops_write_rate": { + Description: "IO requests write rate of the disk offering", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, + + "disk_iops_read_rate_max": { + Description: "IO requests read rate max of the disk offering", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, + + "disk_iops_write_rate_max": { + Description: "IO requests write rate max of the disk offering", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, + + "disk_iops_read_rate_max_length": { + Description: "Burst duration in seconds for read rate max", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, + + "disk_iops_write_rate_max_length": { + Description: "Burst duration in seconds for write rate max", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, + + "disk_bytes_read_rate": { + Description: "Bytes read rate of the disk offering", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, + + "disk_bytes_write_rate": { + Description: "Bytes write rate of the disk offering", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, + + "disk_bytes_read_rate_max": { + Description: "Bytes read rate max of the disk offering", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, + + "disk_bytes_write_rate_max": { + Description: "Bytes write rate max of the disk offering", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, + + "bytes_read_rate_max_length": { + Description: "Burst duration in seconds for bytes read rate max", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, + + "bytes_write_rate_max_length": { + Description: "Burst duration in seconds for bytes write rate max", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, // Hypervisor Parameters (Phase 3) + "hypervisor_snapshot_reserve": { + Description: "Hypervisor snapshot reserve space as a percent of a volume (for managed storage using Xen or VMware)", + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "cache_mode": { + Description: "The cache mode to use for the disk offering. Valid values: none, writeback, writethrough", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "deployment_planner": { + Description: "Deployment planner heuristics to use for the service offering", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "storage_policy": { + Description: "Name of the storage policy (for VMware)", + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + // Low Priority Parameters (Phase 4) + "network_rate": { + Description: "Data transfer rate in megabits per second allowed", + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, + + "purge_resources": { + Description: "Whether to purge resources on deletion", + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated after creation + }, "system_vm_type": { + Description: "The system VM type. Possible values: domainrouter, consoleproxy, secondarystoragevm", + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, // Cannot be updated - defines fundamental VM type + }, + + "disk_offering_id": { + Description: "The ID of the disk offering to associate with this service offering", + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "disk_offering_strictness": { + Description: "Whether to strictly enforce the disk offering (requires disk_offering_id)", + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "external_details": { + Description: "External system metadata (CMDB, billing, etc.)", Type: schema.TypeMap, Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "is_system": { + Description: "Whether this is a system VM offering (WARNING: For CloudStack internal use)", + Type: schema.TypeBool, + Optional: true, ForceNew: true, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, + }, + + "lease_duration": { + Description: "Lease duration in seconds (for temporary offerings with auto-cleanup)", + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + + "lease_expiry_action": { + Description: "Action when lease expires. Possible values: destroy, stop", + Type: schema.TypeString, + Optional: true, + Computed: true, }, }, } @@ -155,52 +444,97 @@ func resourceCloudStackServiceOffering() *schema.Resource { func resourceCloudStackServiceOfferingCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - name := d.Get("name").(string) - display_text := d.Get("display_text").(string) - // Create a new parameter struct - p := cs.ServiceOffering.NewCreateServiceOfferingParams(display_text, name) + // Create parameters structure + p := cs.ServiceOffering.NewCreateServiceOfferingParams( + d.Get("display_text").(string), + d.Get("name").(string), + ) - cpuNumber, cpuNumberOk := d.GetOk("cpu_number") - if cpuNumberOk { - p.SetCpunumber(cpuNumber.(int)) + // Set optional parameters + if v, ok := d.GetOk("cpu_number"); ok { + p.SetCpunumber(v.(int)) } - cpuSpeed, cpuSpeedOk := d.GetOk("cpu_speed") - if cpuSpeedOk { - p.SetCpuspeed(cpuSpeed.(int)) + if v, ok := d.GetOk("cpu_speed"); ok { + p.SetCpuspeed(v.(int)) + } + + if v, ok := d.GetOk("memory"); ok { + p.SetMemory(v.(int)) } if v, ok := d.GetOk("host_tags"); ok { p.SetHosttags(v.(string)) } - if v, ok := d.GetOk("limit_cpu_use"); ok { - p.SetLimitcpuuse(v.(bool)) + if v, ok := d.GetOk("storage_type"); ok { + p.SetStoragetype(v.(string)) } - memory, memoryOk := d.GetOk("memory") - if memoryOk { - p.SetMemory(memory.(int)) + // Handle customized parameter with CloudStack UI logic: + // 1. If user explicitly sets customized, use that value + // 2. If user provides cpu_number AND memory without customized, default to false (Fixed Offering) + // 3. If none specified, CloudStack creates Custom unconstrained (customized=true) + if v, ok := d.GetOkExists("customized"); ok { + // User explicitly configured customized + p.SetCustomized(v.(bool)) + } else { + // User didn't specify customized - check if cpu/memory are provided + _, hasCpuNumber := d.GetOk("cpu_number") + _, hasMemory := d.GetOk("memory") + + if hasCpuNumber && hasMemory { + // Both cpu and memory provided → Fixed Offering (customized=false) + p.SetCustomized(false) + } + // If neither provided → CloudStack will default to Custom unconstrained (customized=true) + // Don't send customized parameter, let CloudStack decide } + // Handle GPU parameters + // GPU configuration uses dedicated API parameters (not serviceofferingdetails) + + // Set vGPU profile ID (UUID) + if v, ok := d.GetOk("gpu_type"); ok { + p.SetVgpuprofileid(v.(string)) + } + + // Set GPU count + if v, ok := d.GetOk("gpu_count"); ok { + p.SetGpucount(v.(int)) + } + + // Set GPU display + if v, ok := d.GetOk("gpu_display"); ok { + p.SetGpudisplay(v.(bool)) + } + + // High Availability if v, ok := d.GetOk("offer_ha"); ok { p.SetOfferha(v.(bool)) } - if v, ok := d.GetOk("storage_type"); ok { - p.SetStoragetype(v.(string)) + // Dynamic Scaling + if v, ok := d.GetOk("dynamic_scaling_enabled"); ok { + p.SetDynamicscalingenabled(v.(bool)) + } + + // Disk Configuration + if v, ok := d.GetOk("root_disk_size"); ok { + p.SetRootdisksize(int64(v.(int))) } - customized := false - if v, ok := d.GetOk("customized"); ok { - customized = v.(bool) + if v, ok := d.GetOk("provisioning_type"); ok { + p.SetProvisioningtype(v.(string)) } - if !cpuNumberOk && !cpuSpeedOk && !memoryOk { - customized = true + + // Security + if v, ok := d.GetOk("encrypt_root"); ok { + p.SetEncryptroot(v.(bool)) } - p.SetCustomized(customized) + // Customized Offering Limits if v, ok := d.GetOk("min_cpu_number"); ok { p.SetMincpunumber(v.(int)) } @@ -217,11 +551,29 @@ func resourceCloudStackServiceOfferingCreate(d *schema.ResourceData, meta interf p.SetMaxmemory(v.(int)) } - if v, ok := d.GetOk("encrypt_root"); ok { - p.SetEncryptroot(v.(bool)) + // IOPS Limits + if v, ok := d.GetOk("min_iops"); ok { + p.SetMiniops(int64(v.(int))) + } + + if v, ok := d.GetOk("max_iops"); ok { + p.SetMaxiops(int64(v.(int))) + } + + // High Priority Parameters + if v, ok := d.GetOk("limit_cpu_use"); ok { + p.SetLimitcpuuse(v.(bool)) + } + + if v, ok := d.GetOk("is_volatile"); ok { + p.SetIsvolatile(v.(bool)) + } + + if v, ok := d.GetOk("customized_iops"); ok { + p.SetCustomizediops(v.(bool)) } - if v, ok := d.GetOk("storage_tags"); ok { + if v, ok := d.GetOk("tags"); ok { p.SetTags(v.(string)) } @@ -233,141 +585,356 @@ func resourceCloudStackServiceOfferingCreate(d *schema.ResourceData, meta interf p.SetServiceofferingdetails(serviceOfferingDetails) } - log.Printf("[DEBUG] Creating Service Offering %s", name) - s, err := cs.ServiceOffering.CreateServiceOffering(p) + if v, ok := d.GetOk("zone_id"); ok { + zones := make([]string, 0) + for _, z := range v.([]interface{}) { + zones = append(zones, z.(string)) + } + p.SetZoneid(zones) + } + + if v, ok := d.GetOk("disk_iops_read_rate"); ok { + p.SetIopsreadrate(int64(v.(int))) + } + + if v, ok := d.GetOk("disk_iops_write_rate"); ok { + p.SetIopswriterate(int64(v.(int))) + } + + if v, ok := d.GetOk("disk_iops_read_rate_max"); ok { + p.SetIopsreadratemax(int64(v.(int))) + } + + if v, ok := d.GetOk("disk_iops_write_rate_max"); ok { + p.SetIopswriteratemax(int64(v.(int))) + } + + if v, ok := d.GetOk("disk_iops_read_rate_max_length"); ok { + p.SetIopsreadratemaxlength(int64(v.(int))) + } + + if v, ok := d.GetOk("disk_iops_write_rate_max_length"); ok { + p.SetIopswriteratemaxlength(int64(v.(int))) + } + + if v, ok := d.GetOk("disk_bytes_read_rate"); ok { + p.SetBytesreadrate(int64(v.(int))) + } + + if v, ok := d.GetOk("disk_bytes_write_rate"); ok { + p.SetByteswriterate(int64(v.(int))) + } + + if v, ok := d.GetOk("disk_bytes_read_rate_max"); ok { + p.SetBytesreadratemax(int64(v.(int))) + } + + if v, ok := d.GetOk("disk_bytes_write_rate_max"); ok { + p.SetByteswriteratemax(int64(v.(int))) + } + + if v, ok := d.GetOk("bytes_read_rate_max_length"); ok { + p.SetBytesreadratemaxlength(int64(v.(int))) + } + + if v, ok := d.GetOk("bytes_write_rate_max_length"); ok { + p.SetByteswriteratemaxlength(int64(v.(int))) + } + + if v, ok := d.GetOk("hypervisor_snapshot_reserve"); ok { + p.SetHypervisorsnapshotreserve(v.(int)) + } + + if v, ok := d.GetOk("cache_mode"); ok { + p.SetCachemode(v.(string)) + } + + if v, ok := d.GetOk("deployment_planner"); ok { + p.SetDeploymentplanner(v.(string)) + } + + if v, ok := d.GetOk("storage_policy"); ok { + p.SetStoragepolicy(v.(string)) + } + if v, ok := d.GetOk("network_rate"); ok { + p.SetNetworkrate(v.(int)) + } + + if v, ok := d.GetOk("purge_resources"); ok { + p.SetPurgeresources(v.(bool)) + } + + if v, ok := d.GetOk("system_vm_type"); ok { + p.SetSystemvmtype(v.(string)) + } + + if v, ok := d.GetOk("disk_offering_id"); ok { + p.SetDiskofferingid(v.(string)) + } + + if v, ok := d.GetOk("disk_offering_strictness"); ok { + p.SetDiskofferingstrictness(v.(bool)) + } + + if v, ok := d.GetOk("external_details"); ok { + extDetails := make(map[string]string) + for key, value := range v.(map[string]interface{}) { + extDetails[key] = value.(string) + } + p.SetExternaldetails(extDetails) + } + + if v, ok := d.GetOk("is_system"); ok { + p.SetIssystem(v.(bool)) + } + + if v, ok := d.GetOk("lease_duration"); ok { + p.SetLeaseduration(v.(int)) + } + + if v, ok := d.GetOk("lease_expiry_action"); ok { + p.SetLeaseexpiryaction(v.(string)) + } + + if v, ok := d.GetOk("service_offering_details"); ok { + details := make(map[string]string) + for key, value := range v.(map[string]interface{}) { + details[key] = value.(string) + } + p.SetServiceofferingdetails(details) + } + + // Create the service offering + so, err := cs.ServiceOffering.CreateServiceOffering(p) if err != nil { - return err + return fmt.Errorf("Error creating service offering %s: %s", d.Get("name").(string), err) } - log.Printf("[DEBUG] Service Offering %s successfully created", name) - d.SetId(s.Id) + d.SetId(so.Id) return resourceCloudStackServiceOfferingRead(d, meta) } func resourceCloudStackServiceOfferingRead(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - log.Printf("[DEBUG] Retrieving Service Offering %s", d.Get("name").(string)) - - // Get the Service Offering details - s, count, err := cs.ServiceOffering.GetServiceOfferingByName(d.Get("name").(string)) + so, count, err := cs.ServiceOffering.GetServiceOfferingByID(d.Id()) if err != nil { if count == 0 { - log.Printf("[DEBUG] Service Offering %s does no longer exist", d.Get("name").(string)) d.SetId("") return nil } return err } - d.SetId(s.Id) + d.Set("name", so.Name) + d.Set("display_text", so.Displaytext) + d.Set("cpu_number", so.Cpunumber) + d.Set("cpu_speed", so.Cpuspeed) + d.Set("memory", so.Memory) + d.Set("host_tags", so.Hosttags) + d.Set("storage_type", so.Storagetype) + + // Set GPU fields from dedicated response fields + // Use gpucardname (not gpucardid) to match what user provides in terraform config + if so.Gpucardname != "" { + d.Set("gpu_card", so.Gpucardname) + } - fields := map[string]interface{}{ - "name": s.Name, - "display_text": s.Displaytext, - "cpu_number": s.Cpunumber, - "cpu_speed": s.Cpuspeed, - "host_tags": s.Hosttags, - "limit_cpu_use": s.Limitcpuuse, - "memory": s.Memory, - "offer_ha": s.Offerha, - "storage_type": s.Storagetype, - "customized": s.Iscustomized, - "min_cpu_number": getIntFromDetails(s.Serviceofferingdetails, "mincpunumber"), - "max_cpu_number": getIntFromDetails(s.Serviceofferingdetails, "maxcpunumber"), - "min_memory": getIntFromDetails(s.Serviceofferingdetails, "minmemory"), - "max_memory": getIntFromDetails(s.Serviceofferingdetails, "maxmemory"), - "encrypt_root": s.Encryptroot, - "storage_tags": s.Storagetags, - "service_offering_details": getServiceOfferingDetails(s.Serviceofferingdetails), + // Use vgpuprofileid (UUID) as configured + if so.Vgpuprofileid != "" { + d.Set("gpu_type", so.Vgpuprofileid) } - for k, v := range fields { - d.Set(k, v) + if so.Gpucount > 0 { + d.Set("gpu_count", so.Gpucount) } - return nil -} + // Set computed fields (CloudStack returns default values) + d.Set("gpu_display", so.Gpudisplay) + d.Set("offer_ha", so.Offerha) + d.Set("dynamic_scaling_enabled", so.Dynamicscalingenabled) + d.Set("encrypt_root", so.Encryptroot) + d.Set("provisioning_type", so.Provisioningtype) -func resourceCloudStackServiceOfferingUpdate(d *schema.ResourceData, meta interface{}) error { - cs := meta.(*cloudstack.CloudStackClient) + if so.Rootdisksize > 0 { + d.Set("root_disk_size", int(so.Rootdisksize)) + } - name := d.Get("name").(string) + // IOPS limits - only set if returned by API (> 0) + if so.Miniops > 0 { + d.Set("min_iops", int(so.Miniops)) + } + if so.Maxiops > 0 { + d.Set("max_iops", int(so.Maxiops)) + } - // Check if the name is changed and if so, update the service offering - if d.HasChange("name") { - log.Printf("[DEBUG] Name changed for %s, starting update", name) + // High Priority Parameters + d.Set("limit_cpu_use", so.Limitcpuuse) + d.Set("is_volatile", so.Isvolatile) + d.Set("customized_iops", so.Iscustomizediops) + d.Set("customized", so.Iscustomized) - // Create a new parameter struct - p := cs.ServiceOffering.NewUpdateServiceOfferingParams(d.Id()) + // Tags field is write-only, not returned by API - skip setting - // Set the new name - p.SetName(d.Get("name").(string)) + // Domain and Zone IDs + if so.Domainid != "" { + d.Set("domain_id", []string{so.Domainid}) + } - // Update the name - _, err := cs.ServiceOffering.UpdateServiceOffering(p) - if err != nil { - return fmt.Errorf( - "Error updating the name for service offering %s: %s", name, err) - } + if so.Zoneid != "" { + d.Set("zone_id", []string{so.Zoneid}) + } + // IOPS/Bandwidth Parameters (Phase 2) + if so.DiskIopsReadRate > 0 { + d.Set("disk_iops_read_rate", int(so.DiskIopsReadRate)) } - // Check if the display text is changed and if so, update seervice offering - if d.HasChange("display_text") { - log.Printf("[DEBUG] Display text changed for %s, starting update", name) + if so.DiskIopsWriteRate > 0 { + d.Set("disk_iops_write_rate", int(so.DiskIopsWriteRate)) + } - // Create a new parameter struct - p := cs.ServiceOffering.NewUpdateServiceOfferingParams(d.Id()) + if so.DiskIopsReadRateMax > 0 { + d.Set("disk_iops_read_rate_max", int(so.DiskIopsReadRateMax)) + } - // Set the new display text - p.SetName(d.Get("display_text").(string)) + if so.DiskIopsWriteRateMax > 0 { + d.Set("disk_iops_write_rate_max", int(so.DiskIopsWriteRateMax)) + } - // Update the display text - _, err := cs.ServiceOffering.UpdateServiceOffering(p) - if err != nil { - return fmt.Errorf( - "Error updating the display text for service offering %s: %s", name, err) - } + if so.DiskIopsReadRateMaxLength > 0 { + d.Set("disk_iops_read_rate_max_length", int(so.DiskIopsReadRateMaxLength)) + } + if so.DiskIopsWriteRateMaxLength > 0 { + d.Set("disk_iops_write_rate_max_length", int(so.DiskIopsWriteRateMaxLength)) } - if d.HasChange("host_tags") { - log.Printf("[DEBUG] Host tags changed for %s, starting update", name) + if so.DiskBytesReadRate > 0 { + d.Set("disk_bytes_read_rate", int(so.DiskBytesReadRate)) + } - // Create a new parameter struct - p := cs.ServiceOffering.NewUpdateServiceOfferingParams(d.Id()) + if so.DiskBytesWriteRate > 0 { + d.Set("disk_bytes_write_rate", int(so.DiskBytesWriteRate)) + } - // Set the new host tags - p.SetHosttags(d.Get("host_tags").(string)) + if so.DiskBytesReadRateMax > 0 { + d.Set("disk_bytes_read_rate_max", int(so.DiskBytesReadRateMax)) + } - // Update the host tags - _, err := cs.ServiceOffering.UpdateServiceOffering(p) - if err != nil { - return fmt.Errorf( - "Error updating the host tags for service offering %s: %s", name, err) - } + if so.DiskBytesWriteRateMax > 0 { + d.Set("disk_bytes_write_rate_max", int(so.DiskBytesWriteRateMax)) + } + + if so.DiskBytesReadRateMaxLength > 0 { + d.Set("bytes_read_rate_max_length", int(so.DiskBytesReadRateMaxLength)) + } + + if so.DiskBytesWriteRateMaxLength > 0 { + d.Set("bytes_write_rate_max_length", int(so.DiskBytesWriteRateMaxLength)) + } + + // Hypervisor Parameters (Phase 3) + if so.Hypervisorsnapshotreserve > 0 { + d.Set("hypervisor_snapshot_reserve", so.Hypervisorsnapshotreserve) + } + + if so.CacheMode != "" { + d.Set("cache_mode", so.CacheMode) + } + + if so.Deploymentplanner != "" { + d.Set("deployment_planner", so.Deploymentplanner) + } + + // Note: storage_policy field doesn't exist in ServiceOffering response + // This is a write-only parameter for VMware environments + + // Low Priority Parameters (Phase 4) + if so.Networkrate > 0 { + d.Set("network_rate", so.Networkrate) + } + + // Note: purge_resources is write-only, not returned by API + // Note: system_vm_type is write-only, not returned by API + + // Final Parameters - Complete SDK Coverage (Phase 5) + // Only set disk_offering_id if it was explicitly configured by user + if _, ok := d.GetOk("disk_offering_id"); ok { + d.Set("disk_offering_id", so.Diskofferingid) + } + // Only set disk_offering_strictness if it was explicitly configured + if _, ok := d.GetOk("disk_offering_strictness"); ok { + d.Set("disk_offering_strictness", so.Diskofferingstrictness) } - if d.HasChange("storage_tags") { - log.Printf("[DEBUG] Tags changed for %s, starting update", name) + // Note: external_details is write-only, not returned by API - // Create a new parameter struct + // Only set is_system if it was explicitly configured + if _, ok := d.GetOk("is_system"); ok { + d.Set("is_system", so.Issystem) + } + + if so.Leaseduration > 0 { + d.Set("lease_duration", so.Leaseduration) + } + + if so.Leaseexpiryaction != "" { + d.Set("lease_expiry_action", so.Leaseexpiryaction) + } + + // Set service offering details (only user-configured keys) + if so.Serviceofferingdetails != nil { + // Only process if user originally configured service_offering_details + if configuredDetails, ok := d.GetOk("service_offering_details"); ok { + details := make(map[string]string) + configuredMap := configuredDetails.(map[string]interface{}) + + // Only include keys that the user explicitly configured + // This prevents drift from CloudStack-managed keys like "External:*", "purge.db.entities", etc. + for userKey := range configuredMap { + if cloudValue, exists := so.Serviceofferingdetails[userKey]; exists { + details[userKey] = cloudValue + } + } + + if len(details) > 0 { + d.Set("service_offering_details", details) + } + } + } + + return nil +} + +func resourceCloudStackServiceOfferingUpdate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + // Check if name, display_text or host_tags changed + if d.HasChange("name") || d.HasChange("display_text") || d.HasChange("host_tags") { + // Create parameters structure p := cs.ServiceOffering.NewUpdateServiceOfferingParams(d.Id()) - // Set the new tags - p.SetStoragetags(d.Get("storage_tags").(string)) + if d.HasChange("name") { + p.SetName(d.Get("name").(string)) + } + + if d.HasChange("display_text") { + p.SetDisplaytext(d.Get("display_text").(string)) + } - // Update the host tags + if d.HasChange("host_tags") { + p.SetHosttags(d.Get("host_tags").(string)) + } + + // Update the service offering _, err := cs.ServiceOffering.UpdateServiceOffering(p) if err != nil { - return fmt.Errorf( - "Error updating the storage tags for service offering %s: %s", name, err) + return fmt.Errorf("Error updating service offering %s: %s", d.Get("name").(string), err) } - } return resourceCloudStackServiceOfferingRead(d, meta) @@ -376,12 +943,13 @@ func resourceCloudStackServiceOfferingUpdate(d *schema.ResourceData, meta interf func resourceCloudStackServiceOfferingDelete(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - // Create a new parameter struct + // Create parameters structure p := cs.ServiceOffering.NewDeleteServiceOfferingParams(d.Id()) - _, err := cs.ServiceOffering.DeleteServiceOffering(p) + // Delete the service offering + _, err := cs.ServiceOffering.DeleteServiceOffering(p) if err != nil { - return fmt.Errorf("Error deleting Service Offering: %s", err) + return fmt.Errorf("Error deleting service offering: %s", err) } return nil diff --git a/cloudstack/resource_cloudstack_service_offering_test.go b/cloudstack/resource_cloudstack_service_offering_test.go index c4034287..2fa03ac7 100644 --- a/cloudstack/resource_cloudstack_service_offering_test.go +++ b/cloudstack/resource_cloudstack_service_offering_test.go @@ -28,32 +28,114 @@ import ( "github.com/hashicorp/terraform-plugin-testing/terraform" ) -func TestAccCloudStackServiceOffering_basic(t *testing.T) { +const ( + testServiceOfferingResourceName = "cloudstack_service_offering" + testServiceOfferingBasic = testServiceOfferingResourceName + ".test1" + testServiceOfferingGPU = testServiceOfferingResourceName + ".gpu" + testServiceOfferingCustom = testServiceOfferingResourceName + ".custom" + testServiceOfferingDiskOpt = testServiceOfferingResourceName + ".disk_optimized" + testServiceOfferingHighPriority = testServiceOfferingResourceName + ".high_priority" +) + +func TestAccServiceOfferingBasic(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOfferingBasicConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingBasic, &so), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "cpu_number", "2"), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "cpu_speed", "2200"), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "memory", "8096"), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "storage_type", "shared"), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "offer_ha", "true"), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "limit_cpu_use", "true"), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "disk_iops_read_rate", "10000"), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "disk_iops_write_rate", "10000"), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "min_iops", "5000"), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "max_iops", "15000"), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "dynamic_scaling_enabled", "true"), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "is_volatile", "false"), + resource.TestCheckResourceAttr(testServiceOfferingBasic, "root_disk_size", "50"), + ), + }, + }, + }) +} + +func TestAccServiceOfferingWithGPU(t *testing.T) { var so cloudstack.ServiceOffering resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, Steps: []resource.TestStep{ { - Config: testAccCloudStackServiceOffering_basic, + Config: testAccCloudStackServiceOfferingGPUConfig, Check: resource.ComposeTestCheckFunc( - testAccCheckCloudStackServiceOfferingExists("cloudstack_service_offering.test1", &so), - resource.TestCheckResourceAttr("cloudstack_service_offering.test1", "cpu_number", "2"), - resource.TestCheckResourceAttr("cloudstack_service_offering.test1", "cpu_speed", "2200"), - resource.TestCheckResourceAttr("cloudstack_service_offering.test1", "memory", "8096"), + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingGPU, &so), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "name", "gpu_offering_1"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "cpu_number", "4"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "cpu_speed", "2400"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "memory", "16384"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "gpu_count", "1"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "gpu_display", "true"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "storage_type", "shared"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "offer_ha", "true"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "disk_iops_read_rate", "20000"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "disk_iops_write_rate", "20000"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "min_iops", "10000"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "max_iops", "30000"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "dynamic_scaling_enabled", "true"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "root_disk_size", "100"), ), }, }, }) } -const testAccCloudStackServiceOffering_basic = ` +const testAccCloudStackServiceOfferingBasicConfig = ` resource "cloudstack_service_offering" "test1" { - name = "service_offering_1" - display_text = "Test" - cpu_number = 2 - cpu_speed = 2200 - memory = 8096 + name = "service_offering_1" + display_text = "Test Basic Offering" + cpu_number = 2 + cpu_speed = 2200 + memory = 8096 + storage_type = "shared" + offer_ha = true + limit_cpu_use = true + disk_iops_read_rate = 10000 + disk_iops_write_rate = 10000 + min_iops = 5000 + max_iops = 15000 + dynamic_scaling_enabled = true + is_volatile = false + root_disk_size = 50 +} +` + +const testAccCloudStackServiceOfferingGPUConfig = ` +resource "cloudstack_service_offering" "gpu" { + name = "gpu_offering_1" + display_text = "Test GPU Offering" + cpu_number = 4 + cpu_speed = 2400 + memory = 16384 + gpu_count = 1 + gpu_display = true + storage_type = "shared" + offer_ha = true + disk_iops_read_rate = 20000 + disk_iops_write_rate = 20000 + min_iops = 10000 + max_iops = 30000 + dynamic_scaling_enabled = true + is_volatile = false + root_disk_size = 100 } ` @@ -71,11 +153,15 @@ func testAccCheckCloudStackServiceOfferingExists(n string, so *cloudstack.Servic cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) resp, _, err := cs.ServiceOffering.GetServiceOfferingByID(rs.Primary.ID) if err != nil { - return err + return fmt.Errorf("Error getting service offering: %s", err) + } + + if resp == nil { + return fmt.Errorf("Service offering (%s) not found", rs.Primary.ID) } if resp.Id != rs.Primary.ID { - return fmt.Errorf("Service offering not found") + return fmt.Errorf("Service offering not found: expected ID %s, got %s", rs.Primary.ID, resp.Id) } *so = *resp @@ -84,31 +170,59 @@ func testAccCheckCloudStackServiceOfferingExists(n string, so *cloudstack.Servic } } -func TestAccCloudStackServiceOffering_customized(t *testing.T) { +func testAccCheckCloudStackServiceOfferingDestroy(s *terraform.State) error { + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + + for _, rs := range s.RootModule().Resources { + if rs.Type != testServiceOfferingResourceName { + continue + } + + if rs.Primary.ID == "" { + continue + } + + resp, _, err := cs.ServiceOffering.GetServiceOfferingByID(rs.Primary.ID) + if err != nil { + // CloudStack returns 431 error code when the resource doesn't exist + // Just return nil in this case as the resource is gone + return nil + } + + if resp != nil { + return fmt.Errorf("Service offering %s still exists", rs.Primary.ID) + } + } + + return nil +} + +func TestAccServiceOfferingCustomized(t *testing.T) { var so cloudstack.ServiceOffering resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, Steps: []resource.TestStep{ { - Config: testAccCloudStackServiceOffering_customized, + Config: testAccCloudStackServiceOfferingCustomConfig, Check: resource.ComposeTestCheckFunc( - testAccCheckCloudStackServiceOfferingExists("cloudstack_service_offering.custom", &so), - resource.TestCheckResourceAttr("cloudstack_service_offering.custom", "customized", "true"), - resource.TestCheckResourceAttr("cloudstack_service_offering.custom", "min_cpu_number", "1"), - resource.TestCheckResourceAttr("cloudstack_service_offering.custom", "max_cpu_number", "8"), - resource.TestCheckResourceAttr("cloudstack_service_offering.custom", "min_memory", "1024"), - resource.TestCheckResourceAttr("cloudstack_service_offering.custom", "max_memory", "16384"), - resource.TestCheckResourceAttr("cloudstack_service_offering.custom", "cpu_speed", "1000"), - resource.TestCheckResourceAttr("cloudstack_service_offering.custom", "encrypt_root", "true"), - resource.TestCheckResourceAttr("cloudstack_service_offering.custom", "storage_tags", "production,ssd"), + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingCustom, &so), + resource.TestCheckResourceAttr(testServiceOfferingCustom, "customized", "true"), + resource.TestCheckResourceAttr(testServiceOfferingCustom, "min_cpu_number", "1"), + resource.TestCheckResourceAttr(testServiceOfferingCustom, "max_cpu_number", "8"), + resource.TestCheckResourceAttr(testServiceOfferingCustom, "min_memory", "1024"), + resource.TestCheckResourceAttr(testServiceOfferingCustom, "max_memory", "16384"), + resource.TestCheckResourceAttr(testServiceOfferingCustom, "cpu_speed", "1000"), + resource.TestCheckResourceAttr(testServiceOfferingCustom, "encrypt_root", "true"), + resource.TestCheckResourceAttr(testServiceOfferingCustom, "tags", "production,ssd"), ), }, }, }) } -const testAccCloudStackServiceOffering_customized = ` +const testAccCloudStackServiceOfferingCustomConfig = ` resource "cloudstack_service_offering" "custom" { name = "custom_service_offering" display_text = "Custom Test" @@ -119,39 +233,40 @@ resource "cloudstack_service_offering" "custom" { max_memory = 16384 cpu_speed = 1000 encrypt_root = true - storage_tags = "production,ssd" + tags = "production,ssd" } ` -func TestAccCloudStackServiceOffering_gpu(t *testing.T) { +func TestAccServiceOfferingWithVGPU(t *testing.T) { var so cloudstack.ServiceOffering resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, Steps: []resource.TestStep{ { - Config: testAccCloudStackServiceOffering_gpu, + Config: testAccCloudStackServiceOfferingVGPUConfig, Check: resource.ComposeTestCheckFunc( - testAccCheckCloudStackServiceOfferingExists("cloudstack_service_offering.gpu", &so), - resource.TestCheckResourceAttr("cloudstack_service_offering.gpu", "name", "gpu_service_offering"), - resource.TestCheckResourceAttr("cloudstack_service_offering.gpu", "display_text", "GPU Test"), - resource.TestCheckResourceAttr("cloudstack_service_offering.gpu", "cpu_number", "4"), - resource.TestCheckResourceAttr("cloudstack_service_offering.gpu", "memory", "16384"), - resource.TestCheckResourceAttr("cloudstack_service_offering.gpu", "service_offering_details.pciDevice", "Group of NVIDIA A6000 GPUs"), - resource.TestCheckResourceAttr("cloudstack_service_offering.gpu", "service_offering_details.vgpuType", "A6000-8A"), + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingGPU, &so), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "name", "gpu_service_offering"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "display_text", "GPU Test"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "cpu_number", "4"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "memory", "16384"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "service_offering_details.pciDevice", "Group of NVIDIA A6000 GPUs"), + resource.TestCheckResourceAttr(testServiceOfferingGPU, "service_offering_details.vgpuType", "A6000-8A"), ), }, }, }) } -const testAccCloudStackServiceOffering_gpu = ` +const testAccCloudStackServiceOfferingVGPUConfig = ` resource "cloudstack_service_offering" "gpu" { name = "gpu_service_offering" display_text = "GPU Test" cpu_number = 4 memory = 16384 - cpu_speed = 1000 + cpu_speed = 1000 service_offering_details = { pciDevice = "Group of NVIDIA A6000 GPUs" @@ -159,3 +274,569 @@ resource "cloudstack_service_offering" "gpu" { } } ` + +func TestAccServiceOfferingDiskOptimized(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOfferingDiskOptimizedConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingDiskOpt, &so), + resource.TestCheckResourceAttr(testServiceOfferingDiskOpt, "name", "disk_optimized_offering"), + resource.TestCheckResourceAttr(testServiceOfferingDiskOpt, "cpu_number", "4"), + resource.TestCheckResourceAttr(testServiceOfferingDiskOpt, "cpu_speed", "2000"), + resource.TestCheckResourceAttr(testServiceOfferingDiskOpt, "memory", "4096"), + resource.TestCheckResourceAttr(testServiceOfferingDiskOpt, "root_disk_size", "100"), + resource.TestCheckResourceAttr(testServiceOfferingDiskOpt, "provisioning_type", "thin"), + resource.TestCheckResourceAttr(testServiceOfferingDiskOpt, "encrypt_root", "true"), + resource.TestCheckResourceAttr(testServiceOfferingDiskOpt, "min_iops", "1000"), + resource.TestCheckResourceAttr(testServiceOfferingDiskOpt, "max_iops", "5000"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOfferingDiskOptimizedConfig = ` +resource "cloudstack_service_offering" "disk_optimized" { + name = "disk_optimized_offering" + display_text = "Disk Optimized Test" + cpu_number = 4 + cpu_speed = 2000 + memory = 4096 + storage_type = "shared" + root_disk_size = 100 + provisioning_type = "thin" + encrypt_root = true + min_iops = 1000 + max_iops = 5000 +} +` + +func TestAccServiceOfferingHighPriority(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOfferingHighPriorityConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingHighPriority, &so), + resource.TestCheckResourceAttr(testServiceOfferingHighPriority, "name", "high_priority_offering"), + resource.TestCheckResourceAttr(testServiceOfferingHighPriority, "limit_cpu_use", "true"), + resource.TestCheckResourceAttr(testServiceOfferingHighPriority, "is_volatile", "true"), + resource.TestCheckResourceAttr(testServiceOfferingHighPriority, "customized_iops", "true"), + resource.TestCheckResourceAttr(testServiceOfferingHighPriority, "tags", "production,tier1"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOfferingHighPriorityConfig = ` +resource "cloudstack_service_offering" "high_priority" { + name = "high_priority_offering" + display_text = "High Priority Parameters Test" + cpu_number = 4 + cpu_speed = 3000 + memory = 8192 + storage_type = "shared" + limit_cpu_use = true + is_volatile = true + customized_iops = true + tags = "production,tier1" +} +` + +// Phase 2: IOPS/Bandwidth Parameters Tests +func TestAccServiceOfferingPerformance(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOfferingPerformanceConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingResourceName+".performance", &so), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "name", "performance_offering"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "disk_iops_read_rate", "2000"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "disk_iops_write_rate", "1000"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "disk_iops_read_rate_max", "5000"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "disk_iops_write_rate_max", "2500"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "disk_iops_read_rate_max_length", "60"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "disk_iops_write_rate_max_length", "60"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "disk_bytes_read_rate", "209715200"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "disk_bytes_write_rate", "104857600"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "disk_bytes_read_rate_max", "524288000"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "disk_bytes_write_rate_max", "262144000"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "bytes_read_rate_max_length", "120"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".performance", "bytes_write_rate_max_length", "120"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOfferingPerformanceConfig = ` +resource "cloudstack_service_offering" "performance" { + name = "performance_offering" + display_text = "Performance with IOPS and Bandwidth Limits" + cpu_number = 8 + cpu_speed = 3000 + memory = 16384 + storage_type = "shared" + + # IOPS limits + disk_iops_read_rate = 2000 + disk_iops_write_rate = 1000 + disk_iops_read_rate_max = 5000 + disk_iops_write_rate_max = 2500 + disk_iops_read_rate_max_length = 60 + disk_iops_write_rate_max_length = 60 + + # Bandwidth limits (bytes/sec) + disk_bytes_read_rate = 209715200 # 200 MB/s + disk_bytes_write_rate = 104857600 # 100 MB/s + disk_bytes_read_rate_max = 524288000 # 500 MB/s burst + disk_bytes_write_rate_max = 262144000 # 250 MB/s burst + bytes_read_rate_max_length = 120 + bytes_write_rate_max_length = 120 +} +` + +// Phase 3: Hypervisor Parameters Tests +func TestAccServiceOfferingHypervisor(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOfferingHypervisorConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingResourceName+".hypervisor", &so), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".hypervisor", "name", "hypervisor_offering"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".hypervisor", "hypervisor_snapshot_reserve", "25"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".hypervisor", "cache_mode", "writeback"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".hypervisor", "deployment_planner", "FirstFitPlanner"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".hypervisor", "storage_policy", "VMware-VSAN-Gold"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOfferingHypervisorConfig = ` +resource "cloudstack_service_offering" "hypervisor" { + name = "hypervisor_offering" + display_text = "Hypervisor-Optimized Offering" + cpu_number = 16 + cpu_speed = 3200 + memory = 32768 + storage_type = "shared" + + # Hypervisor-specific parameters + hypervisor_snapshot_reserve = 25 + cache_mode = "writeback" + deployment_planner = "FirstFitPlanner" + storage_policy = "VMware-VSAN-Gold" +} +` + +// Phase 4: Low Priority Parameters Tests +func TestAccServiceOfferingLowPriority(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOfferingLowPriorityConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingResourceName+".low_priority", &so), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".low_priority", "name", "low_priority_offering"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".low_priority", "network_rate", "1000"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".low_priority", "purge_resources", "true"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".low_priority", "system_vm_type", "domainrouter"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOfferingLowPriorityConfig = ` +resource "cloudstack_service_offering" "low_priority" { + name = "low_priority_offering" + display_text = "System VM and Network Rate Test" + cpu_number = 1 + cpu_speed = 1000 + memory = 512 + storage_type = "local" + + # Low priority parameters + network_rate = 1000 + purge_resources = true + system_vm_type = "domainrouter" +} +` + +// Phase 5: Final SDK Parameters Tests +func TestAccServiceOfferingFinalSDK(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOfferingFinalSDKConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingResourceName+".final_sdk", &so), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".final_sdk", "name", "final_sdk_offering"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".final_sdk", "lease_duration", "86400"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".final_sdk", "lease_expiry_action", "destroy"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".final_sdk", "is_system", "false"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".final_sdk", "disk_offering_strictness", "true"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".final_sdk", "external_details.cmdb_id", "CMDB-12345"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".final_sdk", "external_details.billing_code", "TIER2"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOfferingFinalSDKConfig = ` +resource "cloudstack_service_offering" "final_sdk" { + name = "final_sdk_offering" + display_text = "Final SDK Parameters Test" + cpu_number = 4 + cpu_speed = 2400 + memory = 8192 + storage_type = "shared" + + # Final SDK parameters + lease_duration = 86400 + lease_expiry_action = "destroy" + is_system = false + disk_offering_strictness = true + + external_details = { + cmdb_id = "CMDB-12345" + billing_code = "TIER2" + } +} +` + +// Complete Test: All Parameters +func TestAccServiceOfferingComplete(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOfferingCompleteConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingResourceName+".complete", &so), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "name", "complete_offering"), + // Core parameters + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "cpu_number", "16"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "memory", "65536"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "storage_type", "shared"), + // HA + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "offer_ha", "true"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "dynamic_scaling_enabled", "true"), + // Phase 1 + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "limit_cpu_use", "true"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "is_volatile", "false"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "tags", "production,tier1"), + // Phase 2 - IOPS + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "disk_iops_read_rate", "3000"), + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "disk_bytes_read_rate", "314572800"), + // Phase 3 - Hypervisor + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "cache_mode", "writeback"), + // Phase 4 - Network + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "network_rate", "10000"), + // Phase 5 - Lease + resource.TestCheckResourceAttr(testServiceOfferingResourceName+".complete", "lease_duration", "604800"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOfferingCompleteConfig = ` +resource "cloudstack_service_offering" "complete" { + name = "complete_offering" + display_text = "Complete Test - All 54 Parameters" + + # Core compute + cpu_number = 16 + cpu_speed = 3200 + memory = 65536 + storage_type = "shared" + root_disk_size = 100 + + # HA and scaling + offer_ha = true + dynamic_scaling_enabled = true + encrypt_root = true + + # Phase 1: HIGH Priority + limit_cpu_use = true + is_volatile = false + customized_iops = true + tags = "production,tier1" + + # Phase 2: IOPS/Bandwidth + disk_iops_read_rate = 3000 + disk_iops_write_rate = 1500 + disk_iops_read_rate_max = 6000 + disk_iops_write_rate_max = 3000 + disk_iops_read_rate_max_length = 90 + disk_iops_write_rate_max_length = 90 + disk_bytes_read_rate = 314572800 # 300 MB/s + disk_bytes_write_rate = 157286400 # 150 MB/s + disk_bytes_read_rate_max = 629145600 # 600 MB/s + disk_bytes_write_rate_max = 314572800 # 300 MB/s + bytes_read_rate_max_length = 180 + bytes_write_rate_max_length = 180 + + # Phase 3: Hypervisor + hypervisor_snapshot_reserve = 30 + cache_mode = "writeback" + deployment_planner = "UserConcentratedPodPlanner" + storage_policy = "Premium-Storage" + + # Phase 4: Low Priority + network_rate = 10000 + purge_resources = false + + # Phase 5: Final SDK + lease_duration = 604800 # 7 days + lease_expiry_action = "stop" + is_system = false + + # Metadata + service_offering_details = { + tier = "platinum" + sla = "99.99" + } + + external_details = { + cmdb_id = "ASSET-PROD-001" + cost_center = "ENGINEERING" + compliance = "SOC2" + } +} +` + +// ============================================================================= +// Tests for the 3 CloudStack Service Offering Types (UI Patterns) +// ============================================================================= + +const ( + testServiceOfferingTypeFixed = testServiceOfferingResourceName + ".type_fixed" + testServiceOfferingTypeCustomConstrained = testServiceOfferingResourceName + ".type_custom_constrained" + testServiceOfferingTypeCustomUnconstrained = testServiceOfferingResourceName + ".type_custom_unconstrained" +) + +// Test Type 1: Fixed Offering (CPU/memory fixed - matches CloudStack UI "Fixed Offering") +func TestAccServiceOfferingTypeFixed(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOfferingTypeFixedConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingTypeFixed, &so), + resource.TestCheckResourceAttr(testServiceOfferingTypeFixed, "name", "ui-fixed-offering-test"), + resource.TestCheckResourceAttr(testServiceOfferingTypeFixed, "display_text", "UI Fixed Offering Test - 2 CPU, 4GB RAM"), + resource.TestCheckResourceAttr(testServiceOfferingTypeFixed, "cpu_number", "2"), + resource.TestCheckResourceAttr(testServiceOfferingTypeFixed, "cpu_speed", "2000"), + resource.TestCheckResourceAttr(testServiceOfferingTypeFixed, "memory", "4096"), + resource.TestCheckResourceAttr(testServiceOfferingTypeFixed, "customized", "false"), + resource.TestCheckResourceAttr(testServiceOfferingTypeFixed, "storage_type", "shared"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOfferingTypeFixedConfig = ` +resource "cloudstack_service_offering" "type_fixed" { + name = "ui-fixed-offering-test" + display_text = "UI Fixed Offering Test - 2 CPU, 4GB RAM" + cpu_number = 2 + cpu_speed = 2000 + memory = 4096 + storage_type = "shared" + # customized defaults to false when cpu_number and memory are provided + # This matches CloudStack UI "Fixed Offering" option +} +` + +// Test Type 2: Custom Constrained (customizable with limits - matches CloudStack UI "Custom constrained") +func TestAccServiceOfferingTypeCustomConstrained(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOfferingTypeCustomConstrainedConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingTypeCustomConstrained, &so), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomConstrained, "name", "ui-custom-constrained-test"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomConstrained, "display_text", "UI Custom Constrained Test - 2-8 CPU, 4-32GB RAM"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomConstrained, "customized", "true"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomConstrained, "cpu_speed", "2000"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomConstrained, "min_cpu_number", "2"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomConstrained, "max_cpu_number", "8"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomConstrained, "min_memory", "4096"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomConstrained, "max_memory", "32768"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomConstrained, "storage_type", "shared"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOfferingTypeCustomConstrainedConfig = ` +resource "cloudstack_service_offering" "type_custom_constrained" { + name = "ui-custom-constrained-test" + display_text = "UI Custom Constrained Test - 2-8 CPU, 4-32GB RAM" + customized = true + cpu_speed = 2000 + min_cpu_number = 2 + max_cpu_number = 8 + min_memory = 4096 + max_memory = 32768 + storage_type = "shared" + # This matches CloudStack UI "Custom constrained" option +} +` + +// Test Type 3: Custom Unconstrained (fully customizable - matches CloudStack UI "Custom unconstrained") +func TestAccServiceOfferingTypeCustomUnconstrained(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOfferingTypeCustomUnconstrainedConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingTypeCustomUnconstrained, &so), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomUnconstrained, "name", "ui-custom-unconstrained-test"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomUnconstrained, "display_text", "UI Custom Unconstrained Test - User Defined"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomUnconstrained, "customized", "true"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomUnconstrained, "storage_type", "shared"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOfferingTypeCustomUnconstrainedConfig = ` +resource "cloudstack_service_offering" "type_custom_unconstrained" { + name = "ui-custom-unconstrained-test" + display_text = "UI Custom Unconstrained Test - User Defined" + customized = true + storage_type = "shared" + # This matches CloudStack UI "Custom unconstrained" option +} +` + +// Test All Three UI Types Together (comprehensive test) +func TestAccServiceOfferingAllUITypes(t *testing.T) { + var soFixed, soConstrained, soUnconstrained cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackServiceOfferingDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOfferingAllUITypesConfig, + Check: resource.ComposeTestCheckFunc( + // Fixed Offering + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingTypeFixed, &soFixed), + resource.TestCheckResourceAttr(testServiceOfferingTypeFixed, "customized", "false"), + resource.TestCheckResourceAttr(testServiceOfferingTypeFixed, "cpu_number", "2"), + resource.TestCheckResourceAttr(testServiceOfferingTypeFixed, "memory", "4096"), + + // Custom Constrained + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingTypeCustomConstrained, &soConstrained), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomConstrained, "customized", "true"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomConstrained, "min_cpu_number", "2"), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomConstrained, "max_cpu_number", "8"), + + // Custom Unconstrained + testAccCheckCloudStackServiceOfferingExists(testServiceOfferingTypeCustomUnconstrained, &soUnconstrained), + resource.TestCheckResourceAttr(testServiceOfferingTypeCustomUnconstrained, "customized", "true"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOfferingAllUITypesConfig = ` +# Type 1: Fixed Offering (CloudStack UI: "Fixed Offering") +resource "cloudstack_service_offering" "type_fixed" { + name = "ui-fixed-all-test" + display_text = "UI Fixed Offering - All Types Test" + cpu_number = 2 + cpu_speed = 2000 + memory = 4096 + storage_type = "shared" +} + +# Type 2: Custom Constrained (CloudStack UI: "Custom constrained") +resource "cloudstack_service_offering" "type_custom_constrained" { + name = "ui-custom-constrained-all-test" + display_text = "UI Custom Constrained - All Types Test" + customized = true + cpu_speed = 2000 + min_cpu_number = 2 + max_cpu_number = 8 + min_memory = 4096 + max_memory = 32768 + storage_type = "shared" +} + +# Type 3: Custom Unconstrained (CloudStack UI: "Custom unconstrained") +resource "cloudstack_service_offering" "type_custom_unconstrained" { + name = "ui-custom-unconstrained-all-test" + display_text = "UI Custom Unconstrained - All Types Test" + customized = true + storage_type = "shared" +} +` + +// ============================================================================= +// Tests for the 3 CloudStack Service Offering Types +// ============================================================================= + +const ( + testServiceOfferingFixed = testServiceOfferingResourceName + ".fixed" + testServiceOfferingCustomConstrained = testServiceOfferingResourceName + ".custom_constrained" + testServiceOfferingCustomUnconstrained = testServiceOfferingResourceName + ".custom_unconstrained" +) + +// Test Type 1: Fixed Offering (CPU/memory fixed) diff --git a/cloudstack/service_offering_constrained_resource.go b/cloudstack/service_offering_constrained_resource.go deleted file mode 100644 index 92c80779..00000000 --- a/cloudstack/service_offering_constrained_resource.go +++ /dev/null @@ -1,313 +0,0 @@ -// -// 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 cloudstack - -import ( - "context" - "fmt" - "strconv" - - "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" -) - -var ( - _ resource.Resource = &serviceOfferingConstrainedResource{} - _ resource.ResourceWithConfigure = &serviceOfferingConstrainedResource{} -) - -func NewserviceOfferingConstrainedResource() resource.Resource { - return &serviceOfferingConstrainedResource{} -} - -type serviceOfferingConstrainedResource struct { - client *cloudstack.CloudStackClient -} - -func (r *serviceOfferingConstrainedResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Attributes: serviceOfferingMergeCommonSchema(map[string]schema.Attribute{ - "cpu_speed": schema.Int32Attribute{ - Description: "VMware and Xen based hypervisors this is the CPU speed of the service offering in MHz. For the KVM hypervisor the values of the parameters cpuSpeed and cpuNumber will be used to calculate the `shares` value. This value is used by the KVM hypervisor to calculate how much time the VM will have access to the host's CPU. The `shares` value does not have a unit, and its purpose is being a weight value for the host to compare between its guest VMs. For more information, see https://libvirt.org/formatdomain.html#cpu-tuning.", - Required: true, - PlanModifiers: []planmodifier.Int32{ - int32planmodifier.RequiresReplace(), - }, - }, - "max_cpu_number": schema.Int32Attribute{ - Description: "The maximum number of CPUs to be set with Custom Compute Offering", - Required: true, - PlanModifiers: []planmodifier.Int32{ - int32planmodifier.RequiresReplace(), - }, - }, - "max_memory": schema.Int32Attribute{ - Description: "The maximum memory size of the custom service offering in MB", - Required: true, - PlanModifiers: []planmodifier.Int32{ - int32planmodifier.RequiresReplace(), - }, - }, - "min_cpu_number": schema.Int32Attribute{ - Description: "The minimum number of CPUs to be set with Custom Compute Offering", - Required: true, - PlanModifiers: []planmodifier.Int32{ - int32planmodifier.RequiresReplace(), - }, - }, - "min_memory": schema.Int32Attribute{ - Description: "The minimum memory size of the custom service offering in MB", - Required: true, - PlanModifiers: []planmodifier.Int32{ - int32planmodifier.RequiresReplace(), - }, - }, - }), - } -} - -func (r *serviceOfferingConstrainedResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan serviceOfferingConstrainedResourceModel - var planDiskQosHypervisor ServiceOfferingDiskQosHypervisor - var planDiskOffering ServiceOfferingDiskOffering - var planDiskQosStorage ServiceOfferingDiskQosStorage - - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if !plan.ServiceOfferingDiskQosHypervisor.IsNull() { - resp.Diagnostics.Append(plan.ServiceOfferingDiskQosHypervisor.As(ctx, &planDiskQosHypervisor, basetypes.ObjectAsOptions{})...) - } - if !plan.ServiceOfferingDiskOffering.IsNull() { - resp.Diagnostics.Append(plan.ServiceOfferingDiskOffering.As(ctx, &planDiskOffering, basetypes.ObjectAsOptions{})...) - } - if !plan.ServiceOfferingDiskQosStorage.IsNull() { - resp.Diagnostics.Append(plan.ServiceOfferingDiskQosStorage.As(ctx, &planDiskQosStorage, basetypes.ObjectAsOptions{})...) - } - if resp.Diagnostics.HasError() { - return - } - - // common params - params := r.client.ServiceOffering.NewCreateServiceOfferingParams(plan.DisplayText.ValueString(), plan.Name.ValueString()) - plan.commonCreateParams(ctx, params) - planDiskQosHypervisor.commonCreateParams(ctx, params) - planDiskOffering.commonCreateParams(ctx, params) - planDiskQosStorage.commonCreateParams(ctx, params) - - // resource specific params - if !plan.CpuSpeed.IsNull() { - params.SetCpuspeed(int(plan.CpuSpeed.ValueInt32())) - } - if !plan.MaxCpuNumber.IsNull() { - params.SetMaxcpunumber(int(plan.MaxCpuNumber.ValueInt32())) - } - if !plan.MaxMemory.IsNull() { - params.SetMaxmemory(int(plan.MaxMemory.ValueInt32())) - } - if !plan.MinCpuNumber.IsNull() { - params.SetMincpunumber(int(plan.MinCpuNumber.ValueInt32())) - } - if !plan.MinMemory.IsNull() { - params.SetMinmemory(int(plan.MinMemory.ValueInt32())) - } - - // create offering - cs, err := r.client.ServiceOffering.CreateServiceOffering(params) - if err != nil { - resp.Diagnostics.AddError( - "Error creating service offering", - "Could not create constrained offering, unexpected error: "+err.Error(), - ) - return - } - - // - plan.Id = types.StringValue(cs.Id) - - // - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) -} - -func (r *serviceOfferingConstrainedResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var state serviceOfferingConstrainedResourceModel - var stateDiskQosHypervisor ServiceOfferingDiskQosHypervisor - var stateDiskOffering ServiceOfferingDiskOffering - var stateDiskQosStorage ServiceOfferingDiskQosStorage - - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if !state.ServiceOfferingDiskQosHypervisor.IsNull() { - resp.Diagnostics.Append(state.ServiceOfferingDiskQosHypervisor.As(ctx, &stateDiskQosHypervisor, basetypes.ObjectAsOptions{})...) - } - if !state.ServiceOfferingDiskOffering.IsNull() { - resp.Diagnostics.Append(state.ServiceOfferingDiskOffering.As(ctx, &stateDiskOffering, basetypes.ObjectAsOptions{})...) - } - if !state.ServiceOfferingDiskQosStorage.IsNull() { - resp.Diagnostics.Append(state.ServiceOfferingDiskQosStorage.As(ctx, &stateDiskQosStorage, basetypes.ObjectAsOptions{})...) - } - if resp.Diagnostics.HasError() { - return - } - - cs, _, err := r.client.ServiceOffering.GetServiceOfferingByID(state.Id.ValueString()) - if err != nil { - resp.Diagnostics.AddError( - "Error reading service offering", - "Could not read constrained service offering, unexpected error: "+err.Error(), - ) - return - } - - // resource specific - if cs.Cpuspeed > 0 { - state.CpuSpeed = types.Int32Value(int32(cs.Cpuspeed)) - } - if v, found := cs.Serviceofferingdetails["maxcpunumber"]; found { - i, err := strconv.Atoi(v) - if err != nil { - resp.Diagnostics.AddError( - "Error reading service offering", - "Could not read constrained service offering max cpu, unexpected error: "+err.Error(), - ) - return - } - state.MaxCpuNumber = types.Int32Value(int32(i)) - } - if v, found := cs.Serviceofferingdetails["mincpunumber"]; found { - i, err := strconv.Atoi(v) - if err != nil { - resp.Diagnostics.AddError( - "Error reading service offering", - "Could not read constrained service offering min cpu number, unexpected error: "+err.Error(), - ) - return - } - state.MinCpuNumber = types.Int32Value(int32(i)) - } - if v, found := cs.Serviceofferingdetails["maxmemory"]; found { - i, err := strconv.Atoi(v) - if err != nil { - resp.Diagnostics.AddError( - "Error reading service offering", - "Could not read constrained service offering max memory, unexpected error: "+err.Error(), - ) - return - } - state.MaxMemory = types.Int32Value(int32(i)) - } - if v, found := cs.Serviceofferingdetails["minmemory"]; found { - i, err := strconv.Atoi(v) - if err != nil { - resp.Diagnostics.AddError( - "Error reading service offering", - "Could not read constrained service offering min memory, unexpected error: "+err.Error(), - ) - return - } - state.MinMemory = types.Int32Value(int32(i)) - } - - state.commonRead(ctx, cs) - stateDiskQosHypervisor.commonRead(ctx, cs) - stateDiskOffering.commonRead(ctx, cs) - stateDiskQosStorage.commonRead(ctx, cs) - if resp.Diagnostics.HasError() { - return - } - - resp.Diagnostics.Append(resp.State.Set(ctx, state)...) - -} - -// Update updates the resource and sets the updated Terraform state on success. -func (r *serviceOfferingConstrainedResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var state serviceOfferingConstrainedResourceModel - - resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - params := r.client.ServiceOffering.NewUpdateServiceOfferingParams(state.Id.ValueString()) - state.commonUpdateParams(ctx, params) - - cs, err := r.client.ServiceOffering.UpdateServiceOffering(params) - if err != nil { - resp.Diagnostics.AddError( - "Error updating service offering", - "Could not update constrained service offering, unexpected error: "+err.Error(), - ) - return - } - - state.commonUpdate(ctx, cs) - if resp.Diagnostics.HasError() { - return - } - resp.Diagnostics.Append(resp.State.Set(ctx, state)...) -} - -func (r *serviceOfferingConstrainedResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var state serviceOfferingConstrainedResourceModel - - diags := req.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // Delete the service offering - _, err := r.client.ServiceOffering.DeleteServiceOffering(r.client.ServiceOffering.NewDeleteServiceOfferingParams(state.Id.ValueString())) - if err != nil { - resp.Diagnostics.AddError( - "Error deleting service offering", - "Could not delete constrained offering, unexpected error: "+err.Error(), - ) - return - } -} - -func (r *serviceOfferingConstrainedResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - // Add a nil check when handling ProviderData because Terraform - // sets that data after it calls the ConfigureProvider RPC. - if req.ProviderData == nil { - return - } - - client, ok := req.ProviderData.(*cloudstack.CloudStackClient) - - if !ok { - resp.Diagnostics.AddError( - "Unexpected Data Source Configure Type", - fmt.Sprintf("Expected *cloudstack.CloudStackClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), - ) - return - } - - r.client = client -} - -// Metadata returns the resource type name. -func (r *serviceOfferingConstrainedResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_service_offering_constrained" -} diff --git a/cloudstack/service_offering_constrained_resource_test.go b/cloudstack/service_offering_constrained_resource_test.go deleted file mode 100644 index 5368db7b..00000000 --- a/cloudstack/service_offering_constrained_resource_test.go +++ /dev/null @@ -1,319 +0,0 @@ -// -// 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 cloudstack - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-testing/helper/resource" -) - -func TestAccServiceOfferingConstrained(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccMuxProvider, - Steps: []resource.TestStep{ - { - Config: testAccServiceOfferingCustomConstrained1, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.constrained1", "name", "constrained1"), - ), - }, - { - Config: testAccServiceOfferingCustomConstrained1ZoneAll, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.constrained1", "name", "constrained1"), - ), - }, - { - Config: testAccServiceOfferingCustomConstrained2, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.constrained2", "name", "constrained2"), - ), - }, - { - Config: testAccServiceOfferingCustomConstrained2_update, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.constrained2", "name", "constrained2update"), - ), - }, - { - Config: testAccServiceOfferingCustomConstrained_disk, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.constrained1", "name", "constrained1"), - ), - }, - { - Config: testAccServiceOfferingCustomConstrained_disk_hypervisor, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.disk_hypervisor", "name", "disk_hypervisor"), - ), - }, - { - Config: testAccServiceOfferingCustomConstrained_disk_storage, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_constrained.disk_storage", "name", "disk_storage"), - ), - }, - }, - }) -} - -const testAccServiceOfferingCustomConstrained1 = ` -resource "cloudstack_zone" "test" { - name = "acctest" - dns1 = "8.8.8.8" - internal_dns1 = "8.8.4.4" - network_type = "Advanced" -} - -resource "cloudstack_service_offering_constrained" "constrained1" { - display_text = "constrained1" - name = "constrained1" - - // compute - cpu_speed = 2500 - max_cpu_number = 10 - min_cpu_number = 2 - - // memory - max_memory = 4096 - min_memory = 1024 - - // other - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - // Feature flags - dynamic_scaling_enabled = false - is_volatile = false - limit_cpu_use = false - offer_ha = false - zone_ids = [cloudstack_zone.test.id] - -} -` - -const testAccServiceOfferingCustomConstrained1ZoneAll = ` -resource "cloudstack_service_offering_constrained" "constrained1" { - display_text = "constrained11" - name = "constrained1" - - // compute - cpu_speed = 2500 - max_cpu_number = 10 - min_cpu_number = 2 - - // memory - max_memory = 4096 - min_memory = 1024 - - // other - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - // Feature flags - dynamic_scaling_enabled = false - is_volatile = false - limit_cpu_use = false - offer_ha = false - zone_ids = [] -} -` - -const testAccServiceOfferingCustomConstrained2 = ` -resource "cloudstack_service_offering_constrained" "constrained2" { - display_text = "constrained2" - name = "constrained2" - - // compute - cpu_speed = 2500 - max_cpu_number = 10 - min_cpu_number = 2 - - // memory - max_memory = 4096 - min_memory = 1024 - - // other - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - // Feature flags - dynamic_scaling_enabled = true - is_volatile = true - limit_cpu_use = true - offer_ha = true -} -` -const testAccServiceOfferingCustomConstrained2_update = ` -resource "cloudstack_service_offering_constrained" "constrained2" { - display_text = "constrained2update" - name = "constrained2update" - - // compute - cpu_speed = 2500 - max_cpu_number = 10 - min_cpu_number = 2 - - // memory - max_memory = 4096 - min_memory = 1024 - - // other - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - // Feature flags - dynamic_scaling_enabled = true - is_volatile = true - limit_cpu_use = true - offer_ha = true -} -` - -const testAccServiceOfferingCustomConstrained_disk = ` -resource "cloudstack_service_offering_constrained" "constrained1" { - display_text = "constrained1" - name = "constrained1" - - // compute - cpu_speed = 2500 - max_cpu_number = 10 - min_cpu_number = 2 - - // memory - max_memory = 4096 - min_memory = 1024 - - // other - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - // Feature flags - dynamic_scaling_enabled = false - is_volatile = false - limit_cpu_use = false - offer_ha = false - - disk_offering = { - storage_type = "local" - sdfjklsdf = "sdfjks" - provisioning_type = "thin" - cache_mode = "none" - root_disk_size = "5" - storage_tags = "FOO" - disk_offering_strictness = false - } -} -` - -const testAccServiceOfferingCustomConstrained_disk_hypervisor = ` -resource "cloudstack_service_offering_constrained" "disk_hypervisor" { - display_text = "disk_hypervisor" - name = "disk_hypervisor" - - // compute - cpu_speed = 2500 - max_cpu_number = 10 - min_cpu_number = 2 - - // memory - max_memory = 4096 - min_memory = 1024 - - // other - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - // Feature flags - dynamic_scaling_enabled = false - is_volatile = false - limit_cpu_use = false - offer_ha = false - - disk_offering = { - storage_type = "local" - provisioning_type = "thin" - cache_mode = "none" - root_disk_size = "5" - storage_tags = "FOO" - disk_offering_strictness = false - } - disk_hypervisor = { - bytes_read_rate = 1024 - bytes_read_rate_max = 1024 - bytes_read_rate_max_length = 1024 - bytes_write_rate = 1024 - bytes_write_rate_max = 1024 - bytes_write_rate_max_length = 1024 - } -} -` - -const testAccServiceOfferingCustomConstrained_disk_storage = ` -resource "cloudstack_service_offering_constrained" "disk_storage" { - display_text = "disk_storage" - name = "disk_storage" - - // compute - cpu_speed = 2500 - max_cpu_number = 10 - min_cpu_number = 2 - - // memory - max_memory = 4096 - min_memory = 1024 - - // other - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - // Feature flags - dynamic_scaling_enabled = false - is_volatile = false - limit_cpu_use = false - offer_ha = false - - disk_offering = { - storage_type = "local" - provisioning_type = "thin" - cache_mode = "none" - root_disk_size = "5" - storage_tags = "FOO" - disk_offering_strictness = false - } - disk_hypervisor = { - bytes_read_rate = 1024 - bytes_read_rate_max = 1024 - bytes_read_rate_max_length = 1024 - bytes_write_rate = 1024 - bytes_write_rate_max = 1024 - bytes_write_rate_max_length = 1024 - } -} -` diff --git a/cloudstack/service_offering_fixed_resource.go b/cloudstack/service_offering_fixed_resource.go deleted file mode 100644 index 6b500fd2..00000000 --- a/cloudstack/service_offering_fixed_resource.go +++ /dev/null @@ -1,251 +0,0 @@ -// -// 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 cloudstack - -import ( - "context" - "fmt" - - "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" -) - -var ( - _ resource.Resource = &serviceOfferingFixedResource{} - _ resource.ResourceWithConfigure = &serviceOfferingFixedResource{} -) - -func NewserviceOfferingFixedResource() resource.Resource { - return &serviceOfferingFixedResource{} -} - -type serviceOfferingFixedResource struct { - client *cloudstack.CloudStackClient -} - -func (r *serviceOfferingFixedResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Attributes: serviceOfferingMergeCommonSchema(map[string]schema.Attribute{ - "cpu_number": schema.Int32Attribute{ - Description: "Number of CPU cores", - Required: true, - PlanModifiers: []planmodifier.Int32{ - int32planmodifier.RequiresReplace(), - }, - }, - "cpu_speed": schema.Int32Attribute{ - Description: "the CPU speed of the service offering in MHz. This does not apply to KVM.", - Required: true, - PlanModifiers: []planmodifier.Int32{ - int32planmodifier.RequiresReplace(), - }, - }, - "memory": schema.Int32Attribute{ - Description: "the total memory of the service offering in MB", - Required: true, - PlanModifiers: []planmodifier.Int32{ - int32planmodifier.RequiresReplace(), - }, - }, - }), - } -} - -func (r *serviceOfferingFixedResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan serviceOfferingFixedResourceModel - var planDiskQosHypervisor ServiceOfferingDiskQosHypervisor - var planDiskOffering ServiceOfferingDiskOffering - var planDiskQosStorage ServiceOfferingDiskQosStorage - - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if !plan.ServiceOfferingDiskQosHypervisor.IsNull() { - resp.Diagnostics.Append(plan.ServiceOfferingDiskQosHypervisor.As(ctx, &planDiskQosHypervisor, basetypes.ObjectAsOptions{})...) - } - if !plan.ServiceOfferingDiskOffering.IsNull() { - resp.Diagnostics.Append(plan.ServiceOfferingDiskOffering.As(ctx, &planDiskOffering, basetypes.ObjectAsOptions{})...) - } - if !plan.ServiceOfferingDiskQosStorage.IsNull() { - resp.Diagnostics.Append(plan.ServiceOfferingDiskQosStorage.As(ctx, &planDiskQosStorage, basetypes.ObjectAsOptions{})...) - } - if resp.Diagnostics.HasError() { - return - } - - // cloudstack params - params := r.client.ServiceOffering.NewCreateServiceOfferingParams(plan.DisplayText.ValueString(), plan.Name.ValueString()) - plan.commonCreateParams(ctx, params) - planDiskQosHypervisor.commonCreateParams(ctx, params) - planDiskOffering.commonCreateParams(ctx, params) - planDiskQosStorage.commonCreateParams(ctx, params) - - // resource specific params - if !plan.CpuNumber.IsNull() { - params.SetCpunumber(int(plan.CpuNumber.ValueInt32())) - } - if !plan.CpuSpeed.IsNull() { - params.SetCpuspeed(int(plan.CpuSpeed.ValueInt32())) - } - if !plan.Memory.IsNull() { - params.SetMemory(int(plan.Memory.ValueInt32())) - } - - // create offering - cs, err := r.client.ServiceOffering.CreateServiceOffering(params) - if err != nil { - resp.Diagnostics.AddError( - "Error creating service offering", - "Could not create fixed offering, unexpected error: "+err.Error(), - ) - return - } - - plan.Id = types.StringValue(cs.Id) - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) -} - -func (r *serviceOfferingFixedResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - - var state serviceOfferingFixedResourceModel - var stateDiskQosHypervisor ServiceOfferingDiskQosHypervisor - var stateDiskOffering ServiceOfferingDiskOffering - var stateDiskQosStorage ServiceOfferingDiskQosStorage - - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if !state.ServiceOfferingDiskQosHypervisor.IsNull() { - resp.Diagnostics.Append(state.ServiceOfferingDiskQosHypervisor.As(ctx, &stateDiskQosHypervisor, basetypes.ObjectAsOptions{})...) - } - if !state.ServiceOfferingDiskOffering.IsNull() { - resp.Diagnostics.Append(state.ServiceOfferingDiskOffering.As(ctx, &stateDiskOffering, basetypes.ObjectAsOptions{})...) - } - if !state.ServiceOfferingDiskQosStorage.IsNull() { - resp.Diagnostics.Append(state.ServiceOfferingDiskQosStorage.As(ctx, &stateDiskQosStorage, basetypes.ObjectAsOptions{})...) - } - if resp.Diagnostics.HasError() { - return - } - - cs, _, err := r.client.ServiceOffering.GetServiceOfferingByID(state.Id.ValueString()) - if err != nil { - resp.Diagnostics.AddError( - "Error reading service offering", - "Could not read fixed service offering, unexpected error: "+err.Error(), - ) - return - } - - // resource specific - if cs.Cpunumber > 0 { - state.CpuNumber = types.Int32Value(int32(cs.Cpunumber)) - } - if cs.Cpuspeed > 0 { - state.CpuSpeed = types.Int32Value(int32(cs.Cpuspeed)) - } - if cs.Memory > 0 { - state.Memory = types.Int32Value(int32(cs.Memory)) - } - - state.commonRead(ctx, cs) - stateDiskQosHypervisor.commonRead(ctx, cs) - stateDiskOffering.commonRead(ctx, cs) - stateDiskQosStorage.commonRead(ctx, cs) - if resp.Diagnostics.HasError() { - return - } - - resp.Diagnostics.Append(resp.State.Set(ctx, state)...) - -} - -// Update updates the resource and sets the updated Terraform state on success. -func (r *serviceOfferingFixedResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var state serviceOfferingFixedResourceModel - - resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - params := r.client.ServiceOffering.NewUpdateServiceOfferingParams(state.Id.ValueString()) - state.commonUpdateParams(ctx, params) - - cs, err := r.client.ServiceOffering.UpdateServiceOffering(params) - if err != nil { - resp.Diagnostics.AddError( - "Error updating fixed service offering", - "Could not update fixed service offering, unexpected error: "+err.Error(), - ) - return - } - - state.commonUpdate(ctx, cs) - if resp.Diagnostics.HasError() { - return - } - resp.Diagnostics.Append(resp.State.Set(ctx, state)...) -} - -// Delete deletes the resource and removes the Terraform state on success. -func (r *serviceOfferingFixedResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var state serviceOfferingFixedResourceModel - - diags := req.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // Delete the service offering - _, err := r.client.ServiceOffering.DeleteServiceOffering(r.client.ServiceOffering.NewDeleteServiceOfferingParams(state.Id.ValueString())) - if err != nil { - resp.Diagnostics.AddError( - "Error deleting service offering", - "Could not delete fixed offering, unexpected error: "+err.Error(), - ) - return - } -} - -func (r *serviceOfferingFixedResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - // Add a nil check when handling ProviderData because Terraform - // sets that data after it calls the ConfigureProvider RPC. - if req.ProviderData == nil { - return - } - - client, ok := req.ProviderData.(*cloudstack.CloudStackClient) - if !ok { - resp.Diagnostics.AddError( - "Unexpected Data Source Configure Type", - fmt.Sprintf("Expected *cloudstack.CloudStackClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), - ) - return - } - r.client = client -} - -// Metadata returns the resource type name. -func (r *serviceOfferingFixedResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_service_offering_fixed" -} diff --git a/cloudstack/service_offering_fixed_resource_test.go b/cloudstack/service_offering_fixed_resource_test.go deleted file mode 100644 index 438740e0..00000000 --- a/cloudstack/service_offering_fixed_resource_test.go +++ /dev/null @@ -1,239 +0,0 @@ -// -// 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 cloudstack - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-testing/helper/resource" -) - -func TestAccServiceOfferingFixed(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccMuxProvider, - Steps: []resource.TestStep{ - { - Config: testAccServiceOfferingFixed1, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.fixed1", "name", "fixed1"), - ), - }, - { - Config: testAccServiceOfferingFixed2, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.fixed2", "name", "fixed2"), - ), - }, - { - Config: testAccServiceOfferingFixed2_update, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.fixed2", "name", "fixed2update"), - ), - }, - { - Config: testAccServiceOfferingFixed_disk, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.disk", "name", "disk"), - ), - }, - { - Config: testAccServiceOfferingFixed_disk_hypervisor, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.disk_hypervisor", "name", "disk_hypervisor"), - ), - }, - { - Config: testAccServiceOfferingFixed_disk_storage, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_fixed.disk_storage", "name", "disk_storage"), - ), - }, - }, - }) -} - -const testAccServiceOfferingFixed1 = ` -resource "cloudstack_service_offering_fixed" "fixed1" { - display_text = "fixed1" - name = "fixed1" - - // compute - cpu_number = 2 - cpu_speed = 2500 - memory = 2048 - - // other - host_tags = "test0101, test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = false - is_volatile = false - limit_cpu_use = false - offer_ha = false - -} -` - -const testAccServiceOfferingFixed2 = ` -resource "cloudstack_service_offering_fixed" "fixed2" { - display_text = "fixed2" - name = "fixed2" - - cpu_number = 2 - cpu_speed = 2500 - memory = 2048 - - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = true - is_volatile = true - limit_cpu_use = true - offer_ha = true -} -` - -const testAccServiceOfferingFixed2_update = ` -resource "cloudstack_service_offering_fixed" "fixed2" { - display_text = "fixed2update" - name = "fixed2update" - - cpu_number = 2 - cpu_speed = 2500 - memory = 2048 - - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = true - is_volatile = true - limit_cpu_use = true - offer_ha = true -} -` - -const testAccServiceOfferingFixed_disk = ` -resource "cloudstack_service_offering_fixed" "disk" { - display_text = "disk" - name = "disk" - - // compute - cpu_number = 2 - cpu_speed = 2500 - memory = 2048 - - // other - host_tags = "test0101, test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = false - is_volatile = false - limit_cpu_use = false - offer_ha = false - - disk_offering = { - storage_type = "local" - provisioning_type = "thin" - cache_mode = "none" - root_disk_size = "5" - storage_tags = "test0101,test0202" - disk_offering_strictness = false - } -} -` - -const testAccServiceOfferingFixed_disk_hypervisor = ` -resource "cloudstack_service_offering_fixed" "disk_hypervisor" { - display_text = "disk_hypervisor" - name = "disk_hypervisor" - - // compute - cpu_number = 2 - cpu_speed = 2500 - memory = 2048 - - // other - host_tags = "test0101, test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = false - is_volatile = false - limit_cpu_use = false - offer_ha = false - - disk_offering = { - storage_type = "local" - provisioning_type = "thin" - cache_mode = "none" - root_disk_size = "5" - storage_tags = "test0101,test0202" - disk_offering_strictness = false - } - disk_hypervisor = { - bytes_read_rate = 1024 - bytes_read_rate_max = 1024 - bytes_read_rate_max_length = 1024 - bytes_write_rate = 1024 - bytes_write_rate_max = 1024 - bytes_write_rate_max_length = 1024 - } -} -` - -const testAccServiceOfferingFixed_disk_storage = ` -resource "cloudstack_service_offering_fixed" "disk_storage" { - display_text = "disk_storage" - name = "disk_storage" - - // compute - cpu_number = 2 - cpu_speed = 2500 - memory = 2048 - - // other - host_tags = "test0101, test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = false - is_volatile = false - limit_cpu_use = false - offer_ha = false - - disk_offering = { - storage_type = "local" - provisioning_type = "thin" - cache_mode = "none" - root_disk_size = "5" - storage_tags = "test0101,test0202" - disk_offering_strictness = false - } - disk_storage = { - min_iops = 100 - max_iops = 100 - } -} -` diff --git a/cloudstack/service_offering_models.go b/cloudstack/service_offering_models.go deleted file mode 100644 index a93ffa48..00000000 --- a/cloudstack/service_offering_models.go +++ /dev/null @@ -1,86 +0,0 @@ -// -// 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 cloudstack - -import "github.com/hashicorp/terraform-plugin-framework/types" - -type serviceOfferingConstrainedResourceModel struct { - CpuSpeed types.Int32 `tfsdk:"cpu_speed"` - MaxCpuNumber types.Int32 `tfsdk:"max_cpu_number"` - MaxMemory types.Int32 `tfsdk:"max_memory"` - MinCpuNumber types.Int32 `tfsdk:"min_cpu_number"` - MinMemory types.Int32 `tfsdk:"min_memory"` - serviceOfferingCommonResourceModel -} - -type serviceOfferingUnconstrainedResourceModel struct { - serviceOfferingCommonResourceModel -} - -type serviceOfferingFixedResourceModel struct { - CpuNumber types.Int32 `tfsdk:"cpu_number"` - CpuSpeed types.Int32 `tfsdk:"cpu_speed"` - Memory types.Int32 `tfsdk:"memory"` - serviceOfferingCommonResourceModel -} - -type serviceOfferingCommonResourceModel struct { - DeploymentPlanner types.String `tfsdk:"deployment_planner"` - DiskOfferingId types.String `tfsdk:"disk_offering_id"` - DisplayText types.String `tfsdk:"display_text"` - DomainIds types.Set `tfsdk:"domain_ids"` - DynamicScalingEnabled types.Bool `tfsdk:"dynamic_scaling_enabled"` - HostTags types.String `tfsdk:"host_tags"` - Id types.String `tfsdk:"id"` - IsVolatile types.Bool `tfsdk:"is_volatile"` - LimitCpuUse types.Bool `tfsdk:"limit_cpu_use"` - Name types.String `tfsdk:"name"` - NetworkRate types.Int32 `tfsdk:"network_rate"` - OfferHa types.Bool `tfsdk:"offer_ha"` - ZoneIds types.Set `tfsdk:"zone_ids"` - ServiceOfferingDiskQosHypervisor types.Object `tfsdk:"disk_hypervisor"` - ServiceOfferingDiskOffering types.Object `tfsdk:"disk_offering"` - ServiceOfferingDiskQosStorage types.Object `tfsdk:"disk_storage"` -} - -type ServiceOfferingDiskQosHypervisor struct { - DiskBytesReadRate types.Int64 `tfsdk:"bytes_read_rate"` - DiskBytesReadRateMax types.Int64 `tfsdk:"bytes_read_rate_max"` - DiskBytesReadRateMaxLength types.Int64 `tfsdk:"bytes_read_rate_max_length"` - DiskBytesWriteRate types.Int64 `tfsdk:"bytes_write_rate"` - DiskBytesWriteRateMax types.Int64 `tfsdk:"bytes_write_rate_max"` - DiskBytesWriteRateMaxLength types.Int64 `tfsdk:"bytes_write_rate_max_length"` -} - -type ServiceOfferingDiskOffering struct { - CacheMode types.String `tfsdk:"cache_mode"` - DiskOfferingStrictness types.Bool `tfsdk:"disk_offering_strictness"` - ProvisionType types.String `tfsdk:"provisioning_type"` - RootDiskSize types.Int64 `tfsdk:"root_disk_size"` - StorageType types.String `tfsdk:"storage_type"` - StorageTags types.String `tfsdk:"storage_tags"` -} - -type ServiceOfferingDiskQosStorage struct { - CustomizedIops types.Bool `tfsdk:"customized_iops"` - HypervisorSnapshotReserve types.Int32 `tfsdk:"hypervisor_snapshot_reserve"` - MaxIops types.Int64 `tfsdk:"max_iops"` - MinIops types.Int64 `tfsdk:"min_iops"` -} diff --git a/cloudstack/service_offering_schema.go b/cloudstack/service_offering_schema.go deleted file mode 100644 index 9586d513..00000000 --- a/cloudstack/service_offering_schema.go +++ /dev/null @@ -1,248 +0,0 @@ -// -// 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. -// - -// Nested schema attributes arent validated -// disk_offering, disk_hypervisor, disk_storage -// ref: https://github.com/hashicorp/terraform-plugin-framework/issues/805 - -package cloudstack - -import ( - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int32planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" - "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -func serviceOfferingMergeCommonSchema(s1 map[string]schema.Attribute) map[string]schema.Attribute { - common := map[string]schema.Attribute{ - "deployment_planner": schema.StringAttribute{ - Description: "The deployment planner for the service offering", - Optional: true, - }, - "disk_offering_id": schema.StringAttribute{ - Description: "The ID of the disk offering", - Optional: true, - }, - "display_text": schema.StringAttribute{ - Description: "The display text of the service offering", - Required: true, - }, - "domain_ids": schema.SetAttribute{ - Description: "the ID of the containing domain(s), null for public offerings", - Optional: true, - ElementType: types.StringType, - }, - "dynamic_scaling_enabled": schema.BoolAttribute{ - Description: "Enable dynamic scaling of the service offering", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - Default: booldefault.StaticBool(false), - }, - "host_tags": schema.StringAttribute{ - Description: "The host tag for this service offering", - Optional: true, - }, - "id": schema.StringAttribute{ - Description: "uuid of service offering", - Computed: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.UseStateForUnknown(), - }, - }, - "is_volatile": schema.BoolAttribute{ - Description: "Service offering is volatile", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - Default: booldefault.StaticBool(false), - }, - "limit_cpu_use": schema.BoolAttribute{ - Description: "Restrict the CPU usage to committed service offering", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - Default: booldefault.StaticBool(false), - }, - "name": schema.StringAttribute{ - Description: "The name of the service offering", - Required: true, - }, - "network_rate": schema.Int32Attribute{ - Description: "Data transfer rate in megabits per second", - Optional: true, - }, - "offer_ha": schema.BoolAttribute{ - Description: "The HA for the service offering", - Optional: true, - Computed: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - Default: booldefault.StaticBool(false), - }, - "zone_ids": schema.SetAttribute{ - Description: "The ID of the zone(s)", - Optional: true, - ElementType: types.StringType, - }, - "disk_offering": schema.SingleNestedAttribute{ - Optional: true, - Attributes: map[string]schema.Attribute{ - "cache_mode": schema.StringAttribute{ - Description: "the cache mode to use for this disk offering. none, writeback or writethrough", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "disk_offering_strictness": schema.BoolAttribute{ - Description: "True/False to indicate the strictness of the disk offering association with the compute offering. When set to true, override of disk offering is not allowed when VM is deployed and change disk offering is not allowed for the ROOT disk after the VM is deployed", - Required: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - }, - "provisioning_type": schema.StringAttribute{ - Description: "provisioning type used to create volumes. Valid values are thin, sparse, fat.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "root_disk_size": schema.Int64Attribute{ - Description: "the Root disk size in GB.", - Optional: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - }, - "storage_type": schema.StringAttribute{ - Description: "the storage type of the service offering. Values are local and shared.", - Required: true, - PlanModifiers: []planmodifier.String{ - stringplanmodifier.RequiresReplace(), - }, - }, - "storage_tags": schema.StringAttribute{ - Description: "the tags for the service offering", - Optional: true, - }, - }, - }, - "disk_hypervisor": schema.SingleNestedAttribute{ - Optional: true, - Attributes: map[string]schema.Attribute{ - "bytes_read_rate": schema.Int64Attribute{ - Description: "io requests read rate of the disk offering", - Required: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - }, - "bytes_read_rate_max": schema.Int64Attribute{ - Description: "burst requests read rate of the disk offering", - Required: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - }, - "bytes_read_rate_max_length": schema.Int64Attribute{ - Description: "length (in seconds) of the burst", - Required: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - }, - "bytes_write_rate": schema.Int64Attribute{ - Description: "io requests write rate of the disk offering", - Required: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - }, - "bytes_write_rate_max": schema.Int64Attribute{ - Description: "burst io requests write rate of the disk offering", - Required: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - }, - "bytes_write_rate_max_length": schema.Int64Attribute{ - Description: "length (in seconds) of the burst", - Required: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - }, - }, - }, - "disk_storage": schema.SingleNestedAttribute{ - Optional: true, - Attributes: map[string]schema.Attribute{ - "customized_iops": schema.BoolAttribute{ - Description: "true if disk offering uses custom iops, false otherwise", - Optional: true, - PlanModifiers: []planmodifier.Bool{ - boolplanmodifier.RequiresReplace(), - }, - }, - "hypervisor_snapshot_reserve": schema.Int32Attribute{ - Description: "Hypervisor snapshot reserve space as a percent of a volume (for managed storage using Xen or VMware)", - Optional: true, - PlanModifiers: []planmodifier.Int32{ - int32planmodifier.RequiresReplace(), - }, - }, - "max_iops": schema.Int64Attribute{ - Description: "max iops of the compute offering", - Optional: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - }, - "min_iops": schema.Int64Attribute{ - Description: "min iops of the compute offering", - Optional: true, - PlanModifiers: []planmodifier.Int64{ - int64planmodifier.RequiresReplace(), - }, - }, - }, - }, - } - - for key, value := range s1 { - common[key] = value - } - - return common - -} diff --git a/cloudstack/service_offering_unconstrained_resource.go b/cloudstack/service_offering_unconstrained_resource.go deleted file mode 100644 index 98b937cd..00000000 --- a/cloudstack/service_offering_unconstrained_resource.go +++ /dev/null @@ -1,204 +0,0 @@ -// -// 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 cloudstack - -import ( - "context" - "fmt" - - "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/resource/schema" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/hashicorp/terraform-plugin-framework/types/basetypes" -) - -var ( - _ resource.Resource = &serviceOfferingUnconstrainedResource{} - _ resource.ResourceWithConfigure = &serviceOfferingUnconstrainedResource{} -) - -func NewserviceOfferingUnconstrainedResource() resource.Resource { - return &serviceOfferingUnconstrainedResource{} -} - -type serviceOfferingUnconstrainedResource struct { - client *cloudstack.CloudStackClient -} - -// Schema defines the schema for the resource. -func (r *serviceOfferingUnconstrainedResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = schema.Schema{ - Attributes: serviceOfferingMergeCommonSchema(map[string]schema.Attribute{}), - } -} - -func (r *serviceOfferingUnconstrainedResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - var plan serviceOfferingUnconstrainedResourceModel - var planDiskQosHypervisor ServiceOfferingDiskQosHypervisor - var planDiskOffering ServiceOfferingDiskOffering - var planDiskQosStorage ServiceOfferingDiskQosStorage - - resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) - if !plan.ServiceOfferingDiskQosHypervisor.IsNull() { - resp.Diagnostics.Append(plan.ServiceOfferingDiskQosHypervisor.As(ctx, &planDiskQosHypervisor, basetypes.ObjectAsOptions{})...) - } - if !plan.ServiceOfferingDiskOffering.IsNull() { - resp.Diagnostics.Append(plan.ServiceOfferingDiskOffering.As(ctx, &planDiskOffering, basetypes.ObjectAsOptions{})...) - } - if !plan.ServiceOfferingDiskQosStorage.IsNull() { - resp.Diagnostics.Append(plan.ServiceOfferingDiskQosStorage.As(ctx, &planDiskQosStorage, basetypes.ObjectAsOptions{})...) - } - if resp.Diagnostics.HasError() { - return - } - - // cloudstack params - params := r.client.ServiceOffering.NewCreateServiceOfferingParams(plan.DisplayText.ValueString(), plan.Name.ValueString()) - plan.commonCreateParams(ctx, params) - planDiskQosHypervisor.commonCreateParams(ctx, params) - planDiskOffering.commonCreateParams(ctx, params) - planDiskQosStorage.commonCreateParams(ctx, params) - - // create offering - cs, err := r.client.ServiceOffering.CreateServiceOffering(params) - if err != nil { - resp.Diagnostics.AddError( - "Error creating service offering", - "Could not create unconstrained offering, unexpected error: "+err.Error(), - ) - return - } - - plan.Id = types.StringValue(cs.Id) - resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) - -} - -func (r *serviceOfferingUnconstrainedResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - var state serviceOfferingUnconstrainedResourceModel - var stateDiskQosHypervisor ServiceOfferingDiskQosHypervisor - var stateDiskOffering ServiceOfferingDiskOffering - var stateDiskQosStorage ServiceOfferingDiskQosStorage - - resp.Diagnostics.Append(req.State.Get(ctx, &state)...) - if !state.ServiceOfferingDiskQosHypervisor.IsNull() { - resp.Diagnostics.Append(state.ServiceOfferingDiskQosHypervisor.As(ctx, &stateDiskQosHypervisor, basetypes.ObjectAsOptions{})...) - } - if !state.ServiceOfferingDiskOffering.IsNull() { - resp.Diagnostics.Append(state.ServiceOfferingDiskOffering.As(ctx, &stateDiskOffering, basetypes.ObjectAsOptions{})...) - } - if !state.ServiceOfferingDiskQosStorage.IsNull() { - resp.Diagnostics.Append(state.ServiceOfferingDiskQosStorage.As(ctx, &stateDiskQosStorage, basetypes.ObjectAsOptions{})...) - } - if resp.Diagnostics.HasError() { - return - } - - cs, _, err := r.client.ServiceOffering.GetServiceOfferingByID(state.Id.ValueString()) - if err != nil { - resp.Diagnostics.AddError( - "Error creating service offering", - "Could not read unconstrained service offering, unexpected error: "+err.Error(), - ) - return - } - - state.commonRead(ctx, cs) - stateDiskQosHypervisor.commonRead(ctx, cs) - stateDiskOffering.commonRead(ctx, cs) - stateDiskQosStorage.commonRead(ctx, cs) - if resp.Diagnostics.HasError() { - return - } - - resp.Diagnostics.Append(resp.State.Set(ctx, state)...) - -} - -func (r *serviceOfferingUnconstrainedResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { - var state serviceOfferingUnconstrainedResourceModel - - resp.Diagnostics.Append(req.Plan.Get(ctx, &state)...) - if resp.Diagnostics.HasError() { - return - } - - params := r.client.ServiceOffering.NewUpdateServiceOfferingParams(state.Id.ValueString()) - state.commonUpdateParams(ctx, params) - - cs, err := r.client.ServiceOffering.UpdateServiceOffering(params) - if err != nil { - resp.Diagnostics.AddError( - "Error updating service offering", - "Could not update unconstrained service offering, unexpected error: "+err.Error(), - ) - return - } - - state.commonUpdate(ctx, cs) - if resp.Diagnostics.HasError() { - return - } - resp.Diagnostics.Append(resp.State.Set(ctx, state)...) - -} - -func (r *serviceOfferingUnconstrainedResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - var state serviceOfferingUnconstrainedResourceModel - - diags := req.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) - if resp.Diagnostics.HasError() { - return - } - - // Delete the service offering - _, err := r.client.ServiceOffering.DeleteServiceOffering(r.client.ServiceOffering.NewDeleteServiceOfferingParams(state.Id.ValueString())) - if err != nil { - resp.Diagnostics.AddError( - "Error deleting service offering", - "Could not delete unconstrained offering, unexpected error: "+err.Error(), - ) - return - } -} - -func (r *serviceOfferingUnconstrainedResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { - if req.ProviderData == nil { - return - } - - client, ok := req.ProviderData.(*cloudstack.CloudStackClient) - - if !ok { - resp.Diagnostics.AddError( - "Unexpected Data Source Configure Type", - fmt.Sprintf("Expected *cloudstack.CloudStackClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), - ) - return - } - - r.client = client -} - -func (r *serviceOfferingUnconstrainedResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = req.ProviderTypeName + "_service_offering_unconstrained" -} diff --git a/cloudstack/service_offering_unconstrained_resource_test.go b/cloudstack/service_offering_unconstrained_resource_test.go deleted file mode 100644 index 5aba779f..00000000 --- a/cloudstack/service_offering_unconstrained_resource_test.go +++ /dev/null @@ -1,207 +0,0 @@ -// // -// // 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 cloudstack - -import ( - "testing" - - "github.com/hashicorp/terraform-plugin-testing/helper/resource" -) - -func TestAccServiceOfferingUnconstrained(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - ProtoV6ProviderFactories: testAccMuxProvider, - Steps: []resource.TestStep{ - { - Config: testAccServiceOfferingUnconstrained1, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.unconstrained1", "name", "unconstrained1"), - ), - }, - { - Config: testAccServiceOfferingUnconstrained2, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.unconstrained2", "name", "unconstrained2"), - ), - }, - { - Config: testAccServiceOfferingUnconstrained2_update, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.unconstrained2", "name", "unconstrained2update"), - ), - }, - { - Config: testAccServiceOfferingUnconstrained_disk, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.disk", "name", "disk"), - ), - }, - { - Config: testAccServiceOfferingUnconstrained_disk_hypervisor, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.disk_hypervisor", "name", "disk_hypervisor"), - ), - }, - { - Config: testAccServiceOfferingUnconstrained_disk_storage, - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("cloudstack_service_offering_unconstrained.disk_storage", "name", "disk_storage"), - ), - }, - }, - }) -} - -const testAccServiceOfferingUnconstrained1 = ` -resource "cloudstack_service_offering_unconstrained" "unconstrained1" { - display_text = "unconstrained1" - name = "unconstrained1" - - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = false - is_volatile = false - limit_cpu_use = false - offer_ha = false -} -` - -const testAccServiceOfferingUnconstrained2 = ` -resource "cloudstack_service_offering_unconstrained" "unconstrained2" { - display_text = "unconstrained2" - name = "unconstrained2" - - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = true - is_volatile = true - limit_cpu_use = true - offer_ha = true -} -` - -const testAccServiceOfferingUnconstrained2_update = ` -resource "cloudstack_service_offering_unconstrained" "unconstrained2" { - display_text = "unconstrained2update" - name = "unconstrained2update" - - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = true - is_volatile = true - limit_cpu_use = true - offer_ha = true -} -` - -const testAccServiceOfferingUnconstrained_disk = ` -resource "cloudstack_service_offering_unconstrained" "disk" { - display_text = "disk" - name = "disk" - - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = true - is_volatile = true - limit_cpu_use = true - offer_ha = true - - disk_offering = { - storage_type = "shared" - provisioning_type = "thin" - cache_mode = "none" - root_disk_size = "5" - storage_tags = "test0101,test0202" - disk_offering_strictness = false - } -} -` - -const testAccServiceOfferingUnconstrained_disk_hypervisor = ` -resource "cloudstack_service_offering_unconstrained" "disk_hypervisor" { - display_text = "disk_hypervisor" - name = "disk_hypervisor" - - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = true - is_volatile = true - limit_cpu_use = true - offer_ha = true - - disk_offering = { - storage_type = "shared" - provisioning_type = "thin" - cache_mode = "none" - root_disk_size = "5" - storage_tags = "test0101,test0202" - disk_offering_strictness = false - } - disk_hypervisor = { - bytes_read_rate = 1024 - bytes_read_rate_max = 1024 - bytes_read_rate_max_length = 1024 - bytes_write_rate = 1024 - bytes_write_rate_max = 1024 - bytes_write_rate_max_length = 1024 - } - -} -` - -const testAccServiceOfferingUnconstrained_disk_storage = ` -resource "cloudstack_service_offering_unconstrained" "disk_storage" { - display_text = "disk_storage" - name = "disk_storage" - - host_tags = "test0101,test0202" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = true - is_volatile = true - limit_cpu_use = true - offer_ha = true - - disk_offering = { - storage_type = "shared" - provisioning_type = "thin" - cache_mode = "none" - root_disk_size = "5" - storage_tags = "test0101,test0202" - disk_offering_strictness = false - } - disk_storage = { - min_iops = 100 - max_iops = 100 - } -} -` diff --git a/cloudstack/service_offering_util.go b/cloudstack/service_offering_util.go deleted file mode 100644 index ae52aa1d..00000000 --- a/cloudstack/service_offering_util.go +++ /dev/null @@ -1,277 +0,0 @@ -// 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 cloudstack - -import ( - "context" - "strings" - - "github.com/apache/cloudstack-go/v2/cloudstack" - "github.com/hashicorp/terraform-plugin-framework/types" -) - -// ------------------------------------------------------------------------------------------------------------------------------ -// Common update methods -// - -func (state *serviceOfferingCommonResourceModel) commonUpdate(ctx context.Context, cs *cloudstack.UpdateServiceOfferingResponse) { - if cs.Displaytext != "" { - state.DisplayText = types.StringValue(cs.Displaytext) - } - if cs.Domainid != "" { - state.DomainIds, _ = types.SetValueFrom(ctx, types.StringType, strings.Split(cs.Domainid, ",")) - } - if cs.Hosttags != "" { - state.HostTags = types.StringValue(cs.Hosttags) - } - if cs.Name != "" { - state.Name = types.StringValue(cs.Name) - } - if cs.Zoneid != "" { - state.ZoneIds, _ = types.SetValueFrom(ctx, types.StringType, strings.Split(cs.Zoneid, ",")) - } -} - -func (plan *serviceOfferingCommonResourceModel) commonUpdateParams(ctx context.Context, p *cloudstack.UpdateServiceOfferingParams) *cloudstack.UpdateServiceOfferingParams { - if !plan.DisplayText.IsNull() { - p.SetDisplaytext(plan.DisplayText.ValueString()) - } - if !plan.DomainIds.IsNull() { - p.SetDomainid(plan.DomainIds.String()) - } - if !plan.HostTags.IsNull() { - p.SetHosttags(plan.HostTags.ValueString()) - } - if !plan.Name.IsNull() { - p.SetName(plan.Name.ValueString()) - } - if !plan.ZoneIds.IsNull() && len(plan.ZoneIds.Elements()) > 0 { - p.SetZoneid(plan.ZoneIds.String()) - } else { - p.SetZoneid("all") - } - - return p - -} - -// ------------------------------------------------------------------------------------------------------------------------------ -// common Read methods -// - -func (state *serviceOfferingCommonResourceModel) commonRead(ctx context.Context, cs *cloudstack.ServiceOffering) { - state.Id = types.StringValue(cs.Id) - - if cs.Deploymentplanner != "" { - state.DeploymentPlanner = types.StringValue(cs.Deploymentplanner) - } - if cs.Diskofferingid != "" { - state.DiskOfferingId = types.StringValue(cs.Diskofferingid) - } - if cs.Displaytext != "" { - state.DisplayText = types.StringValue(cs.Displaytext) - } - if cs.Domainid != "" { - state.DomainIds, _ = types.SetValueFrom(ctx, types.StringType, strings.Split(cs.Domainid, ",")) - } - if cs.Hosttags != "" { - state.HostTags = types.StringValue(cs.Hosttags) - } - if cs.Name != "" { - state.Name = types.StringValue(cs.Name) - } - if cs.Networkrate > 0 { - state.NetworkRate = types.Int32Value(int32(cs.Networkrate)) - } - if cs.Zoneid != "" { - state.ZoneIds, _ = types.SetValueFrom(ctx, types.StringType, strings.Split(cs.Zoneid, ",")) - } - - state.DynamicScalingEnabled = types.BoolValue(cs.Dynamicscalingenabled) - state.IsVolatile = types.BoolValue(cs.Isvolatile) - state.LimitCpuUse = types.BoolValue(cs.Limitcpuuse) - state.OfferHa = types.BoolValue(cs.Offerha) - -} - -func (state *ServiceOfferingDiskQosHypervisor) commonRead(ctx context.Context, cs *cloudstack.ServiceOffering) { - if cs.DiskBytesReadRate > 0 { - state.DiskBytesReadRate = types.Int64Value(cs.DiskBytesReadRate) - } - if cs.DiskBytesReadRateMax > 0 { - state.DiskBytesReadRateMax = types.Int64Value(cs.DiskBytesReadRateMax) - } - if cs.DiskBytesReadRateMaxLength > 0 { - state.DiskBytesReadRateMaxLength = types.Int64Value(cs.DiskBytesReadRateMaxLength) - } - if cs.DiskBytesWriteRate > 0 { - state.DiskBytesWriteRate = types.Int64Value(cs.DiskBytesWriteRate) - } - if cs.DiskBytesWriteRateMax > 0 { - state.DiskBytesWriteRateMax = types.Int64Value(cs.DiskBytesWriteRateMax) - } - if cs.DiskBytesWriteRateMaxLength > 0 { - state.DiskBytesWriteRateMaxLength = types.Int64Value(cs.DiskBytesWriteRateMaxLength) - } - -} - -func (state *ServiceOfferingDiskOffering) commonRead(ctx context.Context, cs *cloudstack.ServiceOffering) { - - if cs.CacheMode != "" { - state.CacheMode = types.StringValue(cs.CacheMode) - } - if cs.Diskofferingstrictness { - state.DiskOfferingStrictness = types.BoolValue(cs.Diskofferingstrictness) - } - if cs.Provisioningtype != "" { - state.ProvisionType = types.StringValue(cs.Provisioningtype) - } - if cs.Rootdisksize > 0 { - state.RootDiskSize = types.Int64Value(cs.Rootdisksize) - } - if cs.Storagetype != "" { - state.StorageType = types.StringValue(cs.Storagetype) - } - if cs.Storagetags != "" { - state.StorageTags = types.StringValue(cs.Storagetags) - } -} - -func (state *ServiceOfferingDiskQosStorage) commonRead(ctx context.Context, cs *cloudstack.ServiceOffering) { - if cs.Iscustomizediops { - state.CustomizedIops = types.BoolValue(cs.Iscustomizediops) - } - if cs.Hypervisorsnapshotreserve > 0 { - state.HypervisorSnapshotReserve = types.Int32Value(int32(cs.Hypervisorsnapshotreserve)) - } - if cs.Maxiops > 0 { - state.MaxIops = types.Int64Value(cs.Maxiops) - } - if cs.Miniops > 0 { - state.MinIops = types.Int64Value(cs.Miniops) - } - -} - -// ------------------------------------------------------------------------------------------------------------------------------ -// common Create methods -// - -func (plan *serviceOfferingCommonResourceModel) commonCreateParams(ctx context.Context, p *cloudstack.CreateServiceOfferingParams) *cloudstack.CreateServiceOfferingParams { - if !plan.DeploymentPlanner.IsNull() && !plan.DeploymentPlanner.IsUnknown() { - p.SetDeploymentplanner(plan.DeploymentPlanner.ValueString()) - } else { - plan.DeploymentPlanner = types.StringNull() - } - if !plan.DiskOfferingId.IsNull() { - p.SetDiskofferingid(plan.DiskOfferingId.ValueString()) - } - if !plan.DomainIds.IsNull() { - domainids := make([]string, len(plan.DomainIds.Elements())) - plan.DomainIds.ElementsAs(ctx, &domainids, false) - p.SetDomainid(domainids) - } - if !plan.DynamicScalingEnabled.IsNull() { - p.SetDynamicscalingenabled(plan.DynamicScalingEnabled.ValueBool()) - } - if !plan.HostTags.IsNull() { - p.SetHosttags(plan.HostTags.ValueString()) - } - if !plan.IsVolatile.IsNull() { - p.SetIsvolatile(plan.IsVolatile.ValueBool()) - } - if !plan.LimitCpuUse.IsNull() { - p.SetLimitcpuuse(plan.LimitCpuUse.ValueBool()) - } - if !plan.NetworkRate.IsNull() { - p.SetNetworkrate(int(plan.NetworkRate.ValueInt32())) - } - if !plan.OfferHa.IsNull() { - p.SetOfferha(plan.OfferHa.ValueBool()) - } - if !plan.ZoneIds.IsNull() { - zoneIds := make([]string, len(plan.ZoneIds.Elements())) - plan.ZoneIds.ElementsAs(ctx, &zoneIds, false) - p.SetZoneid(zoneIds) - } - - return p - -} -func (plan *ServiceOfferingDiskQosHypervisor) commonCreateParams(ctx context.Context, p *cloudstack.CreateServiceOfferingParams) *cloudstack.CreateServiceOfferingParams { - if !plan.DiskBytesReadRate.IsNull() { - p.SetBytesreadrate(plan.DiskBytesReadRate.ValueInt64()) - } - if !plan.DiskBytesReadRateMax.IsNull() { - p.SetBytesreadratemax(plan.DiskBytesReadRateMax.ValueInt64()) - } - if !plan.DiskBytesReadRateMaxLength.IsNull() { - p.SetBytesreadratemaxlength(plan.DiskBytesReadRateMaxLength.ValueInt64()) - } - if !plan.DiskBytesWriteRate.IsNull() { - p.SetByteswriterate(plan.DiskBytesWriteRate.ValueInt64()) - } - if !plan.DiskBytesWriteRateMax.IsNull() { - p.SetByteswriteratemax(plan.DiskBytesWriteRateMax.ValueInt64()) - } - if !plan.DiskBytesWriteRateMaxLength.IsNull() { - p.SetByteswriteratemaxlength(plan.DiskBytesWriteRateMaxLength.ValueInt64()) - } - - return p -} - -func (plan *ServiceOfferingDiskOffering) commonCreateParams(ctx context.Context, p *cloudstack.CreateServiceOfferingParams) *cloudstack.CreateServiceOfferingParams { - - if !plan.CacheMode.IsNull() { - p.SetCachemode(plan.CacheMode.ValueString()) - } - if !plan.DiskOfferingStrictness.IsNull() { - p.SetDiskofferingstrictness(plan.DiskOfferingStrictness.ValueBool()) - } - if !plan.ProvisionType.IsNull() { - p.SetProvisioningtype(plan.ProvisionType.ValueString()) - } - if !plan.RootDiskSize.IsNull() { - p.SetRootdisksize(plan.RootDiskSize.ValueInt64()) - } - if !plan.StorageType.IsNull() { - p.SetStoragetype(plan.StorageType.ValueString()) - } - if !plan.StorageTags.IsNull() { - p.SetTags(plan.StorageTags.ValueString()) - } - - return p - -} - -func (plan *ServiceOfferingDiskQosStorage) commonCreateParams(ctx context.Context, p *cloudstack.CreateServiceOfferingParams) *cloudstack.CreateServiceOfferingParams { - if !plan.CustomizedIops.IsNull() { - p.SetCustomizediops(plan.CustomizedIops.ValueBool()) - } - if !plan.HypervisorSnapshotReserve.IsNull() { - p.SetHypervisorsnapshotreserve(int(plan.HypervisorSnapshotReserve.ValueInt32())) - } - if !plan.MaxIops.IsNull() { - p.SetMaxiops(int64(plan.MaxIops.ValueInt64())) - } - if !plan.MinIops.IsNull() { - p.SetMiniops((plan.MinIops.ValueInt64())) - } - - return p -} diff --git a/website/docs/r/service_offering.html.markdown b/website/docs/r/service_offering.html.markdown index 57cfe84d..06937c0a 100644 --- a/website/docs/r/service_offering.html.markdown +++ b/website/docs/r/service_offering.html.markdown @@ -1,107 +1,406 @@ --- layout: default -title: "CloudStack: cloudstack_service_offering" +page_title: "CloudStack: cloudstack_service_offering" sidebar_current: "docs-cloudstack-resource-service_offering" description: |- - Creates a Service Offering + Creates and manages a Service Offering in CloudStack --- -# CloudStack: cloudstack_service_offering +# cloudstack_service_offering -A `cloudstack_service_offering` resource manages a service offering within CloudStack. +Provides a CloudStack Service Offering resource. This resource can be used to create, modify, and delete service offerings that define the compute resources (CPU, memory, storage) available to virtual machines. + +## Understanding CloudStack Service Offering Types + +CloudStack supports **three types** of service offerings based on the `customized` parameter and how CPU/memory are configured: + +### 1. Fixed Offering (Fixed CPU and Memory) + +Users **cannot** change CPU or memory when deploying VMs. The values are fixed. + +**How to create:** Specify both `cpu_number` AND `memory` (do NOT set `customized`). + +```hcl +resource "cloudstack_service_offering" "fixed" { + name = "small-fixed" + display_text = "Small Fixed Instance - 2 CPU, 4GB RAM" + cpu_number = 2 + cpu_speed = 2000 + memory = 4096 + # customized is automatically set to false +} +``` + +### 2. Custom Constrained Offering (User Choice with Limits) + +Users **can** choose CPU and memory **within** the min/max limits you define. + +**How to create:** Set `customized = true` AND specify min/max constraints. + +```hcl +resource "cloudstack_service_offering" "constrained" { + name = "custom-constrained" + display_text = "Custom Constrained - Choose between 2-8 CPU, 2-16GB RAM" + customized = true + min_cpu_number = 2 + max_cpu_number = 8 + min_memory = 2048 # 2 GB + max_memory = 16384 # 16 GB +} +``` + +### 3. Custom Unconstrained Offering (User Choice without Limits) + +Users **can** choose **any** CPU and memory values (no restrictions). + +**How to create:** Set `customized = true` WITHOUT min/max constraints. + +```hcl +resource "cloudstack_service_offering" "unconstrained" { + name = "custom-unlimited" + display_text = "Custom Unlimited - Choose any CPU/RAM" + customized = true + # No min/max limits - users have complete freedom +} +``` ## Example Usage -### Basic Service Offering +### Basic Fixed Service Offering ```hcl -resource "cloudstack_service_offering" "example" { - name = "example-service-offering" - display_text = "Example Service Offering" - cpu_number = 2 - memory = 4096 +resource "cloudstack_service_offering" "basic" { + name = "basic-offering" + display_text = "Basic Service Offering" + cpu_number = 2 + memory = 4096 } ``` ### GPU Service Offering ```hcl -resource "cloudstack_service_offering" "gpu_offering" { - name = "gpu-a6000" - display_text = "GPU A6000 Instance" - cpu_number = 4 - memory = 16384 - - service_offering_details = { - pciDevice = "Group of NVIDIA A6000 GPUs" - vgpuType = "A6000-8A" - } +resource "cloudstack_service_offering" "gpu" { + name = "gpu-a6000" + display_text = "GPU A6000 Instance" + cpu_number = 8 + memory = 32768 + + service_offering_details = { + pciDevice = "Group of NVIDIA A6000 GPUs" + vgpuType = "A6000-8A" + } +} +``` + +### GPU Service Offering with Direct GPU Parameters + +```hcl +resource "cloudstack_service_offering" "tesla_p100_passthrough" { + name = "tesla-p100-passthrough" + display_text = "Tesla P100 GPU - 2 vCPU, 1GB RAM (Passthrough Mode)" + + # Fixed CPU and Memory configuration + cpu_number = 2 + cpu_speed = 1000 + memory = 1024 # 1 GB + + # Storage configuration + storage_type = "shared" + provisioning_type = "thin" + + # Performance and HA settings + offer_ha = false + limit_cpu_use = false + is_volatile = false + dynamic_scaling_enabled = true + encrypt_root = false + + # Cache mode + cache_mode = "none" + + # GPU Configuration - Direct API mapping + # gpu_card -> serviceofferingdetails["pciDevice"] -> resolves to gpucardid + # gpu_type -> SetVgpuprofileid() + serviceofferingdetails["vgpuType"] -> resolves to vgpuprofileid/vgpuprofilename + # gpu_count -> SetGpucount() -> number of GPUs + + gpu_card = "Tesla P100 Auto Created" # GPU Card Name + gpu_type = "8cb04cca-8395-44f8-9d1b-48eb08d48bed" # vGPU Profile UUID + gpu_count = 1 # Number of GPUs } ``` +### High Performance with IOPS Limits + +```hcl +resource "cloudstack_service_offering" "high_performance" { + name = "high-performance" + display_text = "High Performance Instance" + cpu_number = 4 + memory = 8192 + storage_type = "shared" + + # IOPS configuration + bytes_read_rate = 10485760 # 10 MB/s + bytes_write_rate = 10485760 # 10 MB/s + iops_read_rate = 1000 + iops_write_rate = 1000 + + # Additional settings + offer_ha = true + limit_cpu_use = true + host_tags = "high-performance" + storage_tags = "ssd" +} +``` + +### Dynamic Scaling Enabled + +```hcl +resource "cloudstack_service_offering" "scalable" { + name = "scalable-offering" + display_text = "Dynamically Scalable Instance" + cpu_number = 4 + memory = 8192 + dynamic_scaling_enabled = true + disk_iops_min = 500 + disk_iops_max = 2000 +} +``` ## Argument Reference The following arguments are supported: -* `name` - (Required) Name of the service offering. - Changing this forces a new resource to be created. +### Required Arguments + +* `name` - (Required) The name of the service offering. Changing this forces a new resource to be created. + +### Basic Configuration + +* `display_text` - (Optional) The display text of the service offering. If not provided, defaults to the name. + +### CPU and Memory Configuration + +* `cpu_number` - (Optional) The number of CPU cores. **Note:** When specified together with `memory`, creates a **Fixed Offering** (users cannot change CPU/memory). Changing this forces a new resource to be created. + +* `cpu_speed` - (Optional) The clock rate of the CPU cores in MHz. Changing this forces a new resource to be created. + +* `memory` - (Optional) The total memory for the service offering in MB. **Note:** When specified together with `cpu_number`, creates a **Fixed Offering** (users cannot change CPU/memory). Changing this forces a new resource to be created. + +### Customization Options (Choose Offering Type) + +* `customized` - (Optional) Controls whether users can choose CPU and memory when deploying VMs: + - **Not set** + `cpu_number` + `memory` specified = **Fixed Offering** (no user choice) + - **`true`** + min/max limits = **Custom Constrained** (user choice within limits) + - **`true`** + no limits = **Custom Unconstrained** (any user choice) + + Changing this forces a new resource to be created. + +* `customized_iops` - (Optional) Whether compute offering IOPS should be customizable. Changing this forces a new resource to be created. + +### Custom Constrained Limits + +Use these **only** when `customized = true` to set boundaries: + +* `min_cpu_number` - (Optional) Minimum number of CPU cores users can select. Changing this forces a new resource to be created. + +* `max_cpu_number` - (Optional) Maximum number of CPU cores users can select. Changing this forces a new resource to be created. + +* `min_memory` - (Optional) Minimum memory in MB users can select. Changing this forces a new resource to be created. + +* `max_memory` - (Optional) Maximum memory in MB users can select. Changing this forces a new resource to be created. + +### Storage Configuration + +* `storage_type` - (Optional) The storage type of the service offering. Valid values are `local` and `shared`. Changing this forces a new resource to be created. + +* `storage_tags` - (Optional) Comma-separated list of tags for matching storage pools. Changing this forces a new resource to be created. -* `display_text` - (Optional) The display text of the service offering. +* `host_tags` - (Optional) Comma-separated list of tags for matching hosts. Changing this forces a new resource to be created. -* `cpu_number` - (Optional) The number of CPU cores. - Changing this forces a new resource to be created. +* `root_disk_size` - (Optional) The root disk size in GB. Changing this forces a new resource to be created. -* `cpu_speed` - (Optional) The speed of the CPU in Mhz. - Changing this forces a new resource to be created. +* `encrypt_root` - (Optional) Whether to encrypt the root disk. Changing this forces a new resource to be created. -* `memory` - (Optional) Memory reserved by the VM in MB. - Changing this forces a new resource to be created. +### GPU Configuration -* `host_tags` - (Optional) The host tags for the service offering. +* `gpu_card` - (Optional) The GPU card name for GPU-enabled service offerings. This maps to `serviceofferingdetails["pciDevice"]` and CloudStack automatically resolves it to `gpucardid`. Example: `"Tesla P100 Auto Created"`. Changing this forces a new resource to be created. -* `limit_cpu_use` - (Optional) Restrict the CPU usage to committed service offering. - Changing this forces a new resource to be created. +* `gpu_type` - (Optional) The vGPU profile UUID or type for GPU-enabled service offerings. This parameter serves dual purposes: + - Sets the vGPU profile ID via `SetVgpuprofileid()` API parameter + - Populates `serviceofferingdetails["vgpuType"]` + - CloudStack uses this to determine both `vgpuprofileid` and `vgpuprofilename` + + Example: `"8cb04cca-8395-44f8-9d1b-48eb08d48bed"` for passthrough mode. Changing this forces a new resource to be created. -* `offer_ha` - (Optional) The HA for the service offering. - Changing this forces a new resource to be created. +* `gpu_count` - (Optional) The number of GPUs to allocate for this service offering. Maps directly to `SetGpucount()` API parameter. Default is `1` if not specified. Changing this forces a new resource to be created. -* `storage_type` - (Optional) The storage type of the service offering. Values are `local` and `shared`. - Changing this forces a new resource to be created. +~> **Note:** For GPU offerings, ensure your CloudStack hosts are properly configured with GPU passthrough and that the GPU card names and vGPU profile UUIDs match your physical GPU configuration. -* `customized` - (Optional) Whether the service offering allows custom CPU and memory values. Set to `true` to enable users to specify CPU/memory within the min/max constraints for constrained offerings and any value for unconstrained offerings. - Changing this forces a new resource to be created. +### IOPS and Bandwidth Limits -* `min_cpu_number` - (Optional) Minimum number of CPU cores allowed for customized offerings. - Changing this forces a new resource to be created. +~> **Note:** All IOPS and bandwidth parameters are immutable after creation. Any changes will force recreation of the service offering. -* `max_cpu_number` - (Optional) Maximum number of CPU cores allowed for customized offerings. - Changing this forces a new resource to be created. +* `bytes_read_rate` - (Optional) Bytes read rate in bytes per second. Changing this forces a new resource to be created. -* `min_memory` - (Optional) Minimum memory (in MB) allowed for customized offerings. - Changing this forces a new resource to be created. +* `bytes_read_rate_max` - (Optional) Burst bytes read rate in bytes per second. Changing this forces a new resource to be created. -* `max_memory` - (Optional) Maximum memory (in MB) allowed for customized offerings. - Changing this forces a new resource to be created. +* `bytes_read_rate_max_length` - (Optional) Length of the burst bytes read rate in seconds. Changing this forces a new resource to be created. -* `encrypt_root` - (Optional) Whether to encrypt the root disk for VMs using this service offering. - Changing this forces a new resource to be created. +* `bytes_write_rate` - (Optional) Bytes write rate in bytes per second. Changing this forces a new resource to be created. -* `storage_tags` - (Optional) Storage tags to associate with the service offering. +* `bytes_write_rate_max` - (Optional) Burst bytes write rate in bytes per second. Changing this forces a new resource to be created. -* `service_offering_details` - (Optional) A map of service offering details for GPU configuration and other advanced settings. Common keys include `pciDevice` and `vgpuType` for GPU offerings. - Changing this forces a new resource to be created. +* `bytes_write_rate_max_length` - (Optional) Length of the burst bytes write rate in seconds. Changing this forces a new resource to be created. + +* `iops_read_rate` - (Optional) IO requests read rate in IOPS. Changing this forces a new resource to be created. + +* `iops_read_rate_max` - (Optional) Burst IO requests read rate in IOPS. Changing this forces a new resource to be created. + +* `iops_read_rate_max_length` - (Optional) Length of the burst IO requests read rate in seconds. Changing this forces a new resource to be created. + +* `iops_write_rate` - (Optional) IO requests write rate in IOPS. Changing this forces a new resource to be created. + +* `iops_write_rate_max` - (Optional) Burst IO requests write rate in IOPS. Changing this forces a new resource to be created. + +* `iops_write_rate_max_length` - (Optional) Length of the burst IO requests write rate in seconds. Changing this forces a new resource to be created. + +### Dynamic Scaling and IOPS + +* `disk_iops_min` - (Optional) Minimum IOPS for the disk. Changing this forces a new resource to be created. + +* `disk_iops_max` - (Optional) Maximum IOPS for the disk. Changing this forces a new resource to be created. + +* `hypervisor_snapshot_reserve` - (Optional) Hypervisor snapshot reserve space as a percent of a volume (for managed storage using Xen or VMware). Changing this forces a new resource to be created. + +### High Availability and Performance + +* `offer_ha` - (Optional) Whether HA (High Availability) is enabled for the service offering. Changing this forces a new resource to be created. + +* `limit_cpu_use` - (Optional) Restrict the CPU usage to the committed service offering percentage. Changing this forces a new resource to be created. + +* `dynamic_scaling_enabled` - (Optional) Enable dynamic scaling of VM CPU and memory. Changing this forces a new resource to be created. + +### Cache and Deployment + +* `cache_mode` - (Optional) The cache mode to use. Valid values: `none`, `writeback`, `writethrough`. Changing this forces a new resource to be created. + +* `deployment_planner` - (Optional) The deployment planner heuristics used to deploy VMs. Changing this forces a new resource to be created. + +### System VM Configuration + +* `is_system` - (Optional) Whether the offering is for system VMs. Changing this forces a new resource to be created. + +* `system_vm_type` - (Optional) The system VM type. Required when `is_system` is true. Valid values: `domainrouter`, `consoleproxy`, `secondarystoragevm`. Changing this forces a new resource to be created. + +### Zone and Domain + +* `zone_id` - (Optional) The ID of the zone for which the service offering is available. Changing this forces a new resource to be created. + +* `domain_id` - (Optional) The ID of the domain for which the service offering is available. Changing this forces a new resource to be created. + +### Provisioning + +* `provisioning_type` - (Optional) Provisioning type used to create volumes. Valid values: `thin`, `sparse`, `fat`. Changing this forces a new resource to be created. + +* `is_volatile` - (Optional) If true, the VM's original root disk is destroyed and recreated on every reboot. Changing this forces a new resource to be created. + +### Advanced Settings + +* `service_offering_details` - (Optional) A map of service offering details for GPU configuration and other advanced settings. Common keys include: + - `pciDevice` - PCI device for GPU passthrough + - `vgpuType` - vGPU type for GPU offerings + + ~> **Note:** CloudStack may add system keys (like `External:key`, `purge.db.entities`) to this map. Terraform automatically filters these out to prevent state drift. + + Changing this forces a new resource to be created. ## Attributes Reference -The following attributes are exported: +In addition to all arguments above, the following attributes are exported: * `id` - The ID of the service offering. +* `created` - The date when the service offering was created. ## Import -Service offerings can be imported; use `` as the import ID. For example: +Service offerings can be imported using the service offering ID: ```shell -$ terraform import cloudstack_service_offering.example +terraform import cloudstack_service_offering.example 550e8400-e29b-41d4-a716-446655440000 +``` + +When importing resources associated with a project, use the format `project_name/offering_id`: + +```shell +terraform import cloudstack_service_offering.example my-project/550e8400-e29b-41d4-a716-446655440000 +``` + +## Important Notes + +### Understanding Offering Types Decision Tree + +``` +Need to create a Service Offering? +│ +├─ Users should choose CPU/RAM? ──NO──> Fixed Offering +│ (set cpu_number + memory) +│ +└─ YES + │ + ├─ Limit their choices? ──NO──> Custom Unconstrained + │ (customized = true, no min/max) + │ + └─ YES ──> Custom Constrained + (customized = true, set min/max limits) +``` + +### ForceNew Behavior + +⚠️ **Most parameters are immutable** due to CloudStack API limitations. Changes to the following will destroy and recreate the service offering: + +- All CPU, memory, and customization parameters +- All storage and IOPS/bandwidth parameters +- All HA, scaling, and system VM parameters +- `service_offering_details` map + +**Only these can be updated in-place:** +- `display_text` +- `host_tags` +- `storage_tags` + +### CloudStack API Behaviors + +1. **`customized` parameter logic:** + - If you provide both `cpu_number` AND `memory`: Terraform automatically sets `customized = false` (Fixed Offering) + - If you set `customized = true`: Users can choose CPU/RAM at VM deployment time + - If neither are set: CloudStack creates a Custom Unconstrained offering + +2. **`service_offering_details` filtering:** + - CloudStack automatically adds system-managed keys: `External:key`, `External:value`, `purge.db.entities` + - Terraform filters these out automatically to prevent state drift + - Only the keys YOU configure are tracked in Terraform state + +3. **Write-only parameters:** + - Some parameters (`tags`, `lease_duration`) are not returned by CloudStack's Read API + - These are write-only and won't appear in state after creation + +### Performance Configuration Examples + +**Example: 10 MB/s read rate** +```hcl +bytes_read_rate = 10485760 # 10 * 1024 * 1024 = 10 MB/s in bytes +``` + +**Example: 1000 IOPS** +```hcl +iops_read_rate = 1000 # Direct IOPS value +``` + +**Example: Burst configuration** +```hcl +bytes_write_rate = 5242880 # 5 MB/s baseline +bytes_write_rate_max = 10485760 # 10 MB/s burst +bytes_write_rate_max_length = 60 # Burst for 60 seconds ``` diff --git a/website/docs/r/service_offering_constrained.html.markdown b/website/docs/r/service_offering_constrained.html.markdown deleted file mode 100644 index 20a33b52..00000000 --- a/website/docs/r/service_offering_constrained.html.markdown +++ /dev/null @@ -1,96 +0,0 @@ ---- -page_title: "cloudstack_service_offering_constrained Resource" -description: |- - Provides a CloudStack Constrained Service Offering resource. This allows you to create and manage constrained compute offerings in CloudStack. ---- - -# cloudstack_service_offering_constrained - -Provides a CloudStack Constrained Service Offering resource. This resource allows you to create and manage service offerings with constrained CPU and memory parameters. - -## Example Usage - -```hcl -resource "cloudstack_service_offering_constrained" "example" { - display_text = "Example Constrained Offering" - name = "example_constrained" - - cpu_speed = 2500 - max_cpu_number = 10 - min_cpu_number = 2 - max_memory = 4096 - min_memory = 1024 - - host_tags = "tag1,tag2" - network_rate = 1024 - deployment_planner = "UserDispersingPlanner" - - dynamic_scaling_enabled = false - is_volatile = false - limit_cpu_use = false - offer_ha = false - zone_ids = ["zone-uuid"] - - disk_offering { - cache_mode = "none" - disk_offering_strictness = true - provisioning_type = "thin" - storage_type = "local" - } -} -``` - -## Argument Reference - -The following arguments are supported: - -- `display_text` (String, Required) - The display text of the service offering. -- `name` (String, Required) - The name of the service offering. -- `cpu_speed` (Int, Required) - CPU speed in MHz. -- `max_cpu_number` (Int, Required) - Maximum number of CPUs. -- `min_cpu_number` (Int, Required) - Minimum number of CPUs. -- `max_memory` (Int, Required) - Maximum memory in MB. -- `min_memory` (Int, Required) - Minimum memory in MB. -- `deployment_planner` (String, Optional) - The deployment planner for the service offering. -- `disk_offering_id` (String, Optional) - The ID of the disk offering. -- `domain_ids` (Set of String, Optional) - The ID(s) of the containing domain(s), null for public offerings. -- `dynamic_scaling_enabled` (Bool, Optional, Default: false) - Enable dynamic scaling of the service offering. -- `host_tags` (String, Optional) - The host tag for this service offering. -- `is_volatile` (Bool, Optional, Default: false) - Service offering is volatile. -- `limit_cpu_use` (Bool, Optional, Default: false) - Restrict the CPU usage to committed service offering. -- `network_rate` (Int, Optional) - Data transfer rate in megabits per second. -- `offer_ha` (Bool, Optional, Default: false) - Enable HA for the service offering. -- `zone_ids` (Set of String, Optional) - The ID(s) of the zone(s). - -### Nested Blocks - -#### `disk_offering` (Block, Optional) - -- `cache_mode` (String, Required) - The cache mode to use for this disk offering. One of `none`, `writeback`, or `writethrough`. -- `disk_offering_strictness` (Bool, Required) - True/False to indicate the strictness of the disk offering association with the compute offering. -- `provisioning_type` (String, Required) - Provisioning type used to create volumes. Valid values are `thin`, `sparse`, `fat`. -- `root_disk_size` (Int, Optional) - The root disk size in GB. -- `storage_type` (String, Required) - The storage type of the service offering. Values are `local` and `shared`. -- `storage_tags` (String, Optional) - The tags for the service offering. - -#### `disk_hypervisor` (Block, Optional) - -- `bytes_read_rate` (Int, Required) - IO requests read rate of the disk offering. -- `bytes_read_rate_max` (Int, Required) - Burst requests read rate of the disk offering. -- `bytes_read_rate_max_length` (Int, Required) - Length (in seconds) of the burst. -- `bytes_write_rate` (Int, Required) - IO requests write rate of the disk offering. -- `bytes_write_rate_max` (Int, Required) - Burst IO requests write rate of the disk offering. -- `bytes_write_rate_max_length` (Int, Required) - Length (in seconds) of the burst. - -#### `disk_storage` (Block, Optional) - -- `customized_iops` (Bool, Optional) - True if disk offering uses custom IOPS, false otherwise. -- `hypervisor_snapshot_reserve` (Int, Optional) - Hypervisor snapshot reserve space as a percent of a volume (for managed storage using Xen or VMware). -- `max_iops` (Int, Optional) - Max IOPS of the compute offering. -- `min_iops` (Int, Optional) - Min IOPS of the compute offering. - -## Attributes Reference - -In addition to the arguments above, the following attributes are exported: - -- `id` - The UUID of the service offering. diff --git a/website/docs/r/service_offering_fixed.html.markdown b/website/docs/r/service_offering_fixed.html.markdown deleted file mode 100644 index 448baa4c..00000000 --- a/website/docs/r/service_offering_fixed.html.markdown +++ /dev/null @@ -1,101 +0,0 @@ ---- -page_title: "cloudstack_service_offering_fixed Resource" -sidebar_current: terraform-resource-service_offering_fixed -description: |- - Provides a CloudStack Service Offering (Fixed) resource. This resource allows you to create and manage fixed compute service offerings in CloudStack. ---- - -# cloudstack_service_offering_fixed - -Provides a CloudStack Service Offering (Fixed) resource. This resource allows you to create and manage fixed compute service offerings in CloudStack. - -## Example Usage - -```hcl -resource "cloudstack_service_offering_fixed" "fixed1" { - name = "fixed1" - display_text = "fixed1" - cpu_number = 2 - cpu_speed = 2000 - memory = 4096 - # Optional common attributes: - # deployment_planner = "FirstFit" - # disk_offering_id = "..." - # domain_ids = ["...", "..."] - # dynamic_scaling_enabled = false - # host_tags = "..." - # is_volatile = false - # limit_cpu_use = false - # network_rate = 1000 - # offer_ha = false - # zone_ids = ["..."] - # disk_offering { ... } - # disk_hypervisor { ... } - # disk_storage { ... } -} -``` - -## Argument Reference - -The following arguments are supported: - -- `name` (Required) - The name of the service offering. -- `display_text` (Required) - The display text of the service offering. -- `cpu_number` (Required) - Number of CPU cores. -- `cpu_speed` (Required) - The CPU speed in MHz (not applicable to KVM). -- `memory` (Required) - The total memory in MB. - -### Common Attributes - -- `deployment_planner` (Optional) - The deployment planner for the service offering. -- `disk_offering_id` (Optional) - The ID of the disk offering. -- `domain_ids` (Optional) - The ID(s) of the containing domain(s), null for public offerings. -- `dynamic_scaling_enabled` (Optional, Computed) - Enable dynamic scaling of the service offering. Defaults to `false`. -- `host_tags` (Optional) - The host tag for this service offering. -- `id` (Computed) - The UUID of the service offering. -- `is_volatile` (Optional, Computed) - Service offering is volatile. Defaults to `false`. -- `limit_cpu_use` (Optional, Computed) - Restrict the CPU usage to committed service offering. Defaults to `false`. -- `network_rate` (Optional) - Data transfer rate in megabits per second. -- `offer_ha` (Optional, Computed) - The HA for the service offering. Defaults to `false`. -- `zone_ids` (Optional) - The ID(s) of the zone(s). - -### Nested Blocks - -#### `disk_offering` (Optional) - -- `cache_mode` (Required) - The cache mode to use for this disk offering. One of `none`, `writeback`, or `writethrough`. -- `disk_offering_strictness` (Required) - True/False to indicate the strictness of the disk offering association. -- `provisioning_type` (Required) - Provisioning type used to create volumes. Valid values: `thin`, `sparse`, `fat`. -- `root_disk_size` (Optional) - The root disk size in GB. -- `storage_type` (Required) - The storage type. Values: `local`, `shared`. -- `storage_tags` (Optional) - The tags for the service offering. - -#### `disk_hypervisor` (Optional) - -- `bytes_read_rate` (Required) - IO requests read rate. -- `bytes_read_rate_max` (Required) - Burst requests read rate. -- `bytes_read_rate_max_length` (Required) - Length (in seconds) of the burst. -- `bytes_write_rate` (Required) - IO requests write rate. -- `bytes_write_rate_max` (Required) - Burst IO requests write rate. -- `bytes_write_rate_max_length` (Required) - Length (in seconds) of the burst. - -#### `disk_storage` (Optional) - -- `customized_iops` (Optional) - True if disk offering uses custom IOPS. -- `hypervisor_snapshot_reserve` (Optional) - Hypervisor snapshot reserve space as a percent of a volume. -- `max_iops` (Optional) - Max IOPS of the compute offering. -- `min_iops` (Optional) - Min IOPS of the compute offering. - -## Attributes Reference - -In addition to the arguments above, the following attributes are exported: - -- `id` - The ID of the service offering. - -## Import - -Service offerings can be imported using the ID: - -```sh -terraform import cloudstack_service_offering_fixed.example -``` diff --git a/website/docs/r/service_offering_unconstrained.html.markdown b/website/docs/r/service_offering_unconstrained.html.markdown deleted file mode 100644 index 4a71140c..00000000 --- a/website/docs/r/service_offering_unconstrained.html.markdown +++ /dev/null @@ -1,95 +0,0 @@ ---- -page_title: "cloudstack_service_offering_unconstrained Resource" -sidebar_current: terraform-resource-service_offering_unconstrained -description: |- - Provides a CloudStack Service Offering (Unconstrained) resource. This resource allows you to create and manage unconstrained compute service offerings in CloudStack. ---- - -# cloudstack_service_offering_unconstrained - -Provides a CloudStack Service Offering (Unconstrained) resource. This resource allows you to create and manage unconstrained compute service offerings in CloudStack. - -## Example Usage - -```hcl -resource "cloudstack_service_offering_unconstrained" "unconstrained1" { - name = "unconstrained1" - display_text = "unconstrained1" - # Optional common attributes: - # deployment_planner = "FirstFit" - # disk_offering_id = "..." - # domain_ids = ["...", "..."] - # dynamic_scaling_enabled = false - # host_tags = "..." - # is_volatile = false - # limit_cpu_use = false - # network_rate = 1000 - # offer_ha = false - # zone_ids = ["..."] - # disk_offering { ... } - # disk_hypervisor { ... } - # disk_storage { ... } -} -``` - -## Argument Reference - -The following arguments are supported: - -- `name` (Required) - The name of the service offering. -- `display_text` (Required) - The display text of the service offering. - -### Common Attributes - -- `deployment_planner` (Optional) - The deployment planner for the service offering. -- `disk_offering_id` (Optional) - The ID of the disk offering. -- `domain_ids` (Optional) - The ID(s) of the containing domain(s), null for public offerings. -- `dynamic_scaling_enabled` (Optional, Computed) - Enable dynamic scaling of the service offering. Defaults to `false`. -- `host_tags` (Optional) - The host tag for this service offering. -- `id` (Computed) - The UUID of the service offering. -- `is_volatile` (Optional, Computed) - Service offering is volatile. Defaults to `false`. -- `limit_cpu_use` (Optional, Computed) - Restrict the CPU usage to committed service offering. Defaults to `false`. -- `network_rate` (Optional) - Data transfer rate in megabits per second. -- `offer_ha` (Optional, Computed) - The HA for the service offering. Defaults to `false`. -- `zone_ids` (Optional) - The ID(s) of the zone(s). - -### Nested Blocks - -#### `disk_offering` (Optional) - -- `cache_mode` (Required) - The cache mode to use for this disk offering. One of `none`, `writeback`, or `writethrough`. -- `disk_offering_strictness` (Required) - True/False to indicate the strictness of the disk offering association. -- `provisioning_type` (Required) - Provisioning type used to create volumes. Valid values: `thin`, `sparse`, `fat`. -- `root_disk_size` (Optional) - The root disk size in GB. -- `storage_type` (Required) - The storage type. Values: `local`, `shared`. -- `storage_tags` (Optional) - The tags for the service offering. - -#### `disk_hypervisor` (Optional) - -- `bytes_read_rate` (Required) - IO requests read rate. -- `bytes_read_rate_max` (Required) - Burst requests read rate. -- `bytes_read_rate_max_length` (Required) - Length (in seconds) of the burst. -- `bytes_write_rate` (Required) - IO requests write rate. -- `bytes_write_rate_max` (Required) - Burst IO requests write rate. -- `bytes_write_rate_max_length` (Required) - Length (in seconds) of the burst. - -#### `disk_storage` (Optional) - -- `customized_iops` (Optional) - True if disk offering uses custom IOPS. -- `hypervisor_snapshot_reserve` (Optional) - Hypervisor snapshot reserve space as a percent of a volume. -- `max_iops` (Optional) - Max IOPS of the compute offering. -- `min_iops` (Optional) - Min IOPS of the compute offering. - -## Attributes Reference - -In addition to the arguments above, the following attributes are exported: - -- `id` - The ID of the service offering. - -## Import - -Service offerings can be imported using the ID: - -```sh -terraform import cloudstack_service_offering_unconstrained.example -```