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 new file mode 100644 index 000000000..20db9f8da --- /dev/null +++ b/vsphere/distributed_virtual_switch_helper.go @@ -0,0 +1,152 @@ +package vsphere + +import ( + "context" + "fmt" + + "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" +) + +var dvsVersions = []string{ + "5.0.0", + "5.1.0", + "5.5.0", + "6.0.0", + "6.5.0", +} + +// 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 + } + + return dvsFromMOID(client, resp.Returnval.Reference().Value) +} + +// 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 +} + +// 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) + } + + 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) +} + +// 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() + var props mo.VmwareDistributedVirtualSwitch + if err := dvs.Properties(ctx, dvs.Reference(), nil, &props); err != nil { + return nil, err + } + return &props, nil +} + +// 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, + }, + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + resp, err := methods.PerformDvsProductSpecOperation_Task(ctx, client, req) + if err != nil { + 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 + } + + 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() + task, err := dvs.Reconfigure(ctx, spec) + if err != nil { + return err + } + tctx, tcancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer tcancel() + if err := task.Wait(tctx); err != nil { + return err + } + 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, + } + + 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 new file mode 100644 index 000000000..3004fea73 --- /dev/null +++ b/vsphere/distributed_virtual_switch_structure.go @@ -0,0 +1,634 @@ +package vsphere + +import ( + "fmt" + "strings" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/vmware/govmomi/vim25/types" +) + +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 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), +} + +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 detail for this DVS.", + }, + "contact_name": { + Type: schema.TypeString, + Optional: true, + Description: "The contact name for this DVS.", + }, + + // 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, + }, + }, + }, + }, + + // 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: "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), + }, + "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 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), + }, + + "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 +} + +// 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), + } + + 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, + } + + obj := types.DistributedVirtualSwitchHostMemberConfigSpec{ + Host: *hostRef, + Backing: &backing, + } + return obj +} + +// 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) + } + + d["devices"] = devices + + return d +} + +// 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 + } + } + if !found { + spec := expandDistributedVirtualSwitchHostMemberConfigSpec(om) + spec.Operation = string(types.ConfigSpecOperationRemove) + specs = append(specs, spec) + } + } + + // 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 + } + } + spec := expandDistributedVirtualSwitchHostMemberConfigSpec(nm) + if !found { + spec.Operation = string(types.ConfigSpecOperationAdd) + } else { + spec.Operation = string(types.ConfigSpecOperationEdit) + } + specs = append(specs, spec) + } + + // Done! + return specs +} + +// 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 +} + +// 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, + Computed: true, + Description: fmt.Sprintf(shareCountFmt, class), + ValidateFunc: validation.IntAtLeast(0), + } + s[maxMbitKey] = &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: fmt.Sprintf(maxMbitFmt, class), + ValidateFunc: validation.IntAtLeast(-1), + } + s[resMbitKey] = &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: fmt.Sprintf(resMbitFmt, class), + ValidateFunc: validation.IntAtLeast(-1), + } + } + + return s +} + +// 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 allFieldsEmpty(obj) { + return nil + } + obj.Key = key + return obj +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 +} + +// 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 s +} + +// 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 83ea08197..906e1c363 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_distributed_virtual_switch": resourceVSphereDistributedVirtualSwitch(), + "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(), }, 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..c64b57069 --- /dev/null +++ b/vsphere/resource_vsphere_distributed_virtual_switch.go @@ -0,0 +1,289 @@ +package vsphere + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/types" +) + +const ( + retryDVSUpdatePending = "retryDVSUpdatePending" + retryDVSUpdateCompleted = "retryDVSUpdateCompleted" + retryDVSUpdateError = "retryDVSUpdateError" +) + +func resourceVSphereDistributedVirtualSwitch() *schema.Resource { + s := map[string]*schema.Schema{ + "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 + } + + 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.(*VSphereClient).vimClient + if err := validateVirtualCenter(client); err != nil { + return err + } + tagsClient, err := tagsClientIfDefined(d, meta) + if err != nil { + return err + } + + dc, err := datacenterFromID(client, d.Get("datacenter_id").(string)) + if err != nil { + return fmt.Errorf("cannot locate datacenter: %s", err) + } + folder, err := folderFromPath(client, d.Get("folder").(string), vSphereFolderTypeNetwork, dc) + if err != nil { + return fmt.Errorf("cannot locate folder: %s", err) + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + spec := expandDVSCreateSpec(d) + task, err := folder.CreateDVS(ctx, spec) + if err != nil { + return fmt.Errorf("error creating DVS: %s", err) + } + tctx, tcancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer tcancel() + info, err := task.WaitForResult(tctx, nil) + if err != nil { + return fmt.Errorf("error waiting for DVS creation to complete: %s", err) + } + + dvs, err := dvsFromMOID(client, info.Result.(types.ManagedObjectReference).Value) + if err != nil { + 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(props.Uuid) + + // Enable network resource I/O control if it needs to be enabled + if d.Get("network_resource_control_enabled").(bool) { + enableDVSNetworkResourceManagement(client, dvs, true) + } + + // 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 resourceVSphereDistributedVirtualSwitchRead(d, meta) +} + +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("could not find DVS %q: %s", id, err) + } + props, err := dvsProperties(dvs) + if err != nil { + return fmt.Errorf("error fetching DVS properties: %s", err) + } + + // 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) + + // Set the folder + folder, err := rootPathParticleNetwork.SplitRelativeFolder(dvs.InventoryPath) + if err != nil { + return fmt.Errorf("error parsing DVS path %q: %s", dvs.InventoryPath, err) + } + d.Set("folder", normalizeFolderPath(folder)) + + // Read in config info + if err := flattenVMwareDVSConfigInfo(d, props.Config.(*types.VMwareDVSConfigInfo)); err != nil { + return 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 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) + } + + // 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 + } + } + 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) + } + + spec := expandVMwareDVSConfigSpec(d) + if err := updateDVSConfiguration(client, dvs, spec); err != nil { + return fmt.Errorf("could not update DVS: %s", err) + } + + // Modify network I/O control if necessary + if d.HasChange("network_resource_control_enabled") { + enableDVSNetworkResourceManagement(client, dvs, d.Get("network_resource_control_enabled").(bool)) + } + + // 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 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) + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + task, err := dvs.Destroy(ctx) + if err != nil { + 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 new file mode 100644 index 000000000..8fafa0309 --- /dev/null +++ b/vsphere/resource_vsphere_distributed_virtual_switch_test.go @@ -0,0 +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/vim25/types" +) + +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), + ), + }, + }, + }, + }, + } + + for _, tc := range testAccResourceVSphereDistributedVirtualSwitchCases { + t.Run(tc.name, func(t *testing.T) { + tp = t + resource.Test(t, tc.testCase) + }) + } +} + +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 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 + } +} + +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 + } +} + +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 + } +} + +func testAccResourceVSphereDistributedVirtualSwitchHasNetworkResourceControlVersion(expected types.DistributedVirtualSwitchNetworkResourceControlVersion) resource.TestCheckFunc { + return func(s *terraform.State) error { + props, err := testGetDVSProperties(s, "dvs") + if err != nil { + return err + } + 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 + } +} + +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 + } +} + +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 + } +} + +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 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, + } + + 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 + } +} + +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 + } +} + +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 + } +} + +// 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) + } +} + +func testAccResourceVSphereDistributedVirtualSwitchConfig() string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "esxi_hosts" { + default = [ + "%s", + "%s", + "%s", + ] +} + +variable "network_interfaces" { + default = [ + "%s", + "%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"), + os.Getenv("VSPHERE_HOST_NIC1"), + ) +} + +func testAccResourceVSphereDistributedVirtualSwitchConfigStaticVersion(version string) string { + return fmt.Sprintf(` +variable "datacenter" { + default = "%s" +} + +variable "esxi_hosts" { + default = [ + "%s", + "%s", + "%s", + ] +} + +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 new file mode 100644 index 000000000..393f97668 --- /dev/null +++ b/website/docs/r/distributed_virtual_switch.html.markdown @@ -0,0 +1,416 @@ +--- +layout: "vsphere" +page_title: "VMware vSphere: vsphere_distributed_virtual_switch" +sidebar_current: "docs-vsphere-resource-network-distributed-virtual-switch" +description: |- + Provides a vSphere distributed virtual switch resource. This can be used to create and manage DVS resources in vCenter. +--- + +# 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. + +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]. + +[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 + +~> **NOTE:** This resource requires vCenter and is not available on direct ESXi +connections. + +## 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 +variable "esxi_hosts" { + default = [ + "esxi1", + "esxi2", + "esxi3", + ] +} + +variable "network_interfaces" { + default = [ + "vmnic0", + "vmnic1", + "vmnic2", + "vmnic3", + ] +} + +data "vsphere_datacenter" "dc" { + name = "dc1" +} + +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 = ["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}" + + 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. +* `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 5dc241c78..ee4307947 100644 --- a/website/vsphere.erb +++ b/website/vsphere.erb @@ -61,6 +61,9 @@ > Networking Resources