From b3b9306d70c06fa38c2134e0bedd04dca80888a4 Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Fri, 22 Mar 2024 15:30:12 +0200 Subject: [PATCH 01/14] feat: Create resource for supervisor cluster enablement. Signed-off-by: Stoyan Zhelyazkov --- vsphere/provider.go | 1 + vsphere/resource_vsphere_supervisor.go | 252 ++++++++++++++++++++ vsphere/resource_vsphere_supervisor_test.go | 90 +++++++ 3 files changed, 343 insertions(+) create mode 100644 vsphere/resource_vsphere_supervisor.go create mode 100644 vsphere/resource_vsphere_supervisor_test.go diff --git a/vsphere/provider.go b/vsphere/provider.go index 7ff63b92e..b5142c2ae 100644 --- a/vsphere/provider.go +++ b/vsphere/provider.go @@ -133,6 +133,7 @@ func Provider() *schema.Provider { "vsphere_virtual_machine": resourceVSphereVirtualMachine(), "vsphere_nas_datastore": resourceVSphereNasDatastore(), "vsphere_storage_drs_vm_override": resourceVSphereStorageDrsVMOverride(), + "vsphere_supervisor": resourceVsphereSupervisor(), "vsphere_vapp_container": resourceVSphereVAppContainer(), "vsphere_vapp_entity": resourceVSphereVAppEntity(), "vsphere_vmfs_datastore": resourceVSphereVmfsDatastore(), diff --git a/vsphere/resource_vsphere_supervisor.go b/vsphere/resource_vsphere_supervisor.go new file mode 100644 index 000000000..742771ed6 --- /dev/null +++ b/vsphere/resource_vsphere_supervisor.go @@ -0,0 +1,252 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vsphere + +import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-vsphere/vsphere/internal/helper/structure" + "github.com/vmware/govmomi/vapi/namespace" +) + +func resourceVsphereSupervisor() *schema.Resource { + return &schema.Resource{ + Create: resourceVsphereSupervisorCreate, + Read: resourceVsphereSupervisorRead, + Update: resourceVsphereSupervisorUpdate, + Delete: resourceVsphereSupervisorDelete, + Schema: map[string]*schema.Schema{ + "cluster": { + Type: schema.TypeString, + Required: true, + Description: "ID of the vSphere cluster on which workload management will be enabled.", + }, + "storage_policy": { + Type: schema.TypeString, + Required: true, + Description: "TODO", + }, + "management_network": { + Type: schema.TypeList, + Required: true, + Description: "TODO", + MaxItems: 1, + Elem: mgmtNetworkSchema(), + }, + "content_library": { + Type: schema.TypeString, + Required: true, + Description: "TODO", + }, + "dns": { + Type: schema.TypeString, + Required: true, + Description: "TODO", + }, + "edge_cluster": { + Type: schema.TypeString, + Required: true, + Description: "TODO", + }, + "dvs_uuid": { + Type: schema.TypeString, + Required: true, + Description: "TODO", + }, + "sizing_hint": { + Type: schema.TypeString, + Required: true, + Description: "TODO", + }, + "egress_cidr": { + Type: schema.TypeList, + Required: true, + Description: "TODO", + Elem: cidrSchema(), + }, + "ingress_cidr": { + Type: schema.TypeList, + Required: true, + Description: "TODO", + Elem: cidrSchema(), + }, + "pod_cidr": { + Type: schema.TypeList, + Required: true, + Description: "TODO", + Elem: cidrSchema(), + }, + "service_cidr": { + Type: schema.TypeList, + Required: true, + Description: "TODO", + MaxItems: 1, + MinItems: 1, + Elem: cidrSchema(), + }, + "search_domains": { + Type: schema.TypeList, + Required: true, + Description: "TODO", + MaxItems: 1, + MinItems: 1, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func mgmtNetworkSchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "network": { + Type: schema.TypeString, + Required: true, + Description: "TODO", + }, + "starting_address": { + Type: schema.TypeString, + Required: true, + Description: "TODO", + }, + "subnet_mask": { + Type: schema.TypeString, + Required: true, + Description: "TODO", + }, + "gateway": { + Type: schema.TypeString, + Required: true, + Description: "TODO", + }, + "address_count": { + Type: schema.TypeInt, + Required: true, + Description: "TODO", + }, + }, + } +} + +func cidrSchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "address": { + Type: schema.TypeString, + Required: true, + Description: "TODO", + }, + "prefix": { + Type: schema.TypeInt, + Required: true, + Description: "TODO", + }, + }, + } +} + +func resourceVsphereSupervisorCreate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client).restClient + nm := namespace.NewManager(c) + + clusterId := d.Get("cluster").(string) + + spec := buildClusterEnableSpec(d) + + err := nm.EnableCluster(context.Background(), clusterId, spec) + + d.SetId(clusterId) + + return err +} + +func resourceVsphereSupervisorRead(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceVsphereSupervisorUpdate(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func resourceVsphereSupervisorDelete(d *schema.ResourceData, meta interface{}) error { + return nil +} + +func buildClusterEnableSpec(d *schema.ResourceData) *namespace.EnableClusterSpec { + egressCidrs := d.Get("egress_cidr").([]interface{}) + ingressCidrs := d.Get("ingress_cidr").([]interface{}) + podCidrs := d.Get("pod_cidr").([]interface{}) + ncpNetworkSpec := namespace.NcpClusterNetworkSpec{ + NsxEdgeCluster: d.Get("edge_cluster").(string), + ClusterDistributedSwitch: d.Get("dvs_uuid").(string), + EgressCidrs: getCidrs(egressCidrs), + IngressCidrs: getCidrs(ingressCidrs), + PodCidrs: getCidrs(podCidrs), + } + + contentLib := d.Get("content_library").(string) + dns := d.Get("dns").(string) + dnsSearchDomains := d.Get("search_domains").([]interface{}) + storagePolicy := d.Get("storage_policy").(string) + serviceCidrs := d.Get("service_cidr").([]interface{}) + + spec := &namespace.EnableClusterSpec{ + EphemeralStoragePolicy: storagePolicy, + SizeHint: getSizingHint(d.Get("sizing_hint")), + ServiceCidr: &getCidrs(serviceCidrs)[0], + // Only NSX-T backing is supported for now + NetworkProvider: &namespace.NsxtContainerPluginNetworkProvider, + MasterStoragePolicy: storagePolicy, + MasterManagementNetwork: getMgmtNetwork(d), + ImageStorage: namespace.ImageStorageSpec{StoragePolicy: storagePolicy}, + NcpClusterNetworkSpec: &ncpNetworkSpec, + MasterDNS: []string{dns}, + WorkerDNS: []string{dns}, + DefaultKubernetesServiceContentLibrary: contentLib, + MasterDNSSearchDomains: structure.SliceInterfacesToStrings(dnsSearchDomains), + } + return spec +} + +func getMgmtNetwork(d *schema.ResourceData) *namespace.MasterManagementNetwork { + mgmtNetworkProperty := d.Get("management_network").([]interface{}) + mgmtNetworkData := mgmtNetworkProperty[0].(map[string]interface{}) + return &namespace.MasterManagementNetwork{ + Mode: &namespace.StaticRangeIpAssignmentMode, + Network: mgmtNetworkData["network"].(string), + AddressRange: &namespace.AddressRange{ + SubnetMask: mgmtNetworkData["subnet_mask"].(string), + StartingAddress: mgmtNetworkData["starting_address"].(string), + Gateway: mgmtNetworkData["gateway"].(string), + AddressCount: mgmtNetworkData["address_count"].(int), + }, + } +} + +func getCidrs(data []interface{}) []namespace.Cidr { + result := make([]namespace.Cidr, len(data)) + for i, cidrData := range data { + cidr := cidrData.(map[string]interface{}) + result[i] = namespace.Cidr{ + Address: cidr["address"].(string), + Prefix: cidr["prefix"].(int), + } + } + return result +} + +func getSizingHint(data interface{}) *namespace.SizingHint { + switch data { + case "TINY": + return &namespace.TinySizingHint + case "SMALL": + return &namespace.SmallSizingHint + case "MEDIUM": + return &namespace.MediumSizingHint + case "LARGE": + return &namespace.LargeSizingHint + } + + return &namespace.UndefinedSizingHint +} diff --git a/vsphere/resource_vsphere_supervisor_test.go b/vsphere/resource_vsphere_supervisor_test.go new file mode 100644 index 000000000..f1b395e49 --- /dev/null +++ b/vsphere/resource_vsphere_supervisor_test.go @@ -0,0 +1,90 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vsphere + +import ( + "fmt" + "github.com/hashicorp/terraform-provider-vsphere/vsphere/internal/helper/testhelper" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccResourceVSphereSupervisor_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + //PreCheck: func() { + // RunSweepers() + // testAccPreCheck(t) + // testAccCheckEnvVariables(t, []string{"ESX_HOSTNAME", "ESX_USERNAME", "ESX_PASSWORD"}) + //}, + Providers: testAccProviders, + //CheckDestroy: testAccVSphereHostDestroy, + Steps: []resource.TestStep{ + { + Config: testAccVSphereSupervisorConfig(), + Check: resource.ComposeTestCheckFunc(), + }, + }, + }) +} + +func testAccVSphereSupervisorConfig() string { + return fmt.Sprintf(` +%s + +data vsphere_storage_policy image_policy { + name = "vi-cluster1 vSAN Storage Policy" +} + +data vsphere_network mgmt_net { + name = "vi-cluster1-vds-Mgmt-DVPG" + datacenter_id = "${data.vsphere_datacenter.rootdc1.id}" +} + +resource "vsphere_supervisor" "supervisor" { + cluster = "${data.vsphere_compute_cluster.rootcompute_cluster1.id}" + storage_policy = "${data.vsphere_storage_policy.image_policy.id}" + content_library = "22585f3f-68a0-4c3d-8bab-466223c6699e" + dns = "10.0.0.250" + edge_cluster = "1c620ec9-66b7-4e8e-b4d9-92bd3c968e91" + dvs_uuid = "50 05 6c b3 5e 8c a2 a0-0f b5 9a fa 86 2b 91 23" + sizing_hint = "MEDIUM" + + management_network { + network = "${data.vsphere_network.mgmt_net.id}" + subnet_mask = "255.255.255.0" + starting_address = "10.0.0.150" + gateway = "10.0.0.250" + address_count = 5 + } + + ingress_cidr { + address = "10.10.10.0" + prefix = 24 + } + + egress_cidr { + address = "10.10.11.0" + prefix = 24 + } + + pod_cidr { + address = "10.244.10.0" + prefix = 23 + } + + service_cidr { + address = "10.10.12.0" + prefix = 24 + } + + search_domains = [ "vrack.vsphere.local" ] +} +`, + testAccConfigBase()) +} + +func testAccConfigBase() string { + return testhelper.CombineConfigs(testhelper.ConfigDataRootDC1(), testhelper.ConfigDataRootComputeCluster1()) +} From c6ab1d390423381cd699938fa472626829bd65c6 Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Fri, 22 Mar 2024 16:38:03 +0200 Subject: [PATCH 02/14] feat: Add delete and read handlers for supervisor resource Signed-off-by: Stoyan Zhelyazkov --- vsphere/resource_vsphere_supervisor.go | 65 ++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/vsphere/resource_vsphere_supervisor.go b/vsphere/resource_vsphere_supervisor.go index 742771ed6..207a43429 100644 --- a/vsphere/resource_vsphere_supervisor.go +++ b/vsphere/resource_vsphere_supervisor.go @@ -5,9 +5,11 @@ package vsphere import ( "context" + "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-vsphere/vsphere/internal/helper/structure" "github.com/vmware/govmomi/vapi/namespace" + "time" ) func resourceVsphereSupervisor() *schema.Resource { @@ -148,29 +150,43 @@ func cidrSchema() *schema.Resource { func resourceVsphereSupervisorCreate(d *schema.ResourceData, meta interface{}) error { c := meta.(*Client).restClient - nm := namespace.NewManager(c) + m := namespace.NewManager(c) clusterId := d.Get("cluster").(string) spec := buildClusterEnableSpec(d) - err := nm.EnableCluster(context.Background(), clusterId, spec) + if err := m.EnableCluster(context.Background(), clusterId, spec); err != nil { + return err + } d.SetId(clusterId) - return err + return waitForSupervisorEnable(m, d) } func resourceVsphereSupervisorRead(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client).restClient + m := namespace.NewManager(c) + + cluster := getClusterById(m, d.Id()) + + if cluster == nil { + return fmt.Errorf("could not find cluster %s", cluster.ID) + } + return nil } func resourceVsphereSupervisorUpdate(d *schema.ResourceData, meta interface{}) error { - return nil + return fmt.Errorf("updating a supervisor's settings is not supported") } func resourceVsphereSupervisorDelete(d *schema.ResourceData, meta interface{}) error { - return nil + c := meta.(*Client).restClient + m := namespace.NewManager(c) + + return m.DisableCluster(context.Background(), d.Id()) } func buildClusterEnableSpec(d *schema.ResourceData) *namespace.EnableClusterSpec { @@ -250,3 +266,42 @@ func getSizingHint(data interface{}) *namespace.SizingHint { return &namespace.UndefinedSizingHint } + +func waitForSupervisorEnable(m *namespace.Manager, d *schema.ResourceData) error { + ticker := time.NewTicker(time.Minute * time.Duration(1)) + + for { + select { + case <-context.Background().Done(): + case <-ticker.C: + cluster := getClusterById(m, d.Id()) + + if cluster == nil { + return fmt.Errorf("could not find cluster %s", cluster.ID) + } + + if namespace.RunningConfigStatus == *cluster.ConfigStatus { + return nil + } + if namespace.ErrorConfigStatus == *cluster.ConfigStatus { + return fmt.Errorf("could not enable supervisor on cluster %s", cluster.ID) + } + } + } +} + +func getClusterById(m *namespace.Manager, id string) *namespace.ClusterSummary { + clusters, err := m.ListClusters(context.Background()) + + if err != nil { + return nil + } + + for _, cluster := range clusters { + if id == cluster.ID { + return &cluster + } + } + + return nil +} From ce86d65822c019aa7668166aca542ef3534f03c8 Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Fri, 22 Mar 2024 17:02:15 +0200 Subject: [PATCH 03/14] feat: Add descriptions to resource inputs Signed-off-by: Stoyan Zhelyazkov --- vsphere/resource_vsphere_supervisor.go | 52 ++++++++++++--------- vsphere/resource_vsphere_supervisor_test.go | 3 +- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/vsphere/resource_vsphere_supervisor.go b/vsphere/resource_vsphere_supervisor.go index 207a43429..8f379fd02 100644 --- a/vsphere/resource_vsphere_supervisor.go +++ b/vsphere/resource_vsphere_supervisor.go @@ -27,62 +27,67 @@ func resourceVsphereSupervisor() *schema.Resource { "storage_policy": { Type: schema.TypeString, Required: true, - Description: "TODO", + Description: "The name of a storage policy associated with the datastore where the container images will be stored.", }, "management_network": { Type: schema.TypeList, Required: true, - Description: "TODO", + Description: "The name of the management network which the control plane VMs will be connected to.", MaxItems: 1, Elem: mgmtNetworkSchema(), }, "content_library": { Type: schema.TypeString, Required: true, - Description: "TODO", + Description: "ID of the subscribed content library.", }, - "dns": { + "main_dns": { Type: schema.TypeString, Required: true, - Description: "TODO", + Description: "List of DNS servers to use on the Kubernetes API server.", + }, + "worker_dns": { + Type: schema.TypeString, + Required: true, + Description: "List of DNS servers to use on the worker nodes.", }, "edge_cluster": { Type: schema.TypeString, Required: true, - Description: "TODO", + Description: "ID of the NSX Edge Cluster.", }, "dvs_uuid": { Type: schema.TypeString, Required: true, - Description: "TODO", + Description: "The UUID (not ID) of the distributed switch.", }, "sizing_hint": { Type: schema.TypeString, Required: true, - Description: "TODO", + Description: "Size of the Kubernetes API server.", }, "egress_cidr": { Type: schema.TypeList, Required: true, - Description: "TODO", + Description: "CIDR blocks from which NSX assigns IP addresses used for performing SNAT from container IPs to external IPs.", Elem: cidrSchema(), }, "ingress_cidr": { Type: schema.TypeList, Required: true, - Description: "TODO", + Description: "CIDR blocks from which NSX assigns IP addresses for Kubernetes Ingresses and Kubernetes Services of type LoadBalancer.", Elem: cidrSchema(), }, "pod_cidr": { Type: schema.TypeList, Required: true, - Description: "TODO", + Description: "CIDR blocks from which Kubernetes allocates pod IP addresses.", Elem: cidrSchema(), }, "service_cidr": { Type: schema.TypeList, Required: true, - Description: "TODO", + Description: "CIDR block from which Kubernetes allocates service cluster IP addresses.", MaxItems: 1, MinItems: 1, Elem: cidrSchema(), @@ -90,7 +95,7 @@ func resourceVsphereSupervisor() *schema.Resource { "search_domains": { Type: schema.TypeList, Required: true, - Description: "TODO", + Description: "List of DNS search domains.", MaxItems: 1, MinItems: 1, Elem: &schema.Schema{Type: schema.TypeString}, @@ -105,27 +110,27 @@ func mgmtNetworkSchema() *schema.Resource { "network": { Type: schema.TypeString, Required: true, - Description: "TODO", + Description: "ID of the network. (e.g. a distributed portgroup).", }, "starting_address": { Type: schema.TypeString, Required: true, - Description: "TODO", + Description: "Starting address of the management network range.", }, "subnet_mask": { Type: schema.TypeString, Required: true, - Description: "TODO", + Description: "Subnet mask.", }, "gateway": { Type: schema.TypeString, Required: true, - Description: "TODO", + Description: "Gateway IP address.", }, "address_count": { Type: schema.TypeInt, Required: true, - Description: "TODO", + Description: "Number of addresses to allocate. Starts from 'starting_address'", }, }, } @@ -137,12 +142,12 @@ func cidrSchema() *schema.Resource { "address": { Type: schema.TypeString, Required: true, - Description: "TODO", + Description: "Network address.", }, "prefix": { Type: schema.TypeInt, Required: true, - Description: "TODO", + Description: "Subnet prefix.", }, }, } @@ -202,7 +207,8 @@ func buildClusterEnableSpec(d *schema.ResourceData) *namespace.EnableClusterSpec } contentLib := d.Get("content_library").(string) - dns := d.Get("dns").(string) + mainDns := d.Get("main_dns").(string) + workerDns := d.Get("worker_dns").(string) dnsSearchDomains := d.Get("search_domains").([]interface{}) storagePolicy := d.Get("storage_policy").(string) serviceCidrs := d.Get("service_cidr").([]interface{}) @@ -217,8 +223,8 @@ func buildClusterEnableSpec(d *schema.ResourceData) *namespace.EnableClusterSpec MasterManagementNetwork: getMgmtNetwork(d), ImageStorage: namespace.ImageStorageSpec{StoragePolicy: storagePolicy}, NcpClusterNetworkSpec: &ncpNetworkSpec, - MasterDNS: []string{dns}, - WorkerDNS: []string{dns}, + MasterDNS: []string{mainDns}, + WorkerDNS: []string{workerDns}, DefaultKubernetesServiceContentLibrary: contentLib, MasterDNSSearchDomains: structure.SliceInterfacesToStrings(dnsSearchDomains), } diff --git a/vsphere/resource_vsphere_supervisor_test.go b/vsphere/resource_vsphere_supervisor_test.go index f1b395e49..ab408de45 100644 --- a/vsphere/resource_vsphere_supervisor_test.go +++ b/vsphere/resource_vsphere_supervisor_test.go @@ -46,7 +46,8 @@ resource "vsphere_supervisor" "supervisor" { cluster = "${data.vsphere_compute_cluster.rootcompute_cluster1.id}" storage_policy = "${data.vsphere_storage_policy.image_policy.id}" content_library = "22585f3f-68a0-4c3d-8bab-466223c6699e" - dns = "10.0.0.250" + main_dns = "10.0.0.250" + worker_dns = "10.0.0.250" edge_cluster = "1c620ec9-66b7-4e8e-b4d9-92bd3c968e91" dvs_uuid = "50 05 6c b3 5e 8c a2 a0-0f b5 9a fa 86 2b 91 23" sizing_hint = "MEDIUM" From 30483b600cbe56315c399207905ec32c72fea020 Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Fri, 22 Mar 2024 17:23:16 +0200 Subject: [PATCH 04/14] feat: Add env variables for testing data sources Signed-off-by: Stoyan Zhelyazkov --- vsphere/resource_vsphere_supervisor.go | 4 +- vsphere/resource_vsphere_supervisor_test.go | 43 +++++++++++++++------ 2 files changed, 33 insertions(+), 14 deletions(-) diff --git a/vsphere/resource_vsphere_supervisor.go b/vsphere/resource_vsphere_supervisor.go index 8f379fd02..062da7178 100644 --- a/vsphere/resource_vsphere_supervisor.go +++ b/vsphere/resource_vsphere_supervisor.go @@ -81,7 +81,7 @@ func resourceVsphereSupervisor() *schema.Resource { "pod_cidr": { Type: schema.TypeList, Required: true, - Description: "CIDR blocks from which Kubernetes allocates pod IP addresses.", + Description: "CIDR blocks from which Kubernetes allocates pod IP addresses. Minimum subnet size is 23.", Elem: cidrSchema(), }, "service_cidr": { @@ -110,7 +110,7 @@ func mgmtNetworkSchema() *schema.Resource { "network": { Type: schema.TypeString, Required: true, - Description: "ID of the network. (e.g. a distributed portgroup).", + Description: "ID of the network. (e.g. a distributed port group).", }, "starting_address": { Type: schema.TypeString, diff --git a/vsphere/resource_vsphere_supervisor_test.go b/vsphere/resource_vsphere_supervisor_test.go index ab408de45..4f3ce390d 100644 --- a/vsphere/resource_vsphere_supervisor_test.go +++ b/vsphere/resource_vsphere_supervisor_test.go @@ -6,6 +6,7 @@ package vsphere import ( "fmt" "github.com/hashicorp/terraform-provider-vsphere/vsphere/internal/helper/testhelper" + "os" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -13,11 +14,15 @@ import ( func TestAccResourceVSphereSupervisor_basic(t *testing.T) { resource.Test(t, resource.TestCase{ - //PreCheck: func() { - // RunSweepers() - // testAccPreCheck(t) - // testAccCheckEnvVariables(t, []string{"ESX_HOSTNAME", "ESX_USERNAME", "ESX_PASSWORD"}) - //}, + PreCheck: func() { + testAccCheckEnvVariables(t, []string{ + "TF_VAR_STORAGE_POLICY", + "TF_VAR_MANAGEMENT_NETWORK", + "TF_VAR_CONTENT_LIBRARY", + "TF_VAR_DISTRIBUTED_SWITCH", + "TF_VAR_EDGE_CLUSTER", + }) + }, Providers: testAccProviders, //CheckDestroy: testAccVSphereHostDestroy, Steps: []resource.TestStep{ @@ -34,22 +39,31 @@ func testAccVSphereSupervisorConfig() string { %s data vsphere_storage_policy image_policy { - name = "vi-cluster1 vSAN Storage Policy" + name = "%s" } data vsphere_network mgmt_net { - name = "vi-cluster1-vds-Mgmt-DVPG" - datacenter_id = "${data.vsphere_datacenter.rootdc1.id}" + name = "%s" + datacenter_id = "${data.vsphere_datacenter.rootdc1.id}" +} + +data vsphere_content_library subscribed_lib { + name = "%s" +} + +data vsphere_distributed_virtual_switch dvs { + name = "%s" + datacenter_id = "${data.vsphere_datacenter.rootdc1.id}" } resource "vsphere_supervisor" "supervisor" { cluster = "${data.vsphere_compute_cluster.rootcompute_cluster1.id}" storage_policy = "${data.vsphere_storage_policy.image_policy.id}" - content_library = "22585f3f-68a0-4c3d-8bab-466223c6699e" + content_library = "${data.vsphere_content_library.subscribed_lib.id}" main_dns = "10.0.0.250" worker_dns = "10.0.0.250" - edge_cluster = "1c620ec9-66b7-4e8e-b4d9-92bd3c968e91" - dvs_uuid = "50 05 6c b3 5e 8c a2 a0-0f b5 9a fa 86 2b 91 23" + edge_cluster = "%s" + dvs_uuid = "${data.vsphere_distributed_virtual_switch.dvs.id}" sizing_hint = "MEDIUM" management_network { @@ -83,7 +97,12 @@ resource "vsphere_supervisor" "supervisor" { search_domains = [ "vrack.vsphere.local" ] } `, - testAccConfigBase()) + testAccConfigBase(), + os.Getenv("TF_VAR_STORAGE_POLICY"), + os.Getenv("TF_VAR_MANAGEMENT_NETWORK"), + os.Getenv("TF_VAR_CONTENT_LIBRARY"), + os.Getenv("TF_VAR_DISTRIBUTED_SWITCH"), + os.Getenv("TF_VAR_EDGE_CLUSTER")) } func testAccConfigBase() string { From 8850415fd7185f30ef9dc4932eb5df9c904b8f40 Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Fri, 22 Mar 2024 17:40:01 +0200 Subject: [PATCH 05/14] feat: Add validation functions to supervisor inputs Signed-off-by: Stoyan Zhelyazkov --- vsphere/resource_vsphere_supervisor.go | 111 +++++++++++++++---------- 1 file changed, 65 insertions(+), 46 deletions(-) diff --git a/vsphere/resource_vsphere_supervisor.go b/vsphere/resource_vsphere_supervisor.go index 062da7178..af3e33201 100644 --- a/vsphere/resource_vsphere_supervisor.go +++ b/vsphere/resource_vsphere_supervisor.go @@ -7,6 +7,7 @@ import ( "context" "fmt" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-vsphere/vsphere/internal/helper/structure" "github.com/vmware/govmomi/vapi/namespace" "time" @@ -20,14 +21,16 @@ func resourceVsphereSupervisor() *schema.Resource { Delete: resourceVsphereSupervisorDelete, Schema: map[string]*schema.Schema{ "cluster": { - Type: schema.TypeString, - Required: true, - Description: "ID of the vSphere cluster on which workload management will be enabled.", + Type: schema.TypeString, + Required: true, + Description: "ID of the vSphere cluster on which workload management will be enabled.", + ValidateFunc: validation.StringIsNotEmpty, }, "storage_policy": { - Type: schema.TypeString, - Required: true, - Description: "The name of a storage policy associated with the datastore where the container images will be stored.", + Type: schema.TypeString, + Required: true, + Description: "The name of a storage policy associated with the datastore where the container images will be stored.", + ValidateFunc: validation.StringIsNotEmpty, }, "management_network": { Type: schema.TypeList, @@ -37,34 +40,40 @@ func resourceVsphereSupervisor() *schema.Resource { Elem: mgmtNetworkSchema(), }, "content_library": { - Type: schema.TypeString, - Required: true, - Description: "ID of the subscribed content library.", + Type: schema.TypeString, + Required: true, + Description: "ID of the subscribed content library.", + ValidateFunc: validation.StringIsNotEmpty, }, "main_dns": { - Type: schema.TypeString, - Required: true, - Description: "List of DNS servers to use on the Kubernetes API server.", + Type: schema.TypeString, + Required: true, + Description: "List of DNS servers to use on the Kubernetes API server.", + ValidateFunc: validation.IsIPv4Address, }, "worker_dns": { - Type: schema.TypeString, - Required: true, - Description: "List of DNS servers to use on the worker nodes.", + Type: schema.TypeString, + Required: true, + Description: "List of DNS servers to use on the worker nodes.", + ValidateFunc: validation.IsIPv4Address, }, "edge_cluster": { - Type: schema.TypeString, - Required: true, - Description: "ID of the NSX Edge Cluster.", + Type: schema.TypeString, + Required: true, + Description: "ID of the NSX Edge Cluster.", + ValidateFunc: validation.StringIsNotEmpty, }, "dvs_uuid": { - Type: schema.TypeString, - Required: true, - Description: "The UUID (not ID) of the distributed switch.", + Type: schema.TypeString, + Required: true, + Description: "The UUID (not ID) of the distributed switch.", + ValidateFunc: validation.StringIsNotEmpty, }, "sizing_hint": { - Type: schema.TypeString, - Required: true, - Description: "Size of the Kubernetes API server.", + Type: schema.TypeString, + Required: true, + Description: "Size of the Kubernetes API server.", + ValidateFunc: validation.StringInSlice([]string{"TINY", "SMALL", "MEDIUM", "LARGE"}, false), }, "egress_cidr": { Type: schema.TypeList, @@ -98,7 +107,10 @@ func resourceVsphereSupervisor() *schema.Resource { Description: "List of DNS search domains.", MaxItems: 1, MinItems: 1, - Elem: &schema.Schema{Type: schema.TypeString}, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringIsNotEmpty, + }, }, }, } @@ -108,29 +120,34 @@ func mgmtNetworkSchema() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ "network": { - Type: schema.TypeString, - Required: true, - Description: "ID of the network. (e.g. a distributed port group).", + Type: schema.TypeString, + Required: true, + Description: "ID of the network. (e.g. a distributed port group).", + ValidateFunc: validation.StringIsNotEmpty, }, "starting_address": { - Type: schema.TypeString, - Required: true, - Description: "Starting address of the management network range.", + Type: schema.TypeString, + Required: true, + Description: "Starting address of the management network range.", + ValidateFunc: validation.IsIPv4Address, }, "subnet_mask": { - Type: schema.TypeString, - Required: true, - Description: "Subnet mask.", + Type: schema.TypeString, + Required: true, + Description: "Subnet mask.", + ValidateFunc: validation.IsIPv4Address, }, "gateway": { - Type: schema.TypeString, - Required: true, - Description: "Gateway IP address.", + Type: schema.TypeString, + Required: true, + Description: "Gateway IP address.", + ValidateFunc: validation.IsIPv4Address, }, "address_count": { - Type: schema.TypeInt, - Required: true, - Description: "Number of addresses to allocate. Starts from 'starting_address'", + Type: schema.TypeInt, + Required: true, + Description: "Number of addresses to allocate. Starts from 'starting_address'", + ValidateFunc: validation.IntAtLeast(1), }, }, } @@ -140,14 +157,16 @@ func cidrSchema() *schema.Resource { return &schema.Resource{ Schema: map[string]*schema.Schema{ "address": { - Type: schema.TypeString, - Required: true, - Description: "Network address.", + Type: schema.TypeString, + Required: true, + Description: "Network address.", + ValidateFunc: validation.IsIPv4Address, }, "prefix": { - Type: schema.TypeInt, - Required: true, - Description: "Subnet prefix.", + Type: schema.TypeInt, + Required: true, + Description: "Subnet prefix.", + ValidateFunc: validation.IntBetween(0, 32), }, }, } From 2ca4b1045fa75af1763cf4497860a3c454b9b4ed Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Mon, 25 Mar 2024 11:06:31 +0200 Subject: [PATCH 06/14] feat: Update test for supervisor resource Signed-off-by: Stoyan Zhelyazkov --- vsphere/resource_vsphere_supervisor_test.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/vsphere/resource_vsphere_supervisor_test.go b/vsphere/resource_vsphere_supervisor_test.go index 4f3ce390d..656c6c904 100644 --- a/vsphere/resource_vsphere_supervisor_test.go +++ b/vsphere/resource_vsphere_supervisor_test.go @@ -24,11 +24,14 @@ func TestAccResourceVSphereSupervisor_basic(t *testing.T) { }) }, Providers: testAccProviders, - //CheckDestroy: testAccVSphereHostDestroy, Steps: []resource.TestStep{ { + // You can change the network settings in the configuration + // so that they fit your environment Config: testAccVSphereSupervisorConfig(), - Check: resource.ComposeTestCheckFunc(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("vsphere_supervisor.supervisor", "id"), + ), }, }, }) From db27903dbccba84fa1e33d2021b3f45b13a3b625 Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Tue, 26 Mar 2024 18:13:13 +0200 Subject: [PATCH 07/14] feat: add vmclass and namespace subresource Signed-off-by: Stoyan Zhelyazkov --- vsphere/resource_vsphere_supervisor.go | 161 ++++++++++++++++++-- vsphere/resource_vsphere_supervisor_test.go | 15 ++ 2 files changed, 164 insertions(+), 12 deletions(-) diff --git a/vsphere/resource_vsphere_supervisor.go b/vsphere/resource_vsphere_supervisor.go index af3e33201..6d7e134a5 100644 --- a/vsphere/resource_vsphere_supervisor.go +++ b/vsphere/resource_vsphere_supervisor.go @@ -112,6 +112,12 @@ func resourceVsphereSupervisor() *schema.Resource { ValidateFunc: validation.StringIsNotEmpty, }, }, + "namespace": { + Type: schema.TypeList, + Optional: true, + Description: "TODO.", + Elem: namespaceSchema(), + }, }, } } @@ -172,21 +178,73 @@ func cidrSchema() *schema.Resource { } } -func resourceVsphereSupervisorCreate(d *schema.ResourceData, meta interface{}) error { - c := meta.(*Client).restClient - m := namespace.NewManager(c) - - clusterId := d.Get("cluster").(string) - - spec := buildClusterEnableSpec(d) +func namespaceSchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "TODO.", + }, + "content_libraries": { + Type: schema.TypeList, + Optional: true, + Description: "TODO.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "vm_class": { + Type: schema.TypeList, + Optional: true, + Description: "TODO.", + Elem: vmClassSchema(), + }, + }, + } +} - if err := m.EnableCluster(context.Background(), clusterId, spec); err != nil { - return err +func vmClassSchema() *schema.Resource { + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + Description: "TODO.", + }, + "cpus": { + Type: schema.TypeInt, + Required: true, + Description: "TODO.", + }, + "memory": { + Type: schema.TypeInt, + Required: true, + Description: "TODO.", + }, + }, } +} - d.SetId(clusterId) +func resourceVsphereSupervisorCreate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client).restClient + m := namespace.NewManager(c) - return waitForSupervisorEnable(m, d) + //clusterId := d.Get("cluster").(string) + // + //spec := buildClusterEnableSpec(d) + // + //if err := m.EnableCluster(context.Background(), clusterId, spec); err != nil { + // return err + //} + // + //d.SetId(clusterId) + d.SetId("domain-c1007") + + //if err := waitForSupervisorEnable(m, d); err != nil { + // return err + //} + return createNamespaces(m, d) } func resourceVsphereSupervisorRead(d *schema.ResourceData, meta interface{}) error { @@ -210,7 +268,11 @@ func resourceVsphereSupervisorDelete(d *schema.ResourceData, meta interface{}) e c := meta.(*Client).restClient m := namespace.NewManager(c) - return m.DisableCluster(context.Background(), d.Id()) + if err := m.DisableCluster(context.Background(), d.Id()); err != nil { + return err + } + + return waitForSupervisorDisable(m, d) } func buildClusterEnableSpec(d *schema.ResourceData) *namespace.EnableClusterSpec { @@ -315,6 +377,26 @@ func waitForSupervisorEnable(m *namespace.Manager, d *schema.ResourceData) error } } +func waitForSupervisorDisable(m *namespace.Manager, d *schema.ResourceData) error { + ticker := time.NewTicker(time.Minute * time.Duration(1)) + + for { + select { + case <-context.Background().Done(): + case <-ticker.C: + cluster := getClusterById(m, d.Id()) + + if cluster == nil { + return nil + } + + if namespace.ErrorConfigStatus == *cluster.ConfigStatus { + return fmt.Errorf("could not disable supervisor on cluster %s", cluster.ID) + } + } + } +} + func getClusterById(m *namespace.Manager, id string) *namespace.ClusterSummary { clusters, err := m.ListClusters(context.Background()) @@ -330,3 +412,58 @@ func getClusterById(m *namespace.Manager, id string) *namespace.ClusterSummary { return nil } + +func createNamespaces(m *namespace.Manager, d *schema.ResourceData) error { + namespaces := d.Get("namespace").([]interface{}) + + for _, ns := range namespaces { + nsData := ns.(map[string]interface{}) + + namespaceSpec := namespace.NamespacesInstancesCreateSpec{ + Namespace: nsData["name"].(string), + Cluster: d.Id(), + VmServiceSpec: namespace.VmServiceSpec{}, + } + + if contentLibs, contains := nsData["content_libraries"]; contains { + namespaceSpec.VmServiceSpec.ContentLibraries = structure.SliceInterfacesToStrings(contentLibs.([]interface{})) + } + + if vmClassData, contains := nsData["vm_class"]; contains { + vmClasses, err := createVmClasses(m, vmClassData.([]interface{})) + + if err != nil { + return err + } + + namespaceSpec.VmServiceSpec.VmClasses = vmClasses + } + + if err := m.CreateNamespace(context.Background(), namespaceSpec); err != nil { + return err + } + } + + return nil +} + +func createVmClasses(m *namespace.Manager, vmClasses []interface{}) ([]string, error) { + result := make([]string, len(vmClasses)) + + for i, vmClass := range vmClasses { + vmClassData := vmClass.(map[string]interface{}) + vmClassSpec := namespace.VirtualMachineClassesCreateSpec{ + Id: vmClassData["id"].(string), + CpuCount: int64(vmClassData["cpus"].(int)), + MemoryMb: int64(vmClassData["memory"].(int)), + } + + if err := m.CreateVmClass(context.Background(), vmClassSpec); err != nil { + return result, err + } + + result[i] = vmClassSpec.Id + } + + return result, nil +} diff --git a/vsphere/resource_vsphere_supervisor_test.go b/vsphere/resource_vsphere_supervisor_test.go index 656c6c904..ad501cdb7 100644 --- a/vsphere/resource_vsphere_supervisor_test.go +++ b/vsphere/resource_vsphere_supervisor_test.go @@ -98,6 +98,21 @@ resource "vsphere_supervisor" "supervisor" { } search_domains = [ "vrack.vsphere.local" ] + + namespace { + name = "test-namespace-03" + content_libraries = [ "${data.vsphere_content_library.subscribed_lib.id}" ] + vm_class { + id = "test-class-04" + cpus = 4 + memory = 4096 + } + vm_class { + id = "test-class-05" + cpus = 4 + memory = 4096 + } + } } `, testAccConfigBase(), From 7ccb58e3b13237ecd0627e44352ed359f7855217 Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Wed, 27 Mar 2024 11:39:51 +0200 Subject: [PATCH 08/14] feat: add memory & cpu reservation options for vm class creation Signed-off-by: Stoyan Zhelyazkov --- vsphere/resource_vsphere_supervisor.go | 22 ++++++++++++++++++--- vsphere/resource_vsphere_supervisor_test.go | 8 +++++--- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/vsphere/resource_vsphere_supervisor.go b/vsphere/resource_vsphere_supervisor.go index 6d7e134a5..fcfbd32cc 100644 --- a/vsphere/resource_vsphere_supervisor.go +++ b/vsphere/resource_vsphere_supervisor.go @@ -222,6 +222,16 @@ func vmClassSchema() *schema.Resource { Required: true, Description: "TODO.", }, + "cpu_reservation": { + Type: schema.TypeInt, + Optional: true, + Description: "TODO.", + }, + "memory_reservation": { + Type: schema.TypeInt, + Optional: true, + Description: "TODO.", + }, }, } } @@ -453,9 +463,15 @@ func createVmClasses(m *namespace.Manager, vmClasses []interface{}) ([]string, e for i, vmClass := range vmClasses { vmClassData := vmClass.(map[string]interface{}) vmClassSpec := namespace.VirtualMachineClassesCreateSpec{ - Id: vmClassData["id"].(string), - CpuCount: int64(vmClassData["cpus"].(int)), - MemoryMb: int64(vmClassData["memory"].(int)), + Id: vmClassData["id"].(string), + CpuCount: int64(vmClassData["cpus"].(int)), + MemoryMb: int64(vmClassData["memory"].(int)), + CpuReservation: int64(vmClassData["cpu_reservation"].(int)), + MemoryReservation: int64(vmClassData["memory_reservation"].(int)), + } + + vmClassSpec.Devices.VgpuDevices = []namespace.VgpuDevice{ + {ProfileName: "mockup-vmiop-4c"}, } if err := m.CreateVmClass(context.Background(), vmClassSpec); err != nil { diff --git a/vsphere/resource_vsphere_supervisor_test.go b/vsphere/resource_vsphere_supervisor_test.go index ad501cdb7..66d8183ff 100644 --- a/vsphere/resource_vsphere_supervisor_test.go +++ b/vsphere/resource_vsphere_supervisor_test.go @@ -100,17 +100,19 @@ resource "vsphere_supervisor" "supervisor" { search_domains = [ "vrack.vsphere.local" ] namespace { - name = "test-namespace-03" + name = "test-namespace-08" content_libraries = [ "${data.vsphere_content_library.subscribed_lib.id}" ] vm_class { - id = "test-class-04" + id = "test-class-08" cpus = 4 memory = 4096 + memory_reservation = 100 } vm_class { - id = "test-class-05" + id = "test-class-09" cpus = 4 memory = 4096 + memory_reservation = 100 } } } From 845219353407cbc494fc8162238075fd4adcaa4e Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Fri, 29 Mar 2024 11:19:27 +0200 Subject: [PATCH 09/14] feat: add a separate resource for VM classes Signed-off-by: Stoyan Zhelyazkov --- vsphere/provider.go | 3 +- vsphere/resource_vsphere_supervisor.go | 221 +++++++++-------- vsphere/resource_vsphere_supervisor_test.go | 227 +++++++++++++++++- .../resource_vsphere_virtual_machine_class.go | 98 ++++++++ ...urce_vsphere_virtual_machine_class_test.go | 35 +++ 5 files changed, 468 insertions(+), 116 deletions(-) create mode 100644 vsphere/resource_vsphere_virtual_machine_class.go create mode 100644 vsphere/resource_vsphere_virtual_machine_class_test.go diff --git a/vsphere/provider.go b/vsphere/provider.go index b5142c2ae..e0e6e0fc0 100644 --- a/vsphere/provider.go +++ b/vsphere/provider.go @@ -131,13 +131,14 @@ func Provider() *schema.Provider { "vsphere_tag_category": resourceVSphereTagCategory(), "vsphere_virtual_disk": resourceVSphereVirtualDisk(), "vsphere_virtual_machine": resourceVSphereVirtualMachine(), + "vsphere_virtual_machine_class": resourceVsphereVmClass(), + "vsphere_virtual_machine_snapshot": resourceVSphereVirtualMachineSnapshot(), "vsphere_nas_datastore": resourceVSphereNasDatastore(), "vsphere_storage_drs_vm_override": resourceVSphereStorageDrsVMOverride(), "vsphere_supervisor": resourceVsphereSupervisor(), "vsphere_vapp_container": resourceVSphereVAppContainer(), "vsphere_vapp_entity": resourceVSphereVAppEntity(), "vsphere_vmfs_datastore": resourceVSphereVmfsDatastore(), - "vsphere_virtual_machine_snapshot": resourceVSphereVirtualMachineSnapshot(), "vsphere_host": resourceVsphereHost(), "vsphere_vnic": resourceVsphereNic(), "vsphere_vm_storage_policy": resourceVMStoragePolicy(), diff --git a/vsphere/resource_vsphere_supervisor.go b/vsphere/resource_vsphere_supervisor.go index fcfbd32cc..32a5ff25b 100644 --- a/vsphere/resource_vsphere_supervisor.go +++ b/vsphere/resource_vsphere_supervisor.go @@ -113,9 +113,9 @@ func resourceVsphereSupervisor() *schema.Resource { }, }, "namespace": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, - Description: "TODO.", + Description: "List of namespaces associated with the cluster.", Elem: namespaceSchema(), }, }, @@ -184,53 +184,23 @@ func namespaceSchema() *schema.Resource { "name": { Type: schema.TypeString, Required: true, - Description: "TODO.", + Description: "The name of the namespace.", }, "content_libraries": { Type: schema.TypeList, Optional: true, - Description: "TODO.", + Description: "A comma-separated list of content libraries.", Elem: &schema.Schema{ Type: schema.TypeString, }, }, - "vm_class": { + "vm_classes": { Type: schema.TypeList, Optional: true, - Description: "TODO.", - Elem: vmClassSchema(), - }, - }, - } -} - -func vmClassSchema() *schema.Resource { - return &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": { - Type: schema.TypeString, - Required: true, - Description: "TODO.", - }, - "cpus": { - Type: schema.TypeInt, - Required: true, - Description: "TODO.", - }, - "memory": { - Type: schema.TypeInt, - Required: true, - Description: "TODO.", - }, - "cpu_reservation": { - Type: schema.TypeInt, - Optional: true, - Description: "TODO.", - }, - "memory_reservation": { - Type: schema.TypeInt, - Optional: true, - Description: "TODO.", + Description: "A comma-separated list of virtual machine classes.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, }, }, } @@ -240,21 +210,31 @@ func resourceVsphereSupervisorCreate(d *schema.ResourceData, meta interface{}) e c := meta.(*Client).restClient m := namespace.NewManager(c) - //clusterId := d.Get("cluster").(string) - // - //spec := buildClusterEnableSpec(d) - // - //if err := m.EnableCluster(context.Background(), clusterId, spec); err != nil { - // return err - //} - // - //d.SetId(clusterId) - d.SetId("domain-c1007") - - //if err := waitForSupervisorEnable(m, d); err != nil { - // return err - //} - return createNamespaces(m, d) + clusterId := d.Get("cluster").(string) + + spec := buildClusterEnableSpec(d) + + if err := m.EnableCluster(context.Background(), clusterId, spec); err != nil { + return err + } + + d.SetId(clusterId) + + if err := waitForSupervisorEnable(m, d); err != nil { + return err + } + + namespaces := d.Get("namespace").(*schema.Set).List() + + for _, ns := range namespaces { + nsData := ns.(map[string]interface{}) + + if err := createNamespace(m, nsData, d.Id()); err != nil { + return err + } + } + + return nil } func resourceVsphereSupervisorRead(d *schema.ResourceData, meta interface{}) error { @@ -271,7 +251,46 @@ func resourceVsphereSupervisorRead(d *schema.ResourceData, meta interface{}) err } func resourceVsphereSupervisorUpdate(d *schema.ResourceData, meta interface{}) error { - return fmt.Errorf("updating a supervisor's settings is not supported") + if d.HasChange("namespace") { + c := meta.(*Client).restClient + m := namespace.NewManager(c) + + oldRaw, newRaw := d.GetChange("namespace") + oldSet := oldRaw.(*schema.Set) + newSet := newRaw.(*schema.Set) + + // these haven't changed, we don't need to do anything with them + intersection := oldSet.Intersection(newSet) + oldNamespaces := getNamespacesMap(oldSet.Difference(intersection).List()) + newNamespaces := getNamespacesMap(newSet.Difference(intersection).List()) + + for k, v := range oldNamespaces { + if _, found := newNamespaces[k]; found { + // Namespace still exists but has changed + if err := updateNamespace(m, v.(map[string]interface{})); err != nil { + return err + } + } else { + // Namespace no longer exists + if err := deleteNamespace(m, v.(map[string]interface{})); err != nil { + return err + } + } + } + + for k, v := range newNamespaces { + if _, found := oldNamespaces[k]; !found { + // This is a new namespace + if err := createNamespace(m, v.(map[string]interface{}), d.Id()); err != nil { + return err + } + } + } + + return nil + } + + return fmt.Errorf("only updates to namespaces are supported") } func resourceVsphereSupervisorDelete(d *schema.ResourceData, meta interface{}) error { @@ -366,6 +385,7 @@ func getSizingHint(data interface{}) *namespace.SizingHint { func waitForSupervisorEnable(m *namespace.Manager, d *schema.ResourceData) error { ticker := time.NewTicker(time.Minute * time.Duration(1)) + failureCount := 0 for { select { @@ -381,7 +401,16 @@ func waitForSupervisorEnable(m *namespace.Manager, d *schema.ResourceData) error return nil } if namespace.ErrorConfigStatus == *cluster.ConfigStatus { - return fmt.Errorf("could not enable supervisor on cluster %s", cluster.ID) + // The supervisor sometimes reports errors but manages to recover + // We will only give up if we get several consecutive errors + if failureCount > 3 { + return fmt.Errorf("could not enable supervisor on cluster %s", cluster.ID) + } + failureCount++ + } + if namespace.ConfiguringConfigStatus == *cluster.ConfigStatus { + // Reset error counter + failureCount = 0 } } } @@ -423,63 +452,51 @@ func getClusterById(m *namespace.Manager, id string) *namespace.ClusterSummary { return nil } -func createNamespaces(m *namespace.Manager, d *schema.ResourceData) error { - namespaces := d.Get("namespace").([]interface{}) +func getNamespacesMap(namespaces []interface{}) map[string]interface{} { + result := make(map[string]interface{}) - for _, ns := range namespaces { - nsData := ns.(map[string]interface{}) - - namespaceSpec := namespace.NamespacesInstancesCreateSpec{ - Namespace: nsData["name"].(string), - Cluster: d.Id(), - VmServiceSpec: namespace.VmServiceSpec{}, - } - - if contentLibs, contains := nsData["content_libraries"]; contains { - namespaceSpec.VmServiceSpec.ContentLibraries = structure.SliceInterfacesToStrings(contentLibs.([]interface{})) - } + for _, n := range namespaces { + name := n.(map[string]interface{})["name"].(string) + result[name] = n + } - if vmClassData, contains := nsData["vm_class"]; contains { - vmClasses, err := createVmClasses(m, vmClassData.([]interface{})) + return result +} - if err != nil { - return err - } +func createNamespace(m *namespace.Manager, nsData map[string]interface{}, cluster string) error { + namespaceSpec := namespace.NamespacesInstanceCreateSpec{ + Namespace: nsData["name"].(string), + Cluster: cluster, + VmServiceSpec: namespace.VmServiceSpec{}, + } - namespaceSpec.VmServiceSpec.VmClasses = vmClasses - } + if contentLibs, contains := nsData["content_libraries"]; contains { + namespaceSpec.VmServiceSpec.ContentLibraries = structure.SliceInterfacesToStrings(contentLibs.([]interface{})) + } - if err := m.CreateNamespace(context.Background(), namespaceSpec); err != nil { - return err - } + if vmClasses, contains := nsData["vm_classes"]; contains { + namespaceSpec.VmServiceSpec.VmClasses = structure.SliceInterfacesToStrings(vmClasses.([]interface{})) } - return nil + return m.CreateNamespace(context.Background(), namespaceSpec) } -func createVmClasses(m *namespace.Manager, vmClasses []interface{}) ([]string, error) { - result := make([]string, len(vmClasses)) - - for i, vmClass := range vmClasses { - vmClassData := vmClass.(map[string]interface{}) - vmClassSpec := namespace.VirtualMachineClassesCreateSpec{ - Id: vmClassData["id"].(string), - CpuCount: int64(vmClassData["cpus"].(int)), - MemoryMb: int64(vmClassData["memory"].(int)), - CpuReservation: int64(vmClassData["cpu_reservation"].(int)), - MemoryReservation: int64(vmClassData["memory_reservation"].(int)), - } - - vmClassSpec.Devices.VgpuDevices = []namespace.VgpuDevice{ - {ProfileName: "mockup-vmiop-4c"}, - } +func updateNamespace(m *namespace.Manager, nsData map[string]interface{}) error { + spec := namespace.NamespacesInstanceUpdateSpec{ + VmServiceSpec: namespace.VmServiceSpec{}, + } - if err := m.CreateVmClass(context.Background(), vmClassSpec); err != nil { - return result, err - } + if contentLibs, contains := nsData["content_libraries"]; contains { + spec.VmServiceSpec.ContentLibraries = structure.SliceInterfacesToStrings(contentLibs.([]interface{})) + } - result[i] = vmClassSpec.Id + if vmClasses, contains := nsData["vm_classes"]; contains { + spec.VmServiceSpec.VmClasses = structure.SliceInterfacesToStrings(vmClasses.([]interface{})) } - return result, nil + return m.UpdateNamespace(context.Background(), nsData["name"].(string), spec) +} + +func deleteNamespace(m *namespace.Manager, nsData map[string]interface{}) error { + return m.DeleteNamespace(context.Background(), nsData["name"].(string)) } diff --git a/vsphere/resource_vsphere_supervisor_test.go b/vsphere/resource_vsphere_supervisor_test.go index 66d8183ff..6b175ad24 100644 --- a/vsphere/resource_vsphere_supervisor_test.go +++ b/vsphere/resource_vsphere_supervisor_test.go @@ -33,6 +33,22 @@ func TestAccResourceVSphereSupervisor_basic(t *testing.T) { resource.TestCheckResourceAttrSet("vsphere_supervisor.supervisor", "id"), ), }, + { + // You can change the network settings in the configuration + // so that they fit your environment + Config: testAccVSphereSupervisorConfig2(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("vsphere_supervisor.supervisor", "id"), + ), + }, + { + // You can change the network settings in the configuration + // so that they fit your environment + Config: testAccVSphereSupervisorConfig3(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("vsphere_supervisor.supervisor", "id"), + ), + }, }, }) } @@ -59,6 +75,202 @@ data vsphere_distributed_virtual_switch dvs { datacenter_id = "${data.vsphere_datacenter.rootdc1.id}" } +resource "vsphere_virtual_machine_class" "vm_class_1" { + name = "custom-class-15" + cpus = 4 + memory = 4096 +} + +resource "vsphere_virtual_machine_class" "vm_class_2" { + name = "custom-class-16" + cpus = 4 + memory = 4096 + memory_reservation = 100 +} + +resource "vsphere_supervisor" "supervisor" { + cluster = "${data.vsphere_compute_cluster.rootcompute_cluster1.id}" + storage_policy = "${data.vsphere_storage_policy.image_policy.id}" + content_library = "${data.vsphere_content_library.subscribed_lib.id}" + main_dns = "10.0.0.250" + worker_dns = "10.0.0.250" + edge_cluster = "%s" + dvs_uuid = "${data.vsphere_distributed_virtual_switch.dvs.id}" + sizing_hint = "MEDIUM" + + management_network { + network = "${data.vsphere_network.mgmt_net.id}" + subnet_mask = "255.255.255.0" + starting_address = "10.0.0.150" + gateway = "10.0.0.250" + address_count = 5 + } + + ingress_cidr { + address = "10.10.10.0" + prefix = 24 + } + + egress_cidr { + address = "10.10.11.0" + prefix = 24 + } + + pod_cidr { + address = "10.244.10.0" + prefix = 23 + } + + service_cidr { + address = "10.10.12.0" + prefix = 24 + } + + search_domains = [ "vrack.vsphere.local" ] + + namespace { + name = "test-namespace-15" + content_libraries = [ "${data.vsphere_content_library.subscribed_lib.id}" ] + } +} +`, + testAccConfigBase(), + os.Getenv("TF_VAR_STORAGE_POLICY"), + os.Getenv("TF_VAR_MANAGEMENT_NETWORK"), + os.Getenv("TF_VAR_CONTENT_LIBRARY"), + os.Getenv("TF_VAR_DISTRIBUTED_SWITCH"), + os.Getenv("TF_VAR_EDGE_CLUSTER")) +} + +func testAccVSphereSupervisorConfig2() string { + return fmt.Sprintf(` +%s + +data vsphere_storage_policy image_policy { + name = "%s" +} + +data vsphere_network mgmt_net { + name = "%s" + datacenter_id = "${data.vsphere_datacenter.rootdc1.id}" +} + +data vsphere_content_library subscribed_lib { + name = "%s" +} + +data vsphere_distributed_virtual_switch dvs { + name = "%s" + datacenter_id = "${data.vsphere_datacenter.rootdc1.id}" +} + +resource "vsphere_virtual_machine_class" "vm_class_1" { + name = "custom-class-15" + cpus = 4 + memory = 4096 +} + +resource "vsphere_virtual_machine_class" "vm_class_2" { + name = "custom-class-16" + cpus = 4 + memory = 4096 + memory_reservation = 100 +} + +resource "vsphere_supervisor" "supervisor" { + cluster = "${data.vsphere_compute_cluster.rootcompute_cluster1.id}" + storage_policy = "${data.vsphere_storage_policy.image_policy.id}" + content_library = "${data.vsphere_content_library.subscribed_lib.id}" + main_dns = "10.0.0.250" + worker_dns = "10.0.0.250" + edge_cluster = "%s" + dvs_uuid = "${data.vsphere_distributed_virtual_switch.dvs.id}" + sizing_hint = "MEDIUM" + + management_network { + network = "${data.vsphere_network.mgmt_net.id}" + subnet_mask = "255.255.255.0" + starting_address = "10.0.0.150" + gateway = "10.0.0.250" + address_count = 5 + } + + ingress_cidr { + address = "10.10.10.0" + prefix = 24 + } + + egress_cidr { + address = "10.10.11.0" + prefix = 24 + } + + pod_cidr { + address = "10.244.10.0" + prefix = 23 + } + + service_cidr { + address = "10.10.12.0" + prefix = 24 + } + + search_domains = [ "vrack.vsphere.local" ] + + namespace { + name = "test-namespace-15" + content_libraries = [ "${data.vsphere_content_library.subscribed_lib.id}" ] + vm_classes = [ "${vsphere_virtual_machine_class.vm_class_1.id}" ] + } + + namespace { + name = "test-namespace-16" + } +} +`, + testAccConfigBase(), + os.Getenv("TF_VAR_STORAGE_POLICY"), + os.Getenv("TF_VAR_MANAGEMENT_NETWORK"), + os.Getenv("TF_VAR_CONTENT_LIBRARY"), + os.Getenv("TF_VAR_DISTRIBUTED_SWITCH"), + os.Getenv("TF_VAR_EDGE_CLUSTER")) +} + +func testAccVSphereSupervisorConfig3() string { + return fmt.Sprintf(` +%s + +data vsphere_storage_policy image_policy { + name = "%s" +} + +data vsphere_network mgmt_net { + name = "%s" + datacenter_id = "${data.vsphere_datacenter.rootdc1.id}" +} + +data vsphere_content_library subscribed_lib { + name = "%s" +} + +data vsphere_distributed_virtual_switch dvs { + name = "%s" + datacenter_id = "${data.vsphere_datacenter.rootdc1.id}" +} + +resource "vsphere_virtual_machine_class" "vm_class_1" { + name = "custom-class-15" + cpus = 4 + memory = 4096 +} + +resource "vsphere_virtual_machine_class" "vm_class_2" { + name = "custom-class-16" + cpus = 4 + memory = 4096 + memory_reservation = 100 +} + resource "vsphere_supervisor" "supervisor" { cluster = "${data.vsphere_compute_cluster.rootcompute_cluster1.id}" storage_policy = "${data.vsphere_storage_policy.image_policy.id}" @@ -100,20 +312,9 @@ resource "vsphere_supervisor" "supervisor" { search_domains = [ "vrack.vsphere.local" ] namespace { - name = "test-namespace-08" + name = "test-namespace-15" content_libraries = [ "${data.vsphere_content_library.subscribed_lib.id}" ] - vm_class { - id = "test-class-08" - cpus = 4 - memory = 4096 - memory_reservation = 100 - } - vm_class { - id = "test-class-09" - cpus = 4 - memory = 4096 - memory_reservation = 100 - } + vm_classes = [ "${vsphere_virtual_machine_class.vm_class_1.id}", "${vsphere_virtual_machine_class.vm_class_1.id}" ] } } `, diff --git a/vsphere/resource_vsphere_virtual_machine_class.go b/vsphere/resource_vsphere_virtual_machine_class.go new file mode 100644 index 000000000..66dbc945a --- /dev/null +++ b/vsphere/resource_vsphere_virtual_machine_class.go @@ -0,0 +1,98 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vsphere + +import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/govmomi/vapi/namespace" +) + +func resourceVsphereVmClass() *schema.Resource { + return &schema.Resource{ + Create: resourceVsphereVmClassCreate, + Read: resourceVsphereVmClassRead, + Update: resourceVsphereVmClassUpdate, + Delete: resourceVsphereVmClassDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the virtual machine class.", + }, + "cpus": { + Type: schema.TypeInt, + Required: true, + Description: "The number of CPUs.", + }, + "memory": { + Type: schema.TypeInt, + Required: true, + Description: "The amount of memory (in MB).", + }, + "cpu_reservation": { + Type: schema.TypeInt, + Optional: true, + Description: "The percentage of the available CPU capacity which will be reserved.", + }, + "memory_reservation": { + Type: schema.TypeInt, + Optional: true, + Description: "The percentage of the available memory capacity which will be reserved.", + }, + }, + } +} + +func resourceVsphereVmClassCreate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client).restClient + m := namespace.NewManager(c) + + vmClassSpec := namespace.VirtualMachineClassCreateSpec{ + Id: d.Get("name").(string), + CpuCount: int64(d.Get("cpus").(int)), + MemoryMb: int64(d.Get("memory").(int)), + CpuReservation: int64(d.Get("cpu_reservation").(int)), + MemoryReservation: int64(d.Get("memory_reservation").(int)), + } + + if err := m.CreateVmClass(context.Background(), vmClassSpec); err != nil { + return err + } + + d.SetId(vmClassSpec.Id) + + return nil +} + +func resourceVsphereVmClassRead(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client).restClient + m := namespace.NewManager(c) + + _, err := m.GetVmClass(context.Background(), d.Id()) + + return err +} + +func resourceVsphereVmClassUpdate(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client).restClient + m := namespace.NewManager(c) + + vmClassSpec := namespace.VirtualMachineClassUpdateSpec{ + Id: d.Get("name").(string), + CpuCount: int64(d.Get("cpus").(int)), + MemoryMb: int64(d.Get("memory").(int)), + CpuReservation: int64(d.Get("cpu_reservation").(int)), + MemoryReservation: int64(d.Get("memory_reservation").(int)), + } + + return m.UpdateVmClass(context.Background(), d.Id(), vmClassSpec) +} + +func resourceVsphereVmClassDelete(d *schema.ResourceData, meta interface{}) error { + c := meta.(*Client).restClient + m := namespace.NewManager(c) + + return m.DeleteVmClass(context.Background(), d.Id()) +} diff --git a/vsphere/resource_vsphere_virtual_machine_class_test.go b/vsphere/resource_vsphere_virtual_machine_class_test.go new file mode 100644 index 000000000..cff342134 --- /dev/null +++ b/vsphere/resource_vsphere_virtual_machine_class_test.go @@ -0,0 +1,35 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package vsphere + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "testing" +) + +func TestAccResourceVSphereVmClass_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVSphereVmClassConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("vsphere_virtual_machine_class.vm_class_1", "id"), + ), + }, + }, + }) +} + +func testAccVSphereVmClassConfig() string { + return fmt.Sprintf(` + resource "vsphere_virtual_machine_class" "vm_class_1" { + name = "test-class-1" + cpus = 4 + memory = 4096 + memory_reservation = 100 + } +`) +} From b35be031d122ee75bf991663c604421a52d030e9 Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Fri, 29 Mar 2024 13:57:20 +0200 Subject: [PATCH 10/14] feat: add more tests Signed-off-by: Stoyan Zhelyazkov --- vsphere/resource_vsphere_supervisor_test.go | 138 +++--------------- .../resource_vsphere_virtual_machine_class.go | 26 ++++ ...urce_vsphere_virtual_machine_class_test.go | 28 +++- 3 files changed, 73 insertions(+), 119 deletions(-) diff --git a/vsphere/resource_vsphere_supervisor_test.go b/vsphere/resource_vsphere_supervisor_test.go index 6b175ad24..7be7a41ed 100644 --- a/vsphere/resource_vsphere_supervisor_test.go +++ b/vsphere/resource_vsphere_supervisor_test.go @@ -33,18 +33,27 @@ func TestAccResourceVSphereSupervisor_basic(t *testing.T) { resource.TestCheckResourceAttrSet("vsphere_supervisor.supervisor", "id"), ), }, + }, + }) +} + +func TestAccResourceVSphereSupervisor_full(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { + testAccCheckEnvVariables(t, []string{ + "TF_VAR_STORAGE_POLICY", + "TF_VAR_MANAGEMENT_NETWORK", + "TF_VAR_CONTENT_LIBRARY", + "TF_VAR_DISTRIBUTED_SWITCH", + "TF_VAR_EDGE_CLUSTER", + }) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ { // You can change the network settings in the configuration // so that they fit your environment - Config: testAccVSphereSupervisorConfig2(), - Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrSet("vsphere_supervisor.supervisor", "id"), - ), - }, - { - // You can change the network settings in the configuration - // so that they fit your environment - Config: testAccVSphereSupervisorConfig3(), + Config: testAccVSphereSupervisorConfig_withVmClasses(), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttrSet("vsphere_supervisor.supervisor", "id"), ), @@ -75,19 +84,6 @@ data vsphere_distributed_virtual_switch dvs { datacenter_id = "${data.vsphere_datacenter.rootdc1.id}" } -resource "vsphere_virtual_machine_class" "vm_class_1" { - name = "custom-class-15" - cpus = 4 - memory = 4096 -} - -resource "vsphere_virtual_machine_class" "vm_class_2" { - name = "custom-class-16" - cpus = 4 - memory = 4096 - memory_reservation = 100 -} - resource "vsphere_supervisor" "supervisor" { cluster = "${data.vsphere_compute_cluster.rootcompute_cluster1.id}" storage_policy = "${data.vsphere_storage_policy.image_policy.id}" @@ -142,101 +138,7 @@ resource "vsphere_supervisor" "supervisor" { os.Getenv("TF_VAR_EDGE_CLUSTER")) } -func testAccVSphereSupervisorConfig2() string { - return fmt.Sprintf(` -%s - -data vsphere_storage_policy image_policy { - name = "%s" -} - -data vsphere_network mgmt_net { - name = "%s" - datacenter_id = "${data.vsphere_datacenter.rootdc1.id}" -} - -data vsphere_content_library subscribed_lib { - name = "%s" -} - -data vsphere_distributed_virtual_switch dvs { - name = "%s" - datacenter_id = "${data.vsphere_datacenter.rootdc1.id}" -} - -resource "vsphere_virtual_machine_class" "vm_class_1" { - name = "custom-class-15" - cpus = 4 - memory = 4096 -} - -resource "vsphere_virtual_machine_class" "vm_class_2" { - name = "custom-class-16" - cpus = 4 - memory = 4096 - memory_reservation = 100 -} - -resource "vsphere_supervisor" "supervisor" { - cluster = "${data.vsphere_compute_cluster.rootcompute_cluster1.id}" - storage_policy = "${data.vsphere_storage_policy.image_policy.id}" - content_library = "${data.vsphere_content_library.subscribed_lib.id}" - main_dns = "10.0.0.250" - worker_dns = "10.0.0.250" - edge_cluster = "%s" - dvs_uuid = "${data.vsphere_distributed_virtual_switch.dvs.id}" - sizing_hint = "MEDIUM" - - management_network { - network = "${data.vsphere_network.mgmt_net.id}" - subnet_mask = "255.255.255.0" - starting_address = "10.0.0.150" - gateway = "10.0.0.250" - address_count = 5 - } - - ingress_cidr { - address = "10.10.10.0" - prefix = 24 - } - - egress_cidr { - address = "10.10.11.0" - prefix = 24 - } - - pod_cidr { - address = "10.244.10.0" - prefix = 23 - } - - service_cidr { - address = "10.10.12.0" - prefix = 24 - } - - search_domains = [ "vrack.vsphere.local" ] - - namespace { - name = "test-namespace-15" - content_libraries = [ "${data.vsphere_content_library.subscribed_lib.id}" ] - vm_classes = [ "${vsphere_virtual_machine_class.vm_class_1.id}" ] - } - - namespace { - name = "test-namespace-16" - } -} -`, - testAccConfigBase(), - os.Getenv("TF_VAR_STORAGE_POLICY"), - os.Getenv("TF_VAR_MANAGEMENT_NETWORK"), - os.Getenv("TF_VAR_CONTENT_LIBRARY"), - os.Getenv("TF_VAR_DISTRIBUTED_SWITCH"), - os.Getenv("TF_VAR_EDGE_CLUSTER")) -} - -func testAccVSphereSupervisorConfig3() string { +func testAccVSphereSupervisorConfig_withVmClasses() string { return fmt.Sprintf(` %s @@ -314,7 +216,7 @@ resource "vsphere_supervisor" "supervisor" { namespace { name = "test-namespace-15" content_libraries = [ "${data.vsphere_content_library.subscribed_lib.id}" ] - vm_classes = [ "${vsphere_virtual_machine_class.vm_class_1.id}", "${vsphere_virtual_machine_class.vm_class_1.id}" ] + vm_classes = [ "${vsphere_virtual_machine_class.vm_class_1.id}", "${vsphere_virtual_machine_class.vm_class_2.id}" ] } } `, diff --git a/vsphere/resource_vsphere_virtual_machine_class.go b/vsphere/resource_vsphere_virtual_machine_class.go index 66dbc945a..ab6c7ab89 100644 --- a/vsphere/resource_vsphere_virtual_machine_class.go +++ b/vsphere/resource_vsphere_virtual_machine_class.go @@ -41,6 +41,14 @@ func resourceVsphereVmClass() *schema.Resource { Optional: true, Description: "The percentage of the available memory capacity which will be reserved.", }, + "vgpu_devices": { + Type: schema.TypeList, + Optional: true, + Description: "A comma-separated list of GPU devices.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, }, } } @@ -57,6 +65,15 @@ func resourceVsphereVmClassCreate(d *schema.ResourceData, meta interface{}) erro MemoryReservation: int64(d.Get("memory_reservation").(int)), } + vgpuDevices := d.Get("vgpu_devices").([]interface{}) + vmClassSpec.Devices = namespace.VirtualDevices{ + VgpuDevices: make([]namespace.VgpuDevice, len(vgpuDevices)), + } + + for i, g := range vgpuDevices { + vmClassSpec.Devices.VgpuDevices[i] = namespace.VgpuDevice{ProfileName: g.(string)} + } + if err := m.CreateVmClass(context.Background(), vmClassSpec); err != nil { return err } @@ -87,6 +104,15 @@ func resourceVsphereVmClassUpdate(d *schema.ResourceData, meta interface{}) erro MemoryReservation: int64(d.Get("memory_reservation").(int)), } + vgpuDevices := d.Get("vgpu_devices").([]interface{}) + vmClassSpec.Devices = namespace.VirtualDevices{ + VgpuDevices: make([]namespace.VgpuDevice, len(vgpuDevices)), + } + + for i, g := range vgpuDevices { + vmClassSpec.Devices.VgpuDevices[i] = namespace.VgpuDevice{ProfileName: g.(string)} + } + return m.UpdateVmClass(context.Background(), d.Id(), vmClassSpec) } diff --git a/vsphere/resource_vsphere_virtual_machine_class_test.go b/vsphere/resource_vsphere_virtual_machine_class_test.go index cff342134..df780b6e5 100644 --- a/vsphere/resource_vsphere_virtual_machine_class_test.go +++ b/vsphere/resource_vsphere_virtual_machine_class_test.go @@ -23,13 +23,39 @@ func TestAccResourceVSphereVmClass_basic(t *testing.T) { }) } +func TestAccResourceVSphereVmClass_vgpu(t *testing.T) { + resource.Test(t, resource.TestCase{ + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccVSphereVmClassConfig_vgpu(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("vsphere_virtual_machine_class.vm_class_1", "id"), + ), + }, + }, + }) +} + func testAccVSphereVmClassConfig() string { return fmt.Sprintf(` resource "vsphere_virtual_machine_class" "vm_class_1" { - name = "test-class-1" + name = "test-class-11" + cpus = 4 + memory = 4096 + memory_reservation = 100 + } +`) +} + +func testAccVSphereVmClassConfig_vgpu() string { + return fmt.Sprintf(` + resource "vsphere_virtual_machine_class" "vm_class_1" { + name = "test-class-11" cpus = 4 memory = 4096 memory_reservation = 100 + vgpu_devices = [ "mockup-vmiop" ] } `) } From 9f9f921e92b09a69ee8755fdefd6361f982e79c3 Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Wed, 24 Apr 2024 12:39:09 +0300 Subject: [PATCH 11/14] feat: Add a message in the read handler with details of the missing GET binding Signed-off-by: Stoyan Zhelyazkov --- vsphere/resource_vsphere_supervisor.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vsphere/resource_vsphere_supervisor.go b/vsphere/resource_vsphere_supervisor.go index 32a5ff25b..b59e85844 100644 --- a/vsphere/resource_vsphere_supervisor.go +++ b/vsphere/resource_vsphere_supervisor.go @@ -247,6 +247,9 @@ func resourceVsphereSupervisorRead(d *schema.ResourceData, meta interface{}) err return fmt.Errorf("could not find cluster %s", cluster.ID) } + // To fully implement "read" and allow this resource to be imported + // the following API has to be added to Govmomi + // https://developer.vmware.com/apis/vsphere-automation/latest/vcenter/api/vcenter/namespace-management/clusters/cluster/get/ return nil } From 3c704d7fa8159c2bc6ed305f4c1bb149ca15c24c Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Thu, 25 Apr 2024 13:52:32 +0300 Subject: [PATCH 12/14] feat: Add docs for the new resources Signed-off-by: Stoyan Zhelyazkov --- vsphere/resource_vsphere_supervisor.go | 6 +- website/docs/r/supervisor.html.markdown | 100 ++++++++++++++++++ .../r/virtual_machine_class.html.markdown | 43 ++++++++ 3 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 website/docs/r/supervisor.html.markdown create mode 100644 website/docs/r/virtual_machine_class.html.markdown diff --git a/vsphere/resource_vsphere_supervisor.go b/vsphere/resource_vsphere_supervisor.go index b59e85844..2baad1f1d 100644 --- a/vsphere/resource_vsphere_supervisor.go +++ b/vsphere/resource_vsphere_supervisor.go @@ -35,7 +35,7 @@ func resourceVsphereSupervisor() *schema.Resource { "management_network": { Type: schema.TypeList, Required: true, - Description: "The name of the management network which the control plane VMs will be connected to.", + Description: "The configuration for the management network which the control plane VMs will be connected to.", MaxItems: 1, Elem: mgmtNetworkSchema(), }, @@ -189,7 +189,7 @@ func namespaceSchema() *schema.Resource { "content_libraries": { Type: schema.TypeList, Optional: true, - Description: "A comma-separated list of content libraries.", + Description: "A list of content libraries.", Elem: &schema.Schema{ Type: schema.TypeString, }, @@ -197,7 +197,7 @@ func namespaceSchema() *schema.Resource { "vm_classes": { Type: schema.TypeList, Optional: true, - Description: "A comma-separated list of virtual machine classes.", + Description: "A list of virtual machine classes.", Elem: &schema.Schema{ Type: schema.TypeString, }, diff --git a/website/docs/r/supervisor.html.markdown b/website/docs/r/supervisor.html.markdown new file mode 100644 index 000000000..eaf5bdb0b --- /dev/null +++ b/website/docs/r/supervisor.html.markdown @@ -0,0 +1,100 @@ +--- +subcategory: "Workload Management" +layout: "vsphere" +page_title: "VMware vSphere: vsphere_supervisor" +sidebar_current: "docs-vsphere-resource-vsphere-supervisor" +description: |- + Provides a VMware vSphere Supervisor resource.. +--- + +# vsphere\_supervisor + +Provides a resource for configuring Workload Management. + +## Example Usages + +**Enable Workload Management on a compute cluster** + +```hcl +resource "vsphere_virtual_machine_class" "vm_class" { + name = "custom-class" + cpus = 4 + memory = 4096 +} + +resource "vsphere_supervisor" "supervisor" { + cluster = "" + storage_policy = "" + content_library = "" + main_dns = "10.0.0.250" + worker_dns = "10.0.0.250" + edge_cluster = "" + dvs_uuid = "" + sizing_hint = "MEDIUM" + + management_network { + network = "" + subnet_mask = "255.255.255.0" + starting_address = "10.0.0.150" + gateway = "10.0.0.250" + address_count = 5 + } + + ingress_cidr { + address = "10.10.10.0" + prefix = 24 + } + + egress_cidr { + address = "10.10.11.0" + prefix = 24 + } + + pod_cidr { + address = "10.244.10.0" + prefix = 23 + } + + service_cidr { + address = "10.10.12.0" + prefix = 24 + } + + search_domains = [ "vsphere.local" ] + + namespace { + name = "custom-namespace" + content_libraries = [] + vm_clases = [ "${vsphere_virtual_machine_class.vm_class.id}" ] + } +} +``` + +## Argument Reference + +* `cluster` - The identifier of the compute cluster. +* `storage_policy` - The name of the storage policy. +* `management_network` - The configuration for the management network which the control plane VMs will be connected to. +* * `network` - ID of the network. (e.g. a distributed port group). +* * `starting_address` - Starting address of the management network range. +* * `subnet_mask` - Subnet mask. +* * `gateway` - Gateway IP address. +* * `address_count` - Number of addresses to allocate. Starts from `starting_address` +* `content_library` - The identifier of the subscribed content library. +* `main_dns` - The address of the primary DNS server. +* `worker_dns` - The address of the DNS server to use for the worker nodes. +* `edge_cluster` - The identifier of the NSX Edge Cluster. +* `dvs_uuid` - The UUID of the distributed switch. +* `sizing_hint` - The size of the Kubernetes API server. +* `egress_cidr` - CIDR blocks from which NSX assigns IP addresses used for performing SNAT from container IPs to external IPs. +* `ingress_cidr` - CIDR blocks from which NSX assigns IP addresses for Kubernetes Ingresses and Kubernetes Services of type LoadBalancer. +* `pod_cidr` - CIDR blocks from which Kubernetes allocates pod IP addresses. Minimum subnet size is 23. +* `service_cidr` - CIDR block from which Kubernetes allocates service cluster IP addresses. +* `search_domains` - List of DNS search domains. +* `namespace` - The list of namespaces to create in the Supervisor cluster + +### Namespace schema + +* `name` - The name of the namespace +* `content_libraries` - The list of content libraries to associate with the namespace +* `vm_classes` - The list of virtual machine classes to add to the namespace diff --git a/website/docs/r/virtual_machine_class.html.markdown b/website/docs/r/virtual_machine_class.html.markdown new file mode 100644 index 000000000..7eb3f66f8 --- /dev/null +++ b/website/docs/r/virtual_machine_class.html.markdown @@ -0,0 +1,43 @@ +--- +subcategory: "Workload Management" +layout: "vsphere" +page_title: "VMware vSphere: vsphere_virtual_machine_class" +sidebar_current: "docs-vsphere-resource-virtual-machine-class" +description: |- + Provides a VMware vSphere virtual machine class resource.. +--- + +# vsphere\_virtual_machine_class + +Provides a resource for configuring a Virtual Machine class. + +## Example Usages + +**Create a basic class** + +```hcl +resource "vsphere_virtual_machine_class" "basic_class" { + name = "basic-class" + cpus = 4 + memory = 4096 +} +``` + +**Create a class with a vGPU** +```hcl +resource "vsphere_virtual_machine_class" "vgp_class" { + name = "vgpu-class" + cpus = 4 + memory = 4096 + memory_reservation = 100 + vgpu_devices = [ "vgpu1" ] +} +``` + +## Argument Reference + +* `name` - The name for the class. +* `cpus` - The number of CPUs. +* `memory` - The amount of memory in MB. +* `memory_reservation` - The percentage of memory reservation. +* `vgpu_devices` - The identifiers of the vGPU devices for the class. If this is set memory reservation needs to be 100. \ No newline at end of file From 1b712cffab61d2484642489184afaf3d2eb6ee2b Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Thu, 25 Apr 2024 14:15:08 +0300 Subject: [PATCH 13/14] feat: Accept multiple DNS servers Signed-off-by: Stoyan Zhelyazkov --- vsphere/resource_vsphere_supervisor.go | 28 ++++++++++++--------- vsphere/resource_vsphere_supervisor_test.go | 4 +-- website/docs/r/supervisor.html.markdown | 4 +-- 3 files changed, 20 insertions(+), 16 deletions(-) diff --git a/vsphere/resource_vsphere_supervisor.go b/vsphere/resource_vsphere_supervisor.go index 2baad1f1d..78e5573eb 100644 --- a/vsphere/resource_vsphere_supervisor.go +++ b/vsphere/resource_vsphere_supervisor.go @@ -46,16 +46,20 @@ func resourceVsphereSupervisor() *schema.Resource { ValidateFunc: validation.StringIsNotEmpty, }, "main_dns": { - Type: schema.TypeString, - Required: true, - Description: "List of DNS servers to use on the Kubernetes API server.", - ValidateFunc: validation.IsIPv4Address, + Type: schema.TypeList, + Required: true, + Description: "List of DNS servers to use on the Kubernetes API server.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, }, "worker_dns": { - Type: schema.TypeString, - Required: true, - Description: "List of DNS servers to use on the worker nodes.", - ValidateFunc: validation.IsIPv4Address, + Type: schema.TypeList, + Required: true, + Description: "List of DNS servers to use on the worker nodes.", + Elem: &schema.Schema{ + Type: schema.TypeString, + }, }, "edge_cluster": { Type: schema.TypeString, @@ -320,8 +324,8 @@ func buildClusterEnableSpec(d *schema.ResourceData) *namespace.EnableClusterSpec } contentLib := d.Get("content_library").(string) - mainDns := d.Get("main_dns").(string) - workerDns := d.Get("worker_dns").(string) + mainDns := d.Get("main_dns").([]interface{}) + workerDns := d.Get("worker_dns").([]interface{}) dnsSearchDomains := d.Get("search_domains").([]interface{}) storagePolicy := d.Get("storage_policy").(string) serviceCidrs := d.Get("service_cidr").([]interface{}) @@ -336,8 +340,8 @@ func buildClusterEnableSpec(d *schema.ResourceData) *namespace.EnableClusterSpec MasterManagementNetwork: getMgmtNetwork(d), ImageStorage: namespace.ImageStorageSpec{StoragePolicy: storagePolicy}, NcpClusterNetworkSpec: &ncpNetworkSpec, - MasterDNS: []string{mainDns}, - WorkerDNS: []string{workerDns}, + MasterDNS: structure.SliceInterfacesToStrings(mainDns), + WorkerDNS: structure.SliceInterfacesToStrings(workerDns), DefaultKubernetesServiceContentLibrary: contentLib, MasterDNSSearchDomains: structure.SliceInterfacesToStrings(dnsSearchDomains), } diff --git a/vsphere/resource_vsphere_supervisor_test.go b/vsphere/resource_vsphere_supervisor_test.go index 7be7a41ed..394f27a53 100644 --- a/vsphere/resource_vsphere_supervisor_test.go +++ b/vsphere/resource_vsphere_supervisor_test.go @@ -88,8 +88,8 @@ resource "vsphere_supervisor" "supervisor" { cluster = "${data.vsphere_compute_cluster.rootcompute_cluster1.id}" storage_policy = "${data.vsphere_storage_policy.image_policy.id}" content_library = "${data.vsphere_content_library.subscribed_lib.id}" - main_dns = "10.0.0.250" - worker_dns = "10.0.0.250" + main_dns = [ "10.0.0.250" ] + worker_dns = [ "10.0.0.250" ] edge_cluster = "%s" dvs_uuid = "${data.vsphere_distributed_virtual_switch.dvs.id}" sizing_hint = "MEDIUM" diff --git a/website/docs/r/supervisor.html.markdown b/website/docs/r/supervisor.html.markdown index eaf5bdb0b..8631068e5 100644 --- a/website/docs/r/supervisor.html.markdown +++ b/website/docs/r/supervisor.html.markdown @@ -81,8 +81,8 @@ resource "vsphere_supervisor" "supervisor" { * * `gateway` - Gateway IP address. * * `address_count` - Number of addresses to allocate. Starts from `starting_address` * `content_library` - The identifier of the subscribed content library. -* `main_dns` - The address of the primary DNS server. -* `worker_dns` - The address of the DNS server to use for the worker nodes. +* `main_dns` - The list of addresses of the primary DNS servers. +* `worker_dns` - The list of addresses of the DNS servers to use for the worker nodes. * `edge_cluster` - The identifier of the NSX Edge Cluster. * `dvs_uuid` - The UUID of the distributed switch. * `sizing_hint` - The size of the Kubernetes API server. From 9c4275d3ace775a4ac73e2cc6af99eb5a5c9a7d3 Mon Sep 17 00:00:00 2001 From: Stoyan Zhelyazkov Date: Fri, 26 Apr 2024 11:55:04 +0300 Subject: [PATCH 14/14] feat: Update second test case inputs Signed-off-by: Stoyan Zhelyazkov --- vsphere/resource_vsphere_supervisor_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vsphere/resource_vsphere_supervisor_test.go b/vsphere/resource_vsphere_supervisor_test.go index 394f27a53..d0ded1e3f 100644 --- a/vsphere/resource_vsphere_supervisor_test.go +++ b/vsphere/resource_vsphere_supervisor_test.go @@ -92,7 +92,7 @@ resource "vsphere_supervisor" "supervisor" { worker_dns = [ "10.0.0.250" ] edge_cluster = "%s" dvs_uuid = "${data.vsphere_distributed_virtual_switch.dvs.id}" - sizing_hint = "MEDIUM" + sizing_hint = "SMALL" management_network { network = "${data.vsphere_network.mgmt_net.id}" @@ -177,11 +177,11 @@ resource "vsphere_supervisor" "supervisor" { cluster = "${data.vsphere_compute_cluster.rootcompute_cluster1.id}" storage_policy = "${data.vsphere_storage_policy.image_policy.id}" content_library = "${data.vsphere_content_library.subscribed_lib.id}" - main_dns = "10.0.0.250" - worker_dns = "10.0.0.250" + main_dns = [ "10.0.0.250" ] + worker_dns = [ "10.0.0.250" ] edge_cluster = "%s" dvs_uuid = "${data.vsphere_distributed_virtual_switch.dvs.id}" - sizing_hint = "MEDIUM" + sizing_hint = "SMALL" management_network { network = "${data.vsphere_network.mgmt_net.id}"