From edb083629a7b07ad26608b6990d7b614ef82264a Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Wed, 30 Aug 2017 21:46:08 -0700 Subject: [PATCH 1/6] New resource: vsphere_host_virtual_switch This commit adds a new resource, vsphere_host_virtual_switch, which can be used to manage a virtual switch on an ESXi host. --- tf-vsphere-devrc.mk.example | 3 + vsphere/helper_test.go | 38 +++ vsphere/host_network_policy_structure.go | 286 ++++++++++++++++++ vsphere/host_network_system_helper.go | 49 +++ vsphere/host_system_helper.go | 19 ++ vsphere/host_virtual_switch_structure.go | 198 ++++++++++++ vsphere/provider.go | 13 +- .../resource_vsphere_host_virtual_switch.go | 137 +++++++++ ...source_vsphere_host_virtual_switch_test.go | 253 ++++++++++++++++ vsphere/structure_helper.go | 38 +++ .../docs/r/host_virtual_switch.html.markdown | 173 +++++++++++ website/vsphere.erb | 3 + 12 files changed, 1204 insertions(+), 6 deletions(-) create mode 100644 vsphere/host_network_policy_structure.go create mode 100644 vsphere/host_network_system_helper.go create mode 100644 vsphere/host_virtual_switch_structure.go create mode 100644 vsphere/resource_vsphere_host_virtual_switch.go create mode 100644 vsphere/resource_vsphere_host_virtual_switch_test.go create mode 100644 vsphere/structure_helper.go create mode 100644 website/docs/r/host_virtual_switch.html.markdown diff --git a/tf-vsphere-devrc.mk.example b/tf-vsphere-devrc.mk.example index c589946e5..202ee847f 100644 --- a/tf-vsphere-devrc.mk.example +++ b/tf-vsphere-devrc.mk.example @@ -35,5 +35,8 @@ export VSPHERE_INIT_TYPE ?= thin # vDisk type export VSPHERE_ADAPTER_TYPE ?= lsiLogic # Virtual disk adapter type export VSPHERE_LICENSE ?= key # License resource test key export VSPHERE_DC_FOLDER ?= dc-folder # DC resource test folder +export VSPHERE_ESXI_HOST ?= esxi1 # ESXi host to work with +export VSPHERE_HOST_NIC0 ?= vmnic0 # NIC0 for host net tests +export VSPHERE_HOST_NIC1 ?= vmnic1 # NIC1 for host net tests # vi: filetype=make diff --git a/vsphere/helper_test.go b/vsphere/helper_test.go index f08da80cc..a23b22b3b 100644 --- a/vsphere/helper_test.go +++ b/vsphere/helper_test.go @@ -1,10 +1,48 @@ package vsphere import ( + "fmt" "os" "testing" + "time" + + "github.com/hashicorp/terraform/terraform" + "github.com/vmware/govmomi" ) +// testCheckVariables bundles common variables needed by various test checkers. +type testCheckVariables struct { + // A client for various operations. + client *govmomi.Client + + // The subject resource's ID. + resourceID string + + // The ESXi host that a various API call is directed at. + esxiHost string + + // The datacenter that a various API call is directed at. + datacenter string + + // A timeout to pass to various context creation calls. + timeout time.Duration +} + +func testClientVariablesForResource(s *terraform.State, addr string) (testCheckVariables, error) { + rs, ok := s.RootModule().Resources[addr] + if !ok { + return testCheckVariables{}, fmt.Errorf("%s not found in state", addr) + } + + return testCheckVariables{ + client: testAccProvider.Meta().(*govmomi.Client), + resourceID: rs.Primary.ID, + esxiHost: os.Getenv("VSPHERE_ESXI_HOST"), + datacenter: os.Getenv("VSPHERE_DATACENTER"), + timeout: time.Minute * 5, + }, nil +} + // testAccSkipIfNotEsxi skips a test if VSPHERE_TEST_ESXI is not set. func testAccSkipIfNotEsxi(t *testing.T) { if os.Getenv("VSPHERE_TEST_ESXI") == "" { diff --git a/vsphere/host_network_policy_structure.go b/vsphere/host_network_policy_structure.go new file mode 100644 index 000000000..e727b1654 --- /dev/null +++ b/vsphere/host_network_policy_structure.go @@ -0,0 +1,286 @@ +package vsphere + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/vmware/govmomi/vim25/types" +) + +const ( + hostNetworkPolicyNicTeamingPolicyModeLoadbalanceIP = "loadbalance_ip" + hostNetworkPolicyNicTeamingPolicyModeLoadbalanceSrcMac = "loadbalance_srcmac" + hostNetworkPolicyNicTeamingPolicyModeLoadbalanceSrcID = "loadbalance_srcid" + hostNetworkPolicyNicTeamingPolicyModeFailoverExplicit = "failover_explicit" +) + +var hostNetworkPolicyNicTeamingPolicyAllowedValues = []string{ + hostNetworkPolicyNicTeamingPolicyModeLoadbalanceIP, + hostNetworkPolicyNicTeamingPolicyModeLoadbalanceSrcMac, + hostNetworkPolicyNicTeamingPolicyModeLoadbalanceSrcID, + hostNetworkPolicyNicTeamingPolicyModeFailoverExplicit, +} + +// schemaHostNetworkPolicy returns schema items for resources that need to work +// with a HostNetworkPolicy, such as virtual switches and port groups. +func schemaHostNetworkPolicy() map[string]*schema.Schema { + return map[string]*schema.Schema{ + // HostNicTeamingPolicy/HostNicFailureCriteria + "check_beacon": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: "Enable beacon probing. Requires that the vSwitch has been configured to use a beacon. If disabled, link status is used only.", + }, + + // HostNicTeamingPolicy/HostNicOrderPolicy + "active_nics": &schema.Schema{ + Type: schema.TypeList, + Description: "List of active network adapters used for load balancing.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "standby_nics": &schema.Schema{ + Type: schema.TypeList, + Description: "List of standby network adapters used for failover.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + // HostNicTeamingPolicy + "teaming_policy": &schema.Schema{ + Type: schema.TypeString, + Optional: true, + Description: "The network adapter teaming policy. Can be one of loadbalance_ip, loadbalance_srcmac, loadbalance_srcid, or failover_explicit.", + ValidateFunc: validation.StringInSlice(hostNetworkPolicyNicTeamingPolicyAllowedValues, false), + }, + "notify_switches": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: "If true, the teaming policy will notify the broadcast network of a NIC failover, triggering cache updates.", + }, + "failback": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: "If true, the teaming policy will re-activate failed interfaces higher in precedence when they come back up.", + }, + + // HostNetworkSecurityPolicy + "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.", + }, + + // HostNetworkTrafficShapingPolicy + "shaping_average_bandwidth": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Description: "The average bandwidth in bits per second if shaping is enabled on the port.", + }, + "shaping_burst_size": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Description: "The maximum burst size allowed in bytes if shaping is enabled on the port.", + }, + "shaping_enabled": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Description: "True if the traffic shaper is enabled on the port.", + }, + "shaping_peak_bandwidth": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Description: "The peak bandwidth during bursts in bits per second if traffic shaping is enabled on the port.", + }, + } +} + +// expandHostNicFailureCriteria reads certain ResourceData keys and returns a +// HostNicFailureCriteria. +func expandHostNicFailureCriteria(d *schema.ResourceData) *types.HostNicFailureCriteria { + obj := &types.HostNicFailureCriteria{} + + if v, ok := d.GetOkExists("check_beacon"); ok { + obj.CheckBeacon = &([]bool{v.(bool)}[0]) + } + + // These fields are deprecated and are set only to make things work. They are + // not exposed to Terraform. + obj.CheckSpeed = "minimum" + obj.Speed = 10 + obj.CheckDuplex = &([]bool{false}[0]) + obj.FullDuplex = &([]bool{false}[0]) + obj.CheckErrorPercent = &([]bool{false}[0]) + obj.Percentage = 0 + + return obj +} + +// flattenHostNicFailureCriteria reads various fields from a +// HostNicFailureCriteria into the passed in ResourceData. +func flattenHostNicFailureCriteria(d *schema.ResourceData, obj *types.HostNicFailureCriteria) error { + if obj.CheckBeacon != nil { + d.Set("check_beacon", obj.CheckBeacon) + } + return nil +} + +// expandHostNicOrderPolicy reads certain ResourceData keys and returns a +// HostNicOrderPolicy. +func expandHostNicOrderPolicy(d *schema.ResourceData) *types.HostNicOrderPolicy { + obj := &types.HostNicOrderPolicy{} + activeNics, activeOk := d.GetOkExists("active_nics") + standbyNics, standbyOk := d.GetOkExists("standby_nics") + if !activeOk && !standbyOk { + return nil + } + obj.ActiveNic = sliceInterfacesToStrings(activeNics.([]interface{})) + obj.StandbyNic = sliceInterfacesToStrings(standbyNics.([]interface{})) + return obj +} + +// flattenHostNicOrderPolicy reads various fields from a HostNicOrderPolicy +// into the passed in ResourceData. +func flattenHostNicOrderPolicy(d *schema.ResourceData, obj *types.HostNicOrderPolicy) error { + if obj == nil { + return nil + } + if err := d.Set("active_nics", sliceStringsToInterfaces(obj.ActiveNic)); err != nil { + return err + } + if err := d.Set("standby_nics", sliceStringsToInterfaces(obj.StandbyNic)); err != nil { + return err + } + return nil +} + +// expandHostNicTeamingPolicy reads certain ResourceData keys and returns a +// HostNicTeamingPolicy. +func expandHostNicTeamingPolicy(d *schema.ResourceData) *types.HostNicTeamingPolicy { + obj := &types.HostNicTeamingPolicy{ + Policy: d.Get("teaming_policy").(string), + } + if v, ok := d.GetOkExists("failback"); ok { + obj.RollingOrder = &([]bool{!v.(bool)}[0]) + } + if v, ok := d.GetOkExists("notify_switches"); ok { + obj.NotifySwitches = &([]bool{v.(bool)}[0]) + } + obj.FailureCriteria = expandHostNicFailureCriteria(d) + obj.NicOrder = expandHostNicOrderPolicy(d) + + // These fields are deprecated and are set only to make things work. They are + // not exposed to Terraform. + obj.ReversePolicy = &([]bool{true}[0]) + + return obj +} + +// flattenHostNicTeamingPolicy reads various fields from a HostNicTeamingPolicy +// into the passed in ResourceData. +func flattenHostNicTeamingPolicy(d *schema.ResourceData, obj *types.HostNicTeamingPolicy) error { + if obj.RollingOrder != nil { + d.Set("failback", !*obj.RollingOrder) + } + if obj.NotifySwitches != nil { + d.Set("notify_switches", obj.NotifySwitches) + } + d.Set("teaming_policy", obj.Policy) + if err := flattenHostNicFailureCriteria(d, obj.FailureCriteria); err != nil { + return err + } + if err := flattenHostNicOrderPolicy(d, obj.NicOrder); err != nil { + return err + } + return nil +} + +// expandHostNetworkSecurityPolicy reads certain ResourceData keys and returns +// a HostNetworkSecurityPolicy. +func expandHostNetworkSecurityPolicy(d *schema.ResourceData) *types.HostNetworkSecurityPolicy { + obj := &types.HostNetworkSecurityPolicy{} + if v, ok := d.GetOkExists("allow_promiscuous"); ok { + obj.AllowPromiscuous = &([]bool{v.(bool)}[0]) + } + if v, ok := d.GetOkExists("allow_forged_transmits"); ok { + obj.ForgedTransmits = &([]bool{v.(bool)}[0]) + } + if v, ok := d.GetOkExists("allow_mac_changes"); ok { + obj.MacChanges = &([]bool{v.(bool)}[0]) + } + return obj +} + +// flattenHostNetworkSecurityPolicy reads various fields from a +// HostNetworkSecurityPolicy into the passed in ResourceData. +func flattenHostNetworkSecurityPolicy(d *schema.ResourceData, obj *types.HostNetworkSecurityPolicy) error { + if obj.AllowPromiscuous != nil { + d.Set("allow_promiscuous", *obj.AllowPromiscuous) + } + if obj.ForgedTransmits != nil { + d.Set("allow_forged_transmits", *obj.ForgedTransmits) + } + if obj.MacChanges != nil { + d.Set("allow_mac_changes", *obj.MacChanges) + } + return nil +} + +// expandHostNetworkTrafficShapingPolicy reads certain ResourceData keys and +// returns a HostNetworkTrafficShapingPolicy. +func expandHostNetworkTrafficShapingPolicy(d *schema.ResourceData) *types.HostNetworkTrafficShapingPolicy { + obj := &types.HostNetworkTrafficShapingPolicy{ + AverageBandwidth: int64(d.Get("shaping_average_bandwidth").(int)), + BurstSize: int64(d.Get("shaping_burst_size").(int)), + PeakBandwidth: int64(d.Get("shaping_peak_bandwidth").(int)), + } + if v, ok := d.GetOkExists("shaping_enabled"); ok { + obj.Enabled = &([]bool{v.(bool)}[0]) + } + return obj +} + +// flattenHostNetworkTrafficShapingPolicy reads various fields from a +// HostNetworkTrafficShapingPolicy into the passed in ResourceData. +func flattenHostNetworkTrafficShapingPolicy(d *schema.ResourceData, obj *types.HostNetworkTrafficShapingPolicy) error { + if obj.Enabled != nil { + d.Set("shaping_enabled", *obj.Enabled) + } + d.Set("shaping_average_bandwidth", obj.AverageBandwidth) + d.Set("shaping_burst_size", obj.BurstSize) + d.Set("shaping_peak_bandwidth", obj.PeakBandwidth) + return nil +} + +// expandHostNetworkPolicy reads certain ResourceData keys and returns a +// HostNetworkPolicy. +func expandHostNetworkPolicy(d *schema.ResourceData) *types.HostNetworkPolicy { + obj := &types.HostNetworkPolicy{ + Security: expandHostNetworkSecurityPolicy(d), + NicTeaming: expandHostNicTeamingPolicy(d), + ShapingPolicy: expandHostNetworkTrafficShapingPolicy(d), + } + return obj +} + +// flattenHostNetworkPolicy reads various fields from a HostNetworkPolicy into +// the passed in ResourceData. +func flattenHostNetworkPolicy(d *schema.ResourceData, obj *types.HostNetworkPolicy) error { + if err := flattenHostNetworkSecurityPolicy(d, obj.Security); err != nil { + return err + } + if err := flattenHostNicTeamingPolicy(d, obj.NicTeaming); err != nil { + return err + } + if err := flattenHostNetworkTrafficShapingPolicy(d, obj.ShapingPolicy); err != nil { + return err + } + return nil +} diff --git a/vsphere/host_network_system_helper.go b/vsphere/host_network_system_helper.go new file mode 100644 index 000000000..09fef7891 --- /dev/null +++ b/vsphere/host_network_system_helper.go @@ -0,0 +1,49 @@ +package vsphere + +import ( + "context" + "fmt" + + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/mo" + "github.com/vmware/govmomi/vim25/types" +) + +// hostNetworkSystemFromHostSystem locates a HostNetworkSystem from a specified +// HostSystem. +func hostNetworkSystemFromHostSystem(hs *object.HostSystem) (*object.HostNetworkSystem, error) { + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + return hs.ConfigManager().NetworkSystem(ctx) +} + +// hostNetworkSystemFromHostSystemID locates a HostNetworkSystem from a +// specified HostSystem managed object ID. +func hostNetworkSystemFromHostSystemID(client *govmomi.Client, hsID string) (*object.HostNetworkSystem, error) { + hs, err := hostSystemFromID(client, hsID) + if err != nil { + return nil, err + } + return hostNetworkSystemFromHostSystem(hs) +} + +// hostVSwitchFromName locates a virtual switch on the supplied HostSystem by +// name. +func hostVSwitchFromName(client *govmomi.Client, ns *object.HostNetworkSystem, name string) (*types.HostVirtualSwitch, error) { + var mns mo.HostNetworkSystem + pc := client.PropertyCollector() + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + if err := pc.RetrieveOne(ctx, ns.Reference(), []string{"networkInfo.vswitch"}, &mns); err != nil { + return nil, fmt.Errorf("error fetching host network properties: %s", err) + } + + for _, sw := range mns.NetworkInfo.Vswitch { + if sw.Name == name { + return &sw, nil + } + } + + return nil, fmt.Errorf("could not find virtual switch %s", name) +} diff --git a/vsphere/host_system_helper.go b/vsphere/host_system_helper.go index 357602f4c..0dbced922 100644 --- a/vsphere/host_system_helper.go +++ b/vsphere/host_system_helper.go @@ -7,6 +7,7 @@ import ( "github.com/vmware/govmomi" "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/types" ) // hostSystemOrDefault returns a HostSystem from a specific host name and @@ -30,3 +31,21 @@ func hostSystemOrDefault(client *govmomi.Client, name string, dc *object.Datacen } return nil, fmt.Errorf("unsupported ApiType: %s", t) } + +// hostSystemFromID locates a HostSystem by its managed object reference ID. +func hostSystemFromID(client *govmomi.Client, id string) (*object.HostSystem, error) { + finder := find.NewFinder(client.Client, false) + + ref := types.ManagedObjectReference{ + Type: "HostSystem", + Value: id, + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + ds, err := finder.ObjectReference(ctx, ref) + if err != nil { + return nil, fmt.Errorf("could not find host system with id: %s: %s", id, err) + } + return ds.(*object.HostSystem), nil +} diff --git a/vsphere/host_virtual_switch_structure.go b/vsphere/host_virtual_switch_structure.go new file mode 100644 index 000000000..665910b3c --- /dev/null +++ b/vsphere/host_virtual_switch_structure.go @@ -0,0 +1,198 @@ +package vsphere + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/vmware/govmomi/vim25/types" +) + +const hostVirtualSwitchIDPrefix = "tf-HostVirtualSwitch" + +var linkDiscoveryProtocolConfigOperationAllowedValues = []string{ + string(types.LinkDiscoveryProtocolConfigOperationTypeNone), + string(types.LinkDiscoveryProtocolConfigOperationTypeListen), + string(types.LinkDiscoveryProtocolConfigOperationTypeAdvertise), + string(types.LinkDiscoveryProtocolConfigOperationTypeBoth), +} + +var linkDiscoveryProtocolConfigProtocolAllowedValues = []string{ + string(types.LinkDiscoveryProtocolConfigProtocolTypeCdp), + string(types.LinkDiscoveryProtocolConfigProtocolTypeLldp), +} + +// schemaHostVirtualSwitchBondBridge returns schema items for resources that +// need to work with a HostVirtualSwitchBondBridge, such as virtual switches. +func schemaHostVirtualSwitchBondBridge() map[string]*schema.Schema { + return map[string]*schema.Schema{ + // HostVirtualSwitchBeaconConfig + "beacon_interval": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Description: "Determines how often, in seconds, a beacon should be sent to probe for the validity of a link.", + Default: 1, + 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), + }, + + // HostVirtualSwitchBondBridge + "network_adapters": &schema.Schema{ + Type: schema.TypeList, + Required: true, + Description: "The list of network adapters to bind to this virtual switch.", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + } +} + +// expandHostVirtualSwitchBeaconConfig reads certain ResourceData keys and +// returns a HostVirtualSwitchBeaconConfig. +func expandHostVirtualSwitchBeaconConfig(d *schema.ResourceData) *types.HostVirtualSwitchBeaconConfig { + obj := &types.HostVirtualSwitchBeaconConfig{ + Interval: int32(d.Get("beacon_interval").(int)), + } + return obj +} + +// flattenHostVirtualSwitchBeaconConfig reads various fields from a +// HostVirtualSwitchBeaconConfig into the passed in ResourceData. +func flattenHostVirtualSwitchBeaconConfig(d *schema.ResourceData, obj *types.HostVirtualSwitchBeaconConfig) error { + d.Set("beacon_interval", obj.Interval) + return nil +} + +// expandLinkDiscoveryProtocolConfig reads certain ResourceData keys and +// returns a LinkDiscoveryProtocolConfig. +func expandLinkDiscoveryProtocolConfig(d *schema.ResourceData) *types.LinkDiscoveryProtocolConfig { + obj := &types.LinkDiscoveryProtocolConfig{ + Operation: d.Get("link_discovery_operation").(string), + Protocol: d.Get("link_discovery_protocol").(string), + } + return obj +} + +// flattenLinkDiscoveryProtocolConfig reads various fields from a +// LinkDiscoveryProtocolConfig into the passed in ResourceData. +func flattenLinkDiscoveryProtocolConfig(d *schema.ResourceData, obj *types.LinkDiscoveryProtocolConfig) error { + d.Set("link_discovery_operation", obj.Operation) + d.Set("link_discovery_protocol", obj.Protocol) + return nil +} + +// expandHostVirtualSwitchBondBridge reads certain ResourceData keys and +// returns a HostVirtualSwitchBondBridge. +func expandHostVirtualSwitchBondBridge(d *schema.ResourceData) *types.HostVirtualSwitchBondBridge { + obj := &types.HostVirtualSwitchBondBridge{ + NicDevice: sliceInterfacesToStrings(d.Get("network_adapters").([]interface{})), + } + obj.Beacon = expandHostVirtualSwitchBeaconConfig(d) + obj.LinkDiscoveryProtocolConfig = expandLinkDiscoveryProtocolConfig(d) + return obj +} + +// flattenHostVirtualSwitchBondBridge reads various fields from a +// HostVirtualSwitchBondBridge into the passed in ResourceData. +func flattenHostVirtualSwitchBondBridge(d *schema.ResourceData, obj *types.HostVirtualSwitchBondBridge) error { + if err := d.Set("network_adapters", sliceStringsToInterfaces(obj.NicDevice)); err != nil { + return err + } + if err := flattenHostVirtualSwitchBeaconConfig(d, obj.Beacon); err != nil { + return err + } + if err := flattenLinkDiscoveryProtocolConfig(d, obj.LinkDiscoveryProtocolConfig); err != nil { + return err + } + return nil +} + +// schemaHostVirtualSwitchSpec returns schema items for resources that need to +// work with a HostVirtualSwitchSpec, such as virtual switches. +func schemaHostVirtualSwitchSpec() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + // HostVirtualSwitchSpec + "mtu": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Description: "The maximum transmission unit (MTU) of the virtual switch in bytes.", + Default: 1500, + ValidateFunc: validation.IntBetween(1, 9000), + }, + "number_of_ports": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Description: "The number of ports that this virtual switch is configured to use.", + Default: 128, + ValidateFunc: validation.IntBetween(0, 1024), + }, + } + mergeSchema(s, schemaHostVirtualSwitchBondBridge()) + mergeSchema(s, schemaHostNetworkPolicy()) + return s +} + +// expandHostVirtualSwitchSpec reads certain ResourceData keys and returns a +// HostVirtualSwitchSpec. +func expandHostVirtualSwitchSpec(d *schema.ResourceData) *types.HostVirtualSwitchSpec { + obj := &types.HostVirtualSwitchSpec{ + Mtu: int32(d.Get("mtu").(int)), + NumPorts: int32(d.Get("number_of_ports").(int)), + Bridge: expandHostVirtualSwitchBondBridge(d), + Policy: expandHostNetworkPolicy(d), + } + return obj +} + +// flattenHostVirtualSwitchSpec reads various fields from a +// HostVirtualSwitchSpec into the passed in ResourceData. +func flattenHostVirtualSwitchSpec(d *schema.ResourceData, obj *types.HostVirtualSwitchSpec) error { + d.Set("mtu", obj.Mtu) + d.Set("number_of_ports", obj.NumPorts) + if err := flattenHostVirtualSwitchBondBridge(d, obj.Bridge.(*types.HostVirtualSwitchBondBridge)); err != nil { + return err + } + if err := flattenHostNetworkPolicy(d, obj.Policy); err != nil { + return err + } + return nil +} + +// saveHostVirtualSwitchID sets a special ID for a host virtual switch, +// composed of the MOID for the concerned HostSystem and the virtual switch's +// key. +func saveHostVirtualSwitchID(d *schema.ResourceData, hsID, name string) { + d.SetId(fmt.Sprintf("%s:%s:%s", hostVirtualSwitchIDPrefix, hsID, name)) +} + +// splitHostVirtualSwitchID splits a vsphere_host_virtual_switch resource ID +// into its counterparts: the prefix, the HostSystem ID, and the virtual switch +// name. +func splitHostVirtualSwitchID(raw string) (string, string, error) { + s := strings.SplitN(raw, ":", 3) + if len(s) < 3 || s[0] != hostVirtualSwitchIDPrefix || s[1] == "" || s[2] == "" { + return "", "", fmt.Errorf("corrupt ID: %s", raw) + } + return s[1], s[2], nil +} + +// virtualSwitchIDsFromResourceID passes a resource's ID through +// splitHostVirtualSwitchID. +func virtualSwitchIDsFromResourceID(d *schema.ResourceData) (string, string, error) { + return splitHostVirtualSwitchID(d.Id()) +} diff --git a/vsphere/provider.go b/vsphere/provider.go index 350944d69..9a824882a 100644 --- a/vsphere/provider.go +++ b/vsphere/provider.go @@ -69,12 +69,13 @@ func Provider() terraform.ResourceProvider { }, ResourcesMap: map[string]*schema.Resource{ - "vsphere_datacenter": resourceVSphereDatacenter(), - "vsphere_file": resourceVSphereFile(), - "vsphere_folder": resourceVSphereFolder(), - "vsphere_virtual_disk": resourceVSphereVirtualDisk(), - "vsphere_virtual_machine": resourceVSphereVirtualMachine(), - "vsphere_license": resourceVSphereLicense(), + "vsphere_datacenter": resourceVSphereDatacenter(), + "vsphere_file": resourceVSphereFile(), + "vsphere_folder": resourceVSphereFolder(), + "vsphere_host_virtual_switch": resourceVSphereHostVirtualSwitch(), + "vsphere_license": resourceVSphereLicense(), + "vsphere_virtual_disk": resourceVSphereVirtualDisk(), + "vsphere_virtual_machine": resourceVSphereVirtualMachine(), }, DataSourcesMap: map[string]*schema.Resource{ diff --git a/vsphere/resource_vsphere_host_virtual_switch.go b/vsphere/resource_vsphere_host_virtual_switch.go new file mode 100644 index 000000000..43416c8bc --- /dev/null +++ b/vsphere/resource_vsphere_host_virtual_switch.go @@ -0,0 +1,137 @@ +package vsphere + +import ( + "fmt" + + "context" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/vmware/govmomi" +) + +func resourceVSphereHostVirtualSwitch() *schema.Resource { + s := map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Description: "The name of the virtual switch.", + Required: true, + ForceNew: true, + }, + "host_system_id": &schema.Schema{ + Type: schema.TypeString, + Description: "The managed object ID of the host to set the virtual switch up on.", + Required: true, + ForceNew: true, + }, + } + mergeSchema(s, schemaHostVirtualSwitchSpec()) + + // Transform any necessary fields in the schema that need to be updated + // specifically for this resource. + s["active_nics"].Required = true + s["standby_nics"].Required = true + + s["teaming_policy"].Default = hostNetworkPolicyNicTeamingPolicyModeLoadbalanceSrcID + s["check_beacon"].Default = false + s["notify_switches"].Default = true + s["failback"].Default = true + + s["allow_promiscuous"].Default = false + s["allow_forged_transmits"].Default = true + s["allow_mac_changes"].Default = true + + s["shaping_enabled"].Default = false + + return &schema.Resource{ + Create: resourceVSphereHostVirtualSwitchCreate, + Read: resourceVSphereHostVirtualSwitchRead, + Update: resourceVSphereHostVirtualSwitchUpdate, + Delete: resourceVSphereHostVirtualSwitchDelete, + Schema: s, + } +} + +func resourceVSphereHostVirtualSwitchCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*govmomi.Client) + name := d.Get("name").(string) + hsID := d.Get("host_system_id").(string) + ns, err := hostNetworkSystemFromHostSystemID(client, hsID) + if err != nil { + return fmt.Errorf("error loading host network system: %s", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + spec := expandHostVirtualSwitchSpec(d) + if err := ns.AddVirtualSwitch(ctx, name, spec); err != nil { + return fmt.Errorf("error adding host vSwitch: %s", err) + } + + saveHostVirtualSwitchID(d, hsID, name) + + return resourceVSphereHostVirtualSwitchRead(d, meta) +} + +func resourceVSphereHostVirtualSwitchRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*govmomi.Client) + hsID, name, err := virtualSwitchIDsFromResourceID(d) + if err != nil { + return err + } + ns, err := hostNetworkSystemFromHostSystemID(client, hsID) + if err != nil { + return fmt.Errorf("error loading host network system: %s", err) + } + + sw, err := hostVSwitchFromName(client, ns, name) + if err != nil { + return fmt.Errorf("error fetching virtual switch data: %s", err) + } + + if err := flattenHostVirtualSwitchSpec(d, &sw.Spec); err != nil { + return fmt.Errorf("error setting resource data: %s", err) + } + + return nil +} + +func resourceVSphereHostVirtualSwitchUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*govmomi.Client) + hsID, name, err := virtualSwitchIDsFromResourceID(d) + if err != nil { + return err + } + ns, err := hostNetworkSystemFromHostSystemID(client, hsID) + if err != nil { + return fmt.Errorf("error loading host network system: %s", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + spec := expandHostVirtualSwitchSpec(d) + if err := ns.UpdateVirtualSwitch(ctx, name, *spec); err != nil { + return fmt.Errorf("error updating host vSwitch: %s", err) + } + + return resourceVSphereHostVirtualSwitchRead(d, meta) +} + +func resourceVSphereHostVirtualSwitchDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*govmomi.Client) + hsID, name, err := virtualSwitchIDsFromResourceID(d) + if err != nil { + return err + } + ns, err := hostNetworkSystemFromHostSystemID(client, hsID) + if err != nil { + return fmt.Errorf("error loading host network system: %s", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + if err := ns.RemoveVirtualSwitch(ctx, name); err != nil { + return fmt.Errorf("error deleting host vSwitch: %s", err) + } + + return nil +} diff --git a/vsphere/resource_vsphere_host_virtual_switch_test.go b/vsphere/resource_vsphere_host_virtual_switch_test.go new file mode 100644 index 000000000..f4b6638b6 --- /dev/null +++ b/vsphere/resource_vsphere_host_virtual_switch_test.go @@ -0,0 +1,253 @@ +package vsphere + +import ( + "errors" + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccResourceVSphereHostVirtualSwitch(t *testing.T) { + var tp *testing.T + testAccResourceVSphereHostVirtualSwitchCases := []struct { + name string + testCase resource.TestCase + }{ + { + "basic", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereHostVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereHostVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereHostVirtualSwitchConfig(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereHostVirtualSwitchExists(true), + ), + }, + }, + }, + }, + { + "basic, then remove a NIC", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereHostVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereHostVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereHostVirtualSwitchConfig(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereHostVirtualSwitchExists(true), + ), + }, + { + Config: testAccResourceVSphereHostVirtualSwitchConfigSingleNIC(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereHostVirtualSwitchExists(true), + ), + }, + }, + }, + }, + { + "standby with explicit failover order", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereHostVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereHostVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereHostVirtualSwitchConfigStandbyLink(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereHostVirtualSwitchExists(true), + ), + }, + }, + }, + }, + { + "basic, then change to standby with failover order", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereHostVirtualSwitchPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereHostVirtualSwitchExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereHostVirtualSwitchConfig(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereHostVirtualSwitchExists(true), + ), + }, + { + Config: testAccResourceVSphereHostVirtualSwitchConfigStandbyLink(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereHostVirtualSwitchExists(true), + ), + }, + }, + }, + }, + } + + for _, tc := range testAccResourceVSphereHostVirtualSwitchCases { + t.Run(tc.name, func(t *testing.T) { + tp = t + resource.Test(t, tc.testCase) + }) + } +} + +func testAccResourceVSphereHostVirtualSwitchPreCheck(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") + } +} + +func testAccResourceVSphereHostVirtualSwitchExists(expected bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + vars, err := testClientVariablesForResource(s, "vsphere_host_virtual_switch.switch") + if err != nil { + return errors.New("vsphere_host_virtual_switch.switch not found in state") + } + + hsID, name, err := splitHostVirtualSwitchID(vars.resourceID) + if err != nil { + return err + } + ns, err := hostNetworkSystemFromHostSystemID(vars.client, hsID) + if err != nil { + return fmt.Errorf("error loading host network system: %s", err) + } + + _, err = hostVSwitchFromName(vars.client, ns, name) + if err != nil { + if err.Error() == fmt.Sprintf("could not find virtual switch %s", name) && !expected { + // Expected missing + return nil + } + return err + } + if !expected { + return fmt.Errorf("expected vSwitch %s to be missing", name) + } + return nil + } +} + +func testAccResourceVSphereHostVirtualSwitchConfig() string { + return fmt.Sprintf(` +variable "host_nic0" { + type = "string" + default = "%s" +} + +variable "host_nic1" { + type = "string" + default = "%s" +} + +data "vsphere_datacenter" "datacenter" { + name = "%s" +} + +data "vsphere_host" "esxi_host" { + name = "%s" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +} + +resource "vsphere_host_virtual_switch" "switch" { + name = "vSwitchTerraformTest" + host_system_id = "${data.vsphere_host.esxi_host.id}" + + network_adapters = ["${var.host_nic0}", "${var.host_nic1}"] + + active_nics = ["${var.host_nic0}", "${var.host_nic1}"] + standby_nics = [] +} +`, os.Getenv("VSPHERE_HOST_NIC0"), os.Getenv("VSPHERE_HOST_NIC1"), os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST")) +} + +func testAccResourceVSphereHostVirtualSwitchConfigSingleNIC() string { + return fmt.Sprintf(` +variable "host_nic0" { + type = "string" + default = "%s" +} + +data "vsphere_datacenter" "datacenter" { + name = "%s" +} + +data "vsphere_host" "esxi_host" { + name = "%s" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +} + +resource "vsphere_host_virtual_switch" "switch" { + name = "vSwitchTerraformTest" + host_system_id = "${data.vsphere_host.esxi_host.id}" + + network_adapters = ["${var.host_nic0}"] + + active_nics = ["${var.host_nic0}"] + standby_nics = [] +} +`, os.Getenv("VSPHERE_HOST_NIC0"), os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST")) +} + +func testAccResourceVSphereHostVirtualSwitchConfigStandbyLink() string { + return fmt.Sprintf(` +variable "host_nic0" { + type = "string" + default = "%s" +} + +variable "host_nic1" { + type = "string" + default = "%s" +} + +data "vsphere_datacenter" "datacenter" { + name = "%s" +} + +data "vsphere_host" "esxi_host" { + name = "%s" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +} + +resource "vsphere_host_virtual_switch" "switch" { + name = "vSwitchTerraformTest" + host_system_id = "${data.vsphere_host.esxi_host.id}" + + network_adapters = ["${var.host_nic0}", "${var.host_nic1}"] + + active_nics = ["${var.host_nic0}"] + standby_nics = ["${var.host_nic1}"] + teaming_policy = "failover_explicit" +} +`, os.Getenv("VSPHERE_HOST_NIC0"), os.Getenv("VSPHERE_HOST_NIC1"), os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST")) +} diff --git a/vsphere/structure_helper.go b/vsphere/structure_helper.go new file mode 100644 index 000000000..542901116 --- /dev/null +++ b/vsphere/structure_helper.go @@ -0,0 +1,38 @@ +package vsphere + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" +) + +// sliceInterfacesToStrings converts an interface slice to a string slice. The +// function does not attempt to do any sanity checking and will panic if one of +// the items in the slice is not a string. +func sliceInterfacesToStrings(s []interface{}) []string { + var d []string + for _, v := range s { + d = append(d, v.(string)) + } + return d +} + +// sliceStringsToInterfaces converts a string slice to an interface slice. +func sliceStringsToInterfaces(s []string) []interface{} { + var d []interface{} + for _, v := range s { + d = append(d, v) + } + return d +} + +// mergeSchema merges the map[string]*schema.Schema from src into dst. Safety +// against conflicts is enforced by panicing. +func mergeSchema(dst, src map[string]*schema.Schema) { + for k, v := range src { + if _, ok := dst[k]; ok { + panic(fmt.Errorf("conflicting schema key: %s", k)) + } + dst[k] = v + } +} diff --git a/website/docs/r/host_virtual_switch.html.markdown b/website/docs/r/host_virtual_switch.html.markdown new file mode 100644 index 000000000..97bfc807c --- /dev/null +++ b/website/docs/r/host_virtual_switch.html.markdown @@ -0,0 +1,173 @@ +--- +layout: "vsphere" +page_title: "VMware vSphere: vsphere_host_virtual_switch" +sidebar_current: "docs-vsphere-resource-host-virtual-switch" +description: |- + Provides a vSphere Host Virtual Switch Resource. This can be used to configure vSwitches direct on an ESXi host. +--- + +# vsphere\_host\_virtual\_switch + +The `vsphere_host_virtual_switch` resource can be used to manage vSphere +standard switches on an ESXi host. These switches can be used as a backing for +standard port groups, which can be managed by the +[`vsphere_host_port_group`][host-port-group] resource. + +For an overview on vSphere networking concepts, see [this +page][ref-vsphere-net-concepts]. + +[host-port-group]: /docs/providers/vsphere/r/host_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 + +## Example Usages + +**Create a virtual switch with one active and one standby NIC:** + +```hcl +data "vsphere_datacenter" "datacenter" { + name = "dc1" +} + +data "vsphere_host" "host" { + name = "esxi1" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +} + +resource "vsphere_host_virtual_switch" "switch" { + name = "vSwitchTerraformTest" + host_system_id = "${data.vsphere_host.host.id}" + + network_adapters = ["vmnic0", "vmnic1"] + + active_nics = ["vmnic0"] + standby_nics = ["vmnic1"] +} +``` + +**Create a virtual switch with extra networking policy options:** + +```hcl +data "vsphere_datacenter" "datacenter" { + name = "dc1" +} + +data "vsphere_host" "host" { + name = "esxi1" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +} + +resource "vsphere_host_virtual_switch" "switch" { + name = "vSwitchTerraformTest" + host_system_id = "${data.vsphere_host.host.id}" + + network_adapters = ["vmnic0", "vmnic1"] + + active_nics = ["vmnic0"] + standby_nics = ["vmnic1"] + teaming_policy = "failover_explicit" + + allow_promiscuous = false + allow_forged_transmits = false + allow_mac_changes = false + + shaping_enabled = true + shaping_average_bandwidth = 50000000 + shaping_peak_bandwidth = 100000000 + shaping_burst_size = 1000000000 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (String, required, forces new resource) The name of the virtual switch. +* `host_system_id` - (String, required, forces new resource) The managed object + ID of the host to set the virtual switch up on. +* `mtu` - (Integer, optional) The maximum transmission unit (MTU) for the virtual + switch. Default: `1500`. +* `number_of_ports` - (Integer, optional) The number of ports to create with + this virtual switch. Default: `128`. + +~> **NOTE:** Changing the port count requires a reboot of the host. Terraform +will not restart the host for you. + +### Bridge Options + +The following arguments are related to how the virtual switch binds to physical +NICs: + +* `network_adapters` - (Array of strings, required) The network interfaces to + bind to the bridge. +* `beacon_interval` - (Integer, optional) The interval, in seconds, that a NIC + beacon packet is sent out. This can be used with + [`check_beacon`](#check_beacon) to offer link failure capability beyond link + status only. Default: `1`. +* `link_discovery_operation` - (String, optional) Whether to `advertise` or + `listen` for link discovery traffic. Default: `listen`. +* `link_discovery_protocol` - (String, optional) The discovery protocol type. + Valid types are `cpd` and `lldp`. Default: `cdp`. + +### Policy Options + +The following options relate to how network traffic is handled on this virtual +switch. It also controls the NIC failover order. This subset of options is +shared with the [`vsphere_host_port_group`][host-port-group] resource, in which +options can be omitted to ensure options are inherited from the switch +configuration here. + +#### NIC Teaming Options + +~> **NOTE on NIC failover order:** An adapter can be in `active_nics`, +`standby_nics`, or neither to flag it as unused. However, virtual switch +creation or update operations will fail if a NIC is present in both settings, +or if the NIC is not a valid NIC in `network_adapters`. + +~> **NOTE:** VMware recommends using a minimum of 3 NICs when using beacon +probing (configured with [`check_beacon`](#check_beacon)). + +* `active_nics` - (Array of strings, required) The list of active network + adapters used for load balancing. +* `standby_nics` - (Array of strings, required) The list of standby network + adapters used for failover. +* `check_beacon` - (Boolean, optional) Enable beacon probing - this requires + that the [`beacon_interval`](#beacon_interval) option has been set in the + bridge options. If this is false, only link status is used to check for + failed NICs. Default: `false`. +* `teaming_policy` - (String, optional) The network adapter teaming policy. Can + be one of `loadbalance_ip`, `loadbalance_srcmac`, `loadbalance_srcid`, or + `failover_explicit`. Default: `loadbalance_srcid`. +* `notify_switches` - (Boolean, optional) If `true`, the teaming policy will + notify the broadcast network of a NIC failover, triggering cache updates. + Default: `true`. +* `failback` - (Boolean, optional) If `true`, the teaming policy will + re-activate failed interfaces higher in precedence when they come back up. + Default: `true`. + +#### Security Policy Options + +* `allow_promiscuous` - (Boolean, optional) Enable promiscuous mode on the + network. This flag indicates whether or not all traffic is seen on a given + port. Default: `false`. +* `allow_forged_transmits` - (Boolean, optional) Controls whether or not the + virtual network adapter is allowed to send network traffic with a different + MAC address than that of its own. Default: `true`. +* `allow_mac_changes` - (Boolean, optional) Controls whether or not the Media + Access Control (MAC) address can be changed. Default: `true`. + +#### Traffic Shaping Options + +* `shaping_enabled` - (Boolean, optional) `true` if the traffic shaper is + enabled on the port. Default: `false`. +* `shaping_average_bandwidth` - (Integer, optional) The average bandwidth in + bits per second if shaping is enabled on the port. Default: `0` +* `shaping_peak_bandwidth` - (Integer, optional) The peak bandwidth during + bursts in bits per second if traffic shaping is enabled on the port. Default: + `0` +* `shaping_burst_size` - (Integer, optional) The maximum burst size allowed in + bytes if shaping is enabled on the port. Default: `0` + +## Attribute Reference + +The only exported attribute, other than the attributes above, is the `id` of +the resource, which is set to the name of the virtual switch. diff --git a/website/vsphere.erb b/website/vsphere.erb index 6a9ae2a1c..e4776c041 100644 --- a/website/vsphere.erb +++ b/website/vsphere.erb @@ -34,6 +34,9 @@ > vsphere_folder + > + vsphere_host_virtual_switch + > vsphere_license From b061d07c48a68d8a26b88bf480eb2d691f97a96e Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Thu, 31 Aug 2017 10:08:57 -0700 Subject: [PATCH 2/6] Make all *bool literals and assignments easier to read Added a small boolPtr helper function for this. As a TODO, I'd like to streamline helpers possibly into their own package, and add one for int32 which seems to be the standard int type in govmomi. --- vsphere/host_network_policy_structure.go | 22 +++++++++++----------- vsphere/structure_helper.go | 8 ++++++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/vsphere/host_network_policy_structure.go b/vsphere/host_network_policy_structure.go index e727b1654..b4a579312 100644 --- a/vsphere/host_network_policy_structure.go +++ b/vsphere/host_network_policy_structure.go @@ -108,16 +108,16 @@ func expandHostNicFailureCriteria(d *schema.ResourceData) *types.HostNicFailureC obj := &types.HostNicFailureCriteria{} if v, ok := d.GetOkExists("check_beacon"); ok { - obj.CheckBeacon = &([]bool{v.(bool)}[0]) + obj.CheckBeacon = boolPtr(v.(bool)) } // These fields are deprecated and are set only to make things work. They are // not exposed to Terraform. obj.CheckSpeed = "minimum" obj.Speed = 10 - obj.CheckDuplex = &([]bool{false}[0]) - obj.FullDuplex = &([]bool{false}[0]) - obj.CheckErrorPercent = &([]bool{false}[0]) + obj.CheckDuplex = boolPtr(false) + obj.FullDuplex = boolPtr(false) + obj.CheckErrorPercent = boolPtr(false) obj.Percentage = 0 return obj @@ -168,17 +168,17 @@ func expandHostNicTeamingPolicy(d *schema.ResourceData) *types.HostNicTeamingPol Policy: d.Get("teaming_policy").(string), } if v, ok := d.GetOkExists("failback"); ok { - obj.RollingOrder = &([]bool{!v.(bool)}[0]) + obj.RollingOrder = boolPtr(!v.(bool)) } if v, ok := d.GetOkExists("notify_switches"); ok { - obj.NotifySwitches = &([]bool{v.(bool)}[0]) + obj.NotifySwitches = boolPtr(v.(bool)) } obj.FailureCriteria = expandHostNicFailureCriteria(d) obj.NicOrder = expandHostNicOrderPolicy(d) // These fields are deprecated and are set only to make things work. They are // not exposed to Terraform. - obj.ReversePolicy = &([]bool{true}[0]) + obj.ReversePolicy = boolPtr(true) return obj } @@ -207,13 +207,13 @@ func flattenHostNicTeamingPolicy(d *schema.ResourceData, obj *types.HostNicTeami func expandHostNetworkSecurityPolicy(d *schema.ResourceData) *types.HostNetworkSecurityPolicy { obj := &types.HostNetworkSecurityPolicy{} if v, ok := d.GetOkExists("allow_promiscuous"); ok { - obj.AllowPromiscuous = &([]bool{v.(bool)}[0]) + obj.AllowPromiscuous = boolPtr(v.(bool)) } if v, ok := d.GetOkExists("allow_forged_transmits"); ok { - obj.ForgedTransmits = &([]bool{v.(bool)}[0]) + obj.ForgedTransmits = boolPtr(v.(bool)) } if v, ok := d.GetOkExists("allow_mac_changes"); ok { - obj.MacChanges = &([]bool{v.(bool)}[0]) + obj.MacChanges = boolPtr(v.(bool)) } return obj } @@ -242,7 +242,7 @@ func expandHostNetworkTrafficShapingPolicy(d *schema.ResourceData) *types.HostNe PeakBandwidth: int64(d.Get("shaping_peak_bandwidth").(int)), } if v, ok := d.GetOkExists("shaping_enabled"); ok { - obj.Enabled = &([]bool{v.(bool)}[0]) + obj.Enabled = boolPtr(v.(bool)) } return obj } diff --git a/vsphere/structure_helper.go b/vsphere/structure_helper.go index 542901116..344821910 100644 --- a/vsphere/structure_helper.go +++ b/vsphere/structure_helper.go @@ -36,3 +36,11 @@ func mergeSchema(dst, src map[string]*schema.Schema) { dst[k] = v } } + +// boolPtr makes a *bool out of the value passed in through v. +// +// vSphere uses nil values in bools to omit values in the SOAP XML request, and +// helps denote inheritance in certain cases. +func boolPtr(v bool) *bool { + return &v +} From 26ce39adf2195bb0666bdae93c6d23be38c1e227 Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Thu, 31 Aug 2017 10:13:56 -0700 Subject: [PATCH 3/6] r/host_virtual_switch: Small test readability fix for missing case Change !expected to expected == false, to be more explicit. --- vsphere/resource_vsphere_host_virtual_switch_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsphere/resource_vsphere_host_virtual_switch_test.go b/vsphere/resource_vsphere_host_virtual_switch_test.go index f4b6638b6..7a688c9e0 100644 --- a/vsphere/resource_vsphere_host_virtual_switch_test.go +++ b/vsphere/resource_vsphere_host_virtual_switch_test.go @@ -144,7 +144,7 @@ func testAccResourceVSphereHostVirtualSwitchExists(expected bool) resource.TestC _, err = hostVSwitchFromName(vars.client, ns, name) if err != nil { - if err.Error() == fmt.Sprintf("could not find virtual switch %s", name) && !expected { + if err.Error() == fmt.Sprintf("could not find virtual switch %s", name) && expected == false { // Expected missing return nil } From f5bd2487f5d87a6dc1716944625da4c6ab8d1355 Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Thu, 31 Aug 2017 10:32:45 -0700 Subject: [PATCH 4/6] r/host_virtual_switch: Assert exactly 3 elements in ID SplitN should always give 3 elements on a proper ID. --- vsphere/host_virtual_switch_structure.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vsphere/host_virtual_switch_structure.go b/vsphere/host_virtual_switch_structure.go index 665910b3c..b6af6d197 100644 --- a/vsphere/host_virtual_switch_structure.go +++ b/vsphere/host_virtual_switch_structure.go @@ -185,7 +185,7 @@ func saveHostVirtualSwitchID(d *schema.ResourceData, hsID, name string) { // name. func splitHostVirtualSwitchID(raw string) (string, string, error) { s := strings.SplitN(raw, ":", 3) - if len(s) < 3 || s[0] != hostVirtualSwitchIDPrefix || s[1] == "" || s[2] == "" { + if len(s) != 3 || s[0] != hostVirtualSwitchIDPrefix || s[1] == "" || s[2] == "" { return "", "", fmt.Errorf("corrupt ID: %s", raw) } return s[1], s[2], nil From 1f40c638e7300edde30cd673d9481ee28d953500 Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Thu, 31 Aug 2017 10:36:07 -0700 Subject: [PATCH 5/6] r/host_virtual_switch: Break out rolling order negation To make it more explicit that we can't just negate the value of a pointer (which !obj.RollingOrder would be). --- vsphere/host_network_policy_structure.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vsphere/host_network_policy_structure.go b/vsphere/host_network_policy_structure.go index b4a579312..0075f2c29 100644 --- a/vsphere/host_network_policy_structure.go +++ b/vsphere/host_network_policy_structure.go @@ -187,7 +187,8 @@ func expandHostNicTeamingPolicy(d *schema.ResourceData) *types.HostNicTeamingPol // into the passed in ResourceData. func flattenHostNicTeamingPolicy(d *schema.ResourceData, obj *types.HostNicTeamingPolicy) error { if obj.RollingOrder != nil { - d.Set("failback", !*obj.RollingOrder) + v := *obj.RollingOrder + d.Set("failback", !v) } if obj.NotifySwitches != nil { d.Set("notify_switches", obj.NotifySwitches) From 5aaade0ef1d61963f7000aabbefb70fd4fc98072 Mon Sep 17 00:00:00 2001 From: Chris Marchesi Date: Thu, 31 Aug 2017 11:00:08 -0700 Subject: [PATCH 6/6] r/host_virtual_switch: Remove unnecessary type declarations in fixtures These are implied by the defaults. --- vsphere/resource_vsphere_host_virtual_switch_test.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/vsphere/resource_vsphere_host_virtual_switch_test.go b/vsphere/resource_vsphere_host_virtual_switch_test.go index 7a688c9e0..6a3c52455 100644 --- a/vsphere/resource_vsphere_host_virtual_switch_test.go +++ b/vsphere/resource_vsphere_host_virtual_switch_test.go @@ -160,12 +160,10 @@ func testAccResourceVSphereHostVirtualSwitchExists(expected bool) resource.TestC func testAccResourceVSphereHostVirtualSwitchConfig() string { return fmt.Sprintf(` variable "host_nic0" { - type = "string" default = "%s" } variable "host_nic1" { - type = "string" default = "%s" } @@ -193,7 +191,6 @@ resource "vsphere_host_virtual_switch" "switch" { func testAccResourceVSphereHostVirtualSwitchConfigSingleNIC() string { return fmt.Sprintf(` variable "host_nic0" { - type = "string" default = "%s" } @@ -221,12 +218,10 @@ resource "vsphere_host_virtual_switch" "switch" { func testAccResourceVSphereHostVirtualSwitchConfigStandbyLink() string { return fmt.Sprintf(` variable "host_nic0" { - type = "string" default = "%s" } variable "host_nic1" { - type = "string" default = "%s" }