diff --git a/vsphere/distributed_port_group_helper.go b/vsphere/distributed_port_group_helper.go new file mode 100644 index 000000000..669794cf6 --- /dev/null +++ b/vsphere/distributed_port_group_helper.go @@ -0,0 +1,102 @@ +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" +) + +// dvPortgroupFromUUID gets a portgroup object from its UUID. +func dvPortgroupFromUUID(client *govmomi.Client, dvsUUID, pgUUID string) (*object.DistributedVirtualPortgroup, error) { + dvsm := types.ManagedObjectReference{Type: "DistributedVirtualSwitchManager", Value: "DVSManager"} + req := &types.DVSManagerLookupDvPortGroup{ + This: dvsm, + SwitchUuid: dvsUUID, + PortgroupKey: pgUUID, + } + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + resp, err := methods.DVSManagerLookupDvPortGroup(ctx, client, req) + if err != nil { + return nil, err + } + + return dvPortgroupFromMOID(client, resp.Returnval.Reference().Value) +} + +// dvPortgroupFromMOID locates a portgroup by its managed object reference ID. +func dvPortgroupFromMOID(client *govmomi.Client, id string) (*object.DistributedVirtualPortgroup, error) { + finder := find.NewFinder(client.Client, false) + + ref := types.ManagedObjectReference{ + Type: "DistributedVirtualPortgroup", + 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 + // DistributedVirtualPortgroup, then we have bigger problems and to be + // honest we should be panicking anyway. + return ds.(*object.DistributedVirtualPortgroup), nil +} + +// dvPortgroupFromPath gets a portgroup object from its path. +func dvPortgroupFromPath(client *govmomi.Client, name string, dc *object.Datacenter) (*object.DistributedVirtualPortgroup, 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 != "DistributedVirtualPortgroup" { + return nil, fmt.Errorf("network at path %q is not a portgroup (type %s)", name, net.Reference().Type) + } + return dvPortgroupFromMOID(client, net.Reference().Value) +} + +// dvPortgroupProperties is a convenience method that wraps fetching the +// portgroup MO from its higher-level object. +func dvPortgroupProperties(pg *object.DistributedVirtualPortgroup) (*mo.DistributedVirtualPortgroup, error) { + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + var props mo.DistributedVirtualPortgroup + if err := pg.Properties(ctx, pg.Reference(), nil, &props); err != nil { + return nil, err + } + return &props, nil +} + +// createDVPortgroup exposes the CreateDVPortgroup_Task method of the +// DistributedVirtualSwitch MO. This local implementation may go away if this +// is exposed in the higher-level object upstream. +func createDVPortgroup(client *govmomi.Client, dvs *object.VmwareDistributedVirtualSwitch, spec types.DVPortgroupConfigSpec) (*object.Task, error) { + req := &types.CreateDVPortgroup_Task{ + This: dvs.Reference(), + Spec: spec, + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + resp, err := methods.CreateDVPortgroup_Task(ctx, client, req) + if err != nil { + return nil, err + } + + return object.NewTask(client.Client, resp.Returnval.Reference()), nil +} diff --git a/vsphere/distributed_port_group_structure.go b/vsphere/distributed_port_group_structure.go new file mode 100644 index 000000000..aa6cba339 --- /dev/null +++ b/vsphere/distributed_port_group_structure.go @@ -0,0 +1,198 @@ +package vsphere + +import ( + "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" + "github.com/vmware/govmomi/vim25/types" +) + +var distributedVirtualPortgroupPortgroupTypeAllowedValues = []string{ + string(types.DistributedVirtualPortgroupPortgroupTypeEarlyBinding), + string(types.DistributedVirtualPortgroupPortgroupTypeEphemeral), +} + +// schemaDVPortgroupConfigSpec returns schema items for resources that +// need to work with a DVPortgroupConfigSpec. +func schemaDVPortgroupConfigSpec() map[string]*schema.Schema { + s := map[string]*schema.Schema{ + // VMwareDVSPortgroupPolicy + "block_override_allowed": { + Type: schema.TypeBool, + Optional: true, + Description: "Allow the blocked setting of an individual port to override the setting in the portgroup.", + }, + "live_port_moving_allowed": { + Type: schema.TypeBool, + Optional: true, + Description: "Allow a live port to be moved in and out of the portgroup.", + }, + "network_resource_pool_override_allowed": { + Type: schema.TypeBool, + Optional: true, + Description: "Allow the network resource pool of an individual port to override the setting in the portgroup.", + }, + "port_config_reset_at_disconnect": { + Type: schema.TypeBool, + Optional: true, + Description: "Reset the setting of any ports in this portgroup back to the default setting when the port disconnects.", + }, + "shaping_override_allowed": { + Type: schema.TypeBool, + Optional: true, + Description: "Allow the traffic shaping policies of an individual port to override the settings in the portgroup.", + }, + "traffic_filter_override_allowed": { + Type: schema.TypeBool, + Optional: true, + Description: "Allow any filter policies set on the individual port to override those in the portgroup.", + }, + "netflow_override_allowed": { + Type: schema.TypeBool, + Optional: true, + Description: "Allow the enabling or disabling of Netflow on a port, contrary to the policy in the portgroup.", + }, + "security_policy_override_allowed": { + Type: schema.TypeBool, + Optional: true, + Description: "Allow security policy settings on a port to override those on the portgroup.", + }, + "uplink_teaming_override_allowed": { + Type: schema.TypeBool, + Optional: true, + Description: "Allow the uplink teaming policies on a port to override those on the portgroup.", + }, + "vlan_override_allowed": { + Type: schema.TypeBool, + Optional: true, + Description: "Allow the VLAN configuration on a port to override those on the portgroup.", + }, + + // DVPortgroupConfigSpec + "auto_expand": { + Type: schema.TypeString, + Optional: true, + Default: true, + Description: "Auto-expands the port group beyond the port count configured in number_of_ports when necessary.", + }, + "config_version": { + Type: schema.TypeString, + Computed: true, + Description: "Version string of the configuration that this spec is trying to change.", + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: "The description of the portgroup.", + }, + "name": { + Type: schema.TypeString, + Required: true, + Description: "The name of the portgroup.", + }, + "number_of_ports": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + Description: "The number of ports in this portgroup. The DVS will expand and shrink by modifying this setting.", + ValidateFunc: validation.IntAtLeast(0), + }, + "port_name_format": { + Type: schema.TypeString, + Optional: true, + Description: "A template string to use when creating ports in the portgroup.", + }, + "type": { + Type: schema.TypeString, + Optional: true, + Default: string(types.DistributedVirtualPortgroupPortgroupTypeEarlyBinding), + Description: "The type of portgroup. Can be one of earlyBinding (static) or ephemeral.", + ValidateFunc: validation.StringInSlice(distributedVirtualPortgroupPortgroupTypeAllowedValues, false), + }, + "network_resource_pool_key": { + Type: schema.TypeString, + Optional: true, + Default: "-1", + Description: "The key of a network resource pool to associate with this portgroup.", + }, + } + mergeSchema(s, schemaVMwareDVSPortSetting()) + return s +} + +// expandVMwareDVSPortgroupPolicy reads certain ResourceData keys and +// returns a VMwareDVSPortgroupPolicy. +func expandVMwareDVSPortgroupPolicy(d *schema.ResourceData) *types.VMwareDVSPortgroupPolicy { + obj := &types.VMwareDVSPortgroupPolicy{ + DVPortgroupPolicy: types.DVPortgroupPolicy{ + BlockOverrideAllowed: d.Get("block_override_allowed").(bool), + ShapingOverrideAllowed: d.Get("shaping_override_allowed").(bool), + LivePortMovingAllowed: d.Get("live_port_moving_allowed").(bool), + PortConfigResetAtDisconnect: d.Get("port_config_reset_at_disconnect").(bool), + NetworkResourcePoolOverrideAllowed: getBoolPtr(d, "network_resource_pool_override_allowed"), + TrafficFilterOverrideAllowed: getBoolPtr(d, "traffic_filter_override_allowed"), + }, + VlanOverrideAllowed: d.Get("vlan_override_allowed").(bool), + UplinkTeamingOverrideAllowed: d.Get("uplink_teaming_override_allowed").(bool), + SecurityPolicyOverrideAllowed: d.Get("security_policy_override_allowed").(bool), + IpfixOverrideAllowed: getBoolPtr(d, "netflow_override_allowed"), + } + return obj +} + +// flattenVMwareDVSPortgroupPolicy reads various fields from a +// VMwareDVSPortgroupPolicy into the passed in ResourceData. +func flattenVMwareDVSPortgroupPolicy(d *schema.ResourceData, obj *types.VMwareDVSPortgroupPolicy) error { + d.Set("block_override_allowed", obj.BlockOverrideAllowed) + d.Set("shaping_override_allowed", obj.ShapingOverrideAllowed) + d.Set("live_port_moving_allowed", obj.LivePortMovingAllowed) + d.Set("port_config_reset_at_disconnect", obj.PortConfigResetAtDisconnect) + d.Set("vlan_override_allowed", obj.VlanOverrideAllowed) + d.Set("uplink_teaming_override_allowed", obj.UplinkTeamingOverrideAllowed) + d.Set("security_policy_override_allowed", obj.SecurityPolicyOverrideAllowed) + + setBoolPtr(d, "network_resource_pool_override_allowed", obj.NetworkResourcePoolOverrideAllowed) + setBoolPtr(d, "traffic_filter_override_allowed", obj.TrafficFilterOverrideAllowed) + setBoolPtr(d, "netflow_override_allowed", obj.IpfixOverrideAllowed) + return nil +} + +// expandDVPortgroupConfigSpec reads certain ResourceData keys and +// returns a DVPortgroupConfigSpec. +func expandDVPortgroupConfigSpec(d *schema.ResourceData) types.DVPortgroupConfigSpec { + obj := types.DVPortgroupConfigSpec{ + ConfigVersion: d.Get("config_version").(string), + Name: d.Get("name").(string), + NumPorts: int32(d.Get("number_of_ports").(int)), + PortNameFormat: d.Get("port_name_format").(string), + DefaultPortConfig: expandVMwareDVSPortSetting(d), + Description: d.Get("description").(string), + Type: d.Get("type").(string), + Policy: expandVMwareDVSPortgroupPolicy(d), + AutoExpand: getBoolPtr(d, "auto_expand"), + VmVnicNetworkResourcePoolKey: d.Get("network_resource_pool_key").(string), + } + return obj +} + +// flattenDVPortgroupConfigInfo reads various fields from a +// DVPortgroupConfigInfo into the passed in ResourceData. +// +// This is the flatten counterpart to expandDVPortgroupConfigSpec. +func flattenDVPortgroupConfigInfo(d *schema.ResourceData, obj types.DVPortgroupConfigInfo) error { + d.Set("config_version", obj.ConfigVersion) + d.Set("name", obj.Name) + d.Set("number_of_ports", obj.NumPorts) + d.Set("port_name_format", obj.PortNameFormat) + d.Set("description", obj.Description) + d.Set("type", obj.Type) + setBoolPtr(d, "auto_expand", obj.AutoExpand) + d.Set("network_resource_pool_key", obj.VmVnicNetworkResourcePoolKey) + + if err := flattenVMwareDVSPortSetting(d, obj.DefaultPortConfig.(*types.VMwareDVSPortSetting)); err != nil { + return err + } + if err := flattenVMwareDVSPortgroupPolicy(d, obj.Policy.(*types.VMwareDVSPortgroupPolicy)); err != nil { + return err + } + return nil +} diff --git a/vsphere/distributed_virtual_port_setting_structure.go b/vsphere/distributed_virtual_port_setting_structure.go index 7ba2ad3db..03888aed6 100644 --- a/vsphere/distributed_virtual_port_setting_structure.go +++ b/vsphere/distributed_virtual_port_setting_structure.go @@ -1,6 +1,8 @@ package vsphere import ( + "log" + "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/helper/validation" "github.com/vmware/govmomi/vim25/types" @@ -21,27 +23,13 @@ var vmwareUplinkPortTeamingPolicyModeAllowedValues = []string{ // 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, + Computed: 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), @@ -49,11 +37,11 @@ func schemaVMwareDVSPortSetting() map[string]*schema.Schema { // VmwareDistributedVirtualSwitchTrunkVlanSpec "vlan_range": { - Type: schema.TypeList, + Type: schema.TypeSet, Optional: true, + Computed: 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": { @@ -76,6 +64,7 @@ func schemaVMwareDVSPortSetting() map[string]*schema.Schema { "port_private_secondary_vlan_id": { Type: schema.TypeInt, Optional: true, + Computed: true, Description: "The secondary VLAN ID for this port.", ConflictsWith: []string{"vlan_id", "vlan_range"}, ValidateFunc: validation.IntBetween(1, 4094), @@ -85,6 +74,7 @@ func schemaVMwareDVSPortSetting() map[string]*schema.Schema { "check_beacon": { Type: schema.TypeBool, Optional: true, + Computed: true, Description: "Enable beacon probing on the ports this policy applies to.", }, @@ -92,12 +82,14 @@ func schemaVMwareDVSPortSetting() map[string]*schema.Schema { "active_uplinks": { Type: schema.TypeList, Optional: true, + Computed: 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, + Computed: 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}, }, @@ -106,17 +98,20 @@ func schemaVMwareDVSPortSetting() map[string]*schema.Schema { "teaming_policy": { Type: schema.TypeString, Optional: true, + Computed: 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, + Computed: 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, + Computed: true, Description: "If true, the teaming policy will re-activate failed interfaces higher in precedence when they come back up.", }, @@ -124,16 +119,19 @@ func schemaVMwareDVSPortSetting() map[string]*schema.Schema { "allow_promiscuous": &schema.Schema{ Type: schema.TypeBool, Optional: true, + Computed: 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, + Computed: 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, + Computed: true, Description: "Controls whether or not the Media Access Control (MAC) address can be changed.", }, @@ -141,11 +139,13 @@ func schemaVMwareDVSPortSetting() map[string]*schema.Schema { "lacp_enabled": { Type: schema.TypeBool, Optional: true, + Computed: true, Description: "Whether or not to enable LACP on all uplink ports.", }, "lacp_mode": { Type: schema.TypeString, Optional: true, + Computed: true, Description: "The uplink LACP mode to use. Can be one of active or passive.", ValidateFunc: validation.StringInSlice(vmwareUplinkLacpPolicyModeAllowedValues, false), }, @@ -154,21 +154,25 @@ func schemaVMwareDVSPortSetting() map[string]*schema.Schema { "ingress_shaping_average_bandwidth": { Type: schema.TypeInt, Optional: true, + Computed: 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, + Computed: 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, + Computed: true, Description: "True if the traffic shaper is enabled for ingress traffic on the port.", }, "ingress_shaping_peak_bandwidth": { Type: schema.TypeInt, Optional: true, + Computed: true, Description: "The peak ingress bandwidth during bursts in bits per second if ingress traffic shaping is enabled on the port.", }, @@ -176,21 +180,25 @@ func schemaVMwareDVSPortSetting() map[string]*schema.Schema { "egress_shaping_average_bandwidth": { Type: schema.TypeInt, Optional: true, + Computed: 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, + Computed: 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, + Computed: true, Description: "True if the traffic shaper is enabled for egress traffic on the port.", }, "egress_shaping_peak_bandwidth": { Type: schema.TypeInt, Optional: true, + Computed: true, Description: "The peak egress bandwidth during bursts in bits per second if egress traffic shaping is enabled on the port.", }, @@ -198,18 +206,27 @@ func schemaVMwareDVSPortSetting() map[string]*schema.Schema { "block_all_ports": { Type: schema.TypeBool, Optional: true, + Computed: true, Description: "Indicates whether to block all ports by default.", }, "netflow_enabled": { Type: schema.TypeBool, Optional: true, + Computed: true, Description: "Indicates whether to enable netflow on all ports.", }, "tx_uplink": { Type: schema.TypeBool, Optional: true, + Computed: 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.", }, + "directpath_gen2_allowed": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + Description: "Allow VMDirectPath Gen2 on the ports this policy applies to.", + }, } } @@ -233,8 +250,9 @@ func flattenVmwareDistributedVirtualSwitchVlanIDSpec(d *schema.ResourceData, obj // returns a VmwareDistributedVirtualSwitchTrunkVlanSpec. func expandVmwareDistributedVirtualSwitchTrunkVlanSpec(d *schema.ResourceData) *types.VmwareDistributedVirtualSwitchTrunkVlanSpec { var ranges []types.NumericRange - data := d.Get("vlan_range").([]interface{}) + data := d.Get("vlan_range").(*schema.Set).List() for _, v := range data { + log.Printf("[DEBUG] processing range: %#v", v) r := v.(map[string]interface{}) min := r["min_vlan"].(int) max := r["max_vlan"].(int) @@ -244,6 +262,11 @@ func expandVmwareDistributedVirtualSwitchTrunkVlanSpec(d *schema.ResourceData) * } ranges = append(ranges, rng) } + + if len(ranges) < 1 { + return nil + } + obj := &types.VmwareDistributedVirtualSwitchTrunkVlanSpec{ VlanId: ranges, } @@ -288,8 +311,9 @@ func expandBaseVmwareDistributedVirtualSwitchVlanSpec(d *schema.ResourceData) ty var obj types.BaseVmwareDistributedVirtualSwitchVlanSpec _, ide := d.GetOkExists("vlan_id") - _, vte := d.GetOkExists("vlan_range") _, pvid := d.GetOkExists("port_private_secondary_vlan_id") + vteList, vteOK := d.GetOkExists("vlan_range") + vte := vteOK && len(vteList.(*schema.Set).List()) > 0 switch { case vte: obj = expandVmwareDistributedVirtualSwitchTrunkVlanSpec(d) @@ -534,9 +558,10 @@ func flattenDVSTrafficShapingPolicyEgress(d *schema.ResourceData, obj *types.DVS 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), + Blocked: getBoolPolicy(d, "block_all_ports"), + InShapingPolicy: expandDVSTrafficShapingPolicyIngress(d), + OutShapingPolicy: expandDVSTrafficShapingPolicyEgress(d), + VmDirectPathGen2Allowed: getBoolPolicy(d, "directpath_gen2_allowed"), }, Vlan: expandBaseVmwareDistributedVirtualSwitchVlanSpec(d), UplinkTeamingPolicy: expandVmwareUplinkPortTeamingPolicy(d), @@ -562,6 +587,7 @@ func flattenVMwareDVSPortSetting(d *schema.ResourceData, obj *types.VMwareDVSPor setBoolPolicy(d, "block_all_ports", obj.Blocked) setBoolPolicy(d, "netflow_enabled", obj.IpfixEnabled) setBoolPolicy(d, "tx_uplink", obj.TxUplink) + setBoolPolicy(d, "directpath_gen2_allowed", obj.VmDirectPathGen2Allowed) if err := flattenDVSTrafficShapingPolicyIngress(d, obj.InShapingPolicy); err != nil { return err diff --git a/vsphere/helper_test.go b/vsphere/helper_test.go index c62821cc7..e3ef6acb7 100644 --- a/vsphere/helper_test.go +++ b/vsphere/helper_test.go @@ -312,3 +312,23 @@ func testGetDVSProperties(s *terraform.State, resourceName string) (*mo.VmwareDi } return dvsProperties(dvs) } + +// testGetDVPortgroup is a convenience method to fetch a DV portgroup by resource name. +func testGetDVPortgroup(s *terraform.State, resourceName string) (*object.DistributedVirtualPortgroup, error) { + tVars, err := testClientVariablesForResource(s, fmt.Sprintf("vsphere_distributed_port_group.%s", resourceName)) + if err != nil { + return nil, err + } + dvsID := tVars.resourceAttributes["distributed_virtual_switch_uuid"] + return dvPortgroupFromUUID(tVars.client, dvsID, tVars.resourceID) +} + +// testGetDVPortgroupProperties is a convenience method that adds an extra step to +// testGetDVPortgroup to get the properties of a DV portgroup. +func testGetDVPortgroupProperties(s *terraform.State, resourceName string) (*mo.DistributedVirtualPortgroup, error) { + dvs, err := testGetDVPortgroup(s, resourceName) + if err != nil { + return nil, err + } + return dvPortgroupProperties(dvs) +} diff --git a/vsphere/provider.go b/vsphere/provider.go index 906e1c363..4f9d9b1e9 100644 --- a/vsphere/provider.go +++ b/vsphere/provider.go @@ -70,6 +70,7 @@ func Provider() terraform.ResourceProvider { ResourcesMap: map[string]*schema.Resource{ "vsphere_datacenter": resourceVSphereDatacenter(), + "vsphere_distributed_port_group": resourceVSphereDistributedPortGroup(), "vsphere_distributed_virtual_switch": resourceVSphereDistributedVirtualSwitch(), "vsphere_file": resourceVSphereFile(), "vsphere_folder": resourceVSphereFolder(), diff --git a/vsphere/resource_vsphere_distributed_port_group.go b/vsphere/resource_vsphere_distributed_port_group.go new file mode 100644 index 000000000..3a86bb8f1 --- /dev/null +++ b/vsphere/resource_vsphere_distributed_port_group.go @@ -0,0 +1,210 @@ +package vsphere + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/types" +) + +func resourceVSphereDistributedPortGroup() *schema.Resource { + s := map[string]*schema.Schema{ + "distributed_virtual_switch_uuid": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + // Tagging + vSphereTagAttributeKey: tagsSchema(), + } + + mergeSchema(s, schemaDVPortgroupConfigSpec()) + + return &schema.Resource{ + Create: resourceVSphereDistributedPortGroupCreate, + Read: resourceVSphereDistributedPortGroupRead, + Update: resourceVSphereDistributedPortGroupUpdate, + Delete: resourceVSphereDistributedPortGroupDelete, + Importer: &schema.ResourceImporter{ + State: resourceVSphereDistributedPortGroupImport, + }, + Schema: s, + } +} + +func resourceVSphereDistributedPortGroupCreate(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 + } + dvsID := d.Get("distributed_virtual_switch_uuid").(string) + dvs, err := dvsFromUUID(client, dvsID) + if err != nil { + return fmt.Errorf("could not find DVS %q: %s", dvsID, err) + } + + spec := expandDVPortgroupConfigSpec(d) + task, err := createDVPortgroup(client, dvs, spec) + if err != nil { + return fmt.Errorf("error creating portgroup: %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 portgroup creation to complete: %s", err) + } + pg, err := dvPortgroupFromMOID(client, info.Result.(types.ManagedObjectReference).Value) + if err != nil { + return fmt.Errorf("error fetching portgroup after creation: %s", err) + } + props, err := dvPortgroupProperties(pg) + if err != nil { + return fmt.Errorf("error fetching portgroup properties after creation: %s", err) + } + + d.SetId(props.Key) + + // Apply any pending tags now + if tagsClient != nil { + if err := processTagDiff(tagsClient, d, object.NewReference(client.Client, pg.Reference())); err != nil { + return fmt.Errorf("error updating tags: %s", err) + } + } + return resourceVSphereDistributedPortGroupRead(d, meta) +} + +func resourceVSphereDistributedPortGroupRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*VSphereClient).vimClient + if err := validateVirtualCenter(client); err != nil { + return err + } + dvsID := d.Get("distributed_virtual_switch_uuid").(string) + pgID := d.Id() + pg, err := dvPortgroupFromUUID(client, dvsID, pgID) + if err != nil { + return fmt.Errorf("could not find portgroup %q on DVS %q: %s", pgID, dvsID, err) + } + props, err := dvPortgroupProperties(pg) + if err != nil { + return fmt.Errorf("error fetching portgroup properties: %s", err) + } + + if err := flattenDVPortgroupConfigInfo(d, props.Config); err != nil { + return err + } + + if tagsClient, _ := meta.(*VSphereClient).TagsClient(); tagsClient != nil { + if err := readTagsForResource(tagsClient, pg, d); err != nil { + return fmt.Errorf("error reading tags: %s", err) + } + } + return nil +} + +func resourceVSphereDistributedPortGroupUpdate(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 + } + dvsID := d.Get("distributed_virtual_switch_uuid").(string) + pgID := d.Id() + pg, err := dvPortgroupFromUUID(client, dvsID, pgID) + if err != nil { + return fmt.Errorf("could not find portgroup %q on DVS %q: %s", pgID, dvsID, err) + } + spec := expandDVPortgroupConfigSpec(d) + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + task, err := pg.Reconfigure(ctx, spec) + if err != nil { + return fmt.Errorf("error reconfiguring portgroup: %s", err) + } + tctx, tcancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer tcancel() + if err := task.Wait(tctx); err != nil { + return fmt.Errorf("error waiting for portgroup update to complete: %s", err) + } + + // Apply any pending tags now + if tagsClient != nil { + if err := processTagDiff(tagsClient, d, object.NewReference(client.Client, pg.Reference())); err != nil { + return fmt.Errorf("error updating tags: %s", err) + } + } + return resourceVSphereDistributedPortGroupRead(d, meta) +} + +func resourceVSphereDistributedPortGroupDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*VSphereClient).vimClient + if err := validateVirtualCenter(client); err != nil { + return err + } + dvsID := d.Get("distributed_virtual_switch_uuid").(string) + pgID := d.Id() + pg, err := dvPortgroupFromUUID(client, dvsID, pgID) + if err != nil { + return fmt.Errorf("could not find portgroup %q on DVS %q: %s", pgID, dvsID, err) + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + task, err := pg.Destroy(ctx) + if err != nil { + return fmt.Errorf("error deleting portgroup: %s", err) + } + tctx, tcancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer tcancel() + if err := task.Wait(tctx); err != nil { + return fmt.Errorf("error waiting for portgroup deletion to complete: %s", err) + } + return nil +} + +func resourceVSphereDistributedPortGroupImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + // We use the inventory path to the portgroup to import. There is not + // checking to make sure that it belongs to the configured DVS, but on + // subsequent plans, if it is not, the resource will be in an unusable state + // as all query calls for DVS CRUD calls require the correct DVS UUID in + // addition to the portgroup UUID. + client := meta.(*VSphereClient).vimClient + if err := validateVirtualCenter(client); err != nil { + return nil, err + } + p := d.Id() + pg, err := dvPortgroupFromPath(client, p, nil) + if err != nil { + return nil, fmt.Errorf("error locating portgroup: %s", err) + } + props, err := dvPortgroupProperties(pg) + if err != nil { + return nil, fmt.Errorf("error fetching portgroup properties: %s", err) + } + d.SetId(props.Key) + + // We need to populate the DVS UUID here as well or else our read calls will + // fail. + dvsID := props.Config.DistributedVirtualSwitch.Value + dvs, err := dvsFromMOID(client, dvsID) + if err != nil { + return nil, fmt.Errorf("error getting DVS with ID %q: %s", dvsID, err) + } + dvProps, err := dvsProperties(dvs) + if err != nil { + return nil, fmt.Errorf("error fetching DVS properties: %s", err) + } + + d.Set("distributed_virtual_switch_uuid", dvProps.Uuid) + + return []*schema.ResourceData{d}, nil +} diff --git a/vsphere/resource_vsphere_distributed_port_group_test.go b/vsphere/resource_vsphere_distributed_port_group_test.go new file mode 100644 index 000000000..f58db3231 --- /dev/null +++ b/vsphere/resource_vsphere_distributed_port_group_test.go @@ -0,0 +1,516 @@ +package vsphere + +import ( + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" + "github.com/vmware/govmomi/vim25/types" +) + +func TestAccResourceVSphereDistributedPortGroup(t *testing.T) { + var tp *testing.T + testAccResourceVSphereDistributedPortGroupCases := []struct { + name string + testCase resource.TestCase + }{ + { + "basic", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedPortGroupPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedPortGroupExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedPortGroupConfig(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedPortGroupExists(true), + ), + }, + }, + }, + }, + { + "inherit policy diff check", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedPortGroupPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedPortGroupExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedPortGroupConfigPolicyInherit(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedPortGroupExists(true), + ), + }, + }, + }, + }, + { + "inherit policy diff check (vlan range - typeset edition)", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedPortGroupPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedPortGroupExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedPortGroupConfigPolicyInheritVLANRange(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedPortGroupExists(true), + ), + }, + }, + }, + }, + { + "override vlan", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedPortGroupPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedPortGroupExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedPortGroupConfigOverrideVLAN(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedPortGroupExists(true), + testAccResourceVSphereDistributedVirtualSwitchHasVlanRange(1000, 1999), + testAccResourceVSphereDistributedPortGroupHasVlanRange(3000, 3999), + ), + }, + }, + }, + }, + { + "single tag", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedPortGroupPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedPortGroupExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedPortGroupConfigSingleTag(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedPortGroupExists(true), + testAccResourceVSphereDistributedPortGroupCheckTags("terraform-test-tag"), + ), + }, + }, + }, + }, + { + "multi tag", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedPortGroupPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedPortGroupExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedPortGroupConfigMultiTag(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedPortGroupExists(true), + testAccResourceVSphereDistributedPortGroupCheckTags("terraform-test-tags-alt"), + ), + }, + }, + }, + }, + { + "import", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccResourceVSphereDistributedPortGroupPreCheck(tp) + }, + Providers: testAccProviders, + CheckDestroy: testAccResourceVSphereDistributedPortGroupExists(false), + Steps: []resource.TestStep{ + { + Config: testAccResourceVSphereDistributedPortGroupConfig(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedPortGroupExists(true), + ), + }, + { + ResourceName: "vsphere_distributed_port_group.pg", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"vlan_range"}, + ImportStateIdFunc: func(s *terraform.State) (string, error) { + pg, err := testGetDVPortgroup(s, "pg") + if err != nil { + return "", err + } + return pg.InventoryPath, nil + }, + Config: testAccResourceVSphereDistributedPortGroupConfig(), + Check: resource.ComposeTestCheckFunc( + testAccResourceVSphereDistributedPortGroupExists(true), + ), + }, + }, + }, + }, + } + + for _, tc := range testAccResourceVSphereDistributedPortGroupCases { + t.Run(tc.name, func(t *testing.T) { + tp = t + resource.Test(t, tc.testCase) + }) + } +} + +func testAccResourceVSphereDistributedPortGroupPreCheck(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 testAccResourceVSphereDistributedPortGroupExists(expected bool) resource.TestCheckFunc { + return func(s *terraform.State) error { + dvs, err := testGetDVPortgroup(s, "pg") + 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 testAccResourceVSphereDistributedPortGroupHasVlanRange(emin, emax int32) resource.TestCheckFunc { + return func(s *terraform.State) error { + props, err := testGetDVPortgroupProperties(s, "pg") + if err != nil { + return err + } + pc := props.Config.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 testAccResourceVSphereDistributedPortGroupCheckTags(tagResName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + dvs, err := testGetDVPortgroup(s, "pg") + if err != nil { + return err + } + tagsClient, err := testAccProvider.Meta().(*VSphereClient).TagsClient() + if err != nil { + return err + } + return testObjectHasTags(s, tagsClient, dvs, tagResName) + } +} + +func testAccResourceVSphereDistributedPortGroupConfig() 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}" +} + +resource "vsphere_distributed_port_group" "pg" { + name = "terraform-test-pg" + distributed_virtual_switch_uuid = "${vsphere_distributed_virtual_switch.dvs.id}" +} +`, + os.Getenv("VSPHERE_DATACENTER"), + ) +} + +func testAccResourceVSphereDistributedPortGroupConfigPolicyInherit() 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_id = 1000 + + active_uplinks = ["uplink1", "uplink2"] + standby_uplinks = ["uplink3", "uplink4"] + check_beacon = true + failback = true + notify_switches = true + teaming_policy = "failover_explicit" + + lacp_enabled = true + lacp_mode = "active" + + allow_forged_transmits = true + allow_mac_changes = true + allow_promiscuous = true + + ingress_shaping_enabled = true + ingress_shaping_average_bandwidth = 1000000 + ingress_shaping_peak_bandwidth = 10000000 + ingress_shaping_burst_size = 5000000 + + egress_shaping_enabled = true + egress_shaping_average_bandwidth = 1000000 + egress_shaping_peak_bandwidth = 10000000 + egress_shaping_burst_size = 5000000 + + block_all_ports = true + netflow_enabled = true + tx_uplink = true +} + +resource "vsphere_distributed_port_group" "pg" { + name = "terraform-test-pg" + distributed_virtual_switch_uuid = "${vsphere_distributed_virtual_switch.dvs.id}" +} +`, + os.Getenv("VSPHERE_DATACENTER"), + ) +} + +func testAccResourceVSphereDistributedPortGroupConfigPolicyInheritVLANRange() 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 + } + + active_uplinks = ["uplink1", "uplink2"] + standby_uplinks = ["uplink3", "uplink4"] + check_beacon = true + failback = true + notify_switches = true + teaming_policy = "failover_explicit" + + lacp_enabled = true + lacp_mode = "active" + + allow_forged_transmits = true + allow_mac_changes = true + allow_promiscuous = true + + ingress_shaping_enabled = true + ingress_shaping_average_bandwidth = 1000000 + ingress_shaping_peak_bandwidth = 10000000 + ingress_shaping_burst_size = 5000000 + + egress_shaping_enabled = true + egress_shaping_average_bandwidth = 1000000 + egress_shaping_peak_bandwidth = 10000000 + egress_shaping_burst_size = 5000000 + + block_all_ports = true + netflow_enabled = true + tx_uplink = true +} + +resource "vsphere_distributed_port_group" "pg" { + name = "terraform-test-pg" + distributed_virtual_switch_uuid = "${vsphere_distributed_virtual_switch.dvs.id}" +} +`, + os.Getenv("VSPHERE_DATACENTER"), + ) +} + +func testAccResourceVSphereDistributedPortGroupConfigOverrideVLAN() 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 + } +} + +resource "vsphere_distributed_port_group" "pg" { + name = "terraform-test-pg" + distributed_virtual_switch_uuid = "${vsphere_distributed_virtual_switch.dvs.id}" + + vlan_range { + min_vlan = 3000 + max_vlan = 3999 + } +} +`, + os.Getenv("VSPHERE_DATACENTER"), + ) +} + +func testAccResourceVSphereDistributedPortGroupConfigSingleTag() 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 = [ + "VmwareDistributedVirtualPortgroup", + ] +} + +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}" +} + +resource "vsphere_distributed_port_group" "pg" { + name = "terraform-test-pg" + distributed_virtual_switch_uuid = "${vsphere_distributed_virtual_switch.dvs.id}" + tags = ["${vsphere_tag.terraform-test-tag.id}"] +} +`, + os.Getenv("VSPHERE_DATACENTER"), + ) +} + +func testAccResourceVSphereDistributedPortGroupConfigMultiTag() 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 = [ + "VmwareDistributedVirtualPortgroup", + ] +} + +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}" +} + +resource "vsphere_distributed_port_group" "pg" { + name = "terraform-test-pg" + distributed_virtual_switch_uuid = "${vsphere_distributed_virtual_switch.dvs.id}" + tags = ["${vsphere_tag.terraform-test-tags-alt.*.id}"] +} +`, + os.Getenv("VSPHERE_DATACENTER"), + ) +} diff --git a/vsphere/resource_vsphere_distributed_virtual_switch.go b/vsphere/resource_vsphere_distributed_virtual_switch.go index c64b57069..879725bbf 100644 --- a/vsphere/resource_vsphere_distributed_virtual_switch.go +++ b/vsphere/resource_vsphere_distributed_virtual_switch.go @@ -9,12 +9,6 @@ import ( "github.com/vmware/govmomi/vim25/types" ) -const ( - retryDVSUpdatePending = "retryDVSUpdatePending" - retryDVSUpdateCompleted = "retryDVSUpdateCompleted" - retryDVSUpdateError = "retryDVSUpdateError" -) - func resourceVSphereDistributedVirtualSwitch() *schema.Resource { s := map[string]*schema.Schema{ "datacenter_id": { @@ -39,26 +33,6 @@ func resourceVSphereDistributedVirtualSwitch() *schema.Resource { } 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, diff --git a/vsphere/tags_helper.go b/vsphere/tags_helper.go index d0ace86c0..0375a550f 100644 --- a/vsphere/tags_helper.go +++ b/vsphere/tags_helper.go @@ -193,6 +193,8 @@ func tagTypeForObject(obj object.Reference) (string, error) { return vSphereTagTypeVmwareDistributedVirtualSwitch, nil case *object.DistributedVirtualSwitch: return vSphereTagTypeDistributedVirtualSwitch, nil + case *object.DistributedVirtualPortgroup: + return vSphereTagTypeDistributedVirtualPortgroup, nil case *object.Datacenter: return vSphereTagTypeDatacenter, nil case *object.ClusterComputeResource: diff --git a/website/docs/r/distributed_port_group.html.markdown b/website/docs/r/distributed_port_group.html.markdown new file mode 100644 index 000000000..dc5aebc21 --- /dev/null +++ b/website/docs/r/distributed_port_group.html.markdown @@ -0,0 +1,229 @@ +--- +layout: "vsphere" +page_title: "VMware vSphere: vsphere_distributed_port_group" +sidebar_current: "docs-vsphere-resource-network-distributed-port-group" +description: |- + Provides a vSphere distributed virtual portgroup resource. This can be used to create and manage portgroups on a distributed virtual switch. +--- + +# vsphere\_distributed\_port\_group + +The `vsphere_distributed_port_group` resource can be used to manage vSphere +distributed virtual port groups. These port groups are connected to distributed +virtual switches, which can be managed by the +[`vsphere_distributed_virtual_switch`][distributed-virtual-switch] resource. + +Distributed port groups can be used as networks for virtual machines, allowing +VMs to use the networking supplied by a distributed virtual switch (DVS), with +a set of policies that apply to that individual newtork, if desired. + +For an overview on vSphere networking concepts, see [this +page][ref-vsphere-net-concepts]. For more information on vSphere DVS +portgroups, see [this page][ref-vsphere-dvportgroup]. + +[distributed-virtual-switch]: /docs/providers/vsphere/r/distributed_virtual_switch.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-dvportgroup]: https://docs.vmware.com/en/VMware-vSphere/6.5/com.vmware.vsphere.networking.doc/GUID-69933F6E-2442-46CF-AA17-1196CB9A0A09.html + +~> **NOTE:** This resource requires vCenter and is not available on direct ESXi +connections. + +## Example Usage + +The configuration below builds on the example given in the +[`vsphere_distributed_virtual_switch`][distributed-virtual-switch] resource by +adding the `vsphere_distributed_port_group` resource, attaching itself to the +DVS created here and assigning VLAN ID 1000. + +```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}"] + } +} + +resource "vsphere_distributed_port_group" "pg" { + name = "terraform-test-pg" + distributed_virtual_switch_uuid = "${vsphere_distributed_virtual_switch.dvs.id}" + + vlan_id = 1000 +} +``` + +### Overriding DVS policies + +All of the [default port policies][dvs-default-port-policies] available in the +`vsphere_distributed_virtual_switch` resource can be overridden on the port +group level by specifying new settings for them. + +[dvs-default-port-policies]: /docs/providers/vsphere/r/distributed_virtual_switch.html#default-port-group-policy-arguments + +As an example, we also take this example from the +`vsphere_distributed_virtual_switch` resource where we manually specify our +uplink count and uplink order. While the DVS has a default policy of using the +first uplink as an active uplink and the second one as a standby, the +overridden port group policy means that both uplinks will be used as active +uplinks in this specific port group. + +```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"] +} + +resource "vsphere_distributed_port_group" "pg" { + name = "terraform-test-pg" + distributed_virtual_switch_uuid = "${vsphere_distributed_virtual_switch.dvs.id}" + + vlan_id = 1000 + + active_uplinks = ["tfup1", "tfup2"] + standby_uplinks = [] +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required) The name of the port group. +* `distributed_virtual_switch_uuid` - (Required) The ID of the DVS to add the + port group to. Forces a new resource if changed. +* `type` - (Optional) The port group type. Can be one of `earlyBinding` (static + binding) or `ephemeral`. Default: `earlyBinding`. +* `description` - (Optional) An optional description for the port group. +* `number_of_ports` - (Optional) The number of ports available on this port + group. Cannot be decreased below the amount of used ports on the port group. +* `auto_expand` - (Optional) Allows the port group to create additional ports + past the limit specified in `number_of_ports` if necessary. Default: `true`. + +~> **NOTE:** Using `auto_expand` with a statically defined `number_of_ports` +may lead to errors when the port count grows past the amount specified. If you +specify `number_of_ports`, you may wish to set `auto_expand` to `false`. + +* `port_name_format` - (Optional) An optional formatting policy for naming of + the ports in this port group. See the `portNameFormat` attribute listed + [here][ext-vsphere-portname-format] for details on the format syntax. + +[ext-vsphere-portname-format]: https://code.vmware.com/apis/196/vsphere#/doc/vim.dvs.DistributedVirtualPortgroup.ConfigInfo.html#portNameFormat + +* `network_resource_pool_key` - (Optional) The key of a network resource pool + to associate with this port group. The default is `-1`, which implies no + association. + +### Policy options + +In addition to the above options, you can configure any policy option that is +available under the [`vsphere_distributed_virtual_switch` policy +options][dvs-default-port-policies] section. Any policy option that is not set +is inherited from the DVS, its options propagating to the port group. + +See the link for a full list of options that can be set. + +### Port override options + +The following options below control whether or not the policies set in the port +group can be overridden on the individual port: + +* `block_override_allowed` - (Optional) Allow the [port shutdown + policy][port-shutdown-policy] to be overridden on an individual port. +* `live_port_moving_allowed` - (Optional) Allow a port in this port group to be + moved to another port group while it is connected. +* `netflow_override_allowed` - (Optional) Allow the [Netflow + policy][netflow-policy] on this port group to be overridden on an individual + port. +* `network_resource_pool_override_allowed` - (Optional) Allow the network + resource pool set on this port group to be overridden on an individual port. +* `port_config_reset_at_disconnect` - (Optional) Reset a port's settings to the + settings defined on this port group policy when the port disconnects. +* `security_policy_override_allowed` - (Optional) Allow the [security policy + settings][sec-policy-settings] defined in this port group policy to be + overridden on an individual port. +* `shaping_override_allowed` - (Optional) Allow the [traffic shaping + options][traffic-shaping-settings] on this port group policy to be overridden + on an individual port. +* `traffic_filter_override_allowed` - (Optional) Allow any traffic filters on + this port group to be overridden on an individual port. +* `uplink_teaming_override_allowed` - (Optional) Allow the [uplink teaming + options][uplink-teaming-settings] on this port group to be overridden on an + individual port. +* `vlan_override_allowed` - (Optional) Allow the [VLAN settings][vlan-settings] + on this port group to be overridden on an individual port. + +[port-shutdown-policy]: /docs/providers/vsphere/r/distributed_virtual_switch.html#block_all_ports +[netflow-policy]: /docs/providers/vsphere/r/distributed_virtual_switch.html#netflow_enabled +[sec-policy-settings]: /docs/providers/vsphere/r/distributed_virtual_switch.html#security-options +[traffic-shaping-settings]: /docs/providers/vsphere/r/distributed_virtual_switch.html#traffic-shaping-options +[uplink-teaming-settings]: /docs/providers/vsphere/r/distributed_virtual_switch.html#ha-policy-options +[vlan-settings]: /docs/providers/vsphere/r/distributed_virtual_switch.html#vlan-options + +## Attribute Reference + +The following attributes are exported: + +* `id`: The UUID of the created port group. +* `config_version`: The current version of the port group configuration, + incremented by subsequent updates to the port group. + +## Importing + +An existing port group can be [imported][docs-import] into this resource via +the path to the port group, via the following command: + +[docs-import]: https://www.terraform.io/docs/import/index.html + +``` +terraform import vsphere_distributed_port_group.pg /dc1/network/pg +``` + +The above would import the port group named `pg` that is located in the `dc1` +datacenter. diff --git a/website/docs/r/distributed_virtual_switch.html.markdown b/website/docs/r/distributed_virtual_switch.html.markdown index 393f97668..30621a3d6 100644 --- a/website/docs/r/distributed_virtual_switch.html.markdown +++ b/website/docs/r/distributed_virtual_switch.html.markdown @@ -392,6 +392,8 @@ applies to: applies to. * `tx_uplink` - (Optional) Forward all traffic transmitted by ports for which this policy applies to its DVS uplinks. +* `directpath_gen2_allowed` - (Optional) Allow VMDirectPath Gen2 for the ports + for which this policy applies to. ## Attribute Reference diff --git a/website/vsphere.erb b/website/vsphere.erb index ee4307947..1b397d12f 100644 --- a/website/vsphere.erb +++ b/website/vsphere.erb @@ -61,6 +61,9 @@ > Networking Resources