From 743d37431d84c92f393841d45d02c7e175feeeec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Tue, 22 Aug 2017 00:58:53 +0200 Subject: [PATCH 01/21] Add virtual distributed switch resource (one NIC per host) --- vsphere/provider.go | 27 +-- ...urce_vsphere_distributed_virtual_switch.go | 189 ++++++++++++++++++ ...vsphere_distributed_virtual_switch_test.go | 136 +++++++++++++ 3 files changed, 339 insertions(+), 13 deletions(-) create mode 100644 vsphere/resource_vsphere_distributed_virtual_switch.go create mode 100644 vsphere/resource_vsphere_distributed_virtual_switch_test.go diff --git a/vsphere/provider.go b/vsphere/provider.go index 83ea08197..2b8edb261 100644 --- a/vsphere/provider.go +++ b/vsphere/provider.go @@ -69,19 +69,20 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "vsphere_datacenter": resourceVSphereDatacenter(), - "vsphere_file": resourceVSphereFile(), - "vsphere_folder": resourceVSphereFolder(), - "vsphere_host_port_group": resourceVSphereHostPortGroup(), - "vsphere_host_virtual_switch": resourceVSphereHostVirtualSwitch(), - "vsphere_license": resourceVSphereLicense(), - "vsphere_tag": resourceVSphereTag(), - "vsphere_tag_category": resourceVSphereTagCategory(), - "vsphere_virtual_disk": resourceVSphereVirtualDisk(), - "vsphere_virtual_machine": resourceVSphereVirtualMachine(), - "vsphere_nas_datastore": resourceVSphereNasDatastore(), - "vsphere_vmfs_datastore": resourceVSphereVmfsDatastore(), - "vsphere_virtual_machine_snapshot": resourceVSphereVirtualMachineSnapshot(), + "vsphere_datacenter": resourceVSphereDatacenter(), + "vsphere_file": resourceVSphereFile(), + "vsphere_folder": resourceVSphereFolder(), + "vsphere_distributed_virtual_switch": resourceVSphereDistributedVirtualSwitch(), + "vsphere_host_port_group": resourceVSphereHostPortGroup(), + "vsphere_host_virtual_switch": resourceVSphereHostVirtualSwitch(), + "vsphere_license": resourceVSphereLicense(), + "vsphere_tag": resourceVSphereTag(), + "vsphere_tag_category": resourceVSphereTagCategory(), + "vsphere_virtual_disk": resourceVSphereVirtualDisk(), + "vsphere_virtual_machine": resourceVSphereVirtualMachine(), + "vsphere_nas_datastore": resourceVSphereNasDatastore(), + "vsphere_vmfs_datastore": resourceVSphereVmfsDatastore(), + "vsphere_virtual_machine_snapshot": resourceVSphereVirtualMachineSnapshot(), }, DataSourcesMap: map[string]*schema.Resource{ diff --git a/vsphere/resource_vsphere_distributed_virtual_switch.go b/vsphere/resource_vsphere_distributed_virtual_switch.go new file mode 100644 index 000000000..240cdb882 --- /dev/null +++ b/vsphere/resource_vsphere_distributed_virtual_switch.go @@ -0,0 +1,189 @@ +package vsphere + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/helper/schema" + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/methods" + "github.com/vmware/govmomi/vim25/types" + "golang.org/x/net/context" +) + +func resourceVSphereDistributedVirtualSwitch() *schema.Resource { + return &schema.Resource{ + Create: resourceVSphereDistributedVirtualSwitchCreate, + Read: resourceVSphereDistributedVirtualSwitchRead, + Delete: resourceVSphereDistributedVirtualSwitchDelete, + + Schema: map[string]*schema.Schema{ + "datacenter": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "uplinks": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Elem: schema.TypeString, + }, + }, + } +} + +func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta interface{}) error { + + client := meta.(*govmomi.Client) + name := d.Get("name").(string) + + dc, err := getDatacenter(client, d.Get("datacenter").(string)) + if err != nil { + return fmt.Errorf("%s", err) + } + + finder := find.NewFinder(client.Client, true) + finder = finder.SetDatacenter(dc) + + df, err := dc.Folders(context.TODO()) + if err != nil { + return fmt.Errorf("%s", err) + } + + // Configure the host and nic cards used as uplink for the DVS + var host []types.DistributedVirtualSwitchHostMemberConfigSpec + + if kv, ok := d.GetOk("uplinks"); ok { + for k, v := range kv.(map[string]interface{}) { + // Get the HostSystem reference + hs, err := finder.HostSystem(context.TODO(), k) + if err != nil { + return fmt.Errorf("%s", err) + } + + // Get the physical NIC backing + backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) + backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ + PnicDevice: strings.TrimSpace(v.(string)), + }) + h := types.DistributedVirtualSwitchHostMemberConfigSpec{ + Host: hs.Common.Reference(), + Backing: backing, + Operation: "add", // Options: "add", "edit", "remove" + } + host = append(host, h) + } + } + + configSpec := types.DVSConfigSpec{ + Name: name, + Host: host, + } + dvsCreateSpec := types.DVSCreateSpec{ConfigSpec: &configSpec} + + f := df.NetworkFolder + + task, err := f.CreateDVS(context.TODO(), dvsCreateSpec) + if err != nil { + return fmt.Errorf("%s", err) + } + + err = task.Wait(context.TODO()) + if err != nil { + return fmt.Errorf("%s", err) + } + + d.SetId(name) + + return nil +} + +func resourceVSphereDistributedVirtualSwitchRead(d *schema.ResourceData, meta interface{}) error { + _, err := dvsExists(d, meta) + if err != nil { + d.SetId("") + } + + return nil +} + +func dvsExists(d *schema.ResourceData, meta interface{}) (object.NetworkReference, error) { + client := meta.(*govmomi.Client) + name := d.Get("name").(string) + + dc, err := getDatacenter(client, d.Get("datacenter").(string)) + if err != nil { + return nil, err + } + + finder := find.NewFinder(client.Client, true) + finder = finder.SetDatacenter(dc) + + dvs, err := finder.Network(context.TODO(), name) + return dvs, err +} + +func resourceVSphereDVSStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Print("[TRACE] Refreshing distributed virtual switch state") + dvs, err := dvsExists(d, meta) + if err != nil { + switch err.(type) { + case *find.NotFoundError: + log.Printf("[TRACE] Refreshing state. Distributed virtual switch not found: %s", err) + return nil, "InProgress", nil + default: + return nil, "Failed", err + } + } + log.Print("[TRACE] Refreshing state. Distributed virtual switch found") + return dvs, "Created", nil + } +} + +func resourceVSphereDistributedVirtualSwitchDelete(d *schema.ResourceData, meta interface{}) error { + dvs, err := dvsExists(d, meta) + if err != nil { + return fmt.Errorf("%s", err) + } + + client := meta.(*govmomi.Client) + n := object.NewDistributedVirtualSwitch(client.Client, dvs.Reference()) + req := &types.Destroy_Task{ + This: n.Reference(), + } + + _, err = methods.Destroy_Task(context.TODO(), client, req) + if err != nil { + return fmt.Errorf("%s", err) + } + + // Wait for the distributed virtual switch resource to be destroyed + stateConf := &resource.StateChangeConf{ + Pending: []string{"Created"}, + Target: []string{}, + Refresh: resourceVSphereDVSStateRefreshFunc(d, meta), + Timeout: 10 * time.Minute, + MinTimeout: 3 * time.Second, + Delay: 5 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + name := d.Get("name").(string) + return fmt.Errorf("error waiting for distributed virtual switch (%s) to be deleted: %s", name, err) + } + + return nil +} diff --git a/vsphere/resource_vsphere_distributed_virtual_switch_test.go b/vsphere/resource_vsphere_distributed_virtual_switch_test.go new file mode 100644 index 000000000..06e68673f --- /dev/null +++ b/vsphere/resource_vsphere_distributed_virtual_switch_test.go @@ -0,0 +1,136 @@ +package vsphere + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/find" + "golang.org/x/net/context" +) + +const testAccCheckVSphereDVSConfigNoUplinks = ` +resource "vsphere_distributed_virtual_switch" "testDVS" { + datacenter = "%s" + name = "testDVS" +} +` + +const testAccCheckVSphereDVSConfigUplinks = ` +resource "vsphere_distributed_virtual_switch" "testDVS" { + datacenter = "%s" + name = "testDVS" + uplinks = { "10.2.10.57" = "vmnic1", "10.2.10.6" = "vmnic1" } +} +` + +// Create a distributed virtual switch with no uplinks +func TestAccVSphereDVS_createWithoutUplinks(t *testing.T) { + resourceName := "vsphere_distributed_virtual_switch.testDVS" + datacenter := os.Getenv("VSPHERE_DATACENTER") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVSphereDVSDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(testAccCheckVSphereDVSConfigNoUplinks, datacenter), + Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), + }, + }, + }) +} + +// Create a distributed virtual switch with uplinks +func TestAccVSphereDVS_createWithUplinks(t *testing.T) { + resourceName := "vsphere_distributed_virtual_switch.testDVS" + datacenter := os.Getenv("VSPHERE_DATACENTER") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVSphereDVSDestroy, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(testAccCheckVSphereDVSConfigUplinks, datacenter), + Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), + }, + }, + }) +} + +func testAccCheckVSphereDVSDestroy(s *terraform.State) error { + client := testAccProvider.Meta().(*govmomi.Client) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "vsphere_distributed_virtual_switch" { + continue + } + + datacenter := rs.Primary.Attributes["datacenter"] + dc, err := getDatacenter(client, datacenter) + if err != nil { + return err + } + + finder := find.NewFinder(client.Client, true) + finder = finder.SetDatacenter(dc) + + name := rs.Primary.Attributes["name"] + _, err = finder.NetworkList(context.TODO(), name) + if err != nil { + switch err.(type) { + case *find.NotFoundError: + fmt.Printf("Expected error received: %s\n", err) + return nil + default: + return err + } + } else { + return fmt.Errorf("distributed virtual switch '%s' still exists", name) + } + } + + return nil +} + +func testAccCheckVSphereDVSExists(n string, exists bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("resource not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("no ID is set") + } + + client := testAccProvider.Meta().(*govmomi.Client) + + datacenter := rs.Primary.Attributes["datacenter"] + dc, err := getDatacenter(client, datacenter) + if err != nil { + return err + } + + finder := find.NewFinder(client.Client, true) + finder = finder.SetDatacenter(dc) + + name := rs.Primary.Attributes["name"] + _, err = finder.NetworkList(context.TODO(), name) + if err != nil { + switch err.(type) { + case *find.NotFoundError: + fmt.Printf("Expected error received: %s\n", err) + return nil + default: + return err + } + } + return nil + } +} From 33e53e773678f590d93db63f6507ef766a7876e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Tue, 22 Aug 2017 01:37:51 +0200 Subject: [PATCH 02/21] Add documentation for distributed virtual switches --- .../distributed_virtual_switch.html.markdown | 45 +++++++++++++++++++ website/vsphere.erb | 2 + 2 files changed, 47 insertions(+) create mode 100644 website/docs/r/distributed_virtual_switch.html.markdown diff --git a/website/docs/r/distributed_virtual_switch.html.markdown b/website/docs/r/distributed_virtual_switch.html.markdown new file mode 100644 index 000000000..61df5651b --- /dev/null +++ b/website/docs/r/distributed_virtual_switch.html.markdown @@ -0,0 +1,45 @@ +--- +layout: "vsphere" +page_title: "VMware vSphere: vsphere_distributed_virtual_switch" +sidebar_current: "docs-vsphere-resource-distributed-virtual-switch" +description: |- + Provides a VMware vSphere distributed virtual switch resource. A distributed switch configures a switch across all associated hosts, allowing a virtual machine to keep the same network configuration regardless of the actual host it runs in. + + A distributed virtual switch has an uplink port group as well as several distributed port groups. The uplink port group connects physical network cards on the host to the distributed switch. A distributed port group specifies how a connection is made through the distributed switch. + +--- + +# vsphere\_distributed_virtual_switch + +Provides a VMware vSphere distributed virtual switch resource. A distributed switch configures a switch across all associated hosts, allowing a virtual machine to keep the same network configuration regardless of the actual host it runs in. + +## Example Usages + +**Create a distributed virtual switch without specifying uplink port groups (need to be defined manually later):** + +```hcl +resource "vsphere_distributed_virtual_switch" "myDistributedSwitch" { + datacenter = "myDC" + name = "myDistributedSwitch" +} +``` + +**Create a distributed virtual switch specifying uplink port groups):** + +```hcl +resource "vsphere_distributed_virtual_switch" "myDistributedSwitch" { + datacenter = "myDC" + name = "myDistributedSwitch" + uplinks = { "10.0.30.25" = "vmnic1", "host100.mydomain.net" = "vmnic1" } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the distributed virtual switch. +* `datacenter` - (Required) The name of the datacenter containing the distributed virtual switch. +* `uplinks` - (Optional) A map of hosts and physical NICs to attach to the uplink port group. + +~> **NOTE**: Distributed virtual switches cannot be changed once they are created. Modifying any of these attributes will force a new resource! diff --git a/website/vsphere.erb b/website/vsphere.erb index 5dc241c78..069d7e503 100644 --- a/website/vsphere.erb +++ b/website/vsphere.erb @@ -96,6 +96,8 @@ > vsphere_virtual_machine_snapshot + > + vsphere_distributed_virtual_switch From 78b9d0795a705c08dfd2f0e0725d05247df1a4a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Tue, 22 Aug 2017 01:45:22 +0200 Subject: [PATCH 03/21] Paremetrise uplink tests --- vsphere/resource_vsphere_distributed_virtual_switch_test.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/vsphere/resource_vsphere_distributed_virtual_switch_test.go b/vsphere/resource_vsphere_distributed_virtual_switch_test.go index 06e68673f..d3e0dc7d7 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch_test.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch_test.go @@ -23,7 +23,7 @@ const testAccCheckVSphereDVSConfigUplinks = ` resource "vsphere_distributed_virtual_switch" "testDVS" { datacenter = "%s" name = "testDVS" - uplinks = { "10.2.10.57" = "vmnic1", "10.2.10.6" = "vmnic1" } + uplinks = { "%s" = "%s" } } ` @@ -49,6 +49,8 @@ func TestAccVSphereDVS_createWithoutUplinks(t *testing.T) { func TestAccVSphereDVS_createWithUplinks(t *testing.T) { resourceName := "vsphere_distributed_virtual_switch.testDVS" datacenter := os.Getenv("VSPHERE_DATACENTER") + host := os.Getenv("VSPHERE_HOST") + host_pnic := os.Getenv("VSPHERE_HOST_PNIC") resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -56,7 +58,7 @@ func TestAccVSphereDVS_createWithUplinks(t *testing.T) { CheckDestroy: testAccCheckVSphereDVSDestroy, Steps: []resource.TestStep{ { - Config: fmt.Sprintf(testAccCheckVSphereDVSConfigUplinks, datacenter), + Config: fmt.Sprintf(testAccCheckVSphereDVSConfigUplinks, datacenter, host, host_pnic), Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), }, }, From e850ddcb3babc7ebaebee2f74d01d8363afadee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Sun, 27 Aug 2017 19:12:17 +0200 Subject: [PATCH 04/21] Add most options for the DVS to the schema --- .../distributed_virtual_switch_structure.go | 146 ++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 vsphere/distributed_virtual_switch_structure.go diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go new file mode 100644 index 000000000..4b97e29ba --- /dev/null +++ b/vsphere/distributed_virtual_switch_structure.go @@ -0,0 +1,146 @@ +package vsphere + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/vmware/govmomi/vim25/types" +) + +var distributedVirtualSwitchNetworkResourceControlVersionAllowedValues = []string{ + string(types.DistributedVirtualSwitchNetworkResourceControlVersionVersion2), + string(types.DistributedVirtualSwitchNetworkResourceControlVersionVersion3), +} + +var configSpecOperationAllowedValues = []string{ + string(types.VirtualDeviceConfigSpecOperationAdd), + string(types.VirtualDeviceConfigSpecOperationRemove), + string(types.VirtualDeviceConfigSpecOperationEdit), +} + +func schemaDVSContactInfo() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "contact": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The contact information for the person.", + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The name of the person who is responsible for the switch.", + }, + } +} + +func schemaDVPortSetting() map[string]*schema.Schema { + // TBD +} + +func schemaDistributedVirtualSwitchHostMemberConfigSpec() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + "maxProxySwitchPorts": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Description: "Maximum number of ports allowed in the HostProxySwitch.", + Validation: validation.IntAtLeast(0), + }, + "operation": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Description: "Host member operation type.", + Validation: validation.StringInSlice(configSpecOperationAllowedValues, false), + }, + } + mergeSchema(s, schemaDistributedVirtualSwitchHostMemberBacking()) + // XXX TBD host + mergeSchema(s, schemaDistributedVirtualSwitchKeyedOpaqueBlob()) +} + +func schemaDvsHostInfrastructureTrafficResource() map[string]*schema.Schema { + // TBD +} + +func schemaDVSPolicy() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "autoPreInstallAllowed": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: "Whether downloading a new proxy VirtualSwitch module to the host is allowed to be automatically executed by the switch.", + }, + "autoUpgradeAllowed": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: "Whether upgrading of the switch is allowed to be automatically executed by the switch.", + }, + "partialUpgradeAllowed": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: "Whether to allow upgrading a switch when some of the hosts failed to install the needed module.", + }, + } +} + +func schemaDVSUplinkPortPolicy() map[string]*schema.Schema { + // TBD +} + +func schemaDistributedVirtualSwitchKeyedOpaqueBlob() map[string]*schema.Schema { + // TBD should be a map +} + +func schemaDVSConfiSpec() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + "configVersion": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The version string of the configuration that this spec is trying to change. This property is ignored during switch creation.", + }, + "defaultProxySwitchMaxNumPorts": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Description: "The default host proxy switch maximum port number.", + ValidateFunc: validation.IntAtLeast(0), + }, + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Set the description string of the switch.", + }, + "extensionKey": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The key of the extension registered by a remote server that controls the switch.", + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The name of the switch. Must be unique in the parent folder.", + }, + "networkResourceControlVersion": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Indicates the Network Resource Control APIs that are supported on the switch.", + ValidateFunc: validation.StringInSlice(distributedVirtualSwitchNetworkResourceControlVersionAllowedValues, false), + }, + "numStandalonePorts": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Description: "The number of standalone ports in the switch. Standalone ports are ports that do not belong to any portgroup.", + ValidateFunc: validation.IntAtLeast(0), + }, + "switchIpAddress": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "IP address for the switch, specified using IPv4 dot notation. IPv6 address is not supported for this property.", + ValidateFunc: validation.StringInSlice(distributedVirtualSwitchNetworkResourceControlVersionAllowedValues, false), + }, + } + mergeSchema(s, schemaDVSContactInfo()) + mergeSchema(s, schemaDVPortSetting()) + mergeSchema(s, schemaDistributedVirtualSwitchHostMemberConfigSpec()) + mergeSchema(s, schemaDvsHostInfrastructureTrafficResource()) + mergeSchema(s, schemaDVSPolicy()) + // XXX TBD uplinkPortgroup + mergeSchema(s, schemaDVSUplinkPortPolicy()) + mergeSchema(s, schemaDistributedVirtualSwitchKeyedOpaqueBlob()) +} From 1d587ea660b4a906c7b2df0f8045bce9970919de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Thu, 31 Aug 2017 03:19:17 +0200 Subject: [PATCH 05/21] Fix field names. Start using structure --- .../distributed_virtual_switch_structure.go | 72 ++++++++++++------- ...urce_vsphere_distributed_virtual_switch.go | 34 ++++----- 2 files changed, 62 insertions(+), 44 deletions(-) diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index 4b97e29ba..58b3f5571 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -17,62 +17,78 @@ var configSpecOperationAllowedValues = []string{ string(types.VirtualDeviceConfigSpecOperationEdit), } -func schemaDVSContactInfo() map[string]*schema.Schema { - return map[string]*schema.Schema{ - "contact": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "The contact information for the person.", - }, - "name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "The name of the person who is responsible for the switch.", +func schemaDVSContactInfo() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "contact": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The contact information for the person.", + }, + "name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The name of the person who is responsible for the switch.", + }, + }, }, } } func schemaDVPortSetting() map[string]*schema.Schema { // TBD + return nil +} + +func schemaDistributedVirtualSwitchHostMemberPnicBacking() map[string]*schema.Schema { + return nil } func schemaDistributedVirtualSwitchHostMemberConfigSpec() map[string]*schema.Schema { s := map[string]*schema.Schema{ - "maxProxySwitchPorts": &schema.Schema{ + "max_proxy_switch_ports": &schema.Schema{ Type: schema.TypeInt, Optional: true, Description: "Maximum number of ports allowed in the HostProxySwitch.", - Validation: validation.IntAtLeast(0), + //Validation: validation.IntAtLeast(0), }, "operation": &schema.Schema{ Type: schema.TypeInt, Optional: true, Description: "Host member operation type.", - Validation: validation.StringInSlice(configSpecOperationAllowedValues, false), + //Validation: validation.StringInSlice(configSpecOperationAllowedValues, false), }, } - mergeSchema(s, schemaDistributedVirtualSwitchHostMemberBacking()) + // DistributedVirtualSwitchHostMemberPnicBacking extends DistributedVirtualSwitchHostMemberBacking + // which is a base class + mergeSchema(s, schemaDistributedVirtualSwitchHostMemberPnicBacking()) // XXX TBD host mergeSchema(s, schemaDistributedVirtualSwitchKeyedOpaqueBlob()) + + return s } func schemaDvsHostInfrastructureTrafficResource() map[string]*schema.Schema { // TBD + return nil } func schemaDVSPolicy() map[string]*schema.Schema { return map[string]*schema.Schema{ - "autoPreInstallAllowed": &schema.Schema{ + "auto_pre_install_allowed": &schema.Schema{ Type: schema.TypeBool, Optional: true, Description: "Whether downloading a new proxy VirtualSwitch module to the host is allowed to be automatically executed by the switch.", }, - "autoUpgradeAllowed": &schema.Schema{ + "auto_upgrade_allowed": &schema.Schema{ Type: schema.TypeBool, Optional: true, Description: "Whether upgrading of the switch is allowed to be automatically executed by the switch.", }, - "partialUpgradeAllowed": &schema.Schema{ + "partial_upgrade_allowed": &schema.Schema{ Type: schema.TypeBool, Optional: true, Description: "Whether to allow upgrading a switch when some of the hosts failed to install the needed module.", @@ -82,20 +98,24 @@ func schemaDVSPolicy() map[string]*schema.Schema { func schemaDVSUplinkPortPolicy() map[string]*schema.Schema { // TBD + return nil } func schemaDistributedVirtualSwitchKeyedOpaqueBlob() map[string]*schema.Schema { // TBD should be a map + return nil } func schemaDVSConfiSpec() map[string]*schema.Schema { s := map[string]*schema.Schema{ - "configVersion": &schema.Schema{ + "config_version": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "The version string of the configuration that this spec is trying to change. This property is ignored during switch creation.", }, - "defaultProxySwitchMaxNumPorts": &schema.Schema{ + // nested to avoid having two "name" properties + "contact": schemaDVSContactInfo(), + "default_proxy_switch_max_num_ports": &schema.Schema{ Type: schema.TypeInt, Optional: true, Description: "The default host proxy switch maximum port number.", @@ -106,7 +126,7 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { Optional: true, Description: "Set the description string of the switch.", }, - "extensionKey": &schema.Schema{ + "extension_key": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "The key of the extension registered by a remote server that controls the switch.", @@ -116,26 +136,26 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { Optional: true, Description: "The name of the switch. Must be unique in the parent folder.", }, - "networkResourceControlVersion": &schema.Schema{ + "network_resource_control_version": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "Indicates the Network Resource Control APIs that are supported on the switch.", ValidateFunc: validation.StringInSlice(distributedVirtualSwitchNetworkResourceControlVersionAllowedValues, false), }, - "numStandalonePorts": &schema.Schema{ + "num_standalone_ports": &schema.Schema{ Type: schema.TypeInt, Optional: true, Description: "The number of standalone ports in the switch. Standalone ports are ports that do not belong to any portgroup.", ValidateFunc: validation.IntAtLeast(0), }, - "switchIpAddress": &schema.Schema{ + "switch_ip_address": &schema.Schema{ Type: schema.TypeString, Optional: true, Description: "IP address for the switch, specified using IPv4 dot notation. IPv6 address is not supported for this property.", ValidateFunc: validation.StringInSlice(distributedVirtualSwitchNetworkResourceControlVersionAllowedValues, false), }, } - mergeSchema(s, schemaDVSContactInfo()) + //mergeSchema(s, schemaDVSContactInfo()) mergeSchema(s, schemaDVPortSetting()) mergeSchema(s, schemaDistributedVirtualSwitchHostMemberConfigSpec()) mergeSchema(s, schemaDvsHostInfrastructureTrafficResource()) @@ -143,4 +163,6 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { // XXX TBD uplinkPortgroup mergeSchema(s, schemaDVSUplinkPortPolicy()) mergeSchema(s, schemaDistributedVirtualSwitchKeyedOpaqueBlob()) + + return s } diff --git a/vsphere/resource_vsphere_distributed_virtual_switch.go b/vsphere/resource_vsphere_distributed_virtual_switch.go index 240cdb882..8f2d5c888 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch.go @@ -17,29 +17,21 @@ import ( ) func resourceVSphereDistributedVirtualSwitch() *schema.Resource { + s := map[string]*schema.Schema{ + "datacenter": &schema.Schema{ + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + } + mergeSchema(s, schemaDVSConfiSpec()) + return &schema.Resource{ Create: resourceVSphereDistributedVirtualSwitchCreate, Read: resourceVSphereDistributedVirtualSwitchRead, + Update: resourceVSphereDistributedVirtualSwitchUpdate, Delete: resourceVSphereDistributedVirtualSwitchDelete, - - Schema: map[string]*schema.Schema{ - "datacenter": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "uplinks": { - Type: schema.TypeMap, - Optional: true, - ForceNew: true, - Elem: schema.TypeString, - }, - }, + Schema: s, } } @@ -152,6 +144,10 @@ func resourceVSphereDVSStateRefreshFunc(d *schema.ResourceData, meta interface{} } } +func resourceVSphereDistributedVirtualSwitchUpdate(d *schema.ResourceData, meta interface{}) error { + return nil +} + func resourceVSphereDistributedVirtualSwitchDelete(d *schema.ResourceData, meta interface{}) error { dvs, err := dvsExists(d, meta) if err != nil { From ec9f47bc7919efa04a275ebe531b7c631c2452f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Thu, 31 Aug 2017 22:06:22 +0200 Subject: [PATCH 06/21] Modify create method to use the new fields. Test pass now --- .../distributed_virtual_switch_structure.go | 38 ++++++++++++++----- ...urce_vsphere_distributed_virtual_switch.go | 10 +++-- ...vsphere_distributed_virtual_switch_test.go | 7 +++- 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index 58b3f5571..51127b8f4 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -43,30 +43,48 @@ func schemaDVPortSetting() map[string]*schema.Schema { return nil } -func schemaDistributedVirtualSwitchHostMemberPnicBacking() map[string]*schema.Schema { - return nil +func schemaDistributedVirtualSwitchHostMemberPnicBacking() *schema.Schema { + // TODO maybe a set will fit better to avoid the mistake of putting a nic twice? + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + } } -func schemaDistributedVirtualSwitchHostMemberConfigSpec() map[string]*schema.Schema { - s := map[string]*schema.Schema{ +func schemaDistributedVirtualSwitchHostMemberConfigSpec() *schema.Schema { + se := map[string]*schema.Schema{ "max_proxy_switch_ports": &schema.Schema{ Type: schema.TypeInt, Optional: true, Description: "Maximum number of ports allowed in the HostProxySwitch.", //Validation: validation.IntAtLeast(0), }, + // The host name should be enough to get a reference to it, which is what we need here + "host": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Identifies a host member of a DistributedVirtualSwitch for a CreateDVS_Task or DistributedVirtualSwitch.ReconfigureDvs_Task operation.", + }, "operation": &schema.Schema{ Type: schema.TypeInt, Optional: true, Description: "Host member operation type.", //Validation: validation.StringInSlice(configSpecOperationAllowedValues, false), }, + // DistributedVirtualSwitchHostMemberPnicBacking extends DistributedVirtualSwitchHostMemberBacking + // which is a base class + "backing": schemaDistributedVirtualSwitchHostMemberPnicBacking(), + } + mergeSchema(se, schemaDistributedVirtualSwitchKeyedOpaqueBlob()) + + s := &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: se, + }, } - // DistributedVirtualSwitchHostMemberPnicBacking extends DistributedVirtualSwitchHostMemberBacking - // which is a base class - mergeSchema(s, schemaDistributedVirtualSwitchHostMemberPnicBacking()) - // XXX TBD host - mergeSchema(s, schemaDistributedVirtualSwitchKeyedOpaqueBlob()) return s } @@ -131,6 +149,7 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { Optional: true, Description: "The key of the extension registered by a remote server that controls the switch.", }, + "host": schemaDistributedVirtualSwitchHostMemberConfigSpec(), "name": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -157,7 +176,6 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { } //mergeSchema(s, schemaDVSContactInfo()) mergeSchema(s, schemaDVPortSetting()) - mergeSchema(s, schemaDistributedVirtualSwitchHostMemberConfigSpec()) mergeSchema(s, schemaDvsHostInfrastructureTrafficResource()) mergeSchema(s, schemaDVSPolicy()) // XXX TBD uplinkPortgroup diff --git a/vsphere/resource_vsphere_distributed_virtual_switch.go b/vsphere/resource_vsphere_distributed_virtual_switch.go index 8f2d5c888..3fbf3234a 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch.go @@ -56,10 +56,12 @@ func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta // Configure the host and nic cards used as uplink for the DVS var host []types.DistributedVirtualSwitchHostMemberConfigSpec - if kv, ok := d.GetOk("uplinks"); ok { - for k, v := range kv.(map[string]interface{}) { + if v, ok := d.GetOk("host"); ok { + for _, vi := range v.([]interface{}) { + hi := vi.(map[string]interface{}) + bi := hi["backing"].([]interface{}) // Get the HostSystem reference - hs, err := finder.HostSystem(context.TODO(), k) + hs, err := finder.HostSystem(context.TODO(), hi["host"].(string)) if err != nil { return fmt.Errorf("%s", err) } @@ -67,7 +69,7 @@ func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta // Get the physical NIC backing backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ - PnicDevice: strings.TrimSpace(v.(string)), + PnicDevice: strings.TrimSpace(bi[0].(string)), }) h := types.DistributedVirtualSwitchHostMemberConfigSpec{ Host: hs.Common.Reference(), diff --git a/vsphere/resource_vsphere_distributed_virtual_switch_test.go b/vsphere/resource_vsphere_distributed_virtual_switch_test.go index d3e0dc7d7..712264501 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch_test.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch_test.go @@ -22,8 +22,11 @@ resource "vsphere_distributed_virtual_switch" "testDVS" { const testAccCheckVSphereDVSConfigUplinks = ` resource "vsphere_distributed_virtual_switch" "testDVS" { datacenter = "%s" - name = "testDVS" - uplinks = { "%s" = "%s" } + name = "testDVS" + host = [{ + host = "%s" + backing = ["%s"] + }] } ` From 63ee27ba1910a31285a669bddb144385ee4309f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Sat, 2 Sep 2017 00:44:24 +0200 Subject: [PATCH 07/21] Start working on expand/flatten functions plus Update action --- .../distributed_virtual_switch_structure.go | 44 +++++++++++++- ...urce_vsphere_distributed_virtual_switch.go | 58 +++++++------------ 2 files changed, 63 insertions(+), 39 deletions(-) diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index 51127b8f4..528a768ac 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -58,7 +58,7 @@ func schemaDistributedVirtualSwitchHostMemberConfigSpec() *schema.Schema { Type: schema.TypeInt, Optional: true, Description: "Maximum number of ports allowed in the HostProxySwitch.", - //Validation: validation.IntAtLeast(0), + Validation: validation.IntAtLeast(0), }, // The host name should be enough to get a reference to it, which is what we need here "host": &schema.Schema{ @@ -70,7 +70,7 @@ func schemaDistributedVirtualSwitchHostMemberConfigSpec() *schema.Schema { Type: schema.TypeInt, Optional: true, Description: "Host member operation type.", - //Validation: validation.StringInSlice(configSpecOperationAllowedValues, false), + Validation: validation.StringInSlice(configSpecOperationAllowedValues, false), }, // DistributedVirtualSwitchHostMemberPnicBacking extends DistributedVirtualSwitchHostMemberBacking // which is a base class @@ -89,6 +89,37 @@ func schemaDistributedVirtualSwitchHostMemberConfigSpec() *schema.Schema { return s } +func expandDistributedVirtualSwitchHostMemberConfigSpec(d *schema.ResourceData) []types.DistributedVirtualSwitchHostMemberConfigSpec { + // Configure the host and nic cards used as uplink for the DVS + var host []types.DistributedVirtualSwitchHostMemberConfigSpec + + if v, ok := d.GetOk("host"); ok { + for _, vi := range v.([]interface{}) { + hi := vi.(map[string]interface{}) + bi := hi["backing"].([]interface{}) + // Get the HostSystem reference + hs, err := finder.HostSystem(context.TODO(), hi["host"].(string)) + if err != nil { + return fmt.Errorf("%s", err) + } + + // Get the physical NIC backing + backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) + backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ + PnicDevice: strings.TrimSpace(bi[0].(string)), + }) + h := types.DistributedVirtualSwitchHostMemberConfigSpec{ + Host: hs.Common.Reference(), + Backing: backing, + Operation: "add", // Options: "add", "edit", "remove" + } + host = append(host, h) + } + } + + return host +} + func schemaDvsHostInfrastructureTrafficResource() map[string]*schema.Schema { // TBD return nil @@ -184,3 +215,12 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { return s } + +func expandDVSConfiSpec(d *schema.ResourceData) *types.DVSConfigSpec { + name := d.Get("name").(string) + + obj := &types.DVSConfigSpec{ + Name: name, + Host: expandDistributedVirtualSwitchHostMemberConfigSpec(d), + } +} diff --git a/vsphere/resource_vsphere_distributed_virtual_switch.go b/vsphere/resource_vsphere_distributed_virtual_switch.go index 3fbf3234a..d94f36549 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch.go @@ -38,7 +38,6 @@ func resourceVSphereDistributedVirtualSwitch() *schema.Resource { func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*govmomi.Client) - name := d.Get("name").(string) dc, err := getDatacenter(client, d.Get("datacenter").(string)) if err != nil { @@ -52,42 +51,11 @@ func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta if err != nil { return fmt.Errorf("%s", err) } - - // Configure the host and nic cards used as uplink for the DVS - var host []types.DistributedVirtualSwitchHostMemberConfigSpec - - if v, ok := d.GetOk("host"); ok { - for _, vi := range v.([]interface{}) { - hi := vi.(map[string]interface{}) - bi := hi["backing"].([]interface{}) - // Get the HostSystem reference - hs, err := finder.HostSystem(context.TODO(), hi["host"].(string)) - if err != nil { - return fmt.Errorf("%s", err) - } - - // Get the physical NIC backing - backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) - backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ - PnicDevice: strings.TrimSpace(bi[0].(string)), - }) - h := types.DistributedVirtualSwitchHostMemberConfigSpec{ - Host: hs.Common.Reference(), - Backing: backing, - Operation: "add", // Options: "add", "edit", "remove" - } - host = append(host, h) - } - } - - configSpec := types.DVSConfigSpec{ - Name: name, - Host: host, - } - dvsCreateSpec := types.DVSCreateSpec{ConfigSpec: &configSpec} - f := df.NetworkFolder + spec := expandDVSConfigSpec(d) + dvsCreateSpec := types.DVSCreateSpec{ConfigSpec: &spec} + task, err := f.CreateDVS(context.TODO(), dvsCreateSpec) if err != nil { return fmt.Errorf("%s", err) @@ -100,7 +68,7 @@ func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta d.SetId(name) - return nil + return resourceVSphereDistributedVirtualSwitchRead(d, meta) } func resourceVSphereDistributedVirtualSwitchRead(d *schema.ResourceData, meta interface{}) error { @@ -147,7 +115,23 @@ func resourceVSphereDVSStateRefreshFunc(d *schema.ResourceData, meta interface{} } func resourceVSphereDistributedVirtualSwitchUpdate(d *schema.ResourceData, meta interface{}) error { - return nil + dvs, err := dvsExists(d, meta) + if err != nil { + return fmt.Errorf("%s", err) + } + + // I might need to use something different since for example for the uplinks it's + // not enough to remove them from the config spec but keep them there and + // set the operation to "remove" + spec := expandDVSConfigSpec(d) + + n := object.NewDistributedVirtualSwitch(client.Client, dvs.Reference()) + req := &types.ReconfigureDvs_Task{ + This: n.Reference(), + Spec: spec, + } + + return resourceVSphereDistributedVirtualSwitchRead(d, meta) } func resourceVSphereDistributedVirtualSwitchDelete(d *schema.ResourceData, meta interface{}) error { From 158197d5dddabf69787caa0bb589b55460b726bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Sun, 3 Sep 2017 23:01:38 +0200 Subject: [PATCH 08/21] Start using dvs UUID as Id. Start off Update --- vsphere/distributed_virtual_switch_helper.go | 92 ++++++++++++++++++ .../distributed_virtual_switch_structure.go | 95 ++++++++++++------- ...urce_vsphere_distributed_virtual_switch.go | 61 +++++++----- ...vsphere_distributed_virtual_switch_test.go | 23 +++-- 4 files changed, 202 insertions(+), 69 deletions(-) create mode 100644 vsphere/distributed_virtual_switch_helper.go diff --git a/vsphere/distributed_virtual_switch_helper.go b/vsphere/distributed_virtual_switch_helper.go new file mode 100644 index 000000000..95d754baf --- /dev/null +++ b/vsphere/distributed_virtual_switch_helper.go @@ -0,0 +1,92 @@ +package vsphere + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/methods" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +// Get a list of ManagedObjectReference to HostSystem to use in creating the uplinks for the DVS +func getHostSystemManagedObjectReference(d *schema.ResourceData, client *govmomi.Client) ([]types.ManagedObjectReference, error) { + var mor []types.ManagedObjectReference + + if v, ok := d.GetOk("host"); ok { + for _, vi := range v.([]interface{}) { + hi := vi.(map[string]interface{}) + hsID := hi["host_system_id"].(string) + + h, err := hostSystemFromID(client, hsID) + if err != nil { + return nil, err + } + mor = append(mor, h.Common.Reference()) + } + } + return mor, nil +} + +// Check if a DVS exists and return a reference to it in case it does +func dvsExists(d *schema.ResourceData, meta interface{}) (object.NetworkReference, error) { + client := meta.(*govmomi.Client) + name := d.Get("name").(string) + + dc, err := getDatacenter(client, d.Get("datacenter").(string)) + if err != nil { + return nil, err + } + + finder := find.NewFinder(client.Client, true) + finder = finder.SetDatacenter(dc) + + dvs, err := finder.Network(context.TODO(), name) + return dvs, err +} + +func dvsFromName(client *govmomi.Client, datacenter, name string) (*mo.DistributedVirtualSwitch, error) { + dc, err := getDatacenter(client, datacenter) + if err != nil { + return nil, err + } + + finder := find.NewFinder(client.Client, true) + finder = finder.SetDatacenter(dc) + + dvs, err := finder.Network(context.TODO(), name) + + var mdvs mo.DistributedVirtualSwitch + pc := client.PropertyCollector() + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + if err := pc.RetrieveOne(ctx, dvs.Reference(), nil, &mdvs); err != nil { + return nil, fmt.Errorf("error fetching uuid property: %s", err) + } + return &mdvs, nil +} + +func dvsFromUuid(client *govmomi.Client, uuid string) (*mo.DistributedVirtualSwitch, error) { + dvsm := types.ManagedObjectReference{Type: "DistributedVirtualSwitchManager", Value: "DVSManager"} + req := &types.QueryDvsByUuid{ + This: dvsm, + Uuid: uuid, + } + dvs, err := methods.QueryDvsByUuid(context.TODO(), client, req) + if err != nil { + return nil, fmt.Errorf("error fetching dvs from uuid: %s", err) + } + + var mdvs mo.DistributedVirtualSwitch + pc := client.PropertyCollector() + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + if err := pc.RetrieveOne(ctx, dvs.Returnval.Reference(), nil, &mdvs); err != nil { + return nil, fmt.Errorf("error fetching distributed virtual switch: %s", err) + } + return &mdvs, nil +} diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index 528a768ac..8225efef8 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -1,8 +1,11 @@ package vsphere import ( + "strings" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" ) @@ -55,22 +58,22 @@ func schemaDistributedVirtualSwitchHostMemberPnicBacking() *schema.Schema { func schemaDistributedVirtualSwitchHostMemberConfigSpec() *schema.Schema { se := map[string]*schema.Schema{ "max_proxy_switch_ports": &schema.Schema{ - Type: schema.TypeInt, - Optional: true, - Description: "Maximum number of ports allowed in the HostProxySwitch.", - Validation: validation.IntAtLeast(0), + Type: schema.TypeInt, + Optional: true, + Description: "Maximum number of ports allowed in the HostProxySwitch.", + ValidateFunc: validation.IntAtLeast(0), }, // The host name should be enough to get a reference to it, which is what we need here - "host": &schema.Schema{ + "host_system_id": &schema.Schema{ Type: schema.TypeString, Optional: true, - Description: "Identifies a host member of a DistributedVirtualSwitch for a CreateDVS_Task or DistributedVirtualSwitch.ReconfigureDvs_Task operation.", + Description: "The managed object ID of the host to search for NICs on.", }, "operation": &schema.Schema{ - Type: schema.TypeInt, - Optional: true, - Description: "Host member operation type.", - Validation: validation.StringInSlice(configSpecOperationAllowedValues, false), + Type: schema.TypeInt, + Optional: true, + Description: "Host member operation type.", + ValidateFunc: validation.StringInSlice(configSpecOperationAllowedValues, false), }, // DistributedVirtualSwitchHostMemberPnicBacking extends DistributedVirtualSwitchHostMemberBacking // which is a base class @@ -89,35 +92,31 @@ func schemaDistributedVirtualSwitchHostMemberConfigSpec() *schema.Schema { return s } -func expandDistributedVirtualSwitchHostMemberConfigSpec(d *schema.ResourceData) []types.DistributedVirtualSwitchHostMemberConfigSpec { +func expandDistributedVirtualSwitchHostMemberConfigSpec(d *schema.ResourceData, refs []types.ManagedObjectReference) []types.DistributedVirtualSwitchHostMemberConfigSpec { // Configure the host and nic cards used as uplink for the DVS - var host []types.DistributedVirtualSwitchHostMemberConfigSpec - - if v, ok := d.GetOk("host"); ok { - for _, vi := range v.([]interface{}) { - hi := vi.(map[string]interface{}) - bi := hi["backing"].([]interface{}) - // Get the HostSystem reference - hs, err := finder.HostSystem(context.TODO(), hi["host"].(string)) - if err != nil { - return fmt.Errorf("%s", err) - } - - // Get the physical NIC backing - backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) - backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ - PnicDevice: strings.TrimSpace(bi[0].(string)), - }) - h := types.DistributedVirtualSwitchHostMemberConfigSpec{ - Host: hs.Common.Reference(), - Backing: backing, - Operation: "add", // Options: "add", "edit", "remove" + var hmc []types.DistributedVirtualSwitchHostMemberConfigSpec + + if hosts, ok := d.GetOk("host"); ok { + for i, host := range hosts.([]interface{}) { + hi := host.(map[string]interface{}) + + for _, nic := range hi["backing"].([]interface{}) { + // Get the physical NIC backing + backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) + backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ + PnicDevice: strings.TrimSpace(nic.(string)), + }) + h := types.DistributedVirtualSwitchHostMemberConfigSpec{ + Host: refs[i], + Backing: backing, + Operation: "add", // Options: "add", "edit", "remove" + } + hmc = append(hmc, h) } - host = append(host, h) } } - return host + return hmc } func schemaDvsHostInfrastructureTrafficResource() map[string]*schema.Schema { @@ -216,11 +215,35 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { return s } -func expandDVSConfiSpec(d *schema.ResourceData) *types.DVSConfigSpec { +func expandDVSConfigSpec(d *schema.ResourceData, refs []types.ManagedObjectReference) *types.DVSConfigSpec { name := d.Get("name").(string) obj := &types.DVSConfigSpec{ Name: name, - Host: expandDistributedVirtualSwitchHostMemberConfigSpec(d), + Host: expandDistributedVirtualSwitchHostMemberConfigSpec(d, refs), } + + if v, ok := d.GetOkExists("description"); ok { + obj.Description = v.(string) + } + + if v, ok := d.GetOkExists("num_standalone_ports"); ok { + obj.NumStandalonePorts = v.(int32) + } + + //if v, ok := d.GetOkExists("default_proxy_switch_max_num_ports"); ok { + //obj.NumStandalonePorts = v.(int32) + //} + + return obj +} + +func flattenDVSConfigSpec(d *schema.ResourceData, obj *mo.DistributedVirtualSwitch) error { + config := obj.Config.GetDVSConfigInfo() + d.Set("name", config.Name) + d.Set("description", config.Description) + d.Set("num_standalone_ports", config.NumStandalonePorts) + //d.Set("default_proxy_switch_max_num_ports", config.DefaultProxySwitchMaxNumPorts) + + return nil } diff --git a/vsphere/resource_vsphere_distributed_virtual_switch.go b/vsphere/resource_vsphere_distributed_virtual_switch.go index d94f36549..f31ee099e 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch.go @@ -3,7 +3,6 @@ package vsphere import ( "fmt" "log" - "strings" "time" "github.com/hashicorp/terraform/helper/resource" @@ -36,10 +35,11 @@ func resourceVSphereDistributedVirtualSwitch() *schema.Resource { } func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*govmomi.Client) + name := d.Get("name").(string) + datacenter := d.Get("datacenter").(string) - dc, err := getDatacenter(client, d.Get("datacenter").(string)) + dc, err := getDatacenter(client, datacenter) if err != nil { return fmt.Errorf("%s", err) } @@ -53,8 +53,13 @@ func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta } f := df.NetworkFolder - spec := expandDVSConfigSpec(d) - dvsCreateSpec := types.DVSCreateSpec{ConfigSpec: &spec} + hosts_mor, err := getHostSystemManagedObjectReference(d, client) + if err != nil { + return fmt.Errorf("%s", err) + } + + spec := expandDVSConfigSpec(d, hosts_mor) + dvsCreateSpec := types.DVSCreateSpec{ConfigSpec: spec} task, err := f.CreateDVS(context.TODO(), dvsCreateSpec) if err != nil { @@ -66,34 +71,30 @@ func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta return fmt.Errorf("%s", err) } - d.SetId(name) + dvs, err := dvsFromName(client, datacenter, name) + if err != nil { + return fmt.Errorf("%s", err) + } + + d.SetId(dvs.Uuid) return resourceVSphereDistributedVirtualSwitchRead(d, meta) } func resourceVSphereDistributedVirtualSwitchRead(d *schema.ResourceData, meta interface{}) error { - _, err := dvsExists(d, meta) + client := meta.(*govmomi.Client) + uuid := d.Id() + dvs, err := dvsFromUuid(client, uuid) if err != nil { d.SetId("") + return fmt.Errorf("error reading data: %s", err) } - return nil -} - -func dvsExists(d *schema.ResourceData, meta interface{}) (object.NetworkReference, error) { - client := meta.(*govmomi.Client) - name := d.Get("name").(string) - - dc, err := getDatacenter(client, d.Get("datacenter").(string)) - if err != nil { - return nil, err + if err := flattenDVSConfigSpec(d, dvs); err != nil { + return fmt.Errorf("error setting resource data: %s", err) } - finder := find.NewFinder(client.Client, true) - finder = finder.SetDatacenter(dc) - - dvs, err := finder.Network(context.TODO(), name) - return dvs, err + return nil } func resourceVSphereDVSStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { @@ -119,11 +120,14 @@ func resourceVSphereDistributedVirtualSwitchUpdate(d *schema.ResourceData, meta if err != nil { return fmt.Errorf("%s", err) } + client := meta.(*govmomi.Client) - // I might need to use something different since for example for the uplinks it's - // not enough to remove them from the config spec but keep them there and - // set the operation to "remove" - spec := expandDVSConfigSpec(d) + hosts_refs, err := getHostSystemManagedObjectReference(d, client) + if err != nil { + return fmt.Errorf("%s", err) + } + + spec := expandDVSConfigSpec(d, hosts_refs) n := object.NewDistributedVirtualSwitch(client.Client, dvs.Reference()) req := &types.ReconfigureDvs_Task{ @@ -131,6 +135,11 @@ func resourceVSphereDistributedVirtualSwitchUpdate(d *schema.ResourceData, meta Spec: spec, } + _, err = methods.ReconfigureDvs_Task(context.TODO(), client, req) + if err != nil { + return fmt.Errorf("%s", err) + } + return resourceVSphereDistributedVirtualSwitchRead(d, meta) } diff --git a/vsphere/resource_vsphere_distributed_virtual_switch_test.go b/vsphere/resource_vsphere_distributed_virtual_switch_test.go index 712264501..056509378 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch_test.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch_test.go @@ -19,16 +19,28 @@ resource "vsphere_distributed_virtual_switch" "testDVS" { } ` -const testAccCheckVSphereDVSConfigUplinks = ` +func testAccCheckVSphereDVSConfigUplinks() string { + return fmt.Sprintf(` +data "vsphere_datacenter" "datacenter" { + name = "%s" +} + +data "vsphere_host" "esxi_host" { + name = "%s" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +} + resource "vsphere_distributed_virtual_switch" "testDVS" { datacenter = "%s" name = "testDVS" host = [{ - host = "%s" + host_system_id = "${data.vsphere_host.esxi_host.id}" backing = ["%s"] }] } -` +`, os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST"), os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_HOST_NIC0")) + +} // Create a distributed virtual switch with no uplinks func TestAccVSphereDVS_createWithoutUplinks(t *testing.T) { @@ -51,9 +63,6 @@ func TestAccVSphereDVS_createWithoutUplinks(t *testing.T) { // Create a distributed virtual switch with uplinks func TestAccVSphereDVS_createWithUplinks(t *testing.T) { resourceName := "vsphere_distributed_virtual_switch.testDVS" - datacenter := os.Getenv("VSPHERE_DATACENTER") - host := os.Getenv("VSPHERE_HOST") - host_pnic := os.Getenv("VSPHERE_HOST_PNIC") resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -61,7 +70,7 @@ func TestAccVSphereDVS_createWithUplinks(t *testing.T) { CheckDestroy: testAccCheckVSphereDVSDestroy, Steps: []resource.TestStep{ { - Config: fmt.Sprintf(testAccCheckVSphereDVSConfigUplinks, datacenter, host, host_pnic), + Config: testAccCheckVSphereDVSConfigUplinks(), Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), }, }, From 24c7315baf5d032c3aa2c7208eb6bfa945898d6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Tue, 12 Sep 2017 00:38:09 +0200 Subject: [PATCH 09/21] WIP update capability in place, missing more testing --- vsphere/distributed_virtual_switch_helper.go | 20 +++- .../distributed_virtual_switch_structure.go | 106 +++++++++++++++--- ...urce_vsphere_distributed_virtual_switch.go | 49 ++++---- 3 files changed, 133 insertions(+), 42 deletions(-) diff --git a/vsphere/distributed_virtual_switch_helper.go b/vsphere/distributed_virtual_switch_helper.go index 95d754baf..5f7e265b5 100644 --- a/vsphere/distributed_virtual_switch_helper.go +++ b/vsphere/distributed_virtual_switch_helper.go @@ -14,8 +14,8 @@ import ( ) // Get a list of ManagedObjectReference to HostSystem to use in creating the uplinks for the DVS -func getHostSystemManagedObjectReference(d *schema.ResourceData, client *govmomi.Client) ([]types.ManagedObjectReference, error) { - var mor []types.ManagedObjectReference +func getHostSystemManagedObjectReference(d *schema.ResourceData, client *govmomi.Client) (map[string]types.ManagedObjectReference, error) { + mor := make(map[string]types.ManagedObjectReference) if v, ok := d.GetOk("host"); ok { for _, vi := range v.([]interface{}) { @@ -26,7 +26,7 @@ func getHostSystemManagedObjectReference(d *schema.ResourceData, client *govmomi if err != nil { return nil, err } - mor = append(mor, h.Common.Reference()) + mor[hsID] = h.Common.Reference() } } return mor, nil @@ -90,3 +90,17 @@ func dvsFromUuid(client *govmomi.Client, uuid string) (*mo.DistributedVirtualSwi } return &mdvs, nil } + +// check if host is in refs with the help of hosts +// not very efficient, but the number of entries is usually pretty small +func isHostPartOfDVS(hosts []interface{}, refs map[string]types.ManagedObjectReference, host *types.ManagedObjectReference) map[string]interface{} { + for _, h := range hosts { + hi := h.(map[string]interface{}) + if val, ok := refs[hi["host_system_id"].(string)]; ok { + if val == *host { + return hi + } + } + } + return nil +} diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index 8225efef8..435fd0900 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -20,6 +20,18 @@ var configSpecOperationAllowedValues = []string{ string(types.VirtualDeviceConfigSpecOperationEdit), } +var distributedVirtualSwitchHostInfrastructureTrafficClass = []string{ + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassManagement), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassFaultTolerance), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVmotion), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVirtualMachine), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassISCSI), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassNfs), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassHbr), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVsan), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVdp), +} + func schemaDVSContactInfo() *schema.Schema { return &schema.Schema{ Type: schema.TypeSet, @@ -92,22 +104,68 @@ func schemaDistributedVirtualSwitchHostMemberConfigSpec() *schema.Schema { return s } -func expandDistributedVirtualSwitchHostMemberConfigSpec(d *schema.ResourceData, refs []types.ManagedObjectReference) []types.DistributedVirtualSwitchHostMemberConfigSpec { +func expandDistributedVirtualSwitchHostMemberConfigSpec(d *schema.ResourceData, dvs *mo.DistributedVirtualSwitch, refs map[string]types.ManagedObjectReference) []types.DistributedVirtualSwitchHostMemberConfigSpec { // Configure the host and nic cards used as uplink for the DVS var hmc []types.DistributedVirtualSwitchHostMemberConfigSpec if hosts, ok := d.GetOk("host"); ok { - for i, host := range hosts.([]interface{}) { - hi := host.(map[string]interface{}) + hosts := hosts.([]interface{}) + // If the DVS exist we go through all the hosts and see which ones + // we have to delete or modify + if dvs != nil { + config := dvs.Config.GetDVSConfigInfo() + for _, h := range config.Host { + if host := isHostPartOfDVS(hosts, refs, h.Config.Host); host != nil { + // Edit + backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) + for _, nic := range host["backing"].([]interface{}) { + backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ + PnicDevice: strings.TrimSpace(nic.(string)), + }) + } + hcs := types.DistributedVirtualSwitchHostMemberConfigSpec{ + Host: *h.Config.Host, + Backing: backing, + Operation: "edit", // Options: "add", "edit", "remove" + } + hmc = append(hmc, hcs) + + // We take it out from the refs, on the last pass we consider whatever + // is left as to be added + delete(refs, host["host_system_id"].(string)) + } else { + // Remove + // XXX I'm not sure if it's necessary to mention the specific NICs when removing a host completely + backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) + cbp := h.Config.Backing.GetDistributedVirtualSwitchHostMemberBacking() + cb := interface{}(*cbp).(types.DistributedVirtualSwitchHostMemberPnicSpec) + for _, nic := range cb.PnicDevice { + backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ + PnicDevice: string(nic), + }) + } + hcs := types.DistributedVirtualSwitchHostMemberConfigSpec{ + Host: *h.Config.Host, + Backing: backing, + Operation: "remove", // Options: "add", "edit", "remove" + } + hmc = append(hmc, hcs) + } + } + } - for _, nic := range hi["backing"].([]interface{}) { - // Get the physical NIC backing + // Add whatever is left + for _, host := range hosts { + hi := host.(map[string]interface{}) + if val, ok := refs[hi["host_system_id"].(string)]; ok { backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) - backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ - PnicDevice: strings.TrimSpace(nic.(string)), - }) + for _, nic := range hi["backing"].([]interface{}) { + backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ + PnicDevice: strings.TrimSpace(nic.(string)), + }) + } h := types.DistributedVirtualSwitchHostMemberConfigSpec{ - Host: refs[i], + Host: val, Backing: backing, Operation: "add", // Options: "add", "edit", "remove" } @@ -119,9 +177,27 @@ func expandDistributedVirtualSwitchHostMemberConfigSpec(d *schema.ResourceData, return hmc } -func schemaDvsHostInfrastructureTrafficResource() map[string]*schema.Schema { - // TBD - return nil +func schemaDvsHostInfrastructureTrafficResource() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "description": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The description of the host infrastructure resource. This property is ignored for update operation.", + }, + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The key of the host infrastructure resource. Possible value can be of DistributedVirtualSwitchHostInfrastructureTrafficClass.", + ValidateFunc: validation.StringInSlice(distributedVirtualSwitchHostInfrastructureTrafficClass, false), + }, + //"allocationInfo": TBD + }, + }, + } } func schemaDVSPolicy() map[string]*schema.Schema { @@ -180,6 +256,7 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { Description: "The key of the extension registered by a remote server that controls the switch.", }, "host": schemaDistributedVirtualSwitchHostMemberConfigSpec(), + "infrastructure_traffic_resource_config": schemaDvsHostInfrastructureTrafficResource(), "name": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -206,7 +283,6 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { } //mergeSchema(s, schemaDVSContactInfo()) mergeSchema(s, schemaDVPortSetting()) - mergeSchema(s, schemaDvsHostInfrastructureTrafficResource()) mergeSchema(s, schemaDVSPolicy()) // XXX TBD uplinkPortgroup mergeSchema(s, schemaDVSUplinkPortPolicy()) @@ -215,12 +291,12 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { return s } -func expandDVSConfigSpec(d *schema.ResourceData, refs []types.ManagedObjectReference) *types.DVSConfigSpec { +func expandDVSConfigSpec(d *schema.ResourceData, dvs *mo.DistributedVirtualSwitch, refs map[string]types.ManagedObjectReference) *types.DVSConfigSpec { name := d.Get("name").(string) obj := &types.DVSConfigSpec{ Name: name, - Host: expandDistributedVirtualSwitchHostMemberConfigSpec(d, refs), + Host: expandDistributedVirtualSwitchHostMemberConfigSpec(d, dvs, refs), } if v, ok := d.GetOkExists("description"); ok { diff --git a/vsphere/resource_vsphere_distributed_virtual_switch.go b/vsphere/resource_vsphere_distributed_virtual_switch.go index f31ee099e..e1ee13917 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch.go @@ -58,7 +58,7 @@ func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta return fmt.Errorf("%s", err) } - spec := expandDVSConfigSpec(d, hosts_mor) + spec := expandDVSConfigSpec(d, nil, hosts_mor) dvsCreateSpec := types.DVSCreateSpec{ConfigSpec: spec} task, err := f.CreateDVS(context.TODO(), dvsCreateSpec) @@ -71,6 +71,8 @@ func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta return fmt.Errorf("%s", err) } + // Ideally from the CreateDVS opperation we should be able to access the UUID + // but I'm not sure how with the current operations exposed by the SDK dvs, err := dvsFromName(client, datacenter, name) if err != nil { return fmt.Errorf("%s", err) @@ -83,8 +85,7 @@ func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta func resourceVSphereDistributedVirtualSwitchRead(d *schema.ResourceData, meta interface{}) error { client := meta.(*govmomi.Client) - uuid := d.Id() - dvs, err := dvsFromUuid(client, uuid) + dvs, err := dvsFromUuid(client, d.Id()) if err != nil { d.SetId("") return fmt.Errorf("error reading data: %s", err) @@ -97,37 +98,19 @@ func resourceVSphereDistributedVirtualSwitchRead(d *schema.ResourceData, meta in return nil } -func resourceVSphereDVSStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Print("[TRACE] Refreshing distributed virtual switch state") - dvs, err := dvsExists(d, meta) - if err != nil { - switch err.(type) { - case *find.NotFoundError: - log.Printf("[TRACE] Refreshing state. Distributed virtual switch not found: %s", err) - return nil, "InProgress", nil - default: - return nil, "Failed", err - } - } - log.Print("[TRACE] Refreshing state. Distributed virtual switch found") - return dvs, "Created", nil - } -} - func resourceVSphereDistributedVirtualSwitchUpdate(d *schema.ResourceData, meta interface{}) error { - dvs, err := dvsExists(d, meta) + client := meta.(*govmomi.Client) + dvs, err := dvsFromUuid(client, d.Id()) if err != nil { return fmt.Errorf("%s", err) } - client := meta.(*govmomi.Client) hosts_refs, err := getHostSystemManagedObjectReference(d, client) if err != nil { return fmt.Errorf("%s", err) } - spec := expandDVSConfigSpec(d, hosts_refs) + spec := expandDVSConfigSpec(d, dvs, hosts_refs) n := object.NewDistributedVirtualSwitch(client.Client, dvs.Reference()) req := &types.ReconfigureDvs_Task{ @@ -143,6 +126,24 @@ func resourceVSphereDistributedVirtualSwitchUpdate(d *schema.ResourceData, meta return resourceVSphereDistributedVirtualSwitchRead(d, meta) } +func resourceVSphereDVSStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Print("[TRACE] Refreshing distributed virtual switch state") + dvs, err := dvsExists(d, meta) + if err != nil { + switch err.(type) { + case *find.NotFoundError: + log.Printf("[TRACE] Refreshing state. Distributed virtual switch not found: %s", err) + return nil, "InProgress", nil + default: + return nil, "Failed", err + } + } + log.Print("[TRACE] Refreshing state. Distributed virtual switch found") + return dvs, "Created", nil + } +} + func resourceVSphereDistributedVirtualSwitchDelete(d *schema.ResourceData, meta interface{}) error { dvs, err := dvsExists(d, meta) if err != nil { From a0ff6974b2d5a4f7b21e152ec98a2c803ad074d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Tue, 12 Sep 2017 03:23:30 +0200 Subject: [PATCH 10/21] Continue adding fields --- vsphere/distributed_virtual_switch_helper.go | 6 +- .../distributed_virtual_switch_structure.go | 136 ++++++++++++------ ...urce_vsphere_distributed_virtual_switch.go | 8 +- ...vsphere_distributed_virtual_switch_test.go | 70 +++++++-- 4 files changed, 162 insertions(+), 58 deletions(-) diff --git a/vsphere/distributed_virtual_switch_helper.go b/vsphere/distributed_virtual_switch_helper.go index 5f7e265b5..a7fcb6b6a 100644 --- a/vsphere/distributed_virtual_switch_helper.go +++ b/vsphere/distributed_virtual_switch_helper.go @@ -37,7 +37,7 @@ func dvsExists(d *schema.ResourceData, meta interface{}) (object.NetworkReferenc client := meta.(*govmomi.Client) name := d.Get("name").(string) - dc, err := getDatacenter(client, d.Get("datacenter").(string)) + dc, err := datacenterFromID(client, d.Get("datacenter_id").(string)) if err != nil { return nil, err } @@ -49,8 +49,8 @@ func dvsExists(d *schema.ResourceData, meta interface{}) (object.NetworkReferenc return dvs, err } -func dvsFromName(client *govmomi.Client, datacenter, name string) (*mo.DistributedVirtualSwitch, error) { - dc, err := getDatacenter(client, datacenter) +func dvsFromName(client *govmomi.Client, dId, name string) (*mo.DistributedVirtualSwitch, error) { + dc, err := datacenterFromID(client, dId) if err != nil { return nil, err } diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index 435fd0900..7263cf57f 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -32,25 +32,37 @@ var distributedVirtualSwitchHostInfrastructureTrafficClass = []string{ string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVdp), } -func schemaDVSContactInfo() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "contact": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "The contact information for the person.", - }, - "name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "The name of the person who is responsible for the switch.", - }, - }, +func schemaDVSContactInfo() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "contact": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The contact information for the person.", }, + "contact_name": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The name of the person who is responsible for the switch.", + }, + } +} + +func expandDVSContactInfo(d *schema.ResourceData) *types.DVSContactInfo { + dci := &types.DVSContactInfo{} + if v, ok := d.GetOkExists("contact"); ok { + dci.Contact = v.(string) + } + + if v, ok := d.GetOkExists("contact_name"); ok { + dci.Name = v.(string) } + return dci +} + +func flattenDVSContactInfo(d *schema.ResourceData, obj *mo.DistributedVirtualSwitch) { + config := obj.Config.GetDVSConfigInfo() + d.Set("contact", config.Contact.Contact) + d.Set("contact_name", config.Contact.Name) } func schemaDVPortSetting() map[string]*schema.Schema { @@ -89,9 +101,9 @@ func schemaDistributedVirtualSwitchHostMemberConfigSpec() *schema.Schema { }, // DistributedVirtualSwitchHostMemberPnicBacking extends DistributedVirtualSwitchHostMemberBacking // which is a base class - "backing": schemaDistributedVirtualSwitchHostMemberPnicBacking(), + "backing": schemaDistributedVirtualSwitchHostMemberPnicBacking(), + "vendor_specific_config": schemaDistributedVirtualSwitchKeyedOpaqueBlob(), } - mergeSchema(se, schemaDistributedVirtualSwitchKeyedOpaqueBlob()) s := &schema.Schema{ Type: schema.TypeList, @@ -225,23 +237,38 @@ func schemaDVSUplinkPortPolicy() map[string]*schema.Schema { return nil } -func schemaDistributedVirtualSwitchKeyedOpaqueBlob() map[string]*schema.Schema { - // TBD should be a map - return nil +func schemaDistributedVirtualSwitchKeyedOpaqueBlob() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "key": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "A key that identifies the opaque binary blob.", + }, + "opaque_data": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The opaque data. It is recommended that base64 encoding be used for binary data.", + }, + }, + }, + } } func schemaDVSConfiSpec() map[string]*schema.Schema { s := map[string]*schema.Schema{ "config_version": &schema.Schema{ Type: schema.TypeString, - Optional: true, + Computed: true, Description: "The version string of the configuration that this spec is trying to change. This property is ignored during switch creation.", }, - // nested to avoid having two "name" properties - "contact": schemaDVSContactInfo(), "default_proxy_switch_max_num_ports": &schema.Schema{ Type: schema.TypeInt, Optional: true, + Default: 512, Description: "The default host proxy switch maximum port number.", ValidateFunc: validation.IntAtLeast(0), }, @@ -256,7 +283,7 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { Description: "The key of the extension registered by a remote server that controls the switch.", }, "host": schemaDistributedVirtualSwitchHostMemberConfigSpec(), - "infrastructure_traffic_resource_config": schemaDvsHostInfrastructureTrafficResource(), + //"infrastructure_traffic_resource_config": schemaDvsHostInfrastructureTrafficResource(), "name": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -265,61 +292,88 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { "network_resource_control_version": &schema.Schema{ Type: schema.TypeString, Optional: true, + Default: "version2", Description: "Indicates the Network Resource Control APIs that are supported on the switch.", ValidateFunc: validation.StringInSlice(distributedVirtualSwitchNetworkResourceControlVersionAllowedValues, false), }, "num_standalone_ports": &schema.Schema{ Type: schema.TypeInt, Optional: true, + Default: 512, Description: "The number of standalone ports in the switch. Standalone ports are ports that do not belong to any portgroup.", ValidateFunc: validation.IntAtLeast(0), }, "switch_ip_address": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "IP address for the switch, specified using IPv4 dot notation. IPv6 address is not supported for this property.", - ValidateFunc: validation.StringInSlice(distributedVirtualSwitchNetworkResourceControlVersionAllowedValues, false), + Type: schema.TypeString, + Optional: true, + Description: "IP address for the switch, specified using IPv4 dot notation. IPv6 address is not supported for this property.", }, + "vendor_specific_config": schemaDistributedVirtualSwitchKeyedOpaqueBlob(), } - //mergeSchema(s, schemaDVSContactInfo()) + mergeSchema(s, schemaDVSContactInfo()) mergeSchema(s, schemaDVPortSetting()) mergeSchema(s, schemaDVSPolicy()) // XXX TBD uplinkPortgroup mergeSchema(s, schemaDVSUplinkPortPolicy()) - mergeSchema(s, schemaDistributedVirtualSwitchKeyedOpaqueBlob()) return s } func expandDVSConfigSpec(d *schema.ResourceData, dvs *mo.DistributedVirtualSwitch, refs map[string]types.ManagedObjectReference) *types.DVSConfigSpec { - name := d.Get("name").(string) + obj := &types.DVSConfigSpec{} + + obj.Name = d.Get("name").(string) + + if v, ok := d.GetOkExists("network_resource_control_version"); ok { + obj.NetworkResourceControlVersion = v.(string) + } + + if v, ok := d.GetOkExists("config_version"); ok { + obj.ConfigVersion = v.(string) + } + + obj.Contact = expandDVSContactInfo(d) - obj := &types.DVSConfigSpec{ - Name: name, - Host: expandDistributedVirtualSwitchHostMemberConfigSpec(d, dvs, refs), + if v, ok := d.GetOkExists("default_proxy_switch_max_num_ports"); ok { + obj.NumStandalonePorts = int32(v.(int)) } if v, ok := d.GetOkExists("description"); ok { obj.Description = v.(string) } + if v, ok := d.GetOkExists("extension_key"); ok { + obj.ExtensionKey = v.(string) + } + + // Always expand since even when removing we will need to mention hosts and nics + obj.Host = expandDistributedVirtualSwitchHostMemberConfigSpec(d, dvs, refs) + if v, ok := d.GetOkExists("num_standalone_ports"); ok { - obj.NumStandalonePorts = v.(int32) + obj.NumStandalonePorts = int32(v.(int)) } - //if v, ok := d.GetOkExists("default_proxy_switch_max_num_ports"); ok { - //obj.NumStandalonePorts = v.(int32) - //} + if v, ok := d.GetOkExists("switch_ip_addess"); ok { + obj.NumStandalonePorts = int32(v.(int)) + } return obj } func flattenDVSConfigSpec(d *schema.ResourceData, obj *mo.DistributedVirtualSwitch) error { config := obj.Config.GetDVSConfigInfo() + d.Set("config_version", config.ConfigVersion) + d.Set("description", config.Description) + d.Set("extension_key", config.ExtensionKey) d.Set("name", config.Name) + d.Set("network_resource_control_version", config.NetworkResourceControlVersion) d.Set("description", config.Description) + d.Set("contact", config.Contact.Contact) + d.Set("contact_name", config.Contact.Name) d.Set("num_standalone_ports", config.NumStandalonePorts) - //d.Set("default_proxy_switch_max_num_ports", config.DefaultProxySwitchMaxNumPorts) + d.Set("default_proxy_switch_max_num_ports", config.DefaultProxySwitchMaxNumPorts) + d.Set("switch_ip_address", config.SwitchIpAddress) + flattenDVSContactInfo(d, obj) return nil } diff --git a/vsphere/resource_vsphere_distributed_virtual_switch.go b/vsphere/resource_vsphere_distributed_virtual_switch.go index e1ee13917..771595c6e 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch.go @@ -17,7 +17,7 @@ import ( func resourceVSphereDistributedVirtualSwitch() *schema.Resource { s := map[string]*schema.Schema{ - "datacenter": &schema.Schema{ + "datacenter_id": &schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, @@ -37,9 +37,9 @@ func resourceVSphereDistributedVirtualSwitch() *schema.Resource { func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*govmomi.Client) name := d.Get("name").(string) - datacenter := d.Get("datacenter").(string) + dId := d.Get("datacenter_id").(string) - dc, err := getDatacenter(client, datacenter) + dc, err := datacenterFromID(client, dId) if err != nil { return fmt.Errorf("%s", err) } @@ -73,7 +73,7 @@ func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta // Ideally from the CreateDVS opperation we should be able to access the UUID // but I'm not sure how with the current operations exposed by the SDK - dvs, err := dvsFromName(client, datacenter, name) + dvs, err := dvsFromName(client, dId, name) if err != nil { return fmt.Errorf("%s", err) } diff --git a/vsphere/resource_vsphere_distributed_virtual_switch_test.go b/vsphere/resource_vsphere_distributed_virtual_switch_test.go index 056509378..2fa75f4c4 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch_test.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch_test.go @@ -12,15 +12,25 @@ import ( "golang.org/x/net/context" ) -const testAccCheckVSphereDVSConfigNoUplinks = ` +func testAccCheckVSphereDVSConfigNoUplinks() string { + return fmt.Sprintf(` +data "vsphere_datacenter" "datacenter" { + name = "%s" +} + resource "vsphere_distributed_virtual_switch" "testDVS" { - datacenter = "%s" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" name = "testDVS" + contact = "dvsmanager@yourcompany.com" + contact_name = "John Doe" + description = "Test DVS" +} +`, os.Getenv("VSPHERE_DATACENTER")) } -` -func testAccCheckVSphereDVSConfigUplinks() string { - return fmt.Sprintf(` +func testAccCheckVSphereDVSConfigUplinks(uplinks bool) string { + if uplinks { + return fmt.Sprintf(` data "vsphere_datacenter" "datacenter" { name = "%s" } @@ -31,21 +41,36 @@ data "vsphere_host" "esxi_host" { } resource "vsphere_distributed_virtual_switch" "testDVS" { - datacenter = "%s" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" name = "testDVS" host = [{ host_system_id = "${data.vsphere_host.esxi_host.id}" backing = ["%s"] }] } -`, os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST"), os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_HOST_NIC0")) +`, os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST"), os.Getenv("VSPHERE_HOST_NIC0")) + } else { + return fmt.Sprintf(` +data "vsphere_datacenter" "datacenter" { + name = "%s" +} +data "vsphere_host" "esxi_host" { + name = "%s" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +} + +resource "vsphere_distributed_virtual_switch" "testDVS" { + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" + name = "testDVS" +} +`, os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST")) + } } // Create a distributed virtual switch with no uplinks func TestAccVSphereDVS_createWithoutUplinks(t *testing.T) { resourceName := "vsphere_distributed_virtual_switch.testDVS" - datacenter := os.Getenv("VSPHERE_DATACENTER") resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -53,7 +78,7 @@ func TestAccVSphereDVS_createWithoutUplinks(t *testing.T) { CheckDestroy: testAccCheckVSphereDVSDestroy, Steps: []resource.TestStep{ { - Config: fmt.Sprintf(testAccCheckVSphereDVSConfigNoUplinks, datacenter), + Config: testAccCheckVSphereDVSConfigNoUplinks(), Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), }, }, @@ -70,7 +95,32 @@ func TestAccVSphereDVS_createWithUplinks(t *testing.T) { CheckDestroy: testAccCheckVSphereDVSDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckVSphereDVSConfigUplinks(), + Config: testAccCheckVSphereDVSConfigUplinks(true), + Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), + }, + }, + }) +} + +// Create a distributed virtual switch with an uplink, delete it and add it again +func TestAccVSphereDVS_createAndUpdateWithUplinks(t *testing.T) { + resourceName := "vsphere_distributed_virtual_switch.testDVS" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVSphereDVSDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckVSphereDVSConfigUplinks(true), + Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), + }, // XXX checks here need to be more thorough + { + Config: testAccCheckVSphereDVSConfigUplinks(false), + Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), + }, + { + Config: testAccCheckVSphereDVSConfigUplinks(true), Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), }, }, From 0b3f3794fc5b263ee8f21fef7706a8fba694d6f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Tue, 12 Sep 2017 21:36:57 +0200 Subject: [PATCH 11/21] Improve tests --- .../distributed_virtual_switch_structure.go | 31 +++-- ...vsphere_distributed_virtual_switch_test.go | 113 ++++++++---------- 2 files changed, 70 insertions(+), 74 deletions(-) diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index 7263cf57f..c817c0469 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -20,7 +20,7 @@ var configSpecOperationAllowedValues = []string{ string(types.VirtualDeviceConfigSpecOperationEdit), } -var distributedVirtualSwitchHostInfrastructureTrafficClass = []string{ +/*var distributedVirtualSwitchHostInfrastructureTrafficClass = []string{ string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassManagement), string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassFaultTolerance), string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVmotion), @@ -30,7 +30,7 @@ var distributedVirtualSwitchHostInfrastructureTrafficClass = []string{ string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassHbr), string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVsan), string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVdp), -} +}*/ func schemaDVSContactInfo() map[string]*schema.Schema { return map[string]*schema.Schema{ @@ -65,10 +65,10 @@ func flattenDVSContactInfo(d *schema.ResourceData, obj *mo.DistributedVirtualSwi d.Set("contact_name", config.Contact.Name) } -func schemaDVPortSetting() map[string]*schema.Schema { +/*func schemaDVPortSetting() map[string]*schema.Schema { // TBD return nil -} +}*/ func schemaDistributedVirtualSwitchHostMemberPnicBacking() *schema.Schema { // TODO maybe a set will fit better to avoid the mistake of putting a nic twice? @@ -189,7 +189,11 @@ func expandDistributedVirtualSwitchHostMemberConfigSpec(d *schema.ResourceData, return hmc } -func schemaDvsHostInfrastructureTrafficResource() *schema.Schema { +func flattenDistributedVirtualSwitchHostMemberConfigSpec(d *schema.ResourceData, obj *mo.DistributedVirtualSwitch) { + +} + +/*func schemaDvsHostInfrastructureTrafficResource() *schema.Schema { return &schema.Schema{ Type: schema.TypeList, Optional: true, @@ -210,9 +214,9 @@ func schemaDvsHostInfrastructureTrafficResource() *schema.Schema { }, }, } -} +}*/ -func schemaDVSPolicy() map[string]*schema.Schema { +/*func schemaDVSPolicy() map[string]*schema.Schema { return map[string]*schema.Schema{ "auto_pre_install_allowed": &schema.Schema{ Type: schema.TypeBool, @@ -230,12 +234,12 @@ func schemaDVSPolicy() map[string]*schema.Schema { Description: "Whether to allow upgrading a switch when some of the hosts failed to install the needed module.", }, } -} +}*/ -func schemaDVSUplinkPortPolicy() map[string]*schema.Schema { +/*func schemaDVSUplinkPortPolicy() map[string]*schema.Schema { // TBD return nil -} +}*/ func schemaDistributedVirtualSwitchKeyedOpaqueBlob() *schema.Schema { return &schema.Schema{ @@ -311,10 +315,10 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { "vendor_specific_config": schemaDistributedVirtualSwitchKeyedOpaqueBlob(), } mergeSchema(s, schemaDVSContactInfo()) - mergeSchema(s, schemaDVPortSetting()) - mergeSchema(s, schemaDVSPolicy()) + //mergeSchema(s, schemaDVPortSetting()) + //mergeSchema(s, schemaDVSPolicy()) // XXX TBD uplinkPortgroup - mergeSchema(s, schemaDVSUplinkPortPolicy()) + //mergeSchema(s, schemaDVSUplinkPortPolicy()) return s } @@ -374,6 +378,7 @@ func flattenDVSConfigSpec(d *schema.ResourceData, obj *mo.DistributedVirtualSwit d.Set("default_proxy_switch_max_num_ports", config.DefaultProxySwitchMaxNumPorts) d.Set("switch_ip_address", config.SwitchIpAddress) flattenDVSContactInfo(d, obj) + flattenDistributedVirtualSwitchHostMemberConfigSpec(d, obj) return nil } diff --git a/vsphere/resource_vsphere_distributed_virtual_switch_test.go b/vsphere/resource_vsphere_distributed_virtual_switch_test.go index 2fa75f4c4..e6d90ea72 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch_test.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch_test.go @@ -8,8 +8,8 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "github.com/vmware/govmomi" - "github.com/vmware/govmomi/find" - "golang.org/x/net/context" + // "github.com/vmware/govmomi/find" + // "golang.org/x/net/context" ) func testAccCheckVSphereDVSConfigNoUplinks() string { @@ -28,9 +28,9 @@ resource "vsphere_distributed_virtual_switch" "testDVS" { `, os.Getenv("VSPHERE_DATACENTER")) } -func testAccCheckVSphereDVSConfigUplinks(uplinks bool) string { +func testAccCheckVSphereDVSConfigUplinks(uplinks bool, multiple bool) string { if uplinks { - return fmt.Sprintf(` + conf := ` data "vsphere_datacenter" "datacenter" { name = "%s" } @@ -48,7 +48,13 @@ resource "vsphere_distributed_virtual_switch" "testDVS" { backing = ["%s"] }] } -`, os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST"), os.Getenv("VSPHERE_HOST_NIC0")) +` + if multiple { + backing := fmt.Sprintf("%s\",\"%s", os.Getenv("VSPHERE_HOST_NIC0"), os.Getenv("VSPHERE_HOST_NIC1")) + return fmt.Sprintf(conf, os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST"), backing) + } else { + return fmt.Sprintf(conf, os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST"), os.Getenv("VSPHERE_HOST_NIC0")) + } } else { return fmt.Sprintf(` data "vsphere_datacenter" "datacenter" { @@ -79,7 +85,7 @@ func TestAccVSphereDVS_createWithoutUplinks(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccCheckVSphereDVSConfigNoUplinks(), - Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), + Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 0)), }, }, }) @@ -95,8 +101,8 @@ func TestAccVSphereDVS_createWithUplinks(t *testing.T) { CheckDestroy: testAccCheckVSphereDVSDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckVSphereDVSConfigUplinks(true), - Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), + Config: testAccCheckVSphereDVSConfigUplinks(true, false), + Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 1)), }, }, }) @@ -112,16 +118,24 @@ func TestAccVSphereDVS_createAndUpdateWithUplinks(t *testing.T) { CheckDestroy: testAccCheckVSphereDVSDestroy, Steps: []resource.TestStep{ { - Config: testAccCheckVSphereDVSConfigUplinks(true), - Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), - }, // XXX checks here need to be more thorough + Config: testAccCheckVSphereDVSConfigUplinks(true, false), + Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 1)), + }, { - Config: testAccCheckVSphereDVSConfigUplinks(false), - Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), + Config: testAccCheckVSphereDVSConfigUplinks(false, false), + Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 0)), }, { - Config: testAccCheckVSphereDVSConfigUplinks(true), - Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, true)), + Config: testAccCheckVSphereDVSConfigUplinks(true, false), + Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 1)), + }, + { + Config: testAccCheckVSphereDVSConfigUplinks(false, false), + Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 0)), + }, + { + Config: testAccCheckVSphereDVSConfigUplinks(true, true), + Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 2)), }, }, }) @@ -135,64 +149,41 @@ func testAccCheckVSphereDVSDestroy(s *terraform.State) error { continue } - datacenter := rs.Primary.Attributes["datacenter"] - dc, err := getDatacenter(client, datacenter) - if err != nil { - return err - } - - finder := find.NewFinder(client.Client, true) - finder = finder.SetDatacenter(dc) - - name := rs.Primary.Attributes["name"] - _, err = finder.NetworkList(context.TODO(), name) + id := rs.Primary.ID + _, err := dvsFromUuid(client, id) if err != nil { - switch err.(type) { - case *find.NotFoundError: - fmt.Printf("Expected error received: %s\n", err) - return nil - default: - return err - } + fmt.Printf("Expected error received: %s\n", err) + return nil } else { - return fmt.Errorf("distributed virtual switch '%s' still exists", name) + return fmt.Errorf("distributed virtual switch '%s' still exists", id) } } - return nil } -func testAccCheckVSphereDVSExists(n string, exists bool) resource.TestCheckFunc { +func testAccCheckVSphereDVSExists(name string, n int) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("resource not found: %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("no ID is set") - } - client := testAccProvider.Meta().(*govmomi.Client) - datacenter := rs.Primary.Attributes["datacenter"] - dc, err := getDatacenter(client, datacenter) - if err != nil { - return err - } - - finder := find.NewFinder(client.Client, true) - finder = finder.SetDatacenter(dc) + for _, rs := range s.RootModule().Resources { + if rs.Type != "vsphere_distributed_virtual_switch" { + continue + } + if rs.Primary.Attributes["name"] != name { + continue + } - name := rs.Primary.Attributes["name"] - _, err = finder.NetworkList(context.TODO(), name) - if err != nil { - switch err.(type) { - case *find.NotFoundError: - fmt.Printf("Expected error received: %s\n", err) + id := rs.Primary.ID + dvs, err := dvsFromUuid(client, id) + if err != nil { + return fmt.Errorf("distributed virtual switch '%s' doesn't exists", id) + } else { + config := dvs.Config.GetDVSConfigInfo() + if len(config.Host) != n { + return fmt.Errorf("expected '%s' uplinks, found '%s'", n, len(config.Host)) + } + fmt.Println("DVS exists and has the correct number of uplinks") return nil - default: - return err } } return nil From 54c2d006594a93361764aa1264ee2c3e5a620a90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Tue, 12 Sep 2017 21:54:10 +0200 Subject: [PATCH 12/21] Fix printf type --- vsphere/resource_vsphere_distributed_virtual_switch_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsphere/resource_vsphere_distributed_virtual_switch_test.go b/vsphere/resource_vsphere_distributed_virtual_switch_test.go index e6d90ea72..d338172ee 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch_test.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch_test.go @@ -180,7 +180,7 @@ func testAccCheckVSphereDVSExists(name string, n int) resource.TestCheckFunc { } else { config := dvs.Config.GetDVSConfigInfo() if len(config.Host) != n { - return fmt.Errorf("expected '%s' uplinks, found '%s'", n, len(config.Host)) + return fmt.Errorf("expected '%d' uplinks, found '%d'", n, len(config.Host)) } fmt.Println("DVS exists and has the correct number of uplinks") return nil From c020423d911c0c106dc34976088f9481403574f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Thu, 14 Sep 2017 19:32:50 +0200 Subject: [PATCH 13/21] Add check for presence of required ENV variables to run tests --- ...vsphere_distributed_virtual_switch_test.go | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/vsphere/resource_vsphere_distributed_virtual_switch_test.go b/vsphere/resource_vsphere_distributed_virtual_switch_test.go index d338172ee..4e07c899a 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch_test.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch_test.go @@ -74,12 +74,30 @@ resource "vsphere_distributed_virtual_switch" "testDVS" { } } +func testAccResourceVSphereDistributedVirtualSwitchPreCheck(t *testing.T) { + if os.Getenv("VSPHERE_DATACENTER") == "" { + t.Skip("set VSPHERE_DATACENTER to run vsphere_distributed_virtual_switch acceptance tests") + } + if os.Getenv("VSPHERE_HOST_NIC0") == "" { + t.Skip("set VSPHERE_HOST_NIC0 to run vsphere_distributed_virtual_switch acceptance tests") + } + if os.Getenv("VSPHERE_HOST_NIC1") == "" { + t.Skip("set VSPHERE_HOST_NIC0 to run vsphere_distributed_virtual_switch acceptance tests") + } + if os.Getenv("VSPHERE_ESXI_HOST") == "" { + t.Skip("set VSPHERE_HOST_NIC0 to run vsphere_distributed_virtual_switch acceptance tests") + } +} + // Create a distributed virtual switch with no uplinks func TestAccVSphereDVS_createWithoutUplinks(t *testing.T) { resourceName := "vsphere_distributed_virtual_switch.testDVS" resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(t) + }, Providers: testAccProviders, CheckDestroy: testAccCheckVSphereDVSDestroy, Steps: []resource.TestStep{ @@ -91,12 +109,15 @@ func TestAccVSphereDVS_createWithoutUplinks(t *testing.T) { }) } -// Create a distributed virtual switch with uplinks +// Create a distributed virtual switch with one uplink func TestAccVSphereDVS_createWithUplinks(t *testing.T) { resourceName := "vsphere_distributed_virtual_switch.testDVS" resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(t) + }, Providers: testAccProviders, CheckDestroy: testAccCheckVSphereDVSDestroy, Steps: []resource.TestStep{ @@ -113,7 +134,10 @@ func TestAccVSphereDVS_createAndUpdateWithUplinks(t *testing.T) { resourceName := "vsphere_distributed_virtual_switch.testDVS" resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(t) + }, Providers: testAccProviders, CheckDestroy: testAccCheckVSphereDVSDestroy, Steps: []resource.TestStep{ From 186274b37ff741534b5f081e485b5870700212cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Tue, 19 Sep 2017 02:01:03 +0200 Subject: [PATCH 14/21] Working delete and flatten --- vsphere/distributed_virtual_switch_helper.go | 3 + .../distributed_virtual_switch_structure.go | 184 ++++++++++++------ ...urce_vsphere_distributed_virtual_switch.go | 6 +- 3 files changed, 130 insertions(+), 63 deletions(-) diff --git a/vsphere/distributed_virtual_switch_helper.go b/vsphere/distributed_virtual_switch_helper.go index a7fcb6b6a..a7a30988f 100644 --- a/vsphere/distributed_virtual_switch_helper.go +++ b/vsphere/distributed_virtual_switch_helper.go @@ -94,6 +94,9 @@ func dvsFromUuid(client *govmomi.Client, uuid string) (*mo.DistributedVirtualSwi // check if host is in refs with the help of hosts // not very efficient, but the number of entries is usually pretty small func isHostPartOfDVS(hosts []interface{}, refs map[string]types.ManagedObjectReference, host *types.ManagedObjectReference) map[string]interface{} { + if hosts == nil { + return nil + } for _, h := range hosts { hi := h.(map[string]interface{}) if val, ok := refs[hi["host_system_id"].(string)]; ok { diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index c817c0469..1099e3175 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -1,12 +1,17 @@ package vsphere import ( + "log" "strings" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/object" "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" + "golang.org/x/net/context" ) var distributedVirtualSwitchNetworkResourceControlVersionAllowedValues = []string{ @@ -116,72 +121,90 @@ func schemaDistributedVirtualSwitchHostMemberConfigSpec() *schema.Schema { return s } -func expandDistributedVirtualSwitchHostMemberConfigSpec(d *schema.ResourceData, dvs *mo.DistributedVirtualSwitch, refs map[string]types.ManagedObjectReference) []types.DistributedVirtualSwitchHostMemberConfigSpec { +func expandDistributedVirtualSwitchHostMemberConfigSpec(client *govmomi.Client, d *schema.ResourceData, dvs *mo.DistributedVirtualSwitch, refs map[string]types.ManagedObjectReference) []types.DistributedVirtualSwitchHostMemberConfigSpec { // Configure the host and nic cards used as uplink for the DVS var hmc []types.DistributedVirtualSwitchHostMemberConfigSpec - if hosts, ok := d.GetOk("host"); ok { - hosts := hosts.([]interface{}) - // If the DVS exist we go through all the hosts and see which ones - // we have to delete or modify - if dvs != nil { - config := dvs.Config.GetDVSConfigInfo() - for _, h := range config.Host { - if host := isHostPartOfDVS(hosts, refs, h.Config.Host); host != nil { - // Edit - backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) - for _, nic := range host["backing"].([]interface{}) { - backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ - PnicDevice: strings.TrimSpace(nic.(string)), - }) - } - hcs := types.DistributedVirtualSwitchHostMemberConfigSpec{ - Host: *h.Config.Host, - Backing: backing, - Operation: "edit", // Options: "add", "edit", "remove" + var hosts []interface{} + if h, ok := d.GetOk("host"); ok { + hosts = h.([]interface{}) + } + // If the DVS exist we go through all the hosts and see which ones + // we have to delete or modify + if dvs != nil { + config := dvs.Config.GetDVSConfigInfo() + for _, h := range config.Host { + if host := isHostPartOfDVS(hosts, refs, h.Config.Host); host != nil { + // Edit + backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) + for _, nic := range host["backing"].([]interface{}) { + backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ + PnicDevice: strings.TrimSpace(nic.(string)), + }) + } + hcs := types.DistributedVirtualSwitchHostMemberConfigSpec{ + Host: *h.Config.Host, + Backing: backing, + Operation: "edit", // Options: "add", "edit", "remove" + } + hmc = append(hmc, hcs) + + // We take it out from the refs, on the last pass we consider whatever + // is left as to be added + delete(refs, host["host_system_id"].(string)) + } else { + log.Print("[TRACE] Removing host from uplinks") + // Remove + backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) + finder := find.NewFinder(client.Client, false) + + ds, err := finder.ObjectReference(context.TODO(), *h.Config.Host) + if err != nil { + continue + } + dso := ds.(*object.HostSystem) + var mh mo.HostSystem + err = dso.Properties(context.TODO(), ds.Reference(), []string{"config"}, &mh) + if err != nil { + continue + } + + for _, ps := range mh.Config.Network.ProxySwitch { + if ps.DvsUuid == d.Id() { + for _, nic := range ps.Pnic { + backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ + PnicDevice: string(nic), + }) + } + hcs := types.DistributedVirtualSwitchHostMemberConfigSpec{ + Host: *h.Config.Host, + Backing: backing, + Operation: "remove", // Options: "add", "edit", "remove" + } + hmc = append(hmc, hcs) } - hmc = append(hmc, hcs) - - // We take it out from the refs, on the last pass we consider whatever - // is left as to be added - delete(refs, host["host_system_id"].(string)) - } else { - // Remove - // XXX I'm not sure if it's necessary to mention the specific NICs when removing a host completely + } + } + } + + if hosts != nil { + // Add whatever is left + for _, host := range hosts { + hi := host.(map[string]interface{}) + if val, ok := refs[hi["host_system_id"].(string)]; ok { backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) - cbp := h.Config.Backing.GetDistributedVirtualSwitchHostMemberBacking() - cb := interface{}(*cbp).(types.DistributedVirtualSwitchHostMemberPnicSpec) - for _, nic := range cb.PnicDevice { + for _, nic := range hi["backing"].([]interface{}) { backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ - PnicDevice: string(nic), + PnicDevice: strings.TrimSpace(nic.(string)), }) } - hcs := types.DistributedVirtualSwitchHostMemberConfigSpec{ - Host: *h.Config.Host, + h := types.DistributedVirtualSwitchHostMemberConfigSpec{ + Host: val, Backing: backing, - Operation: "remove", // Options: "add", "edit", "remove" + Operation: "add", // Options: "add", "edit", "remove" } - hmc = append(hmc, hcs) - } - } - } - - // Add whatever is left - for _, host := range hosts { - hi := host.(map[string]interface{}) - if val, ok := refs[hi["host_system_id"].(string)]; ok { - backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) - for _, nic := range hi["backing"].([]interface{}) { - backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ - PnicDevice: strings.TrimSpace(nic.(string)), - }) + hmc = append(hmc, h) } - h := types.DistributedVirtualSwitchHostMemberConfigSpec{ - Host: val, - Backing: backing, - Operation: "add", // Options: "add", "edit", "remove" - } - hmc = append(hmc, h) } } } @@ -189,8 +212,49 @@ func expandDistributedVirtualSwitchHostMemberConfigSpec(d *schema.ResourceData, return hmc } -func flattenDistributedVirtualSwitchHostMemberConfigSpec(d *schema.ResourceData, obj *mo.DistributedVirtualSwitch) { +func flattenDistributedVirtualSwitchHostMemberConfigSpec(client *govmomi.Client, d *schema.ResourceData, obj *mo.DistributedVirtualSwitch) { + log.Printf("[TRACE] Flattening DistributedVirtualSwitchHostMemberConfigSpec %v", d.Get("host")) + config := obj.Config.GetDVSConfigInfo() + var hosts []interface{} + for _, host := range config.Host { + log.Print("[TRACE] One host") + hm := make(map[string]interface{}) + hm["host_system_id"] = host.Config.Host + + backing := []string{} + + finder := find.NewFinder(client.Client, false) + + ds, err := finder.ObjectReference(context.TODO(), *host.Config.Host) + if err != nil { + continue + } + dso := ds.(*object.HostSystem) + var mh mo.HostSystem + err = dso.Properties(context.TODO(), ds.Reference(), []string{"config"}, &mh) + if err != nil { + continue + } + + for _, ps := range mh.Config.Network.ProxySwitch { + log.Print("[TRACE] One proxy switch") + if ps.DvsUuid == d.Id() { + log.Print("[TRACE] Found the proxy switch for this DVS") + for _, nic := range ps.Pnic { + log.Printf("[TRACE] Pnic %s", nic) + sn := strings.Split(nic, "-") + backing = append(backing, sn[len(sn)-1]) + } + } + } + if len(backing) > 0 { + hm["backing"] = backing + } + hosts = append(hosts, hm) + log.Print("[TRACE] Host after flattening %+v", hosts) + } + d.Set("host", hosts) } /*func schemaDvsHostInfrastructureTrafficResource() *schema.Schema { @@ -323,7 +387,7 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { return s } -func expandDVSConfigSpec(d *schema.ResourceData, dvs *mo.DistributedVirtualSwitch, refs map[string]types.ManagedObjectReference) *types.DVSConfigSpec { +func expandDVSConfigSpec(client *govmomi.Client, d *schema.ResourceData, dvs *mo.DistributedVirtualSwitch, refs map[string]types.ManagedObjectReference) *types.DVSConfigSpec { obj := &types.DVSConfigSpec{} obj.Name = d.Get("name").(string) @@ -351,7 +415,7 @@ func expandDVSConfigSpec(d *schema.ResourceData, dvs *mo.DistributedVirtualSwitc } // Always expand since even when removing we will need to mention hosts and nics - obj.Host = expandDistributedVirtualSwitchHostMemberConfigSpec(d, dvs, refs) + obj.Host = expandDistributedVirtualSwitchHostMemberConfigSpec(client, d, dvs, refs) if v, ok := d.GetOkExists("num_standalone_ports"); ok { obj.NumStandalonePorts = int32(v.(int)) @@ -364,7 +428,7 @@ func expandDVSConfigSpec(d *schema.ResourceData, dvs *mo.DistributedVirtualSwitc return obj } -func flattenDVSConfigSpec(d *schema.ResourceData, obj *mo.DistributedVirtualSwitch) error { +func flattenDVSConfigSpec(client *govmomi.Client, d *schema.ResourceData, obj *mo.DistributedVirtualSwitch) error { config := obj.Config.GetDVSConfigInfo() d.Set("config_version", config.ConfigVersion) d.Set("description", config.Description) @@ -378,7 +442,7 @@ func flattenDVSConfigSpec(d *schema.ResourceData, obj *mo.DistributedVirtualSwit d.Set("default_proxy_switch_max_num_ports", config.DefaultProxySwitchMaxNumPorts) d.Set("switch_ip_address", config.SwitchIpAddress) flattenDVSContactInfo(d, obj) - flattenDistributedVirtualSwitchHostMemberConfigSpec(d, obj) + flattenDistributedVirtualSwitchHostMemberConfigSpec(client, d, obj) return nil } diff --git a/vsphere/resource_vsphere_distributed_virtual_switch.go b/vsphere/resource_vsphere_distributed_virtual_switch.go index 771595c6e..35eaaf195 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch.go @@ -58,7 +58,7 @@ func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta return fmt.Errorf("%s", err) } - spec := expandDVSConfigSpec(d, nil, hosts_mor) + spec := expandDVSConfigSpec(client, d, nil, hosts_mor) dvsCreateSpec := types.DVSCreateSpec{ConfigSpec: spec} task, err := f.CreateDVS(context.TODO(), dvsCreateSpec) @@ -91,7 +91,7 @@ func resourceVSphereDistributedVirtualSwitchRead(d *schema.ResourceData, meta in return fmt.Errorf("error reading data: %s", err) } - if err := flattenDVSConfigSpec(d, dvs); err != nil { + if err := flattenDVSConfigSpec(client, d, dvs); err != nil { return fmt.Errorf("error setting resource data: %s", err) } @@ -110,7 +110,7 @@ func resourceVSphereDistributedVirtualSwitchUpdate(d *schema.ResourceData, meta return fmt.Errorf("%s", err) } - spec := expandDVSConfigSpec(d, dvs, hosts_refs) + spec := expandDVSConfigSpec(client, d, dvs, hosts_refs) n := object.NewDistributedVirtualSwitch(client.Client, dvs.Reference()) req := &types.ReconfigureDvs_Task{ From 748f3f96d81375387052d52e51d70cd0d3d0d353 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Tue, 19 Sep 2017 02:04:05 +0200 Subject: [PATCH 15/21] Fix go vet --- vsphere/distributed_virtual_switch_structure.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index 1099e3175..97df19e43 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -252,7 +252,7 @@ func flattenDistributedVirtualSwitchHostMemberConfigSpec(client *govmomi.Client, hm["backing"] = backing } hosts = append(hosts, hm) - log.Print("[TRACE] Host after flattening %+v", hosts) + log.Print("[TRACE] Host after flattening %v", hosts) } d.Set("host", hosts) } From bc450df1c9743cf6784e9bc910f1d10114df07c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Tue, 19 Sep 2017 02:10:25 +0200 Subject: [PATCH 16/21] Fit go vet (really this time) --- vsphere/distributed_virtual_switch_structure.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index 97df19e43..7fa72e72e 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -252,7 +252,7 @@ func flattenDistributedVirtualSwitchHostMemberConfigSpec(client *govmomi.Client, hm["backing"] = backing } hosts = append(hosts, hm) - log.Print("[TRACE] Host after flattening %v", hosts) + log.Printf("[TRACE] Host after flattening %v", hosts) } d.Set("host", hosts) } From c8a7e528e9b5d992de20420ac09f521c07b68a19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Tue, 19 Sep 2017 03:42:50 +0200 Subject: [PATCH 17/21] Improve tests, fixes --- .../distributed_virtual_switch_structure.go | 34 ++++++------- ...urce_vsphere_distributed_virtual_switch.go | 36 ++++++++++++++ ...vsphere_distributed_virtual_switch_test.go | 48 +++++++++++++++---- 3 files changed, 91 insertions(+), 27 deletions(-) diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index 7fa72e72e..4c0175eb6 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -186,25 +186,25 @@ func expandDistributedVirtualSwitchHostMemberConfigSpec(client *govmomi.Client, } } } + } - if hosts != nil { - // Add whatever is left - for _, host := range hosts { - hi := host.(map[string]interface{}) - if val, ok := refs[hi["host_system_id"].(string)]; ok { - backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) - for _, nic := range hi["backing"].([]interface{}) { - backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ - PnicDevice: strings.TrimSpace(nic.(string)), - }) - } - h := types.DistributedVirtualSwitchHostMemberConfigSpec{ - Host: val, - Backing: backing, - Operation: "add", // Options: "add", "edit", "remove" - } - hmc = append(hmc, h) + if hosts != nil { + // Add whatever is left + for _, host := range hosts { + hi := host.(map[string]interface{}) + if val, ok := refs[hi["host_system_id"].(string)]; ok { + backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) + for _, nic := range hi["backing"].([]interface{}) { + backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ + PnicDevice: strings.TrimSpace(nic.(string)), + }) + } + h := types.DistributedVirtualSwitchHostMemberConfigSpec{ + Host: val, + Backing: backing, + Operation: "add", // Options: "add", "edit", "remove" } + hmc = append(hmc, h) } } } diff --git a/vsphere/resource_vsphere_distributed_virtual_switch.go b/vsphere/resource_vsphere_distributed_virtual_switch.go index 35eaaf195..6ecc3be0d 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch.go @@ -123,9 +123,45 @@ func resourceVSphereDistributedVirtualSwitchUpdate(d *schema.ResourceData, meta return fmt.Errorf("%s", err) } + // Wait for the distributed virtual switch resource to be destroyed + stateConf := &resource.StateChangeConf{ + Pending: []string{"Updating"}, + Target: []string{"Updated"}, + Refresh: resourceVSphereDVSStateUpdateRefreshFunc(d, meta), + Timeout: 10 * time.Minute, + MinTimeout: 3 * time.Second, + Delay: 5 * time.Second, + } + + _, err = stateConf.WaitForState() + if err != nil { + name := d.Get("name").(string) + return fmt.Errorf("error waiting for distributed virtual switch (%s) to be updated: %s", name, err) + } + return resourceVSphereDistributedVirtualSwitchRead(d, meta) } +func resourceVSphereDVSStateUpdateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + log.Print("[TRACE] Refreshing distributed virtual switch state, looking for config changes") + client := meta.(*govmomi.Client) + dvs, err := dvsFromUuid(client, d.Id()) + if err != nil { + return nil, "Failed", err + } + config := dvs.Config.GetDVSConfigInfo() + cv := d.Get("config_version").(string) + log.Printf("[TRACE] Current version %s. Old version %s", config.ConfigVersion, cv) + if config.ConfigVersion != cv { + log.Print("[TRACE] Distributed virtual switch config updated") + return dvs, "Updated", nil + } else { + return dvs, "Updating", nil + } + } +} + func resourceVSphereDVSStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { return func() (interface{}, string, error) { log.Print("[TRACE] Refreshing distributed virtual switch state") diff --git a/vsphere/resource_vsphere_distributed_virtual_switch_test.go b/vsphere/resource_vsphere_distributed_virtual_switch_test.go index 4e07c899a..9037faf34 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch_test.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch_test.go @@ -8,8 +8,10 @@ import ( "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "github.com/vmware/govmomi" - // "github.com/vmware/govmomi/find" - // "golang.org/x/net/context" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/mo" + "golang.org/x/net/context" ) func testAccCheckVSphereDVSConfigNoUplinks() string { @@ -91,7 +93,7 @@ func testAccResourceVSphereDistributedVirtualSwitchPreCheck(t *testing.T) { // Create a distributed virtual switch with no uplinks func TestAccVSphereDVS_createWithoutUplinks(t *testing.T) { - resourceName := "vsphere_distributed_virtual_switch.testDVS" + resourceName := "testDVS" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -111,7 +113,7 @@ func TestAccVSphereDVS_createWithoutUplinks(t *testing.T) { // Create a distributed virtual switch with one uplink func TestAccVSphereDVS_createWithUplinks(t *testing.T) { - resourceName := "vsphere_distributed_virtual_switch.testDVS" + resourceName := "testDVS" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -131,7 +133,7 @@ func TestAccVSphereDVS_createWithUplinks(t *testing.T) { // Create a distributed virtual switch with an uplink, delete it and add it again func TestAccVSphereDVS_createAndUpdateWithUplinks(t *testing.T) { - resourceName := "vsphere_distributed_virtual_switch.testDVS" + resourceName := "testDVS" resource.Test(t, resource.TestCase{ PreCheck: func() { @@ -189,6 +191,7 @@ func testAccCheckVSphereDVSExists(name string, n int) resource.TestCheckFunc { return func(s *terraform.State) error { client := testAccProvider.Meta().(*govmomi.Client) + var id string for _, rs := range s.RootModule().Resources { if rs.Type != "vsphere_distributed_virtual_switch" { continue @@ -197,19 +200,44 @@ func testAccCheckVSphereDVSExists(name string, n int) resource.TestCheckFunc { continue } - id := rs.Primary.ID + id = rs.Primary.ID dvs, err := dvsFromUuid(client, id) if err != nil { return fmt.Errorf("distributed virtual switch '%s' doesn't exists", id) } else { config := dvs.Config.GetDVSConfigInfo() - if len(config.Host) != n { - return fmt.Errorf("expected '%d' uplinks, found '%d'", n, len(config.Host)) + + for _, h := range config.Host { + finder := find.NewFinder(client.Client, false) + + ds, err := finder.ObjectReference(context.TODO(), *h.Config.Host) + if err != nil { + continue + } + dso := ds.(*object.HostSystem) + var mh mo.HostSystem + err = dso.Properties(context.TODO(), ds.Reference(), []string{"config"}, &mh) + if err != nil { + continue + } + + var j int + for _, ps := range mh.Config.Network.ProxySwitch { + if ps.DvsName == name { + j = len(ps.Pnic) + } + } + if j != n { + return fmt.Errorf("expected '%d' uplinks, found '%d'", n, j) + } + fmt.Println("DVS exists and has the correct number of uplinks") + return nil } - fmt.Println("DVS exists and has the correct number of uplinks") - return nil } } + if id == "" { + return fmt.Errorf("DVS not found") + } return nil } } From 60b6800d0ab0e04dfdbe16c4708e8d39a8c3a4b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Tue, 19 Sep 2017 03:45:59 +0200 Subject: [PATCH 18/21] Remove commented code --- .../distributed_virtual_switch_structure.go | 76 ------------------- 1 file changed, 76 deletions(-) diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index 4c0175eb6..8fe1b3b63 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -25,18 +25,6 @@ var configSpecOperationAllowedValues = []string{ string(types.VirtualDeviceConfigSpecOperationEdit), } -/*var distributedVirtualSwitchHostInfrastructureTrafficClass = []string{ - string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassManagement), - string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassFaultTolerance), - string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVmotion), - string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVirtualMachine), - string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassISCSI), - string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassNfs), - string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassHbr), - string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVsan), - string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVdp), -}*/ - func schemaDVSContactInfo() map[string]*schema.Schema { return map[string]*schema.Schema{ "contact": &schema.Schema{ @@ -70,11 +58,6 @@ func flattenDVSContactInfo(d *schema.ResourceData, obj *mo.DistributedVirtualSwi d.Set("contact_name", config.Contact.Name) } -/*func schemaDVPortSetting() map[string]*schema.Schema { - // TBD - return nil -}*/ - func schemaDistributedVirtualSwitchHostMemberPnicBacking() *schema.Schema { // TODO maybe a set will fit better to avoid the mistake of putting a nic twice? return &schema.Schema{ @@ -98,12 +81,6 @@ func schemaDistributedVirtualSwitchHostMemberConfigSpec() *schema.Schema { Optional: true, Description: "The managed object ID of the host to search for NICs on.", }, - "operation": &schema.Schema{ - Type: schema.TypeInt, - Optional: true, - Description: "Host member operation type.", - ValidateFunc: validation.StringInSlice(configSpecOperationAllowedValues, false), - }, // DistributedVirtualSwitchHostMemberPnicBacking extends DistributedVirtualSwitchHostMemberBacking // which is a base class "backing": schemaDistributedVirtualSwitchHostMemberPnicBacking(), @@ -257,54 +234,6 @@ func flattenDistributedVirtualSwitchHostMemberConfigSpec(client *govmomi.Client, d.Set("host", hosts) } -/*func schemaDvsHostInfrastructureTrafficResource() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "description": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "The description of the host infrastructure resource. This property is ignored for update operation.", - }, - "key": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "The key of the host infrastructure resource. Possible value can be of DistributedVirtualSwitchHostInfrastructureTrafficClass.", - ValidateFunc: validation.StringInSlice(distributedVirtualSwitchHostInfrastructureTrafficClass, false), - }, - //"allocationInfo": TBD - }, - }, - } -}*/ - -/*func schemaDVSPolicy() map[string]*schema.Schema { - return map[string]*schema.Schema{ - "auto_pre_install_allowed": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Description: "Whether downloading a new proxy VirtualSwitch module to the host is allowed to be automatically executed by the switch.", - }, - "auto_upgrade_allowed": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Description: "Whether upgrading of the switch is allowed to be automatically executed by the switch.", - }, - "partial_upgrade_allowed": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Description: "Whether to allow upgrading a switch when some of the hosts failed to install the needed module.", - }, - } -}*/ - -/*func schemaDVSUplinkPortPolicy() map[string]*schema.Schema { - // TBD - return nil -}*/ - func schemaDistributedVirtualSwitchKeyedOpaqueBlob() *schema.Schema { return &schema.Schema{ Type: schema.TypeList, @@ -351,7 +280,6 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { Description: "The key of the extension registered by a remote server that controls the switch.", }, "host": schemaDistributedVirtualSwitchHostMemberConfigSpec(), - //"infrastructure_traffic_resource_config": schemaDvsHostInfrastructureTrafficResource(), "name": &schema.Schema{ Type: schema.TypeString, Optional: true, @@ -379,10 +307,6 @@ func schemaDVSConfiSpec() map[string]*schema.Schema { "vendor_specific_config": schemaDistributedVirtualSwitchKeyedOpaqueBlob(), } mergeSchema(s, schemaDVSContactInfo()) - //mergeSchema(s, schemaDVPortSetting()) - //mergeSchema(s, schemaDVSPolicy()) - // XXX TBD uplinkPortgroup - //mergeSchema(s, schemaDVSUplinkPortPolicy()) return s } From e3ef7bb3a83729794386ad1e957c88f458b28884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Wed, 20 Sep 2017 21:46:10 +0200 Subject: [PATCH 19/21] Start updating docs to match implementation --- .../distributed_virtual_switch.html.markdown | 39 +++++++++++++++---- website/vsphere.erb | 3 ++ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/website/docs/r/distributed_virtual_switch.html.markdown b/website/docs/r/distributed_virtual_switch.html.markdown index 61df5651b..323847f32 100644 --- a/website/docs/r/distributed_virtual_switch.html.markdown +++ b/website/docs/r/distributed_virtual_switch.html.markdown @@ -18,19 +18,44 @@ Provides a VMware vSphere distributed virtual switch resource. A distributed swi **Create a distributed virtual switch without specifying uplink port groups (need to be defined manually later):** ```hcl +data "vsphere_datacenter" "datacenter" { + name = "myDC" +} + resource "vsphere_distributed_virtual_switch" "myDistributedSwitch" { - datacenter = "myDC" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" name = "myDistributedSwitch" } ``` -**Create a distributed virtual switch specifying uplink port groups):** +**Create a distributed virtual switch with connected hosts and their NICs as part of the uplink port group:** ```hcl +data "vsphere_datacenter" "datacenter" { + name = "myDC" +} + +data "vsphere_host" "esxi_host1" { + name = "node1" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +} + +data "vsphere_host" "esxi_host2" { + name = "node2" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +} + resource "vsphere_distributed_virtual_switch" "myDistributedSwitch" { - datacenter = "myDC" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" name = "myDistributedSwitch" - uplinks = { "10.0.30.25" = "vmnic1", "host100.mydomain.net" = "vmnic1" } + + host = [{ + host_system_id = "${data.vsphere_host.esxi_host1.id}" + backing = ["vmnic1","vmnic2"] + },{ + host_system_id = "${data.vsphere_host.esxi_host2.id}" + backing = ["vmnic0"] + }] } ``` @@ -39,7 +64,5 @@ resource "vsphere_distributed_virtual_switch" "myDistributedSwitch" { The following arguments are supported: * `name` - (Required) The name of the distributed virtual switch. -* `datacenter` - (Required) The name of the datacenter containing the distributed virtual switch. -* `uplinks` - (Optional) A map of hosts and physical NICs to attach to the uplink port group. - -~> **NOTE**: Distributed virtual switches cannot be changed once they are created. Modifying any of these attributes will force a new resource! +* `datacenter_id` - (Required) The ID of the datacenter where the distributed virtual switch will be created. +* `host` - (Optional) A map of hosts and physical NICs to attach to the uplink port group. diff --git a/website/vsphere.erb b/website/vsphere.erb index 069d7e503..98e3c3bf2 100644 --- a/website/vsphere.erb +++ b/website/vsphere.erb @@ -67,6 +67,9 @@ > vsphere_host_virtual_switch + > + vsphere_distributed_virtual_switch + From a87c84ac7861e1fb1337c5405edbcb25bca5b418 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Nu=CC=81n=CC=83ez?= Date: Wed, 20 Sep 2017 22:02:59 +0200 Subject: [PATCH 20/21] Updated docs --- .../r/distributed_virtual_switch.html.markdown | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/website/docs/r/distributed_virtual_switch.html.markdown b/website/docs/r/distributed_virtual_switch.html.markdown index 323847f32..a37327d22 100644 --- a/website/docs/r/distributed_virtual_switch.html.markdown +++ b/website/docs/r/distributed_virtual_switch.html.markdown @@ -64,5 +64,19 @@ resource "vsphere_distributed_virtual_switch" "myDistributedSwitch" { The following arguments are supported: * `name` - (Required) The name of the distributed virtual switch. +* `description` - (Optional) The description string of the distributed virtual switch. +* `default_proxy_switch_max_num_ports` - (Optional) The default host proxy switch maximum port number. +* `extension_key` - (Optional) The key of the extension registered by a remote server that controls the switch. +* `network_resource_control_version` - (Optional) Indicates the Network Resource Control APIs that are supported on the switch. +* `num_standalone_ports` - (Optional) The number of standalone ports in the switch. Standalone ports are ports that do not belong to any portgroup. +* `switch_ip_address` - (Optional) IP address for the switch, specified using IPv4 dot notation. IPv6 address is not supported for this property. * `datacenter_id` - (Required) The ID of the datacenter where the distributed virtual switch will be created. -* `host` - (Optional) A map of hosts and physical NICs to attach to the uplink port group. +* `host` - (Optional) A list of hosts and physical NICs to attach to the uplink port group. + * max_proxy_switch_ports - (Optional) Maximum number of ports allowed in the HostProxySwitch. + * host_system_id - (Required) The managed object ID of the host to search for NICs on. + * backing - (Optional) + * vendor_specific_config - (Optional) A list of key/blob vendor specific config. + * key - (Optional) A key that identifies the opaque binary blob. + * opaque_data - (Optional) The opaque data. It is recommended that base64 encoding be used for binary data. +* `contact` - (Optional) The contact information for the person. +* `contact_name` - (Optional) The name of the person who is responsible for the switch. From 1627dbf5688b1610bf4f317498dc7a53fd27f16d Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Sat, 7 Oct 2017 21:06:30 -0700 Subject: [PATCH 21/21] r/distributed_virtual_switch: Refactor - new resource This commit is a major refactor of the DVS resource provided in PR #135. More options have been added, the DVS has been fixed so that it uses the VMware DVS object instead of the generic DVS object, which is more or less just used for 3rd party DVS switches. This meant that the DVS was missing a number of features that more than likely would have been expected in the DVS. The breadth of the features added can be seen in the documentation. It should be noted that this does not include the ability to set private VLANs, filtering policies, or port mirroring sessions, which will be added at a later date and pending business case. This work also contains a bunch of up front work that will be necessary for the vsphere_distributed_port_group resource, which will be following not too long after this resource. --- ...tributed_virtual_port_setting_structure.go | 585 ++++++++ vsphere/distributed_virtual_switch_helper.go | 169 ++- .../distributed_virtual_switch_structure.go | 824 +++++++---- vsphere/folder_helper.go | 52 +- vsphere/helper_test.go | 19 + vsphere/provider.go | 2 +- ...urce_vsphere_distributed_virtual_switch.go | 340 +++-- ...vsphere_distributed_virtual_switch_test.go | 1296 ++++++++++++++--- vsphere/structure_helper.go | 226 +++ vsphere/vim_helper.go | 59 +- .../distributed_virtual_switch.html.markdown | 438 +++++- website/vsphere.erb | 6 +- 12 files changed, 3281 insertions(+), 735 deletions(-) create mode 100644 vsphere/distributed_virtual_port_setting_structure.go diff --git a/vsphere/distributed_virtual_port_setting_structure.go b/vsphere/distributed_virtual_port_setting_structure.go new file mode 100644 index 000000000..7ba2ad3db --- /dev/null +++ b/vsphere/distributed_virtual_port_setting_structure.go @@ -0,0 +1,585 @@ +package vsphere + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/vmware/govmomi/vim25/types" +) + +var vmwareUplinkLacpPolicyModeAllowedValues = []string{ + string(types.VMwareUplinkLacpModeActive), + string(types.VMwareUplinkLacpModePassive), +} + +var vmwareUplinkPortTeamingPolicyModeAllowedValues = []string{ + string(types.DistributedVirtualSwitchNicTeamingPolicyModeLoadbalance_ip), + string(types.DistributedVirtualSwitchNicTeamingPolicyModeLoadbalance_srcmac), + string(types.DistributedVirtualSwitchNicTeamingPolicyModeLoadbalance_srcid), + string(types.DistributedVirtualSwitchNicTeamingPolicyModeFailover_explicit), + string(types.DistributedVirtualSwitchNicTeamingPolicyModeLoadbalance_loadbased), +} + +// schemaVMwareDVSPortSetting returns schema items for resources that +// need to work with a VMwareDVSPortSetting. +// +// Some sub-fields/features have been explicitly omitted from this +// configuration for now, and may be added in future versions if required: +// +// * Duplex, speed, and error checking for failover: These are not exposed in +// either vSphere console and may be seldom used. (Part of +// VmwareUplinkPortTeamingPolicy/DVSFailureCriteria) +// * FilterPolicy - complex configuration for network filters +// * NetworkResourcePoolKey - may be included when we add network resource +// pools +// * VendorSpecificConfig - applies to 3rd party DVS switches which TF does not +// support. +// * VmDirectPathGen2Allowed - directpath I/O option which we are not +// necessarily supporting modification of in other resources - leaving this +// until we have a need for it. +func schemaVMwareDVSPortSetting() map[string]*schema.Schema { + return map[string]*schema.Schema{ + // VmwareDistributedVirtualSwitchVlanIdSpec + "vlan_id": { + Type: schema.TypeInt, + Optional: true, + Description: "The VLAN ID for single VLAN mode. 0 denotes no VLAN.", + ConflictsWith: []string{"vlan_range", "port_private_secondary_vlan_id"}, + ValidateFunc: validation.IntBetween(0, 4094), + }, + + // VmwareDistributedVirtualSwitchTrunkVlanSpec + "vlan_range": { + Type: schema.TypeList, + Optional: true, + Description: "The VLAN ID for single VLAN mode. 0 denotes no VLAN.", + ConflictsWith: []string{"vlan_id", "port_private_secondary_vlan_id"}, + MinItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "min_vlan": { + Type: schema.TypeInt, + Required: true, + Description: "The minimum VLAN to use in the range.", + ValidateFunc: validation.IntBetween(0, 4094), + }, + "max_vlan": { + Type: schema.TypeInt, + Required: true, + Description: "The minimum VLAN to use in the range.", + ValidateFunc: validation.IntBetween(0, 4094), + }, + }, + }, + }, + + // VmwareDistributedVirtualSwitchPvlanSpec + "port_private_secondary_vlan_id": { + Type: schema.TypeInt, + Optional: true, + Description: "The secondary VLAN ID for this port.", + ConflictsWith: []string{"vlan_id", "vlan_range"}, + ValidateFunc: validation.IntBetween(1, 4094), + }, + + // VmwareUplinkPortTeamingPolicy/DVSFailureCriteria + "check_beacon": { + Type: schema.TypeBool, + Optional: true, + Description: "Enable beacon probing on the ports this policy applies to.", + }, + + // VmwareUplinkPortTeamingPolicy/VMwareUplinkPortOrderPolicy + "active_uplinks": { + Type: schema.TypeList, + Optional: true, + Description: "List of active uplinks used for load balancing, matching the names of the uplinks assigned in the DVS.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "standby_uplinks": { + Type: schema.TypeList, + Optional: true, + Description: "List of active uplinks used for load balancing, matching the names of the uplinks assigned in the DVS.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + // VmwareUplinkPortTeamingPolicy + "teaming_policy": { + Type: schema.TypeString, + Optional: true, + Description: "The network adapter teaming policy. Can be one of loadbalance_ip, loadbalance_srcmac, loadbalance_srcid, failover_explicit, or loadbalance_loadbased.", + ValidateFunc: validation.StringInSlice(vmwareUplinkPortTeamingPolicyModeAllowedValues, false), + }, + "notify_switches": { + Type: schema.TypeBool, + Optional: true, + Description: "If true, the teaming policy will notify the broadcast network of a NIC failover, triggering cache updates.", + }, + "failback": { + Type: schema.TypeBool, + Optional: true, + Description: "If true, the teaming policy will re-activate failed interfaces higher in precedence when they come back up.", + }, + + // DVSSecurityPolicy + "allow_promiscuous": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: "Enable promiscuous mode on the network. This flag indicates whether or not all traffic is seen on a given port.", + }, + "allow_forged_transmits": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: "Controls whether or not the virtual network adapter is allowed to send network traffic with a different MAC address than that of its own.", + }, + "allow_mac_changes": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: "Controls whether or not the Media Access Control (MAC) address can be changed.", + }, + + // VMwareUplinkLacpPolicy + "lacp_enabled": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether or not to enable LACP on all uplink ports.", + }, + "lacp_mode": { + Type: schema.TypeString, + Optional: true, + Description: "The uplink LACP mode to use. Can be one of active or passive.", + ValidateFunc: validation.StringInSlice(vmwareUplinkLacpPolicyModeAllowedValues, false), + }, + + // DVSTrafficShapingPolicy - ingress + "ingress_shaping_average_bandwidth": { + Type: schema.TypeInt, + Optional: true, + Description: "The average ingress bandwidth in bits per second if ingress shaping is enabled on the port.", + }, + "ingress_shaping_burst_size": { + Type: schema.TypeInt, + Optional: true, + Description: "The maximum ingress burst size allowed in bytes if ingress shaping is enabled on the port.", + }, + "ingress_shaping_enabled": { + Type: schema.TypeBool, + Optional: true, + Description: "True if the traffic shaper is enabled for ingress traffic on the port.", + }, + "ingress_shaping_peak_bandwidth": { + Type: schema.TypeInt, + Optional: true, + Description: "The peak ingress bandwidth during bursts in bits per second if ingress traffic shaping is enabled on the port.", + }, + + // DVSTrafficShapingPolicy - egress + "egress_shaping_average_bandwidth": { + Type: schema.TypeInt, + Optional: true, + Description: "The average egress bandwidth in bits per second if egress shaping is enabled on the port.", + }, + "egress_shaping_burst_size": { + Type: schema.TypeInt, + Optional: true, + Description: "The maximum egress burst size allowed in bytes if egress shaping is enabled on the port.", + }, + "egress_shaping_enabled": { + Type: schema.TypeBool, + Optional: true, + Description: "True if the traffic shaper is enabled for egress traffic on the port.", + }, + "egress_shaping_peak_bandwidth": { + Type: schema.TypeInt, + Optional: true, + Description: "The peak egress bandwidth during bursts in bits per second if egress traffic shaping is enabled on the port.", + }, + + // VMwareDVSPortSetting + "block_all_ports": { + Type: schema.TypeBool, + Optional: true, + Description: "Indicates whether to block all ports by default.", + }, + "netflow_enabled": { + Type: schema.TypeBool, + Optional: true, + Description: "Indicates whether to enable netflow on all ports.", + }, + "tx_uplink": { + Type: schema.TypeBool, + Optional: true, + Description: "If true, a copy of packets sent to the switch will always be forwarded to an uplink in addition to the regular packet forwarded done by the switch.", + }, + } +} + +// expandVmwareDistributedVirtualSwitchVlanIDSpec reads certain ResourceData keys and +// returns a VmwareDistributedVirtualSwitchVlanIdSpec. +func expandVmwareDistributedVirtualSwitchVlanIDSpec(d *schema.ResourceData) *types.VmwareDistributedVirtualSwitchVlanIdSpec { + obj := &types.VmwareDistributedVirtualSwitchVlanIdSpec{ + VlanId: int32(d.Get("vlan_id").(int)), + } + return obj +} + +// flattenVmwareDistributedVirtualSwitchVlanIDSpec reads various fields from a +// VmwareDistributedVirtualSwitchVlanIdSpec into the passed in ResourceData. +func flattenVmwareDistributedVirtualSwitchVlanIDSpec(d *schema.ResourceData, obj *types.VmwareDistributedVirtualSwitchVlanIdSpec) error { + d.Set("vlan_id", obj.VlanId) + return nil +} + +// expandVmwareDistributedVirtualSwitchTrunkVlanSpec reads certain ResourceData keys and +// returns a VmwareDistributedVirtualSwitchTrunkVlanSpec. +func expandVmwareDistributedVirtualSwitchTrunkVlanSpec(d *schema.ResourceData) *types.VmwareDistributedVirtualSwitchTrunkVlanSpec { + var ranges []types.NumericRange + data := d.Get("vlan_range").([]interface{}) + for _, v := range data { + r := v.(map[string]interface{}) + min := r["min_vlan"].(int) + max := r["max_vlan"].(int) + rng := types.NumericRange{ + Start: int32(min), + End: int32(max), + } + ranges = append(ranges, rng) + } + obj := &types.VmwareDistributedVirtualSwitchTrunkVlanSpec{ + VlanId: ranges, + } + return obj +} + +// flattenVmwareDistributedVirtualSwitchTrunkVlanSpec reads various fields from a +// VmwareDistributedVirtualSwitchTrunkVlanSpec into the passed in ResourceData. +func flattenVmwareDistributedVirtualSwitchTrunkVlanSpec(d *schema.ResourceData, obj *types.VmwareDistributedVirtualSwitchTrunkVlanSpec) error { + var s []interface{} + for _, rng := range obj.VlanId { + m := make(map[string]interface{}) + m["min_vlan"] = rng.Start + m["max_vlan"] = rng.End + s = append(s, m) + } + if err := d.Set("vlan_range", s); err != nil { + return err + } + return nil +} + +// expandVmwareDistributedVirtualSwitchPvlanSpec reads certain ResourceData keys and +// returns a VmwareDistributedVirtualSwitchPvlanSpec. +func expandVmwareDistributedVirtualSwitchPvlanSpec(d *schema.ResourceData) *types.VmwareDistributedVirtualSwitchPvlanSpec { + obj := &types.VmwareDistributedVirtualSwitchPvlanSpec{ + PvlanId: int32(d.Get("port_private_secondary_vlan_id").(int)), + } + return obj +} + +// flattenVmwareDistributedVirtualSwitchPvlanSpec reads various fields from a +// VmwareDistributedVirtualSwitchPvlanSpec into the passed in ResourceData. +func flattenVmwareDistributedVirtualSwitchPvlanSpec(d *schema.ResourceData, obj *types.VmwareDistributedVirtualSwitchPvlanSpec) error { + d.Set("port_private_secondary_vlan_id", obj.PvlanId) + return nil +} + +// expandBaseVmwareDistributedVirtualSwitchVlanSpec reads certain ResourceData keys and +// returns a BaseVmwareDistributedVirtualSwitchVlanSpec. +func expandBaseVmwareDistributedVirtualSwitchVlanSpec(d *schema.ResourceData) types.BaseVmwareDistributedVirtualSwitchVlanSpec { + var obj types.BaseVmwareDistributedVirtualSwitchVlanSpec + + _, ide := d.GetOkExists("vlan_id") + _, vte := d.GetOkExists("vlan_range") + _, pvid := d.GetOkExists("port_private_secondary_vlan_id") + switch { + case vte: + obj = expandVmwareDistributedVirtualSwitchTrunkVlanSpec(d) + case pvid: + obj = expandVmwareDistributedVirtualSwitchPvlanSpec(d) + case ide: + obj = expandVmwareDistributedVirtualSwitchVlanIDSpec(d) + } + + return obj +} + +// flattenBaseVmwareDistributedVirtualSwitchVlanSpec reads various fields from a +// BaseVmwareDistributedVirtualSwitchVlanSpec into the passed in ResourceData. +func flattenBaseVmwareDistributedVirtualSwitchVlanSpec(d *schema.ResourceData, obj types.BaseVmwareDistributedVirtualSwitchVlanSpec) error { + if obj == nil { + return nil + } + + var err error + + switch t := obj.(type) { + case *types.VmwareDistributedVirtualSwitchVlanIdSpec: + err = flattenVmwareDistributedVirtualSwitchVlanIDSpec(d, t) + case *types.VmwareDistributedVirtualSwitchTrunkVlanSpec: + err = flattenVmwareDistributedVirtualSwitchTrunkVlanSpec(d, t) + case *types.VmwareDistributedVirtualSwitchPvlanSpec: + err = flattenVmwareDistributedVirtualSwitchPvlanSpec(d, t) + } + + return err +} + +// expandDVSFailureCriteria reads certain ResourceData keys and +// returns a DVSFailureCriteria. +func expandDVSFailureCriteria(d *schema.ResourceData) *types.DVSFailureCriteria { + obj := &types.DVSFailureCriteria{ + CheckBeacon: getBoolPolicy(d, "check_beacon"), + } + + if allFieldsEmpty(obj) { + return nil + } + return obj +} + +// flattenDVSFailureCriteria reads various fields from a +// DVSFailureCriteria into the passed in ResourceData. +func flattenDVSFailureCriteria(d *schema.ResourceData, obj *types.DVSFailureCriteria) error { + if obj == nil { + return nil + } + + setBoolPolicy(d, "check_beacon", obj.CheckBeacon) + return nil +} + +// expandVMwareUplinkPortOrderPolicy reads certain ResourceData keys and +// returns a VMwareUplinkPortOrderPolicy. +func expandVMwareUplinkPortOrderPolicy(d *schema.ResourceData) *types.VMwareUplinkPortOrderPolicy { + obj := &types.VMwareUplinkPortOrderPolicy{ + ActiveUplinkPort: sliceInterfacesToStrings(d.Get("active_uplinks").([]interface{})), + StandbyUplinkPort: sliceInterfacesToStrings(d.Get("standby_uplinks").([]interface{})), + } + + if allFieldsEmpty(obj) { + return nil + } + return obj +} + +// flattenVMwareUplinkPortOrderPolicy reads various fields from a +// VMwareUplinkPortOrderPolicy into the passed in ResourceData. +func flattenVMwareUplinkPortOrderPolicy(d *schema.ResourceData, obj *types.VMwareUplinkPortOrderPolicy) error { + if obj == nil { + return nil + } + + if err := d.Set("active_uplinks", obj.ActiveUplinkPort); err != nil { + return err + } + if err := d.Set("standby_uplinks", obj.StandbyUplinkPort); err != nil { + return err + } + return nil +} + +// expandVmwareUplinkPortTeamingPolicy reads certain ResourceData keys and +// returns a VmwareUplinkPortTeamingPolicy. +func expandVmwareUplinkPortTeamingPolicy(d *schema.ResourceData) *types.VmwareUplinkPortTeamingPolicy { + obj := &types.VmwareUplinkPortTeamingPolicy{ + Policy: getStringPolicy(d, "teaming_policy"), + NotifySwitches: getBoolPolicy(d, "notify_switches"), + RollingOrder: getBoolPolicyReverse(d, "failback"), + FailureCriteria: expandDVSFailureCriteria(d), + UplinkPortOrder: expandVMwareUplinkPortOrderPolicy(d), + } + + if allFieldsEmpty(obj) { + return nil + } + return obj +} + +// flattenVmwareUplinkPortTeamingPolicy reads various fields from a +// VmwareUplinkPortTeamingPolicy into the passed in ResourceData. +func flattenVmwareUplinkPortTeamingPolicy(d *schema.ResourceData, obj *types.VmwareUplinkPortTeamingPolicy) error { + if obj == nil { + return nil + } + + setStringPolicy(d, "teaming_policy", obj.Policy) + setBoolPolicy(d, "notify_switches", obj.NotifySwitches) + setBoolPolicyReverse(d, "failback", obj.RollingOrder) + + if err := flattenDVSFailureCriteria(d, obj.FailureCriteria); err != nil { + return err + } + if err := flattenVMwareUplinkPortOrderPolicy(d, obj.UplinkPortOrder); err != nil { + return err + } + return nil +} + +// expandDVSSecurityPolicy reads certain ResourceData keys and +// returns a DVSSecurityPolicy. +func expandDVSSecurityPolicy(d *schema.ResourceData) *types.DVSSecurityPolicy { + obj := &types.DVSSecurityPolicy{ + AllowPromiscuous: getBoolPolicy(d, "allow_promiscuous"), + MacChanges: getBoolPolicy(d, "allow_mac_changes"), + ForgedTransmits: getBoolPolicy(d, "allow_forged_transmits"), + } + + if allFieldsEmpty(obj) { + return nil + } + return obj +} + +// flattenDVSSecurityPolicy reads various fields from a +// DVSSecurityPolicy into the passed in ResourceData. +func flattenDVSSecurityPolicy(d *schema.ResourceData, obj *types.DVSSecurityPolicy) error { + if obj == nil { + return nil + } + + setBoolPolicy(d, "allow_promiscuous", obj.AllowPromiscuous) + setBoolPolicy(d, "allow_mac_changes", obj.MacChanges) + setBoolPolicy(d, "allow_forged_transmits", obj.ForgedTransmits) + return nil +} + +// expandVMwareUplinkLacpPolicy reads certain ResourceData keys and +// returns a VMwareUplinkLacpPolicy. +func expandVMwareUplinkLacpPolicy(d *schema.ResourceData) *types.VMwareUplinkLacpPolicy { + obj := &types.VMwareUplinkLacpPolicy{ + Enable: getBoolPolicy(d, "lacp_enabled"), + Mode: getStringPolicy(d, "lacp_mode"), + } + + if allFieldsEmpty(obj) { + return nil + } + return obj +} + +// flattenVMwareUplinkLacpPolicy reads various fields from a +// VMwareUplinkLacpPolicy into the passed in ResourceData. +func flattenVMwareUplinkLacpPolicy(d *schema.ResourceData, obj *types.VMwareUplinkLacpPolicy) error { + if obj == nil { + return nil + } + + setBoolPolicy(d, "lacp_enabled", obj.Enable) + setStringPolicy(d, "lacp_mode", obj.Mode) + return nil +} + +// expandDVSTrafficShapingPolicyIngress reads certain ResourceData keys and +// returns a DVSTrafficShapingPolicy for ingress traffic. +func expandDVSTrafficShapingPolicyIngress(d *schema.ResourceData) *types.DVSTrafficShapingPolicy { + obj := &types.DVSTrafficShapingPolicy{ + Enabled: getBoolPolicy(d, "ingress_shaping_enabled"), + AverageBandwidth: getLongPolicy(d, "ingress_shaping_average_bandwidth"), + PeakBandwidth: getLongPolicy(d, "ingress_shaping_peak_bandwidth"), + BurstSize: getLongPolicy(d, "ingress_shaping_burst_size"), + } + + if allFieldsEmpty(obj) { + return nil + } + return obj +} + +// flattenDVSTrafficShapingPolicyIngress reads various fields from the +// DVSTrafficShapingPolicy ingress policy into the passed in ResourceData. +func flattenDVSTrafficShapingPolicyIngress(d *schema.ResourceData, obj *types.DVSTrafficShapingPolicy) error { + if obj == nil { + return nil + } + + setBoolPolicy(d, "ingress_shaping_enabled", obj.Enabled) + setLongPolicy(d, "ingress_shaping_average_bandwidth", obj.AverageBandwidth) + setLongPolicy(d, "ingress_shaping_peak_bandwidth", obj.PeakBandwidth) + setLongPolicy(d, "ingress_shaping_burst_size", obj.BurstSize) + + return nil +} + +// expandDVSTrafficShapingPolicyEgress reads certain ResourceData keys and +// returns a DVSTrafficShapingPolicy for egress traffic. +func expandDVSTrafficShapingPolicyEgress(d *schema.ResourceData) *types.DVSTrafficShapingPolicy { + obj := &types.DVSTrafficShapingPolicy{ + Enabled: getBoolPolicy(d, "egress_shaping_enabled"), + AverageBandwidth: getLongPolicy(d, "egress_shaping_average_bandwidth"), + PeakBandwidth: getLongPolicy(d, "egress_shaping_peak_bandwidth"), + BurstSize: getLongPolicy(d, "egress_shaping_burst_size"), + } + + if allFieldsEmpty(obj) { + return nil + } + return obj +} + +// flattenDVSTrafficShapingPolicyEgress reads various fields from the +// DVSTrafficShapingPolicy egress policy into the passed in ResourceData. +func flattenDVSTrafficShapingPolicyEgress(d *schema.ResourceData, obj *types.DVSTrafficShapingPolicy) error { + if obj == nil { + return nil + } + + setBoolPolicy(d, "egress_shaping_enabled", obj.Enabled) + setLongPolicy(d, "egress_shaping_average_bandwidth", obj.AverageBandwidth) + setLongPolicy(d, "egress_shaping_peak_bandwidth", obj.PeakBandwidth) + setLongPolicy(d, "egress_shaping_burst_size", obj.BurstSize) + return nil +} + +// expandVMwareDVSPortSetting reads certain ResourceData keys and +// returns a VMwareDVSPortSetting. +func expandVMwareDVSPortSetting(d *schema.ResourceData) *types.VMwareDVSPortSetting { + obj := &types.VMwareDVSPortSetting{ + DVPortSetting: types.DVPortSetting{ + Blocked: getBoolPolicy(d, "block_all_ports"), + InShapingPolicy: expandDVSTrafficShapingPolicyIngress(d), + OutShapingPolicy: expandDVSTrafficShapingPolicyEgress(d), + }, + Vlan: expandBaseVmwareDistributedVirtualSwitchVlanSpec(d), + UplinkTeamingPolicy: expandVmwareUplinkPortTeamingPolicy(d), + SecurityPolicy: expandDVSSecurityPolicy(d), + IpfixEnabled: getBoolPolicy(d, "netflow_enabled"), + TxUplink: getBoolPolicy(d, "tx_uplink"), + LacpPolicy: expandVMwareUplinkLacpPolicy(d), + } + + if allFieldsEmpty(obj) { + return nil + } + return obj +} + +// flattenVMwareDVSPortSetting reads various fields from a +// VMwareDVSPortSetting into the passed in ResourceData. +func flattenVMwareDVSPortSetting(d *schema.ResourceData, obj *types.VMwareDVSPortSetting) error { + if obj == nil { + return nil + } + + setBoolPolicy(d, "block_all_ports", obj.Blocked) + setBoolPolicy(d, "netflow_enabled", obj.IpfixEnabled) + setBoolPolicy(d, "tx_uplink", obj.TxUplink) + + if err := flattenDVSTrafficShapingPolicyIngress(d, obj.InShapingPolicy); err != nil { + return err + } + if err := flattenDVSTrafficShapingPolicyEgress(d, obj.OutShapingPolicy); err != nil { + return err + } + if err := flattenBaseVmwareDistributedVirtualSwitchVlanSpec(d, obj.Vlan); err != nil { + return err + } + if err := flattenVmwareUplinkPortTeamingPolicy(d, obj.UplinkTeamingPolicy); err != nil { + return err + } + if err := flattenDVSSecurityPolicy(d, obj.SecurityPolicy); err != nil { + return err + } + if err := flattenVMwareUplinkLacpPolicy(d, obj.LacpPolicy); err != nil { + return err + } + return nil +} diff --git a/vsphere/distributed_virtual_switch_helper.go b/vsphere/distributed_virtual_switch_helper.go index a7a30988f..20db9f8da 100644 --- a/vsphere/distributed_virtual_switch_helper.go +++ b/vsphere/distributed_virtual_switch_helper.go @@ -4,7 +4,6 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform/helper/schema" "github.com/vmware/govmomi" "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" @@ -13,97 +12,141 @@ import ( "github.com/vmware/govmomi/vim25/types" ) -// Get a list of ManagedObjectReference to HostSystem to use in creating the uplinks for the DVS -func getHostSystemManagedObjectReference(d *schema.ResourceData, client *govmomi.Client) (map[string]types.ManagedObjectReference, error) { - mor := make(map[string]types.ManagedObjectReference) - - if v, ok := d.GetOk("host"); ok { - for _, vi := range v.([]interface{}) { - hi := vi.(map[string]interface{}) - hsID := hi["host_system_id"].(string) - - h, err := hostSystemFromID(client, hsID) - if err != nil { - return nil, err - } - mor[hsID] = h.Common.Reference() - } - } - return mor, nil +var dvsVersions = []string{ + "5.0.0", + "5.1.0", + "5.5.0", + "6.0.0", + "6.5.0", } -// Check if a DVS exists and return a reference to it in case it does -func dvsExists(d *schema.ResourceData, meta interface{}) (object.NetworkReference, error) { - client := meta.(*govmomi.Client) - name := d.Get("name").(string) - - dc, err := datacenterFromID(client, d.Get("datacenter_id").(string)) +// dvsFromUUID gets a DVS object from its UUID. +func dvsFromUUID(client *govmomi.Client, uuid string) (*object.VmwareDistributedVirtualSwitch, error) { + dvsm := types.ManagedObjectReference{Type: "DistributedVirtualSwitchManager", Value: "DVSManager"} + req := &types.QueryDvsByUuid{ + This: dvsm, + Uuid: uuid, + } + resp, err := methods.QueryDvsByUuid(context.TODO(), client, req) if err != nil { return nil, err } - finder := find.NewFinder(client.Client, true) - finder = finder.SetDatacenter(dc) - - dvs, err := finder.Network(context.TODO(), name) - return dvs, err + return dvsFromMOID(client, resp.Returnval.Reference().Value) } -func dvsFromName(client *govmomi.Client, dId, name string) (*mo.DistributedVirtualSwitch, error) { - dc, err := datacenterFromID(client, dId) +// dvsFromMOID locates a DVS by its managed object reference ID. +func dvsFromMOID(client *govmomi.Client, id string) (*object.VmwareDistributedVirtualSwitch, error) { + finder := find.NewFinder(client.Client, false) + + ref := types.ManagedObjectReference{ + Type: "VmwareDistributedVirtualSwitch", + Value: id, + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + ds, err := finder.ObjectReference(ctx, ref) if err != nil { return nil, err } + // Should be safe to return here. If our reference returned here and is not a + // VmwareDistributedVirtualSwitch, then we have bigger problems and to be + // honest we should be panicking anyway. + return ds.(*object.VmwareDistributedVirtualSwitch), nil +} - finder := find.NewFinder(client.Client, true) - finder = finder.SetDatacenter(dc) +// dvsFromPath gets a DVS object from its path. +func dvsFromPath(client *govmomi.Client, name string, dc *object.Datacenter) (*object.VmwareDistributedVirtualSwitch, error) { + finder := find.NewFinder(client.Client, false) + if dc != nil { + finder.SetDatacenter(dc) + } - dvs, err := finder.Network(context.TODO(), name) + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + net, err := finder.Network(ctx, name) + if err != nil { + return nil, err + } + if net.Reference().Type != "VmwareDistributedVirtualSwitch" { + return nil, fmt.Errorf("network at path %q is not a VMware distributed virtual switch (type %s)", name, net.Reference().Type) + } + return dvsFromMOID(client, net.Reference().Value) +} - var mdvs mo.DistributedVirtualSwitch - pc := client.PropertyCollector() +// dvsProperties is a convenience method that wraps fetching the DVS MO from +// its higher-level object. +func dvsProperties(dvs *object.VmwareDistributedVirtualSwitch) (*mo.VmwareDistributedVirtualSwitch, error) { ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) defer cancel() - if err := pc.RetrieveOne(ctx, dvs.Reference(), nil, &mdvs); err != nil { - return nil, fmt.Errorf("error fetching uuid property: %s", err) + var props mo.VmwareDistributedVirtualSwitch + if err := dvs.Properties(ctx, dvs.Reference(), nil, &props); err != nil { + return nil, err } - return &mdvs, nil + return &props, nil } -func dvsFromUuid(client *govmomi.Client, uuid string) (*mo.DistributedVirtualSwitch, error) { - dvsm := types.ManagedObjectReference{Type: "DistributedVirtualSwitchManager", Value: "DVSManager"} - req := &types.QueryDvsByUuid{ - This: dvsm, - Uuid: uuid, +// upgradeDVS upgrades a DVS to a specific version. Downgrades are not +// supported and will result in an error. This should be checked before running +// this function. +func upgradeDVS(client *govmomi.Client, dvs *object.VmwareDistributedVirtualSwitch, version string) error { + req := &types.PerformDvsProductSpecOperation_Task{ + This: dvs.Reference(), + Operation: "upgrade", + ProductSpec: &types.DistributedVirtualSwitchProductSpec{ + Version: version, + }, } - dvs, err := methods.QueryDvsByUuid(context.TODO(), client, req) + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + resp, err := methods.PerformDvsProductSpecOperation_Task(ctx, client, req) if err != nil { - return nil, fmt.Errorf("error fetching dvs from uuid: %s", err) + return err + } + task := object.NewTask(client.Client, resp.Returnval) + tctx, tcancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer tcancel() + if err := task.Wait(tctx); err != nil { + return err } - var mdvs mo.DistributedVirtualSwitch - pc := client.PropertyCollector() + return nil +} + +// updateDVSConfiguration contains the atomic update/wait operation for a DVS. +func updateDVSConfiguration(client *govmomi.Client, dvs *object.VmwareDistributedVirtualSwitch, spec *types.VMwareDVSConfigSpec) error { ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) defer cancel() - if err := pc.RetrieveOne(ctx, dvs.Returnval.Reference(), nil, &mdvs); err != nil { - return nil, fmt.Errorf("error fetching distributed virtual switch: %s", err) + task, err := dvs.Reconfigure(ctx, spec) + if err != nil { + return err } - return &mdvs, nil + tctx, tcancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer tcancel() + if err := task.Wait(tctx); err != nil { + return err + } + return nil } -// check if host is in refs with the help of hosts -// not very efficient, but the number of entries is usually pretty small -func isHostPartOfDVS(hosts []interface{}, refs map[string]types.ManagedObjectReference, host *types.ManagedObjectReference) map[string]interface{} { - if hosts == nil { - return nil +// enableDVSNetworkResourceManagement exposes the +// EnableNetworkResourceManagement method of the DistributedVirtualSwitch MO. +// This local implementation may go away if this is exposed in the higher-level +// object upstream. +func enableDVSNetworkResourceManagement(client *govmomi.Client, dvs *object.VmwareDistributedVirtualSwitch, enabled bool) error { + req := &types.EnableNetworkResourceManagement{ + This: dvs.Reference(), + Enable: enabled, } - for _, h := range hosts { - hi := h.(map[string]interface{}) - if val, ok := refs[hi["host_system_id"].(string)]; ok { - if val == *host { - return hi - } - } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + _, err := methods.EnableNetworkResourceManagement(ctx, client, req) + if err != nil { + return err } + return nil } diff --git a/vsphere/distributed_virtual_switch_structure.go b/vsphere/distributed_virtual_switch_structure.go index 8fe1b3b63..3004fea73 100644 --- a/vsphere/distributed_virtual_switch_structure.go +++ b/vsphere/distributed_virtual_switch_structure.go @@ -1,372 +1,634 @@ package vsphere import ( - "log" + "fmt" "strings" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" - "github.com/vmware/govmomi" - "github.com/vmware/govmomi/find" - "github.com/vmware/govmomi/object" - "github.com/vmware/govmomi/vim25/mo" "github.com/vmware/govmomi/vim25/types" - "golang.org/x/net/context" ) -var distributedVirtualSwitchNetworkResourceControlVersionAllowedValues = []string{ +var lacpAPIVersionAllowedValues = []string{ + string(types.VMwareDvsLacpApiVersionSingleLag), + string(types.VMwareDvsLacpApiVersionMultipleLag), +} + +var multicastFilteringModeAllowedValues = []string{ + string(types.VMwareDvsMulticastFilteringModeLegacyFiltering), + string(types.VMwareDvsMulticastFilteringModeSnooping), +} + +var privateVLANTypeAllowedValues = []string{ + string(types.VmwareDistributedVirtualSwitchPvlanPortTypePromiscuous), + string(types.VmwareDistributedVirtualSwitchPvlanPortTypeIsolated), + string(types.VmwareDistributedVirtualSwitchPvlanPortTypeCommunity), +} + +var networkResourceControlAllowedValues = []string{ string(types.DistributedVirtualSwitchNetworkResourceControlVersionVersion2), string(types.DistributedVirtualSwitchNetworkResourceControlVersionVersion3), } -var configSpecOperationAllowedValues = []string{ - string(types.VirtualDeviceConfigSpecOperationAdd), - string(types.VirtualDeviceConfigSpecOperationRemove), - string(types.VirtualDeviceConfigSpecOperationEdit), +var infrastructureTrafficClassValues = []string{ + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassManagement), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassFaultTolerance), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVmotion), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVirtualMachine), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassISCSI), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassNfs), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassHbr), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVsan), + string(types.DistributedVirtualSwitchHostInfrastructureTrafficClassVdp), } -func schemaDVSContactInfo() map[string]*schema.Schema { - return map[string]*schema.Schema{ - "contact": &schema.Schema{ +var sharesLevelAllowedValues = []string{ + string(types.SharesLevelLow), + string(types.SharesLevelNormal), + string(types.SharesLevelHigh), + string(types.SharesLevelCustom), +} + +// schemaVMwareDVSConfigSpec returns schema items for resources that need to work +// with a VMwareDVSConfigSpec. +func schemaVMwareDVSConfigSpec() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + // DVSContactInfo + "contact_detail": { Type: schema.TypeString, Optional: true, - Description: "The contact information for the person.", + Description: "The contact detail for this DVS.", }, - "contact_name": &schema.Schema{ + "contact_name": { Type: schema.TypeString, Optional: true, - Description: "The name of the person who is responsible for the switch.", + Description: "The contact name for this DVS.", }, - } -} -func expandDVSContactInfo(d *schema.ResourceData) *types.DVSContactInfo { - dci := &types.DVSContactInfo{} - if v, ok := d.GetOkExists("contact"); ok { - dci.Contact = v.(string) - } - - if v, ok := d.GetOkExists("contact_name"); ok { - dci.Name = v.(string) - } - return dci -} - -func flattenDVSContactInfo(d *schema.ResourceData, obj *mo.DistributedVirtualSwitch) { - config := obj.Config.GetDVSConfigInfo() - d.Set("contact", config.Contact.Contact) - d.Set("contact_name", config.Contact.Name) -} - -func schemaDistributedVirtualSwitchHostMemberPnicBacking() *schema.Schema { - // TODO maybe a set will fit better to avoid the mistake of putting a nic twice? - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - } -} + // DistributedVirtualSwitchHostMemberConfigSpec + "host": { + Type: schema.TypeSet, + Optional: true, + Description: "A host member specification.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + // DistributedVirtualSwitchHostMemberPnicSpec + "devices": { + Type: schema.TypeList, + Description: "Name of the physical NIC to be added to the proxy switch.", + Required: true, + MinItems: 1, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "host_system_id": { + Type: schema.TypeString, + Required: true, + Description: "The managed object ID of the host this specification applies to.", + ValidateFunc: validation.NoZeroValues, + }, + }, + }, + }, -func schemaDistributedVirtualSwitchHostMemberConfigSpec() *schema.Schema { - se := map[string]*schema.Schema{ - "max_proxy_switch_ports": &schema.Schema{ + // VMwareIpfixConfig (Netflow) + "netflow_active_flow_timeout": { + Type: schema.TypeInt, + Optional: true, + Description: "The number of seconds after which active flows are forced to be exported to the collector.", + Default: 60, + ValidateFunc: validation.IntBetween(60, 3600), + }, + "netflow_collector_ip_address": { + Type: schema.TypeString, + Optional: true, + Description: "IP address for the netflow collector, using IPv4 or IPv6. IPv6 is supported in vSphere Distributed Switch Version 6.0 or later.", + }, + "netflow_collector_port": { + Type: schema.TypeInt, + Optional: true, + Description: "The port for the netflow collector.", + ValidateFunc: validation.IntBetween(0, 65535), + }, + "netflow_idle_flow_timeout": { Type: schema.TypeInt, Optional: true, - Description: "Maximum number of ports allowed in the HostProxySwitch.", + Description: "The number of seconds after which idle flows are forced to be exported to the collector.", + Default: 15, + ValidateFunc: validation.IntBetween(10, 600), + }, + "netflow_internal_flows_only": { + Type: schema.TypeBool, + Optional: true, + Description: "Whether to limit analysis to traffic that has both source and destination served by the same host.", + }, + "netflow_observation_domain_id": { + Type: schema.TypeInt, + Optional: true, + Description: "The observation Domain ID for the netflow collector.", ValidateFunc: validation.IntAtLeast(0), }, - // The host name should be enough to get a reference to it, which is what we need here - "host_system_id": &schema.Schema{ + "netflow_sampling_rate": { + Type: schema.TypeInt, + Optional: true, + Description: "The ratio of total number of packets to the number of packets analyzed. Set to 0 to disable sampling, meaning that all packets are analyzed.", + ValidateFunc: validation.IntAtLeast(0), + }, + + // LinkDiscoveryProtocolConfig + "link_discovery_operation": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "Whether to advertise or listen for link discovery. Valid values are advertise, both, listen, and none.", + Default: string(types.LinkDiscoveryProtocolConfigOperationTypeListen), + ValidateFunc: validation.StringInSlice(linkDiscoveryProtocolConfigOperationAllowedValues, false), + }, + "link_discovery_protocol": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The discovery protocol type. Valid values are cdp and lldp.", + Default: string(types.LinkDiscoveryProtocolConfigProtocolTypeCdp), + ValidateFunc: validation.StringInSlice(linkDiscoveryProtocolConfigProtocolAllowedValues, false), + }, + + // DVSNameArrayUplinkPortPolicy + "uplinks": &schema.Schema{ + Type: schema.TypeList, + Optional: true, + Computed: true, + Description: "A list of uplink ports. The contents of this list control both the uplink count and names of the uplinks on the DVS across hosts.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name for the DVS. Must be unique in the folder that it is being created in.", + }, + "description": { Type: schema.TypeString, Optional: true, - Description: "The managed object ID of the host to search for NICs on.", + Description: "The description of the DVS.", + }, + "ipv4_address": { + Type: schema.TypeString, + Optional: true, + Description: "The IPv4 address of the switch. This can be used to see the DVS as a unique device with NetFlow.", + }, + "lacp_api_version": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The Link Aggregation Control Protocol group version in the switch. Can be one of singleLag or multipleLag.", + ValidateFunc: validation.StringInSlice(lacpAPIVersionAllowedValues, false), + }, + "max_mtu": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "The maximum MTU on the switch.", + ValidateFunc: validation.IntBetween(1, 9000), + }, + "multicast_filtering_mode": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The multicast filtering mode on the switch. Can be one of legacyFiltering, or snooping.", + ValidateFunc: validation.StringInSlice(multicastFilteringModeAllowedValues, false), + }, + "network_resource_control_version": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "The network I/O control version to use. Can be one of version2 or version3.", + ValidateFunc: validation.StringInSlice(networkResourceControlAllowedValues, false), }, - // DistributedVirtualSwitchHostMemberPnicBacking extends DistributedVirtualSwitchHostMemberBacking - // which is a base class - "backing": schemaDistributedVirtualSwitchHostMemberPnicBacking(), - "vendor_specific_config": schemaDistributedVirtualSwitchKeyedOpaqueBlob(), - } - s := &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: se, + "config_version": { + Type: schema.TypeString, + Computed: true, + Description: "The version string of the configuration that this spec is trying to change.", }, } + mergeSchema(s, schemaVMwareDVSPortSetting()) + mergeSchema(s, schemaDvsHostInfrastructureTrafficResource()) return s } -func expandDistributedVirtualSwitchHostMemberConfigSpec(client *govmomi.Client, d *schema.ResourceData, dvs *mo.DistributedVirtualSwitch, refs map[string]types.ManagedObjectReference) []types.DistributedVirtualSwitchHostMemberConfigSpec { - // Configure the host and nic cards used as uplink for the DVS - var hmc []types.DistributedVirtualSwitchHostMemberConfigSpec - - var hosts []interface{} - if h, ok := d.GetOk("host"); ok { - hosts = h.([]interface{}) - } - // If the DVS exist we go through all the hosts and see which ones - // we have to delete or modify - if dvs != nil { - config := dvs.Config.GetDVSConfigInfo() - for _, h := range config.Host { - if host := isHostPartOfDVS(hosts, refs, h.Config.Host); host != nil { - // Edit - backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) - for _, nic := range host["backing"].([]interface{}) { - backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ - PnicDevice: strings.TrimSpace(nic.(string)), - }) - } - hcs := types.DistributedVirtualSwitchHostMemberConfigSpec{ - Host: *h.Config.Host, - Backing: backing, - Operation: "edit", // Options: "add", "edit", "remove" - } - hmc = append(hmc, hcs) - - // We take it out from the refs, on the last pass we consider whatever - // is left as to be added - delete(refs, host["host_system_id"].(string)) - } else { - log.Print("[TRACE] Removing host from uplinks") - // Remove - backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) - finder := find.NewFinder(client.Client, false) - - ds, err := finder.ObjectReference(context.TODO(), *h.Config.Host) - if err != nil { - continue - } - dso := ds.(*object.HostSystem) - var mh mo.HostSystem - err = dso.Properties(context.TODO(), ds.Reference(), []string{"config"}, &mh) - if err != nil { - continue - } - - for _, ps := range mh.Config.Network.ProxySwitch { - if ps.DvsUuid == d.Id() { - for _, nic := range ps.Pnic { - backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ - PnicDevice: string(nic), - }) - } - hcs := types.DistributedVirtualSwitchHostMemberConfigSpec{ - Host: *h.Config.Host, - Backing: backing, - Operation: "remove", // Options: "add", "edit", "remove" - } - hmc = append(hmc, hcs) - } - } - } - } +// expandDVSContactInfo reads certain ResourceData keys and +// returns a DVSContactInfo. +func expandDVSContactInfo(d *schema.ResourceData) *types.DVSContactInfo { + obj := &types.DVSContactInfo{ + Name: d.Get("contact_name").(string), + Contact: d.Get("contact_detail").(string), + } + return obj +} + +// flattenDVSContactInfo reads various fields from a +// DVSContactInfo into the passed in ResourceData. +func flattenDVSContactInfo(d *schema.ResourceData, obj types.DVSContactInfo) error { + d.Set("contact_name", obj.Name) + d.Set("conatct_detail", obj.Contact) + return nil +} + +// expandDistributedVirtualSwitchHostMemberConfigSpec reads certain keys from a +// Set object map and returns a DistributedVirtualSwitchHostMemberConfigSpec. +func expandDistributedVirtualSwitchHostMemberConfigSpec(d map[string]interface{}) types.DistributedVirtualSwitchHostMemberConfigSpec { + hostRef := &types.ManagedObjectReference{ + Type: "HostSystem", + Value: d["host_system_id"].(string), } - if hosts != nil { - // Add whatever is left - for _, host := range hosts { - hi := host.(map[string]interface{}) - if val, ok := refs[hi["host_system_id"].(string)]; ok { - backing := new(types.DistributedVirtualSwitchHostMemberPnicBacking) - for _, nic := range hi["backing"].([]interface{}) { - backing.PnicSpec = append(backing.PnicSpec, types.DistributedVirtualSwitchHostMemberPnicSpec{ - PnicDevice: strings.TrimSpace(nic.(string)), - }) - } - h := types.DistributedVirtualSwitchHostMemberConfigSpec{ - Host: val, - Backing: backing, - Operation: "add", // Options: "add", "edit", "remove" - } - hmc = append(hmc, h) - } + var pnSpecs []types.DistributedVirtualSwitchHostMemberPnicSpec + nics := sliceInterfacesToStrings(d["devices"].([]interface{})) + for _, nic := range nics { + pnSpec := types.DistributedVirtualSwitchHostMemberPnicSpec{ + PnicDevice: nic, } + pnSpecs = append(pnSpecs, pnSpec) + } + backing := types.DistributedVirtualSwitchHostMemberPnicBacking{ + PnicSpec: pnSpecs, } - return hmc + obj := types.DistributedVirtualSwitchHostMemberConfigSpec{ + Host: *hostRef, + Backing: &backing, + } + return obj } -func flattenDistributedVirtualSwitchHostMemberConfigSpec(client *govmomi.Client, d *schema.ResourceData, obj *mo.DistributedVirtualSwitch) { - log.Printf("[TRACE] Flattening DistributedVirtualSwitchHostMemberConfigSpec %v", d.Get("host")) - config := obj.Config.GetDVSConfigInfo() - var hosts []interface{} - for _, host := range config.Host { - log.Print("[TRACE] One host") - hm := make(map[string]interface{}) - - hm["host_system_id"] = host.Config.Host +// flattenDistributedVirtualSwitchHostMemberConfigSpec reads various fields +// from a DistributedVirtualSwitchHostMemberConfigSpec and returns a Set object +// map. +// +// This is the flatten counterpart to +// expandDistributedVirtualSwitchHostMemberConfigSpec. +func flattenDistributedVirtualSwitchHostMember(obj types.DistributedVirtualSwitchHostMember) map[string]interface{} { + d := make(map[string]interface{}) + d["host_system_id"] = obj.Config.Host.Value + + var devices []string + backing := obj.Config.Backing.(*types.DistributedVirtualSwitchHostMemberPnicBacking) + for _, spec := range backing.PnicSpec { + devices = append(devices, spec.PnicDevice) + } - backing := []string{} + d["devices"] = devices - finder := find.NewFinder(client.Client, false) + return d +} - ds, err := finder.ObjectReference(context.TODO(), *host.Config.Host) - if err != nil { - continue +// expandSliceOfDistributedVirtualSwitchHostMemberConfigSpec expands all host +// entires for a VMware DVS, detecting if a host spec needs to be added, +// removed, or updated as well. The whole slice is returned. +func expandSliceOfDistributedVirtualSwitchHostMemberConfigSpec(d *schema.ResourceData) []types.DistributedVirtualSwitchHostMemberConfigSpec { + var specs []types.DistributedVirtualSwitchHostMemberConfigSpec + o, n := d.GetChange("host") + os := o.(*schema.Set) + ns := n.(*schema.Set) + + // Make an intersection set. These hosts have not changed so we don't bother + // with them. + is := os.Intersection(ns) + os = os.Difference(is) + ns = ns.Difference(is) + + // Our old and new sets now have an accurate description of hosts that may + // have been added, removed, or changed. Add removed and modified hosts + // first. + for _, oe := range os.List() { + om := oe.(map[string]interface{}) + var found bool + for _, ne := range ns.List() { + nm := ne.(map[string]interface{}) + if nm["host_system_id"] == om["host_system_id"] { + found = true + } } - dso := ds.(*object.HostSystem) - var mh mo.HostSystem - err = dso.Properties(context.TODO(), ds.Reference(), []string{"config"}, &mh) - if err != nil { - continue + if !found { + spec := expandDistributedVirtualSwitchHostMemberConfigSpec(om) + spec.Operation = string(types.ConfigSpecOperationRemove) + specs = append(specs, spec) } + } - for _, ps := range mh.Config.Network.ProxySwitch { - log.Print("[TRACE] One proxy switch") - if ps.DvsUuid == d.Id() { - log.Print("[TRACE] Found the proxy switch for this DVS") - for _, nic := range ps.Pnic { - log.Printf("[TRACE] Pnic %s", nic) - sn := strings.Split(nic, "-") - backing = append(backing, sn[len(sn)-1]) - } + // Process new hosts now. These are ones that are only present in the new + // set. + for _, ne := range ns.List() { + nm := ne.(map[string]interface{}) + var found bool + for _, oe := range os.List() { + om := oe.(map[string]interface{}) + if om["host_system_id"] == nm["host_system_id"] { + found = true } } - if len(backing) > 0 { - hm["backing"] = backing + spec := expandDistributedVirtualSwitchHostMemberConfigSpec(nm) + if !found { + spec.Operation = string(types.ConfigSpecOperationAdd) + } else { + spec.Operation = string(types.ConfigSpecOperationEdit) } - hosts = append(hosts, hm) - log.Printf("[TRACE] Host after flattening %v", hosts) + specs = append(specs, spec) } - d.Set("host", hosts) + + // Done! + return specs } -func schemaDistributedVirtualSwitchKeyedOpaqueBlob() *schema.Schema { - return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "key": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "A key that identifies the opaque binary blob.", - }, - "opaque_data": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "The opaque data. It is recommended that base64 encoding be used for binary data.", - }, - }, - }, +// flattenSliceOfDistributedVirtualSwitchHostMember creates a set of all host +// entries for a supplied slice of DistributedVirtualSwitchHostMember. +// +// This is the flatten counterpart to +// expandSliceOfDistributedVirtualSwitchHostMemberConfigSpec. +func flattenSliceOfDistributedVirtualSwitchHostMember(d *schema.ResourceData, members []types.DistributedVirtualSwitchHostMember) error { + var hosts []map[string]interface{} + for _, m := range members { + hosts = append(hosts, flattenDistributedVirtualSwitchHostMember(m)) } + if err := d.Set("host", hosts); err != nil { + return err + } + return nil } -func schemaDVSConfiSpec() map[string]*schema.Schema { - s := map[string]*schema.Schema{ - "config_version": &schema.Schema{ - Type: schema.TypeString, - Computed: true, - Description: "The version string of the configuration that this spec is trying to change. This property is ignored during switch creation.", - }, - "default_proxy_switch_max_num_ports": &schema.Schema{ +// expandVMwareIpfixConfig reads certain ResourceData keys and +// returns a VMwareIpfixConfig. +func expandVMwareIpfixConfig(d *schema.ResourceData) *types.VMwareIpfixConfig { + obj := &types.VMwareIpfixConfig{ + ActiveFlowTimeout: int32(d.Get("netflow_active_flow_timeout").(int)), + CollectorIpAddress: d.Get("netflow_collector_ip_address").(string), + CollectorPort: int32(d.Get("netflow_collector_port").(int)), + IdleFlowTimeout: int32(d.Get("netflow_idle_flow_timeout").(int)), + InternalFlowsOnly: d.Get("netflow_internal_flows_only").(bool), + ObservationDomainId: int64(d.Get("netflow_observation_domain_id").(int)), + SamplingRate: int32(d.Get("netflow_sampling_rate").(int)), + } + return obj +} + +// flattenVMwareIpfixConfig reads various fields from a +// VMwareIpfixConfig into the passed in ResourceData. +func flattenVMwareIpfixConfig(d *schema.ResourceData, obj *types.VMwareIpfixConfig) error { + d.Set("netflow_active_flow_timeout", obj.ActiveFlowTimeout) + d.Set("netflow_collector_ip_address", obj.CollectorIpAddress) + d.Set("netflow_collector_port", obj.CollectorPort) + d.Set("netflow_idle_flow_timeout", obj.IdleFlowTimeout) + d.Set("netflow_internal_flows_only", obj.InternalFlowsOnly) + d.Set("netflow_observation_domain_id", obj.ObservationDomainId) + d.Set("netflow_sampling_rate", obj.SamplingRate) + return nil +} + +// schemaDvsHostInfrastructureTrafficResource returns the respective schema +// keys for the various kinds of network I/O control traffic classes. The +// schema items are generated dynamically off of the list of available traffic +// classes for the currently supported vSphere API. Not all traffic classes may +// be supported across all DVS and network I/O control versions. +func schemaDvsHostInfrastructureTrafficResource() map[string]*schema.Schema { + s := make(map[string]*schema.Schema) + shareLevelFmt := "The allocation level for the %s traffic class. Can be one of high, low, normal, or custom." + shareCountFmt := "The amount of shares to allocate to the %s traffic class for a custom share level." + maxMbitFmt := "The maximum allowed usage for the %s traffic class, in Mbits/sec." + resMbitFmt := "The amount of guaranteed bandwidth for the %s traffic class, in Mbits/sec." + + for _, class := range infrastructureTrafficClassValues { + shareLevelKey := fmt.Sprintf("%s_share_level", strings.ToLower(class)) + shareCountKey := fmt.Sprintf("%s_share_count", strings.ToLower(class)) + maxMbitKey := fmt.Sprintf("%s_maximum_mbit", strings.ToLower(class)) + resMbitKey := fmt.Sprintf("%s_reservation_mbit", strings.ToLower(class)) + + s[shareLevelKey] = &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: fmt.Sprintf(shareLevelFmt, class), + ValidateFunc: validation.StringInSlice(sharesLevelAllowedValues, false), + } + s[shareCountKey] = &schema.Schema{ Type: schema.TypeInt, Optional: true, - Default: 512, - Description: "The default host proxy switch maximum port number.", + Computed: true, + Description: fmt.Sprintf(shareCountFmt, class), ValidateFunc: validation.IntAtLeast(0), - }, - "description": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "Set the description string of the switch.", - }, - "extension_key": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "The key of the extension registered by a remote server that controls the switch.", - }, - "host": schemaDistributedVirtualSwitchHostMemberConfigSpec(), - "name": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "The name of the switch. Must be unique in the parent folder.", - }, - "network_resource_control_version": &schema.Schema{ - Type: schema.TypeString, + } + s[maxMbitKey] = &schema.Schema{ + Type: schema.TypeInt, Optional: true, - Default: "version2", - Description: "Indicates the Network Resource Control APIs that are supported on the switch.", - ValidateFunc: validation.StringInSlice(distributedVirtualSwitchNetworkResourceControlVersionAllowedValues, false), - }, - "num_standalone_ports": &schema.Schema{ + Computed: true, + Description: fmt.Sprintf(maxMbitFmt, class), + ValidateFunc: validation.IntAtLeast(-1), + } + s[resMbitKey] = &schema.Schema{ Type: schema.TypeInt, Optional: true, - Default: 512, - Description: "The number of standalone ports in the switch. Standalone ports are ports that do not belong to any portgroup.", - ValidateFunc: validation.IntAtLeast(0), - }, - "switch_ip_address": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Description: "IP address for the switch, specified using IPv4 dot notation. IPv6 address is not supported for this property.", - }, - "vendor_specific_config": schemaDistributedVirtualSwitchKeyedOpaqueBlob(), + Computed: true, + Description: fmt.Sprintf(resMbitFmt, class), + ValidateFunc: validation.IntAtLeast(-1), + } } - mergeSchema(s, schemaDVSContactInfo()) return s } -func expandDVSConfigSpec(client *govmomi.Client, d *schema.ResourceData, dvs *mo.DistributedVirtualSwitch, refs map[string]types.ManagedObjectReference) *types.DVSConfigSpec { - obj := &types.DVSConfigSpec{} - - obj.Name = d.Get("name").(string) +// expandDvsHostInfrastructureTrafficResource reads the network I/O control +// resource data keys for the traffic class supplied by key and returns an +// appropriate types.DvsHostInfrastructureTrafficResource reference. This +// should be checked for nil to see if it should be added to the slice in the +// config. +func expandDvsHostInfrastructureTrafficResource(d *schema.ResourceData, key string) *types.DvsHostInfrastructureTrafficResource { + shareLevelKey := fmt.Sprintf("%s_share_level", strings.ToLower(key)) + shareCountKey := fmt.Sprintf("%s_share_count", strings.ToLower(key)) + maxMbitKey := fmt.Sprintf("%s_maximum_mbit", strings.ToLower(key)) + resMbitKey := fmt.Sprintf("%s_reservation_mbit", strings.ToLower(key)) + + obj := &types.DvsHostInfrastructureTrafficResource{ + AllocationInfo: types.DvsHostInfrastructureTrafficResourceAllocation{ + Limit: getInt64Ptr(d, maxMbitKey), + Reservation: getInt64Ptr(d, resMbitKey), + }, + } + shares := &types.SharesInfo{ + Level: types.SharesLevel(d.Get(shareLevelKey).(string)), + Shares: int32(d.Get(shareCountKey).(int)), + } + if !allFieldsEmpty(shares) { + obj.AllocationInfo.Shares = shares + } - if v, ok := d.GetOkExists("network_resource_control_version"); ok { - obj.NetworkResourceControlVersion = v.(string) + if allFieldsEmpty(obj) { + return nil } + obj.Key = key + return obj +} - if v, ok := d.GetOkExists("config_version"); ok { - obj.ConfigVersion = v.(string) +// flattenDvsHostInfrastructureTrafficResource reads various fields from a +// DvsHostInfrastructureTrafficResource and sets appropriate keys in the +// supplied ResourceData. +func flattenDvsHostInfrastructureTrafficResource(d *schema.ResourceData, obj types.DvsHostInfrastructureTrafficResource, key string) error { + shareLevelKey := fmt.Sprintf("%s_share_level", strings.ToLower(key)) + shareCountKey := fmt.Sprintf("%s_share_count", strings.ToLower(key)) + maxMbitKey := fmt.Sprintf("%s_maximum_mbit", strings.ToLower(key)) + resMbitKey := fmt.Sprintf("%s_reservation_mbit", strings.ToLower(key)) + + setInt64Ptr(d, maxMbitKey, obj.AllocationInfo.Limit) + setInt64Ptr(d, resMbitKey, obj.AllocationInfo.Reservation) + if obj.AllocationInfo.Shares != nil { + d.Set(shareLevelKey, obj.AllocationInfo.Shares.Level) + d.Set(shareCountKey, obj.AllocationInfo.Shares.Shares) } + return nil +} - obj.Contact = expandDVSContactInfo(d) +// expandSliceOfDvsHostInfrastructureTrafficResource expands all network I/O +// control resource entries that are currently supported in API, and returns a +// slice of DvsHostInfrastructureTrafficResource. +func expandSliceOfDvsHostInfrastructureTrafficResource(d *schema.ResourceData) []types.DvsHostInfrastructureTrafficResource { + var s []types.DvsHostInfrastructureTrafficResource + for _, key := range infrastructureTrafficClassValues { + v := expandDvsHostInfrastructureTrafficResource(d, key) + if v != nil { + s = append(s, *v) + } + } + return s +} - if v, ok := d.GetOkExists("default_proxy_switch_max_num_ports"); ok { - obj.NumStandalonePorts = int32(v.(int)) +// flattenSliceOfDvsHostInfrastructureTrafficResource reads in the supplied network I/O control allocation entries supplied via a respective DVSConfigInfo field and sets the appropriate keys in the supplied ResourceData. +func flattenSliceOfDvsHostInfrastructureTrafficResource(d *schema.ResourceData, s []types.DvsHostInfrastructureTrafficResource) error { + for _, v := range s { + if err := flattenDvsHostInfrastructureTrafficResource(d, v, v.Key); err != nil { + return err + } } + return nil +} - if v, ok := d.GetOkExists("description"); ok { - obj.Description = v.(string) +// expandDVSNameArrayUplinkPortPolicy reads certain ResourceData keys and +// returns a DVSNameArrayUplinkPortPolicy. +func expandDVSNameArrayUplinkPortPolicy(d *schema.ResourceData) *types.DVSNameArrayUplinkPortPolicy { + obj := &types.DVSNameArrayUplinkPortPolicy{ + UplinkPortName: sliceInterfacesToStrings(d.Get("uplinks").([]interface{})), + } + if allFieldsEmpty(obj) { + return nil } + return obj +} - if v, ok := d.GetOkExists("extension_key"); ok { - obj.ExtensionKey = v.(string) +// flattenDVSNameArrayUplinkPortPolicy reads various fields from a +// DVSNameArrayUplinkPortPolicy into the passed in ResourceData. +func flattenDVSNameArrayUplinkPortPolicy(d *schema.ResourceData, obj *types.DVSNameArrayUplinkPortPolicy) error { + if err := d.Set("uplinks", obj.UplinkPortName); err != nil { + return err } + return nil +} - // Always expand since even when removing we will need to mention hosts and nics - obj.Host = expandDistributedVirtualSwitchHostMemberConfigSpec(client, d, dvs, refs) +// expandVMwareDVSConfigSpec reads certain ResourceData keys and +// returns a VMwareDVSConfigSpec. +func expandVMwareDVSConfigSpec(d *schema.ResourceData) *types.VMwareDVSConfigSpec { + obj := &types.VMwareDVSConfigSpec{ + DVSConfigSpec: types.DVSConfigSpec{ + Name: d.Get("name").(string), + ConfigVersion: d.Get("config_version").(string), + DefaultPortConfig: expandVMwareDVSPortSetting(d), + Host: expandSliceOfDistributedVirtualSwitchHostMemberConfigSpec(d), + Description: d.Get("description").(string), + Contact: expandDVSContactInfo(d), + SwitchIpAddress: d.Get("ipv4_address").(string), + InfrastructureTrafficResourceConfig: expandSliceOfDvsHostInfrastructureTrafficResource(d), + NetworkResourceControlVersion: d.Get("network_resource_control_version").(string), + UplinkPortPolicy: expandDVSNameArrayUplinkPortPolicy(d), + }, + MaxMtu: int32(d.Get("max_mtu").(int)), + LinkDiscoveryProtocolConfig: expandLinkDiscoveryProtocolConfig(d), + IpfixConfig: expandVMwareIpfixConfig(d), + LacpApiVersion: d.Get("lacp_api_version").(string), + MulticastFilteringMode: d.Get("multicast_filtering_mode").(string), + } + return obj +} - if v, ok := d.GetOkExists("num_standalone_ports"); ok { - obj.NumStandalonePorts = int32(v.(int)) +// flattenVMwareDVSConfigInfo reads various fields from a +// VMwareDVSConfigInfo into the passed in ResourceData. +// +// This is the flatten counterpart to expandVMwareDVSConfigSpec, as the +// configuration info from a DVS comes back as this type instead of a specific +// ConfigSpec. +func flattenVMwareDVSConfigInfo(d *schema.ResourceData, obj *types.VMwareDVSConfigInfo) error { + d.Set("name", obj.Name) + d.Set("config_version", obj.ConfigVersion) + d.Set("description", obj.Description) + d.Set("ipv4_address", obj.SwitchIpAddress) + d.Set("max_mtu", obj.MaxMtu) + d.Set("lacp_api_version", obj.LacpApiVersion) + d.Set("multicast_filtering_mode", obj.MulticastFilteringMode) + d.Set("network_resource_control_version", obj.NetworkResourceControlVersion) + // This is not available in ConfigSpec but is available in ConfigInfo, so + // flatten it here. + d.Set("network_resource_control_enabled", obj.NetworkResourceManagementEnabled) + + // Version is set in this object too as ConfigInfo has the productInfo + // property that is outside of this ConfigSpec structure. + d.Set("version", obj.ProductInfo.Version) + + if err := flattenDVSNameArrayUplinkPortPolicy(d, obj.UplinkPortPolicy.(*types.DVSNameArrayUplinkPortPolicy)); err != nil { + return err + } + if err := flattenVMwareDVSPortSetting(d, obj.DefaultPortConfig.(*types.VMwareDVSPortSetting)); err != nil { + return err + } + if err := flattenSliceOfDistributedVirtualSwitchHostMember(d, obj.Host); err != nil { + return err + } + if err := flattenSliceOfDvsHostInfrastructureTrafficResource(d, obj.InfrastructureTrafficResourceConfig); err != nil { + return err } + if err := flattenDVSContactInfo(d, obj.Contact); err != nil { + return err + } + if err := flattenLinkDiscoveryProtocolConfig(d, obj.LinkDiscoveryProtocolConfig); err != nil { + return err + } + if err := flattenVMwareIpfixConfig(d, obj.IpfixConfig); err != nil { + return err + } + return nil +} - if v, ok := d.GetOkExists("switch_ip_addess"); ok { - obj.NumStandalonePorts = int32(v.(int)) +// schemaDVSCreateSpec returns schema items for resources that +// need to work with a DVSCreateSpec. +func schemaDVSCreateSpec() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + // DistributedVirtualSwitchProductSpec + "version": &schema.Schema{ + Type: schema.TypeString, + Computed: true, + Description: "The version of this virtual switch. Allowed versions are 6.5.0, 6.0.0, 5.5.0, 5.1.0, and 5.0.0.", + Optional: true, + ValidateFunc: validation.StringInSlice(dvsVersions, false), + }, } + mergeSchema(s, schemaVMwareDVSConfigSpec()) - return obj + return s } -func flattenDVSConfigSpec(client *govmomi.Client, d *schema.ResourceData, obj *mo.DistributedVirtualSwitch) error { - config := obj.Config.GetDVSConfigInfo() - d.Set("config_version", config.ConfigVersion) - d.Set("description", config.Description) - d.Set("extension_key", config.ExtensionKey) - d.Set("name", config.Name) - d.Set("network_resource_control_version", config.NetworkResourceControlVersion) - d.Set("description", config.Description) - d.Set("contact", config.Contact.Contact) - d.Set("contact_name", config.Contact.Name) - d.Set("num_standalone_ports", config.NumStandalonePorts) - d.Set("default_proxy_switch_max_num_ports", config.DefaultProxySwitchMaxNumPorts) - d.Set("switch_ip_address", config.SwitchIpAddress) - flattenDVSContactInfo(d, obj) - flattenDistributedVirtualSwitchHostMemberConfigSpec(client, d, obj) - - return nil +// expandDVSCreateSpec reads certain ResourceData keys and +// returns a DVSCreateSpec. +func expandDVSCreateSpec(d *schema.ResourceData) types.DVSCreateSpec { + // Since we are only working with the version string from the product spec, + // we don't have a separate expander/flattener for it. Just do that here. + obj := types.DVSCreateSpec{ + ProductInfo: &types.DistributedVirtualSwitchProductSpec{ + Version: d.Get("version").(string), + }, + ConfigSpec: expandVMwareDVSConfigSpec(d), + } + return obj } diff --git a/vsphere/folder_helper.go b/vsphere/folder_helper.go index c65082602..b451eb9c7 100644 --- a/vsphere/folder_helper.go +++ b/vsphere/folder_helper.go @@ -170,9 +170,11 @@ func folderFromObject(client *govmomi.Client, obj interface{}, folderType rootPa var p string var err error switch o := obj.(type) { - case (*object.Datastore): + case *object.VmwareDistributedVirtualSwitch: + p, err = rootPathParticleNetwork.PathFromNewRoot(o.InventoryPath, folderType, relative) + case *object.Datastore: p, err = rootPathParticleDatastore.PathFromNewRoot(o.InventoryPath, folderType, relative) - case (*object.HostSystem): + case *object.HostSystem: p, err = rootPathParticleHost.PathFromNewRoot(o.InventoryPath, folderType, relative) default: return nil, fmt.Errorf("unsupported object type %T", o) @@ -195,8 +197,20 @@ func datastoreFolderFromObject(client *govmomi.Client, obj interface{}, relative return validateDatastoreFolder(folder) } +// networkFolderFromObject returns an *object.Folder from a given object, +// and relative network folder path. If no such folder is found, of if it is +// not a network folder, an appropriate error will be returned. +func networkFolderFromObject(client *govmomi.Client, obj interface{}, relative string) (*object.Folder, error) { + folder, err := folderFromObject(client, obj, rootPathParticleNetwork, relative) + if err != nil { + return nil, err + } + + return validateNetworkFolder(folder) +} + // validateDatastoreFolder checks to make sure the folder is a datastore -// folder, and returns it if it is not, or an error if it isn't. +// folder, and returns it if it is, or an error if it isn't. func validateDatastoreFolder(folder *object.Folder) (*object.Folder, error) { ft, err := findFolderType(folder) if err != nil { @@ -208,6 +222,19 @@ func validateDatastoreFolder(folder *object.Folder) (*object.Folder, error) { return folder, nil } +// validateNetworkFolder checks to make sure the folder is a network folder, +// and returns it if it is, or an error if it isn't. +func validateNetworkFolder(folder *object.Folder) (*object.Folder, error) { + ft, err := findFolderType(folder) + if err != nil { + return nil, err + } + if ft != vSphereFolderTypeNetwork { + return nil, fmt.Errorf("%q is not a network folder", folder.InventoryPath) + } + return folder, nil +} + // pathIsEmpty checks a folder path to see if it's "empty" (ie: would resolve // to the root inventory path for a given type in a datacenter - "" or "/"). func pathIsEmpty(path string) bool { @@ -236,13 +263,12 @@ func moveObjectToFolder(ref types.ManagedObjectReference, folder *object.Folder) return task.Wait(tctx) } -// parentFolderFromPath takes a relative object path (usually a folder), an -// object type, and an optional supplied datacenter, and returns the parent -// *object.Folder if it exists. +// folderFromPath takes a relative folder path, an object type, and an optional +// supplied datacenter, and returns the respective *object.Folder if it exists. // // The datacenter supplied in dc cannot be nil if the folder type supplied by // ft is something else other than vSphereFolderTypeDatacenter. -func parentFolderFromPath(c *govmomi.Client, p string, ft vSphereFolderType, dc *object.Datacenter) (*object.Folder, error) { +func folderFromPath(c *govmomi.Client, p string, ft vSphereFolderType, dc *object.Datacenter) (*object.Folder, error) { var fp string if ft == vSphereFolderTypeDatacenter { fp = "/" + p @@ -250,7 +276,17 @@ func parentFolderFromPath(c *govmomi.Client, p string, ft vSphereFolderType, dc pt := rootPathParticle(ft) fp = pt.PathFromDatacenter(dc, p) } - return folderFromAbsolutePath(c, path.Dir(fp)) + return folderFromAbsolutePath(c, fp) +} + +// parentFolderFromPath takes a relative object path (usually a folder), an +// object type, and an optional supplied datacenter, and returns the parent +// *object.Folder if it exists. +// +// The datacenter supplied in dc cannot be nil if the folder type supplied by +// ft is something else other than vSphereFolderTypeDatacenter. +func parentFolderFromPath(c *govmomi.Client, p string, ft vSphereFolderType, dc *object.Datacenter) (*object.Folder, error) { + return folderFromPath(c, path.Dir(p), ft, dc) } // folderFromID locates a Folder by its managed object reference ID. diff --git a/vsphere/helper_test.go b/vsphere/helper_test.go index cd48227a4..c62821cc7 100644 --- a/vsphere/helper_test.go +++ b/vsphere/helper_test.go @@ -293,3 +293,22 @@ func testGetFolderProperties(s *terraform.State, resourceName string) (*mo.Folde } return folderProperties(folder) } + +// testGetDVS is a convenience method to fetch a DVS by resource name. +func testGetDVS(s *terraform.State, resourceName string) (*object.VmwareDistributedVirtualSwitch, error) { + tVars, err := testClientVariablesForResource(s, fmt.Sprintf("vsphere_distributed_virtual_switch.%s", resourceName)) + if err != nil { + return nil, err + } + return dvsFromUUID(tVars.client, tVars.resourceID) +} + +// testGetDVSProperties is a convenience method that adds an extra step to +// testGetDVS to get the properties of a DVS. +func testGetDVSProperties(s *terraform.State, resourceName string) (*mo.VmwareDistributedVirtualSwitch, error) { + dvs, err := testGetDVS(s, resourceName) + if err != nil { + return nil, err + } + return dvsProperties(dvs) +} diff --git a/vsphere/provider.go b/vsphere/provider.go index 2b8edb261..906e1c363 100644 --- a/vsphere/provider.go +++ b/vsphere/provider.go @@ -70,9 +70,9 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "vsphere_datacenter": resourceVSphereDatacenter(), + "vsphere_distributed_virtual_switch": resourceVSphereDistributedVirtualSwitch(), "vsphere_file": resourceVSphereFile(), "vsphere_folder": resourceVSphereFolder(), - "vsphere_distributed_virtual_switch": resourceVSphereDistributedVirtualSwitch(), "vsphere_host_port_group": resourceVSphereHostPortGroup(), "vsphere_host_virtual_switch": resourceVSphereHostVirtualSwitch(), "vsphere_license": resourceVSphereLicense(), diff --git a/vsphere/resource_vsphere_distributed_virtual_switch.go b/vsphere/resource_vsphere_distributed_virtual_switch.go index 6ecc3be0d..c64b57069 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch.go @@ -1,217 +1,289 @@ package vsphere import ( + "context" "fmt" - "log" - "time" - "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/helper/schema" - "github.com/vmware/govmomi" - "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" - "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/types" - "golang.org/x/net/context" +) + +const ( + retryDVSUpdatePending = "retryDVSUpdatePending" + retryDVSUpdateCompleted = "retryDVSUpdateCompleted" + retryDVSUpdateError = "retryDVSUpdateError" ) func resourceVSphereDistributedVirtualSwitch() *schema.Resource { s := map[string]*schema.Schema{ - "datacenter_id": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, + "datacenter_id": { + Type: schema.TypeString, + Description: "The ID of the datacenter to create this virtual switch in.", + Required: true, + ForceNew: true, + }, + "folder": { + Type: schema.TypeString, + Description: "The folder to create this virtual switch in, relative to the datacenter.", + Optional: true, + ForceNew: true, }, + "network_resource_control_enabled": { + Type: schema.TypeBool, + Description: "Whether or not to enable network resource control, enabling advanced traffic shaping and resource control features.", + Optional: true, + }, + // Tagging + vSphereTagAttributeKey: tagsSchema(), + } + mergeSchema(s, schemaDVSCreateSpec()) + + // Some keys end up taking on defaults and need to be computed as a result - + // these are mainly in the default port setting policies. + csk := []string{ + "egress_shaping_average_bandwidth", + "egress_shaping_burst_size", + "egress_shaping_peak_bandwidth", + "failback", + "ingress_shaping_average_bandwidth", + "ingress_shaping_burst_size", + "ingress_shaping_peak_bandwidth", + "lacp_mode", + "notify_switches", + "teaming_policy", + "active_uplinks", + "standby_uplinks", + } + for _, k := range csk { + s[k].Computed = true } - mergeSchema(s, schemaDVSConfiSpec()) return &schema.Resource{ Create: resourceVSphereDistributedVirtualSwitchCreate, Read: resourceVSphereDistributedVirtualSwitchRead, Update: resourceVSphereDistributedVirtualSwitchUpdate, Delete: resourceVSphereDistributedVirtualSwitchDelete, + Importer: &schema.ResourceImporter{ + State: resourceVSphereDistributedVirtualSwitchImport, + }, Schema: s, } } func resourceVSphereDistributedVirtualSwitchCreate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*govmomi.Client) - name := d.Get("name").(string) - dId := d.Get("datacenter_id").(string) - - dc, err := datacenterFromID(client, dId) + client := meta.(*VSphereClient).vimClient + if err := validateVirtualCenter(client); err != nil { + return err + } + tagsClient, err := tagsClientIfDefined(d, meta) if err != nil { - return fmt.Errorf("%s", err) + return err } - finder := find.NewFinder(client.Client, true) - finder = finder.SetDatacenter(dc) - - df, err := dc.Folders(context.TODO()) + dc, err := datacenterFromID(client, d.Get("datacenter_id").(string)) if err != nil { - return fmt.Errorf("%s", err) + return fmt.Errorf("cannot locate datacenter: %s", err) } - f := df.NetworkFolder - - hosts_mor, err := getHostSystemManagedObjectReference(d, client) + folder, err := folderFromPath(client, d.Get("folder").(string), vSphereFolderTypeNetwork, dc) if err != nil { - return fmt.Errorf("%s", err) + return fmt.Errorf("cannot locate folder: %s", err) } - spec := expandDVSConfigSpec(client, d, nil, hosts_mor) - dvsCreateSpec := types.DVSCreateSpec{ConfigSpec: spec} - - task, err := f.CreateDVS(context.TODO(), dvsCreateSpec) + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + spec := expandDVSCreateSpec(d) + task, err := folder.CreateDVS(ctx, spec) if err != nil { - return fmt.Errorf("%s", err) + return fmt.Errorf("error creating DVS: %s", err) } - - err = task.Wait(context.TODO()) + tctx, tcancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer tcancel() + info, err := task.WaitForResult(tctx, nil) if err != nil { - return fmt.Errorf("%s", err) + return fmt.Errorf("error waiting for DVS creation to complete: %s", err) } - // Ideally from the CreateDVS opperation we should be able to access the UUID - // but I'm not sure how with the current operations exposed by the SDK - dvs, err := dvsFromName(client, dId, name) + dvs, err := dvsFromMOID(client, info.Result.(types.ManagedObjectReference).Value) if err != nil { - return fmt.Errorf("%s", err) + return fmt.Errorf("error fetching DVS after creation: %s", err) + } + props, err := dvsProperties(dvs) + if err != nil { + return fmt.Errorf("error fetching DVS properties after creation: %s", err) } - d.SetId(dvs.Uuid) - - return resourceVSphereDistributedVirtualSwitchRead(d, meta) -} + d.SetId(props.Uuid) -func resourceVSphereDistributedVirtualSwitchRead(d *schema.ResourceData, meta interface{}) error { - client := meta.(*govmomi.Client) - dvs, err := dvsFromUuid(client, d.Id()) - if err != nil { - d.SetId("") - return fmt.Errorf("error reading data: %s", err) + // Enable network resource I/O control if it needs to be enabled + if d.Get("network_resource_control_enabled").(bool) { + enableDVSNetworkResourceManagement(client, dvs, true) } - if err := flattenDVSConfigSpec(client, d, dvs); err != nil { - return fmt.Errorf("error setting resource data: %s", err) + // Apply any pending tags now + if tagsClient != nil { + if err := processTagDiff(tagsClient, d, object.NewReference(client.Client, dvs.Reference())); err != nil { + return fmt.Errorf("error updating tags: %s", err) + } } - return nil + return resourceVSphereDistributedVirtualSwitchRead(d, meta) } -func resourceVSphereDistributedVirtualSwitchUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*govmomi.Client) - dvs, err := dvsFromUuid(client, d.Id()) +func resourceVSphereDistributedVirtualSwitchRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*VSphereClient).vimClient + if err := validateVirtualCenter(client); err != nil { + return err + } + id := d.Id() + dvs, err := dvsFromUUID(client, id) if err != nil { - return fmt.Errorf("%s", err) + return fmt.Errorf("could not find DVS %q: %s", id, err) } - - hosts_refs, err := getHostSystemManagedObjectReference(d, client) + props, err := dvsProperties(dvs) if err != nil { - return fmt.Errorf("%s", err) + return fmt.Errorf("error fetching DVS properties: %s", err) } - spec := expandDVSConfigSpec(client, d, dvs, hosts_refs) - - n := object.NewDistributedVirtualSwitch(client.Client, dvs.Reference()) - req := &types.ReconfigureDvs_Task{ - This: n.Reference(), - Spec: spec, + // Set the datacenter ID, for completion's sake when importing + dcp, err := rootPathParticleNetwork.SplitDatacenter(dvs.InventoryPath) + if err != nil { + return fmt.Errorf("error parsing datacenter from inventory path: %s", err) + } + dc, err := getDatacenter(client, dcp) + if err != nil { + return fmt.Errorf("error locating datacenter: %s", err) } + d.Set("datacenter_id", dc.Reference().Value) - _, err = methods.ReconfigureDvs_Task(context.TODO(), client, req) + // Set the folder + folder, err := rootPathParticleNetwork.SplitRelativeFolder(dvs.InventoryPath) if err != nil { - return fmt.Errorf("%s", err) + return fmt.Errorf("error parsing DVS path %q: %s", dvs.InventoryPath, err) } + d.Set("folder", normalizeFolderPath(folder)) - // Wait for the distributed virtual switch resource to be destroyed - stateConf := &resource.StateChangeConf{ - Pending: []string{"Updating"}, - Target: []string{"Updated"}, - Refresh: resourceVSphereDVSStateUpdateRefreshFunc(d, meta), - Timeout: 10 * time.Minute, - MinTimeout: 3 * time.Second, - Delay: 5 * time.Second, + // Read in config info + if err := flattenVMwareDVSConfigInfo(d, props.Config.(*types.VMwareDVSConfigInfo)); err != nil { + return err } - _, err = stateConf.WaitForState() - if err != nil { - name := d.Get("name").(string) - return fmt.Errorf("error waiting for distributed virtual switch (%s) to be updated: %s", name, err) + // Read tags if we have the ability to do so + if tagsClient, _ := meta.(*VSphereClient).TagsClient(); tagsClient != nil { + if err := readTagsForResource(tagsClient, dvs, d); err != nil { + return fmt.Errorf("error reading tags: %s", err) + } } - return resourceVSphereDistributedVirtualSwitchRead(d, meta) + return nil } -func resourceVSphereDVSStateUpdateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Print("[TRACE] Refreshing distributed virtual switch state, looking for config changes") - client := meta.(*govmomi.Client) - dvs, err := dvsFromUuid(client, d.Id()) - if err != nil { - return nil, "Failed", err - } - config := dvs.Config.GetDVSConfigInfo() - cv := d.Get("config_version").(string) - log.Printf("[TRACE] Current version %s. Old version %s", config.ConfigVersion, cv) - if config.ConfigVersion != cv { - log.Print("[TRACE] Distributed virtual switch config updated") - return dvs, "Updated", nil - } else { - return dvs, "Updating", nil - } +func resourceVSphereDistributedVirtualSwitchUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*VSphereClient).vimClient + if err := validateVirtualCenter(client); err != nil { + return err + } + tagsClient, err := tagsClientIfDefined(d, meta) + if err != nil { + return err + } + id := d.Id() + dvs, err := dvsFromUUID(client, id) + if err != nil { + return fmt.Errorf("could not find DVS %q: %s", id, err) } -} -func resourceVSphereDVSStateRefreshFunc(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - log.Print("[TRACE] Refreshing distributed virtual switch state") - dvs, err := dvsExists(d, meta) - if err != nil { - switch err.(type) { - case *find.NotFoundError: - log.Printf("[TRACE] Refreshing state. Distributed virtual switch not found: %s", err) - return nil, "InProgress", nil - default: - return nil, "Failed", err + // If we have a pending version upgrade, do that first. + if d.HasChange("version") { + old, new := d.GetChange("version") + var ovi, nvi int + for n, v := range dvsVersions { + if old.(string) == v { + ovi = n + } + if new.(string) == v { + nvi = n } } - log.Print("[TRACE] Refreshing state. Distributed virtual switch found") - return dvs, "Created", nil + if nvi < ovi { + return fmt.Errorf("downgrading dvSwitches are not allowed (old: %s new: %s)", old, new) + } + if err := upgradeDVS(client, dvs, new.(string)); err != nil { + return fmt.Errorf("could not upgrade DVS: %s", err) + } + props, err := dvsProperties(dvs) + if err != nil { + return fmt.Errorf("could not get DVS properties after upgrade: %s", err) + } + // ConfigVersion increments after a DVS upgrade, which means this needs to + // be updated before the post-update read to ensure that we don't run into + // ConcurrentAccess errors on the update operation below. + d.Set("config_version", props.Config.(*types.VMwareDVSConfigInfo).ConfigVersion) } -} -func resourceVSphereDistributedVirtualSwitchDelete(d *schema.ResourceData, meta interface{}) error { - dvs, err := dvsExists(d, meta) - if err != nil { - return fmt.Errorf("%s", err) + spec := expandVMwareDVSConfigSpec(d) + if err := updateDVSConfiguration(client, dvs, spec); err != nil { + return fmt.Errorf("could not update DVS: %s", err) } - client := meta.(*govmomi.Client) - n := object.NewDistributedVirtualSwitch(client.Client, dvs.Reference()) - req := &types.Destroy_Task{ - This: n.Reference(), + // Modify network I/O control if necessary + if d.HasChange("network_resource_control_enabled") { + enableDVSNetworkResourceManagement(client, dvs, d.Get("network_resource_control_enabled").(bool)) } - _, err = methods.Destroy_Task(context.TODO(), client, req) - if err != nil { - return fmt.Errorf("%s", err) + // Apply any pending tags now + if tagsClient != nil { + if err := processTagDiff(tagsClient, d, object.NewReference(client.Client, dvs.Reference())); err != nil { + return fmt.Errorf("error updating tags: %s", err) + } } - // Wait for the distributed virtual switch resource to be destroyed - stateConf := &resource.StateChangeConf{ - Pending: []string{"Created"}, - Target: []string{}, - Refresh: resourceVSphereDVSStateRefreshFunc(d, meta), - Timeout: 10 * time.Minute, - MinTimeout: 3 * time.Second, - Delay: 5 * time.Second, + return resourceVSphereDistributedVirtualSwitchRead(d, meta) +} + +func resourceVSphereDistributedVirtualSwitchDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*VSphereClient).vimClient + if err := validateVirtualCenter(client); err != nil { + return err + } + id := d.Id() + dvs, err := dvsFromUUID(client, id) + if err != nil { + return fmt.Errorf("could not find DVS %q: %s", id, err) } - _, err = stateConf.WaitForState() + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + task, err := dvs.Destroy(ctx) if err != nil { - name := d.Get("name").(string) - return fmt.Errorf("error waiting for distributed virtual switch (%s) to be deleted: %s", name, err) + return fmt.Errorf("error deleting DVS: %s", err) + } + tctx, tcancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer tcancel() + if err := task.Wait(tctx); err != nil { + return fmt.Errorf("error waiting for DVS deletion to complete: %s", err) } return nil } + +func resourceVSphereDistributedVirtualSwitchImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + // Due to the relative difficulty in trying to fetch a DVS's UUID, we use the + // inventory path to the DVS instead, and just run it through finder. A full + // path is required unless the default datacenter can be utilized. + client := meta.(*VSphereClient).vimClient + if err := validateVirtualCenter(client); err != nil { + return nil, err + } + p := d.Id() + dvs, err := dvsFromPath(client, p, nil) + if err != nil { + return nil, fmt.Errorf("error locating DVS: %s", err) + } + props, err := dvsProperties(dvs) + if err != nil { + return nil, fmt.Errorf("error fetching DVS properties: %s", err) + } + d.SetId(props.Uuid) + return []*schema.ResourceData{d}, nil +} diff --git a/vsphere/resource_vsphere_distributed_virtual_switch_test.go b/vsphere/resource_vsphere_distributed_virtual_switch_test.go index 9037faf34..8fafa0309 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch_test.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch_test.go @@ -1,243 +1,1155 @@ package vsphere import ( + "errors" "fmt" "os" + "path" + "reflect" "testing" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" - "github.com/vmware/govmomi" - "github.com/vmware/govmomi/find" - "github.com/vmware/govmomi/object" - "github.com/vmware/govmomi/vim25/mo" - "golang.org/x/net/context" + "github.com/vmware/govmomi/vim25/types" ) -func testAccCheckVSphereDVSConfigNoUplinks() string { - return fmt.Sprintf(` -data "vsphere_datacenter" "datacenter" { - name = "%s" -} +func TestAccResourceVSphereDistributedVirtualSwitch(t *testing.T) { + var tp *testing.T + testAccResourceVSphereDistributedVirtualSwitchCases := []struct { + name string + testCase resource.TestCase + }{ + { + "basic", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfig(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + ), + }, + }, + }, + }, + { + "no hosts", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigNoHosts(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + ), + }, + }, + }, + }, + { + "remove a NIC", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfig(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + ), + }, + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigSingleNIC(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + ), + }, + }, + }, + }, + { + "standby with explicit failover order", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigStandbyLink(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchHasUplinks([]string{"tfup1", "tfup2"}), + testAccResourceVSphereDistributedVirtualSwitchHasActiveUplinks([]string{"tfup1"}), + testAccResourceVSphereDistributedVirtualSwitchHasStandbyUplinks([]string{"tfup2"}), + ), + }, + }, + }, + }, + { + "basic, then change to standby with failover order", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfig(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + ), + }, + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigStandbyLink(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchHasUplinks([]string{"tfup1", "tfup2"}), + testAccResourceVSphereDistributedVirtualSwitchHasActiveUplinks([]string{"tfup1"}), + testAccResourceVSphereDistributedVirtualSwitchHasStandbyUplinks([]string{"tfup2"}), + ), + }, + }, + }, + }, + { + "upgrade version", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigStaticVersion("6.0.0"), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchHasVersion("6.0.0"), + ), + }, + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigStaticVersion("6.5.0"), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchHasVersion("6.5.0"), + ), + }, + }, + }, + }, + { + "network resource control", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigNetworkResourceControl(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchHasNetworkResourceControlEnabled(), + testAccResourceVSphereDistributedVirtualSwitchHasNetworkResourceControlVersion( + types.DistributedVirtualSwitchNetworkResourceControlVersionVersion3, + ), + ), + }, + }, + }, + }, + { + "explicit uplinks", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigUplinks(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchHasUplinks([]string{"tfup1", "tfup2"}), + ), + }, + }, + }, + }, + { + "modify uplinks", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfig(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchHasUplinks( + []string{ + "uplink1", + "uplink2", + "uplink3", + "uplink4", + }, + ), + ), + }, + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigStandbyLink(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchHasUplinks( + []string{ + "tfup1", + "tfup2", + }, + ), + ), + }, + }, + }, + }, + { + "in folder", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigInFolder(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchMatchInventoryPath("tf-network-folder"), + ), + }, + }, + }, + }, + { + "single tag", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigSingleTag(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchCheckTags("terraform-test-tag"), + ), + }, + }, + }, + }, + { + "modify tags", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigSingleTag(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchCheckTags("terraform-test-tag"), + ), + }, + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigMultiTag(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchCheckTags("terraform-test-tags-alt"), + ), + }, + }, + }, + }, + { + "netflow", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigNetflow(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchHasNetflow(), + ), + }, + }, + }, + }, + { + "vlan ranges", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfigMultiVlanRange(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + testAccResourceVSphereDistributedVirtualSwitchHasVlanRange(1000, 1999), + testAccResourceVSphereDistributedVirtualSwitchHasVlanRange(3000, 3999), + ), + }, + }, + }, + }, + { + "import", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedVirtualSwitchConfig(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + ), + }, + { + ResourceName: "vsphere_distributed_virtual_switch.dvs", + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + dvs, err := testGetDVS(s, "dvs") + if err != nil { + return "", err + } + return dvs.InventoryPath, nil + }, + Config: testAccResourceVSphereDistributedVirtualSwitchConfig(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedVirtualSwitchExists(true), + ), + }, + }, + }, + }, + } -resource "vsphere_distributed_virtual_switch" "testDVS" { - datacenter_id = "${data.vsphere_datacenter.datacenter.id}" - name = "testDVS" - contact = "dvsmanager@yourcompany.com" - contact_name = "John Doe" - description = "Test DVS" + for _, tc := range testAccResourceVSphereDistributedVirtualSwitchCases { + t.Run(tc.name, func(t *testing.T) { + tp = t + resource.Test(t, tc.testCase) + }) + } } -`, os.Getenv("VSPHERE_DATACENTER")) + +func testAccResourceVSphereDistributedVirtualSwitchPreCheck(t *testing.T) { + if os.Getenv("VSPHERE_HOST_NIC0") == "" { + t.Skip("set VSPHERE_HOST_NIC0 to run vsphere_host_virtual_switch acceptance tests") + } + if os.Getenv("VSPHERE_HOST_NIC1") == "" { + t.Skip("set VSPHERE_HOST_NIC1 to run vsphere_host_virtual_switch acceptance tests") + } + if os.Getenv("VSPHERE_ESXI_HOST") == "" { + t.Skip("set VSPHERE_ESXI_HOST to run vsphere_host_virtual_switch acceptance tests") + } + if os.Getenv("VSPHERE_ESXI_HOST2") == "" { + t.Skip("set VSPHERE_ESXI_HOST2 to run vsphere_host_virtual_switch acceptance tests") + } + if os.Getenv("VSPHERE_ESXI_HOST3") == "" { + t.Skip("set VSPHERE_ESXI_HOST3 to run vsphere_host_virtual_switch acceptance tests") + } } -func testAccCheckVSphereDVSConfigUplinks(uplinks bool, multiple bool) string { - if uplinks { - conf := ` -data "vsphere_datacenter" "datacenter" { - name = "%s" +func testAccResourceVSphereDistributedVirtualSwitchExists(expected bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + dvs, err := testGetDVS(s, "dvs") + if err != nil { + if isAnyNotFoundError(err) && expected == false { + // Expected missing + return nil + } + return err + } + if !expected { + return fmt.Errorf("expected DVS %s to be missing", dvs.Reference().Value) + } + return nil + } } -data "vsphere_host" "esxi_host" { - name = "%s" - datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +func testAccResourceVSphereDistributedVirtualSwitchHasVersion(expected string) resource.TestCheckFunc { + return func(s *terraform.State) error { + props, err := testGetDVSProperties(s, "dvs") + if err != nil { + return err + } + actual := props.Summary.ProductInfo.Version + if expected != actual { + return fmt.Errorf("expected version to be %q, got %q", expected, actual) + } + return nil + } } -resource "vsphere_distributed_virtual_switch" "testDVS" { - datacenter_id = "${data.vsphere_datacenter.datacenter.id}" - name = "testDVS" - host = [{ - host_system_id = "${data.vsphere_host.esxi_host.id}" - backing = ["%s"] - }] +func testAccResourceVSphereDistributedVirtualSwitchHasNetworkResourceControlEnabled() resource.TestCheckFunc { + return func(s *terraform.State) error { + props, err := testGetDVSProperties(s, "dvs") + if err != nil { + return err + } + actual := props.Config.(*types.VMwareDVSConfigInfo).NetworkResourceManagementEnabled + if actual == nil || !*actual { + return errors.New("expected network resource control to be enabled") + } + return nil + } } -` - if multiple { - backing := fmt.Sprintf("%s\",\"%s", os.Getenv("VSPHERE_HOST_NIC0"), os.Getenv("VSPHERE_HOST_NIC1")) - return fmt.Sprintf(conf, os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST"), backing) - } else { - return fmt.Sprintf(conf, os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST"), os.Getenv("VSPHERE_HOST_NIC0")) + +func testAccResourceVSphereDistributedVirtualSwitchHasNetworkResourceControlVersion(expected types.DistributedVirtualSwitchNetworkResourceControlVersion) resource.TestCheckFunc { + return func(s *terraform.State) error { + props, err := testGetDVSProperties(s, "dvs") + if err != nil { + return err } - } else { - return fmt.Sprintf(` -data "vsphere_datacenter" "datacenter" { - name = "%s" + actual := props.Config.(*types.VMwareDVSConfigInfo).NetworkResourceControlVersion + if string(expected) != actual { + return fmt.Errorf("expected network resource control version to be %q, got %q", expected, actual) + } + return nil + } } -data "vsphere_host" "esxi_host" { - name = "%s" - datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +func testAccResourceVSphereDistributedVirtualSwitchHasUplinks(expected []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + props, err := testGetDVSProperties(s, "dvs") + if err != nil { + return err + } + policy := props.Config.(*types.VMwareDVSConfigInfo).UplinkPortPolicy.(*types.DVSNameArrayUplinkPortPolicy) + actual := policy.UplinkPortName + if !reflect.DeepEqual(expected, actual) { + return fmt.Errorf("expected uplinks to be %#v, got %#v", expected, actual) + } + return nil + } } -resource "vsphere_distributed_virtual_switch" "testDVS" { - datacenter_id = "${data.vsphere_datacenter.datacenter.id}" - name = "testDVS" +func testAccResourceVSphereDistributedVirtualSwitchHasActiveUplinks(expected []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + props, err := testGetDVSProperties(s, "dvs") + if err != nil { + return err + } + pc := props.Config.(*types.VMwareDVSConfigInfo).DefaultPortConfig.(*types.VMwareDVSPortSetting) + actual := pc.UplinkTeamingPolicy.UplinkPortOrder.ActiveUplinkPort + if !reflect.DeepEqual(expected, actual) { + return fmt.Errorf("expected active uplinks to be %#v, got %#v", expected, actual) + } + return nil + } } -`, os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST")) + +func testAccResourceVSphereDistributedVirtualSwitchHasStandbyUplinks(expected []string) resource.TestCheckFunc { + return func(s *terraform.State) error { + props, err := testGetDVSProperties(s, "dvs") + if err != nil { + return err + } + pc := props.Config.(*types.VMwareDVSConfigInfo).DefaultPortConfig.(*types.VMwareDVSPortSetting) + actual := pc.UplinkTeamingPolicy.UplinkPortOrder.StandbyUplinkPort + if !reflect.DeepEqual(expected, actual) { + return fmt.Errorf("expected standby uplinks to be %#v, got %#v", expected, actual) + } + return nil } } -func testAccResourceVSphereDistributedVirtualSwitchPreCheck(t *testing.T) { - if os.Getenv("VSPHERE_DATACENTER") == "" { - t.Skip("set VSPHERE_DATACENTER to run vsphere_distributed_virtual_switch acceptance tests") +func testAccResourceVSphereDistributedVirtualSwitchHasNetflow() resource.TestCheckFunc { + expectedIPv4Addr := "10.0.0.100" + expectedIpfixConfig := &types.VMwareIpfixConfig{ + CollectorIpAddress: "10.0.0.10", + CollectorPort: 9000, + ObservationDomainId: 1000, + ActiveFlowTimeout: 90, + IdleFlowTimeout: 20, + SamplingRate: 10, + InternalFlowsOnly: true, } - if os.Getenv("VSPHERE_HOST_NIC0") == "" { - t.Skip("set VSPHERE_HOST_NIC0 to run vsphere_distributed_virtual_switch acceptance tests") + + return func(s *terraform.State) error { + props, err := testGetDVSProperties(s, "dvs") + if err != nil { + return err + } + actualIPv4Addr := props.Config.(*types.VMwareDVSConfigInfo).SwitchIpAddress + actualIpfixConfig := props.Config.(*types.VMwareDVSConfigInfo).IpfixConfig + + if expectedIPv4Addr != actualIPv4Addr { + return fmt.Errorf("expected switch IP to be %s, got %s", expectedIPv4Addr, actualIPv4Addr) + } + if !reflect.DeepEqual(expectedIpfixConfig, actualIpfixConfig) { + return fmt.Errorf("expected netflow config to be %#v, got %#v", expectedIpfixConfig, actualIpfixConfig) + } + return nil } - if os.Getenv("VSPHERE_HOST_NIC1") == "" { - t.Skip("set VSPHERE_HOST_NIC0 to run vsphere_distributed_virtual_switch acceptance tests") +} + +func testAccResourceVSphereDistributedVirtualSwitchHasVlanRange(emin, emax int32) resource.TestCheckFunc { + return func(s *terraform.State) error { + props, err := testGetDVSProperties(s, "dvs") + if err != nil { + return err + } + pc := props.Config.(*types.VMwareDVSConfigInfo).DefaultPortConfig.(*types.VMwareDVSPortSetting) + ranges := pc.Vlan.(*types.VmwareDistributedVirtualSwitchTrunkVlanSpec).VlanId + var found bool + for _, rng := range ranges { + if rng.Start == emin && rng.End == emax { + found = true + } + } + if !found { + return fmt.Errorf("could not find start %d and end %d in %#v", emin, emax, ranges) + } + return nil } - if os.Getenv("VSPHERE_ESXI_HOST") == "" { - t.Skip("set VSPHERE_HOST_NIC0 to run vsphere_distributed_virtual_switch acceptance tests") +} + +func testAccResourceVSphereDistributedVirtualSwitchMatchInventoryPath(expected string) resource.TestCheckFunc { + return func(s *terraform.State) error { + dvs, err := testGetDVS(s, "dvs") + if err != nil { + return err + } + + expected, err := rootPathParticleNetwork.PathFromNewRoot(dvs.InventoryPath, rootPathParticleNetwork, expected) + actual := path.Dir(dvs.InventoryPath) + if err != nil { + return fmt.Errorf("bad: %s", err) + } + if expected != actual { + return fmt.Errorf("expected path to be %s, got %s", expected, actual) + } + return nil } } -// Create a distributed virtual switch with no uplinks -func TestAccVSphereDVS_createWithoutUplinks(t *testing.T) { - resourceName := "testDVS" +// testAccResourceVSphereDistributedVirtualSwitchCheckTags is a check to ensure that any tags +// that have been created with the supplied resource name have been attached to +// the folder. +func testAccResourceVSphereDistributedVirtualSwitchCheckTags(tagResName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + dvs, err := testGetDVS(s, "dvs") + if err != nil { + return err + } + tagsClient, err := testAccProvider.Meta().(*VSphereClient).TagsClient() + if err != nil { + return err + } + return testObjectHasTags(s, tagsClient, dvs, tagResName) + } +} - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccResourceVSphereDistributedVirtualSwitchPreCheck(t) - }, - Providers: testAccProviders, - CheckDestroy: testAccCheckVSphereDVSDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCheckVSphereDVSConfigNoUplinks(), - Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 0)), - }, - }, - }) +func testAccResourceVSphereDistributedVirtualSwitchConfig() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" } -// Create a distributed virtual switch with one uplink -func TestAccVSphereDVS_createWithUplinks(t *testing.T) { - resourceName := "testDVS" +variable "esxi_hosts" { + default = [ + "%s", + "%s", + "%s", + ] +} - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccResourceVSphereDistributedVirtualSwitchPreCheck(t) - }, - Providers: testAccProviders, - CheckDestroy: testAccCheckVSphereDVSDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCheckVSphereDVSConfigUplinks(true, false), - Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 1)), - }, - }, - }) +variable "network_interfaces" { + default = [ + "%s", + "%s", + ] } -// Create a distributed virtual switch with an uplink, delete it and add it again -func TestAccVSphereDVS_createAndUpdateWithUplinks(t *testing.T) { - resourceName := "testDVS" +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} - resource.Test(t, resource.TestCase{ - PreCheck: func() { - testAccPreCheck(t) - testAccResourceVSphereDistributedVirtualSwitchPreCheck(t) - }, - Providers: testAccProviders, - CheckDestroy: testAccCheckVSphereDVSDestroy, - Steps: []resource.TestStep{ - { - Config: testAccCheckVSphereDVSConfigUplinks(true, false), - Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 1)), - }, - { - Config: testAccCheckVSphereDVSConfigUplinks(false, false), - Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 0)), - }, - { - Config: testAccCheckVSphereDVSConfigUplinks(true, false), - Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 1)), - }, - { - Config: testAccCheckVSphereDVSConfigUplinks(false, false), - Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 0)), - }, - { - Config: testAccCheckVSphereDVSConfigUplinks(true, true), - Check: resource.ComposeTestCheckFunc(testAccCheckVSphereDVSExists(resourceName, 2)), - }, - }, - }) +data "vsphere_host" "host" { + count = "${length(var.esxi_hosts)}" + name = "${var.esxi_hosts[count.index]}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" } -func testAccCheckVSphereDVSDestroy(s *terraform.State) error { - client := testAccProvider.Meta().(*govmomi.Client) +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" - for _, rs := range s.RootModule().Resources { - if rs.Type != "vsphere_distributed_virtual_switch" { - continue - } + host { + host_system_id = "${data.vsphere_host.host.0.id}" + devices = ["${var.network_interfaces}"] + } - id := rs.Primary.ID - _, err := dvsFromUuid(client, id) - if err != nil { - fmt.Printf("Expected error received: %s\n", err) - return nil - } else { - return fmt.Errorf("distributed virtual switch '%s' still exists", id) - } - } - return nil + host { + host_system_id = "${data.vsphere_host.host.1.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.2.id}" + devices = ["${var.network_interfaces}"] + } +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_ESXI_HOST"), + os.Getenv("VSPHERE_ESXI_HOST2"), + os.Getenv("VSPHERE_ESXI_HOST3"), + os.Getenv("VSPHERE_HOST_NIC0"), + os.Getenv("VSPHERE_HOST_NIC1"), + ) } -func testAccCheckVSphereDVSExists(name string, n int) resource.TestCheckFunc { - return func(s *terraform.State) error { - client := testAccProvider.Meta().(*govmomi.Client) +func testAccResourceVSphereDistributedVirtualSwitchConfigStaticVersion(version string) string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} - var id string - for _, rs := range s.RootModule().Resources { - if rs.Type != "vsphere_distributed_virtual_switch" { - continue - } - if rs.Primary.Attributes["name"] != name { - continue - } +variable "esxi_hosts" { + default = [ + "%s", + "%s", + "%s", + ] +} - id = rs.Primary.ID - dvs, err := dvsFromUuid(client, id) - if err != nil { - return fmt.Errorf("distributed virtual switch '%s' doesn't exists", id) - } else { - config := dvs.Config.GetDVSConfigInfo() - - for _, h := range config.Host { - finder := find.NewFinder(client.Client, false) - - ds, err := finder.ObjectReference(context.TODO(), *h.Config.Host) - if err != nil { - continue - } - dso := ds.(*object.HostSystem) - var mh mo.HostSystem - err = dso.Properties(context.TODO(), ds.Reference(), []string{"config"}, &mh) - if err != nil { - continue - } - - var j int - for _, ps := range mh.Config.Network.ProxySwitch { - if ps.DvsName == name { - j = len(ps.Pnic) - } - } - if j != n { - return fmt.Errorf("expected '%d' uplinks, found '%d'", n, j) - } - fmt.Println("DVS exists and has the correct number of uplinks") - return nil - } - } - } - if id == "" { - return fmt.Errorf("DVS not found") - } - return nil - } +variable "network_interfaces" { + default = [ + "%s", + "%s", + ] +} + +variable "dvs_version" { + default = "%s" +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +data "vsphere_host" "host" { + count = "${length(var.esxi_hosts)}" + name = "${var.esxi_hosts[count.index]}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" + version = "${var.dvs_version}" + + host { + host_system_id = "${data.vsphere_host.host.0.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.1.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.2.id}" + devices = ["${var.network_interfaces}"] + } +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_ESXI_HOST"), + os.Getenv("VSPHERE_ESXI_HOST2"), + os.Getenv("VSPHERE_ESXI_HOST3"), + os.Getenv("VSPHERE_HOST_NIC0"), + os.Getenv("VSPHERE_HOST_NIC1"), + version, + ) +} + +func testAccResourceVSphereDistributedVirtualSwitchConfigSingleNIC() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "esxi_hosts" { + default = [ + "%s", + "%s", + "%s", + ] +} + +variable "network_interfaces" { + default = [ + "%s", + ] +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +data "vsphere_host" "host" { + count = "${length(var.esxi_hosts)}" + name = "${var.esxi_hosts[count.index]}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" + + host { + host_system_id = "${data.vsphere_host.host.0.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.1.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.2.id}" + devices = ["${var.network_interfaces}"] + } +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_ESXI_HOST"), + os.Getenv("VSPHERE_ESXI_HOST2"), + os.Getenv("VSPHERE_ESXI_HOST3"), + os.Getenv("VSPHERE_HOST_NIC0"), + ) +} + +func testAccResourceVSphereDistributedVirtualSwitchConfigNetworkResourceControl() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "esxi_hosts" { + default = [ + "%s", + "%s", + "%s", + ] +} + +variable "network_interfaces" { + default = [ + "%s", + ] +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +data "vsphere_host" "host" { + count = "${length(var.esxi_hosts)}" + name = "${var.esxi_hosts[count.index]}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" + + network_resource_control_enabled = true + network_resource_control_version = "version3" + + host { + host_system_id = "${data.vsphere_host.host.0.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.1.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.2.id}" + devices = ["${var.network_interfaces}"] + } +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_ESXI_HOST"), + os.Getenv("VSPHERE_ESXI_HOST2"), + os.Getenv("VSPHERE_ESXI_HOST3"), + os.Getenv("VSPHERE_HOST_NIC0"), + ) +} + +func testAccResourceVSphereDistributedVirtualSwitchConfigUplinks() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "esxi_hosts" { + default = [ + "%s", + "%s", + "%s", + ] +} + +variable "network_interfaces" { + default = [ + "%s", + ] +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +data "vsphere_host" "host" { + count = "${length(var.esxi_hosts)}" + name = "${var.esxi_hosts[count.index]}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" + + uplinks = ["tfup1", "tfup2"] + + host { + host_system_id = "${data.vsphere_host.host.0.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.1.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.2.id}" + devices = ["${var.network_interfaces}"] + } +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_ESXI_HOST"), + os.Getenv("VSPHERE_ESXI_HOST2"), + os.Getenv("VSPHERE_ESXI_HOST3"), + os.Getenv("VSPHERE_HOST_NIC0"), + ) +} + +func testAccResourceVSphereDistributedVirtualSwitchConfigStandbyLink() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "esxi_hosts" { + default = [ + "%s", + "%s", + "%s", + ] +} + +variable "network_interfaces" { + default = [ + "%s", + ] +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +data "vsphere_host" "host" { + count = "${length(var.esxi_hosts)}" + name = "${var.esxi_hosts[count.index]}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" + + uplinks = ["tfup1", "tfup2"] + active_uplinks = ["tfup1"] + standby_uplinks = ["tfup2"] + + host { + host_system_id = "${data.vsphere_host.host.0.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.1.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.2.id}" + devices = ["${var.network_interfaces}"] + } +} +`, + os.Getenv("VSPHERE_DATACENTER"), + os.Getenv("VSPHERE_ESXI_HOST"), + os.Getenv("VSPHERE_ESXI_HOST2"), + os.Getenv("VSPHERE_ESXI_HOST3"), + os.Getenv("VSPHERE_HOST_NIC0"), + ) +} + +func testAccResourceVSphereDistributedVirtualSwitchConfigNoHosts() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} +`, + os.Getenv("VSPHERE_DATACENTER"), + ) +} + +func testAccResourceVSphereDistributedVirtualSwitchConfigInFolder() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +resource "vsphere_folder" "folder" { + path = "tf-network-folder" + type = "network" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} + +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" + folder = "${vsphere_folder.folder.path}" +} +`, + os.Getenv("VSPHERE_DATACENTER"), + ) +} + +func testAccResourceVSphereDistributedVirtualSwitchConfigSingleTag() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +resource "vsphere_tag_category" "terraform-test-category" { + name = "terraform-test-tag-category" + cardinality = "MULTIPLE" + + associable_types = [ + "VmwareDistributedVirtualSwitch", + ] +} + +resource "vsphere_tag" "terraform-test-tag" { + name = "terraform-test-tag" + category_id = "${vsphere_tag_category.terraform-test-category.id}" +} + +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" + tags = ["${vsphere_tag.terraform-test-tag.id}"] +} +`, + os.Getenv("VSPHERE_DATACENTER"), + ) +} + +func testAccResourceVSphereDistributedVirtualSwitchConfigMultiTag() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "extra_tags" { + default = [ + "terraform-test-thing1", + "terraform-test-thing2", + ] +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +resource "vsphere_tag_category" "terraform-test-category" { + name = "terraform-test-tag-category" + cardinality = "MULTIPLE" + + associable_types = [ + "VmwareDistributedVirtualSwitch", + ] +} + +resource "vsphere_tag" "terraform-test-tag" { + name = "terraform-test-tag" + category_id = "${vsphere_tag_category.terraform-test-category.id}" +} + +resource "vsphere_tag" "terraform-test-tags-alt" { + count = "${length(var.extra_tags)}" + name = "${var.extra_tags[count.index]}" + category_id = "${vsphere_tag_category.terraform-test-category.id}" +} + +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" + tags = ["${vsphere_tag.terraform-test-tags-alt.*.id}"] +} +`, + os.Getenv("VSPHERE_DATACENTER"), + ) +} + +func testAccResourceVSphereDistributedVirtualSwitchConfigNetflow() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" + + ipv4_address = "10.0.0.100" + netflow_enabled = true + netflow_active_flow_timeout = 90 + netflow_collector_ip_address = "10.0.0.10" + netflow_collector_port = 9000 + netflow_idle_flow_timeout = 20 + netflow_internal_flows_only = true + netflow_observation_domain_id = 1000 + netflow_sampling_rate = 10 +} +`, + os.Getenv("VSPHERE_DATACENTER"), + ) +} + +func testAccResourceVSphereDistributedVirtualSwitchConfigMultiVlanRange() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +data "vsphere_datacenter" "dc" { + name = "${var.datacenter}" +} + +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" + + vlan_range { + min_vlan = 1000 + max_vlan = 1999 + } + + vlan_range { + min_vlan = 3000 + max_vlan = 3999 + } +} +`, + os.Getenv("VSPHERE_DATACENTER"), + ) } diff --git a/vsphere/structure_helper.go b/vsphere/structure_helper.go index f198800d6..c8f7e9218 100644 --- a/vsphere/structure_helper.go +++ b/vsphere/structure_helper.go @@ -2,8 +2,10 @@ package vsphere import ( "fmt" + "reflect" "github.com/hashicorp/terraform/helper/schema" + "github.com/vmware/govmomi/vim25/types" ) // sliceInterfacesToStrings converts an interface slice to a string slice. The @@ -45,6 +47,55 @@ func boolPtr(v bool) *bool { return &v } +// getBoolPtr reads a ResourceData and returns an appropriate *bool for the +// state of the definition. nil is returned if it does not exist. +func getBoolPtr(d *schema.ResourceData, key string) *bool { + v, e := d.GetOkExists(key) + if e { + return boolPtr(v.(bool)) + } + return nil +} + +// setBoolPtr sets a ResourceData field depending on if a *bool exists or not. +// The field is not set if it's nil. +func setBoolPtr(d *schema.ResourceData, key string, val *bool) error { + if val == nil { + return nil + } + if err := d.Set(key, val); err != nil { + return err + } + return nil +} + +// int64Ptr makes an *int64 out of the value passed in through v. +func int64Ptr(v int64) *int64 { + return &v +} + +// getInt64Ptr reads a ResourceData and returns an appropriate *int64 for the +// state of the definition. nil is returned if it does not exist. +func getInt64Ptr(d *schema.ResourceData, key string) *int64 { + v, e := d.GetOkExists(key) + if e { + return int64Ptr(int64(v.(int))) + } + return nil +} + +// setInt64Ptr sets a ResourceData field depending on if an *int64 exists or +// not. The field is not set if it's nil. +func setInt64Ptr(d *schema.ResourceData, key string, val *int64) error { + if val == nil { + return nil + } + if err := d.Set(key, val); err != nil { + return err + } + return nil +} + // byteToMB returns n/1000000. The input must be an integer that can be divisible // by 1000000. func byteToMB(n interface{}) interface{} { @@ -58,3 +109,178 @@ func byteToMB(n interface{}) interface{} { } panic(fmt.Errorf("non-integer type %T for value", n)) } + +// boolPolicy converts a bool into a VMware BoolPolicy value. +func boolPolicy(b bool) *types.BoolPolicy { + bp := &types.BoolPolicy{ + Value: boolPtr(b), + } + return bp +} + +// getBoolPolicy reads a ResourceData and returns an appropriate BoolPolicy for +// the state of the definition. nil is returned if it does not exist. +func getBoolPolicy(d *schema.ResourceData, key string) *types.BoolPolicy { + v, e := d.GetOkExists(key) + if e { + return boolPolicy(v.(bool)) + } + return nil +} + +// setBoolPolicy sets a ResourceData field depending on if a BoolPolicy exists +// or not. The field is not set if it's nil. +func setBoolPolicy(d *schema.ResourceData, key string, val *types.BoolPolicy) error { + if val == nil { + return nil + } + if err := d.Set(key, val.Value); err != nil { + return err + } + return nil +} + +// getBoolPolicyReverse acts like getBoolPolicy, but the value is inverted. +func getBoolPolicyReverse(d *schema.ResourceData, key string) *types.BoolPolicy { + v, e := d.GetOkExists(key) + if e { + return boolPolicy(!v.(bool)) + } + return nil +} + +// setBoolPolicyReverse acts like setBoolPolicy, but the value is inverted. +func setBoolPolicyReverse(d *schema.ResourceData, key string, val *types.BoolPolicy) error { + if val == nil { + return nil + } + if err := d.Set(key, !*val.Value); err != nil { + return err + } + return nil +} + +// stringPolicy converts a string into a VMware StringPolicy value. +func stringPolicy(s string) *types.StringPolicy { + sp := &types.StringPolicy{ + Value: s, + } + return sp +} + +// getStringPolicy reads a ResourceData and returns an appropriate StringPolicy +// for the state of the definition. nil is returned if it does not exist. +func getStringPolicy(d *schema.ResourceData, key string) *types.StringPolicy { + v, e := d.GetOkExists(key) + if e { + return stringPolicy(v.(string)) + } + return nil +} + +// setStringPolicy sets a ResourceData field depending on if a StringPolicy +// exists or not. The field is not set if it's nil. +func setStringPolicy(d *schema.ResourceData, key string, val *types.StringPolicy) error { + if val == nil { + return nil + } + if err := d.Set(key, val.Value); err != nil { + return err + } + return nil +} + +// longPolicy converts a supported number into a VMware LongPolicy value. This +// will panic if there is no implicit conversion of the value into an int64. +func longPolicy(n interface{}) *types.LongPolicy { + lp := &types.LongPolicy{} + switch v := n.(type) { + case int: + lp.Value = int64(v) + case int8: + lp.Value = int64(v) + case int16: + lp.Value = int64(v) + case int32: + lp.Value = int64(v) + case uint: + lp.Value = int64(v) + case uint8: + lp.Value = int64(v) + case uint16: + lp.Value = int64(v) + case uint32: + lp.Value = int64(v) + case int64: + lp.Value = v + default: + panic(fmt.Errorf("non-convertible type %T for value", n)) + } + return lp +} + +// getLongPolicy reads a ResourceData and returns an appropriate LongPolicy +// for the state of the definition. nil is returned if it does not exist. +func getLongPolicy(d *schema.ResourceData, key string) *types.LongPolicy { + v, e := d.GetOkExists(key) + if e { + return longPolicy(v) + } + return nil +} + +// setLongPolicy sets a ResourceData field depending on if a LongPolicy +// exists or not. The field is not set if it's nil. +func setLongPolicy(d *schema.ResourceData, key string, val *types.LongPolicy) error { + if val == nil { + return nil + } + if err := d.Set(key, val.Value); err != nil { + return err + } + return nil +} + +// allFieldsEmpty checks to see if all fields in a given struct are zero +// values. It does not recurse, so finer-grained checking should be done for +// deep accuracy when necessary. It also does not dereference pointers, except +// if the value itself is a pointer and is not nil. +func allFieldsEmpty(v interface{}) bool { + if v == nil { + return true + } + + t := reflect.TypeOf(v) + if t.Kind() != reflect.Struct && (t.Kind() == reflect.Ptr && t.Elem().Kind() != reflect.Struct) { + if reflect.Zero(t).Interface() != reflect.ValueOf(v).Interface() { + return false + } + return true + } + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + for i := 0; i < t.NumField(); i++ { + var fv reflect.Value + if reflect.ValueOf(v).Kind() == reflect.Ptr { + fv = reflect.ValueOf(v).Elem().Field(i) + } else { + fv = reflect.ValueOf(v).Elem().Field(i) + } + + ft := t.Field(i).Type + fz := reflect.Zero(ft) + switch ft.Kind() { + case reflect.Map, reflect.Slice: + if fv.Len() > 0 { + return false + } + default: + if fz.Interface() != fv.Interface() { + return false + } + } + } + + return true +} diff --git a/vsphere/vim_helper.go b/vsphere/vim_helper.go index feba51423..f7a023c4c 100644 --- a/vsphere/vim_helper.go +++ b/vsphere/vim_helper.go @@ -9,6 +9,7 @@ import ( "github.com/vmware/govmomi" "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/task" "github.com/vmware/govmomi/vim25/methods" "github.com/vmware/govmomi/vim25/soap" "github.com/vmware/govmomi/vim25/types" @@ -26,7 +27,7 @@ func soapFault(err error) (*soap.Fault, bool) { return nil, false } -// vimSoapFault extracts the VIM fault Check the returned boolean value to see +// vimSoapFault extracts the VIM fault. Check the returned boolean value to see // if you have a fault, which will need to be further asserted into the error // that you are looking for. func vimSoapFault(err error) (types.AnyType, bool) { @@ -36,6 +37,16 @@ func vimSoapFault(err error) (types.AnyType, bool) { return nil, false } +// taskFault extracts the task fault from a supplied task.Error. Check the +// returned boolean value to see if the fault was extracted correctly, after +// which you will need to do further checking. +func taskFault(err error) (types.BaseMethodFault, bool) { + if te, ok := err.(task.Error); ok { + return te.Fault(), true + } + return nil, false +} + // isManagedObjectNotFoundError checks an error to see if it's of the // ManagedObjectNotFound type. func isManagedObjectNotFoundError(err error) bool { @@ -47,6 +58,32 @@ func isManagedObjectNotFoundError(err error) bool { return false } +// isNotFoundError checks an error to see if it's of the NotFoundError type. +// +// Note this is different from the other "not found" faults and is an error +// type in its own right. Use isAnyNotFoundError to check for any "not found" +// type. +func isNotFoundError(err error) bool { + if f, ok := vimSoapFault(err); ok { + if _, ok := f.(types.NotFound); ok { + return true + } + } + return false +} + +// isAnyNotFoundError checks to see if the fault is of any not found error type +// that we track. +func isAnyNotFoundError(err error) bool { + switch { + case isManagedObjectNotFoundError(err): + fallthrough + case isNotFoundError(err): + return true + } + return false +} + // isResourceInUseError checks an error to see if it's of the // ResourceInUse type. func isResourceInUseError(err error) bool { @@ -58,6 +95,26 @@ func isResourceInUseError(err error) bool { return false } +// isConcurrentAccessError checks an error to see if it's of the +// ConcurrentAccess type. +func isConcurrentAccessError(err error) bool { + // ConcurrentAccess comes from a task more than it usually does from a direct + // SOAP call, so we need to handle both here. + var f types.AnyType + var ok bool + f, ok = vimSoapFault(err) + if !ok { + f, ok = taskFault(err) + } + if ok { + switch f.(type) { + case types.ConcurrentAccess, *types.ConcurrentAccess: + return true + } + } + return false +} + // renameObject renames a MO and tracks the task to make sure it completes. func renameObject(client *govmomi.Client, ref types.ManagedObjectReference, new string) error { req := types.Rename_Task{ diff --git a/website/docs/r/distributed_virtual_switch.html.markdown b/website/docs/r/distributed_virtual_switch.html.markdown index a37327d22..393f97668 100644 --- a/website/docs/r/distributed_virtual_switch.html.markdown +++ b/website/docs/r/distributed_virtual_switch.html.markdown @@ -1,82 +1,416 @@ --- layout: "vsphere" page_title: "VMware vSphere: vsphere_distributed_virtual_switch" -sidebar_current: "docs-vsphere-resource-distributed-virtual-switch" +sidebar_current: "docs-vsphere-resource-network-distributed-virtual-switch" description: |- - Provides a VMware vSphere distributed virtual switch resource. A distributed switch configures a switch across all associated hosts, allowing a virtual machine to keep the same network configuration regardless of the actual host it runs in. + Provides a vSphere distributed virtual switch resource. This can be used to create and manage DVS resources in vCenter. +--- - A distributed virtual switch has an uplink port group as well as several distributed port groups. The uplink port group connects physical network cards on the host to the distributed switch. A distributed port group specifies how a connection is made through the distributed switch. +# vsphere\_distributed\_virtual\_switch ---- +The `vsphere_distributed_virtual_switch` can be used to manage VMware +Distributed Virtual Switches. + +An essential component of a distributed, scalable VMware datacenter, the +vSphere Distributed Virtual Switch (DVS) provides centralized management and +monitoring of the networking configuration of all the hosts that are associated +with the switch. In addition to adding port groups (see the +[`vsphere_distributed_port_group`][distributed-port-group] resource) that can +be used as networks for virtual machines, a DVS can be configured to perform +advanced high availability, traffic shaping, network monitoring, and more. -# vsphere\_distributed_virtual_switch +For an overview on vSphere networking concepts, see [this +page][ref-vsphere-net-concepts]. For more information on vSphere DVS, see [this +page][ref-vsphere-dvs]. -Provides a VMware vSphere distributed virtual switch resource. A distributed switch configures a switch across all associated hosts, allowing a virtual machine to keep the same network configuration regardless of the actual host it runs in. +[distributed-port-group]: /docs/providers/vsphere/r/distributed_port_group.html +[ref-vsphere-net-concepts]: https://docs.vmware.com/en/VMware-vSphere/6.5/com.vmware.vsphere.networking.doc/GUID-2B11DBB8-CB3C-4AFF-8885-EFEA0FC562F4.html +[ref-vsphere-dvs]: https://docs.vmware.com/en/VMware-vSphere/6.5/com.vmware.vsphere.networking.doc/GUID-375B45C7-684C-4C51-BA3C-70E48DFABF04.html -## Example Usages +~> **NOTE:** This resource requires vCenter and is not available on direct ESXi +connections. -**Create a distributed virtual switch without specifying uplink port groups (need to be defined manually later):** +## Example Usage + +The following example below demonstrates a "standard" example of configuring a +vSphere DVS in a 3-node vSphere datacenter named `dc1`, across 4 NICs with two +being used as active, and two being used as passive. Note that the NIC failover +order propagates to any port groups configured on this DVS and can be overridden +there. ```hcl -data "vsphere_datacenter" "datacenter" { - name = "myDC" +variable "esxi_hosts" { + default = [ + "esxi1", + "esxi2", + "esxi3", + ] } -resource "vsphere_distributed_virtual_switch" "myDistributedSwitch" { - datacenter_id = "${data.vsphere_datacenter.datacenter.id}" - name = "myDistributedSwitch" +variable "network_interfaces" { + default = [ + "vmnic0", + "vmnic1", + "vmnic2", + "vmnic3", + ] } -``` - -**Create a distributed virtual switch with connected hosts and their NICs as part of the uplink port group:** -```hcl -data "vsphere_datacenter" "datacenter" { - name = "myDC" +data "vsphere_datacenter" "dc" { + name = "dc1" } -data "vsphere_host" "esxi_host1" { - name = "node1" - datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +data "vsphere_host" "host" { + count = "${length(var.esxi_hosts)}" + name = "${var.esxi_hosts[count.index]}" + datacenter_id = "${data.vsphere_datacenter.dc.id}" } -data "vsphere_host" "esxi_host2" { - name = "node2" - datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" + + uplinks = ["uplink1", "uplink2", "uplink3", "uplink4"] + active_uplinks = ["uplink1", "uplink2"] + standby_uplinks = ["uplink3", "uplink4"] + + host { + host_system_id = "${data.vsphere_host.host.0.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.1.id}" + devices = ["${var.network_interfaces}"] + } + + host { + host_system_id = "${data.vsphere_host.host.2.id}" + devices = ["${var.network_interfaces}"] + } } +``` + +### Uplink name and count control + +The following abridged example below demonstrates how you can manage the number +of uplinks, and the name of the uplinks via the `uplinks` parameter. + +Note that if you change the uplink naming and count after creating the DVS, you +may need to explicitly specify `active_uplinks` and `standby_uplinks` as these +values are saved to Terraform state after creation, regardless of being +specified in config, and will drift if not modified, causing errors. + +```hcl +resource "vsphere_distributed_virtual_switch" "dvs" { + name = "terraform-test-dvs" + datacenter_id = "${data.vsphere_datacenter.dc.id}" -resource "vsphere_distributed_virtual_switch" "myDistributedSwitch" { - datacenter_id = "${data.vsphere_datacenter.datacenter.id}" - name = "myDistributedSwitch" - - host = [{ - host_system_id = "${data.vsphere_host.esxi_host1.id}" - backing = ["vmnic1","vmnic2"] - },{ - host_system_id = "${data.vsphere_host.esxi_host2.id}" - backing = ["vmnic0"] - }] + uplinks = ["tfup1", "tfup2"] + active_uplinks = ["tfup1"] + standby_uplinks = ["tfup2"] } ``` +~> **NOTE:** The default uplink names when a DVS is created are `uplink1` +through to `uplink4`, however this default is not guaranteed to be stable and +you are encouraged to set your own. + ## Argument Reference The following arguments are supported: * `name` - (Required) The name of the distributed virtual switch. -* `description` - (Optional) The description string of the distributed virtual switch. -* `default_proxy_switch_max_num_ports` - (Optional) The default host proxy switch maximum port number. -* `extension_key` - (Optional) The key of the extension registered by a remote server that controls the switch. -* `network_resource_control_version` - (Optional) Indicates the Network Resource Control APIs that are supported on the switch. -* `num_standalone_ports` - (Optional) The number of standalone ports in the switch. Standalone ports are ports that do not belong to any portgroup. -* `switch_ip_address` - (Optional) IP address for the switch, specified using IPv4 dot notation. IPv6 address is not supported for this property. -* `datacenter_id` - (Required) The ID of the datacenter where the distributed virtual switch will be created. -* `host` - (Optional) A list of hosts and physical NICs to attach to the uplink port group. - * max_proxy_switch_ports - (Optional) Maximum number of ports allowed in the HostProxySwitch. - * host_system_id - (Required) The managed object ID of the host to search for NICs on. - * backing - (Optional) - * vendor_specific_config - (Optional) A list of key/blob vendor specific config. - * key - (Optional) A key that identifies the opaque binary blob. - * opaque_data - (Optional) The opaque data. It is recommended that base64 encoding be used for binary data. -* `contact` - (Optional) The contact information for the person. -* `contact_name` - (Optional) The name of the person who is responsible for the switch. +* `datacenter_id` - (Required) The ID of the datacenter where the distributed + virtual switch will be created. Forces a new resource if changed. +* `folder` - (Optional) The folder to create the DVS in. Forces a new resource + if changed. +* `description` - (Optional) A detailed description for the DVS. +* `contact_name` - (Optional) The name of the person who is responsible for the + DVS. +* `contact_detail` - (Optional) The detailed contact information for the person + who is responsible for the DVS. +* `ipv4_address` - (Optional) An IPv4 address to identify the switch. This is + mostly useful when used with the [Netflow arguments](#netflow-arguments) found + below. +* `lacp_api_version` - (Optional) The Link Aggregation Control Protocol group + version to use with the switch. Possible values are `singleLag` and + `multipleLag`. +* `link_discovery_operation` - (Optional) Whether to `advertise` or `listen` + for link discovery traffic. +* `link_discovery_protocol` - (Optional) The discovery protocol type. Valid + types are `cdp` and `lldp`. +* `max_mtu` - (Optional) The maximum transmission unit (MTU) for the virtual + switch. +* `multicast_filtering_mode` - (Optional) The multicast filtering mode to use + with the switch. Can be one of `legacyFiltering` or `snooping`. +* `version` - (Optional) - The version of the DVS to create. The default is to + create the DVS at the latest version supported by the version of vSphere + being used. A DVS can be upgraded to another version, but cannot be + downgraded. +* `tags` - (Optional) The IDs of any tags to attach to this resource. See + [here][docs-applying-tags] for a reference on how to apply tags. + +[docs-applying-tags]: /docs/providers/vsphere/r/tag.html#using-tags-in-a-supported-resource + +~> **NOTE:** Tagging support requires vCenter 6.0 or higher. + +### Uplink arguments + +* `uplinks` - (Optional) A list of strings that uniquely identifies the names + of the uplinks on the DVS across hosts. The number of items in this list + controls the number of uplinks that exist on the DVS, in addition to the + names. See [here](#uplink-name-and-count-control) for an example on how to + use this option. + +### Host management arguments + +* `host` - (Optional) Use the `host` sub-resource to declare a host + specification. The options are: + * `host_system_id` - (Required) The host system ID of the host to add to the + DVS. + * `devices` - (Required) The list of NIC devices to map to uplinks on the DVS, + added in order they are specified. + +### Netflow arguments + +The following options control settings that you can use to configure Netflow on +the DVS: + +* `netflow_active_flow_timeout` - (Optional) The number of seconds after which + active flows are forced to be exported to the collector. Allowed range is + `60` to `3600`. Default: `60`. +* `netflow_collector_ip_address` - (Optional) IP address for the Netflow + collector, using IPv4 or IPv6. IPv6 is supported in vSphere Distributed + Switch Version 6.0 or later. Must be set before Netflow can be enabled. +* `netflow_collector_port` - (Optional) Port for the Netflow collector. This + must be set before Netflow can be enabled. +* `netflow_idle_flow_timeout` - (Optional) The number of seconds after which + idle flows are forced to be exported to the collector. Allowed range is `10` + to `600`. Default: `15`. +* `netflow_internal_flows_only` - (Optional) Whether to limit analysis to + traffic that has both source and destination served by the same host. + Default: `false`. +* `netflow_observation_domain_id` - (Optional) The observation domain ID for + the Netflow collector. +* `netflow_sampling_rate` - (Optional) The ratio of total number of packets to + the number of packets analyzed. The default is `0`, which indicates that the + switch should analyze all packets. The maximum value is `1000`, which + indicates an analysis rate of 0.001%. + +### Network I/O control arguments + +The following arguments manage network I/O control. Network I/O control (also +known as network resource control) can be used to set up advanced traffic +shaping for the DVS, allowing control of various classes of traffic in a +fashion similar to how resource pools work for virtual machines. Configuration +of network I/O control is also a requirement for the use of network resource +pools, if their use is so desired. + +#### General network I/O control arguments + +* `network_resource_control_enabled` - (Optional) Set to `true` to enable + network I/O control. Default: `false`. +* `network_resource_control_version` - (Optional) The version of network I/O + control to use. Can be one of `version2` or `version3`. Default: `version2`. + +#### Network I/O control traffic classes + +There are currently 9 traffic classes that can be used for network I/O +control - they are below. + +Each of these classes has 4 options that can be tuned that are discussed in the +next section. + + + + + + + + + + + + +
TypeClass Name
Fault Tolerance (FT) Traffic`faulttolerance`
vSphere Replication (VR) Traffic`hbr`
iSCSI Traffic`iscsi`
Management Traffic`management`
NFS Traffic`nfs`
vSphere Data Protection`vdp`
Virtual Machine Traffic`virtualmachine`
vMotion Traffic`vmotion`
VSAN Traffic`vsan`
+ +#### Traffic class resource options + +There are 4 traffic resource options for each class, prefixed with the name of +the traffic classes seen above. + +For example, to set the traffic class resource options for virtual machine +traffic, see the example below: + +```hcl +resource "vsphere_distributed_virtual_switch" "dvs" { + ... + virtualmachine_share_level = "custom" + virtualmachine_share_count = 150 + virtualmachine_maximum_mbit = 200 + virtualmachine_reservation_mbit = 20 +} +``` + +The options are: + +* `share_level` - (Optional) A pre-defined share level that can be assigned to + this resource class. Can be one of `low`, `normal`, `high`, or `custom`. +* `share_count` - (Optional) The number of shares for a custom level. This is + ignored if `share_level` is not `custom`. +* `maximum_mbit` - (Optional) The maximum amount of bandwidth allowed for this + traffic class in Mbits/sec. +* `reservation_mbit` - (Optional) The guaranteed amount of bandwidth for this + traffic class in Mbits/sec. + +### Default port group policy arguments + +The following arguments are shared with the +[`vsphere_distributed_port_group`][distributed-port-group] resource. Setting +them here defines a default policy here that will be inherited by other port +groups on this switch that do not have these values otherwise overridden. Not +defining these options in a DVS will infer defaults that can be seen in the +Terraform state after the initial apply. + +Of particular note to a DVS are the [HA policy options](#ha-policy-options), +which is where the `active_uplinks` and `standby_uplinks` options are +controlled, allowing the ability to create a NIC failover policy that applies +to the entire DVS and all portgroups within it that don't override the policy. + +#### VLAN options + +The following options control the VLAN behaviour of the port groups the port +policy applies to. One one of these 3 options may be set: + +* `vlan` - (Optional) The member VLAN for the ports this policy applies to. A + value of `0` means no VLAN. +* `vlan_range` - (Optional) Used to denote VLAN trunking. Use the `min_vlan` + and `max_vlan` sub-arguments to define the tagged VLAN range. Multiple + `vlan_range` definitions are allowed, but they must not overlap. Example + below: + +```hcl +resource "vsphere_distributed_virtual_switch" "dvs" { + ... + vlan_range { + min_vlan = 1 + max_vlan = 1000 + } + vlan_range { + min_vlan = 2000 + max_vlan = 4094 + } +} +``` + +* `port_private_secondary_vlan_id` - (Optional) Used to define a secondary VLAN + ID when using private VLANs. + +#### HA policy options + +The following options control HA policy for ports that this policy applies to: + +* `active_uplinks` - (Optional) A list of active uplinks to be used in load + balancing. These uplinks need to match the definitions in the + [`uplinks`](#uplinks) DVS argument. See + [here](#uplink-name-and-count-control) for more details. +* `standby_uplinks` - (Optional) A list of standby uplinks to be used in + failover. These uplinks need to match the definitions in the + [`uplinks`](#uplinks) DVS argument. See + [here](#uplink-name-and-count-control) for more details. +* `check_beacon` - (Optional) Enables beacon probing as an additional measure + to detect NIC failure. + +~> **NOTE:** VMware recommends using a minimum of 3 NICs when using beacon +probing. + +* `failback` - (Optional) If `true`, the teaming policy will re-activate failed + uplinks higher in precedence when they come back up. +* `notify_switches` - (Optional) If `true`, the teaming policy will notify the + broadcast network of an uplink failover, triggering cache updates. +* `teaming_policy` - (Optional) The uplink teaming policy. Can be one of + `loadbalance_ip`, `loadbalance_srcmac`, `loadbalance_srcid`, or + `failover_explicit`. + +#### LACP options + +The following options allow the use of LACP for NIC teaming for ports that this +policy applies to. + +~> **NOTE:** These options are ignored for non-uplink port groups and hence are +only useful at the DVS level. + +* `lacp_enabled` - (Optional) Enables LACP for the ports that this policy + applies to. +* `lacp_mode` - (Optional) The LACP mode. Can be one of `active` or `passive`. + +#### Security options + +The following options control security settings for the ports that this policy +applies to: + +* `allow_forged_transmits` - (Optional) Controls whether or not a virtual + network adapter is allowed to send network traffic with a different MAC + address than that of its own. +* `allow_mac_changes` - (Optional) Controls whether or not the Media Access + Control (MAC) address can be changed. +* `allow_promiscuous` - (Optional) Enable promiscuous mode on the network. This + flag indicates whether or not all traffic is seen on a given port. + +#### Traffic shaping options + +The following options control traffic shaping settings for the ports that this +policy applies to: + +* `ingress_shaping_enabled` - (Optional) `true` if the traffic shaper is + enabled on the port for ingress traffic. +* `ingress_shaping_average_bandwidth` - (Optional) The average bandwidth in + bits per second if ingress traffic shaping is enabled on the port. +* `ingress_shaping_peak_bandwidth` - (Optional) The peak bandwidth during + bursts in bits per second if ingress traffic shaping is enabled on the port. +* `ingress_shaping_burst_size` - (Optional) The maximum burst size allowed in + bytes if ingress traffic shaping is enabled on the port. +* `egress_shaping_enabled` - (Optional) `true` if the traffic shaper is enabled + on the port for egress traffic. +* `egress_shaping_average_bandwidth` - (Optional) The average bandwidth in bits + per second if egress traffic shaping is enabled on the port. +* `egress_shaping_peak_bandwidth` - (Optional) The peak bandwidth during bursts + in bits per second if egress traffic shaping is enabled on the port. +* `egress_shaping_burst_size` - (Optional) The maximum burst size allowed in + bytes if egress traffic shaping is enabled on the port. + +#### Miscellaneous options + +The following are some general options that also affect ports that this policy +applies to: + +* `block_all_ports` - (Optional) Shuts down all ports in the port groups that + this policy applies to, effectively blocking all network access to connected + virtual devices. +* `netflow_enabled` - (Optional) Enables Netflow on all ports that this policy + applies to. +* `tx_uplink` - (Optional) Forward all traffic transmitted by ports for which + this policy applies to its DVS uplinks. + +## Attribute Reference + +The following attributes are exported: + +* `id`: The UUID of the created DVS. +* `config_version`: The current version of the DVS configuration, incremented + by subsequent updates to the DVS. + +## Importing + +An existing DVS can be [imported][docs-import] into this resource via the path +to the DVS, via the following command: + +[docs-import]: https://www.terraform.io/docs/import/index.html + +``` +terraform import vsphere_distributed_virtual_switch.dvs /dc1/network/dvs +``` + +The above would import the DVS named `dvs` that is located in the `dc1` +datacenter. diff --git a/website/vsphere.erb b/website/vsphere.erb index 98e3c3bf2..ee4307947 100644 --- a/website/vsphere.erb +++ b/website/vsphere.erb @@ -61,15 +61,15 @@ > Networking Resources