Skip to content

Commit

Permalink
Merge pull request hashicorp#139 from terraform-providers/f-host-port…
Browse files Browse the repository at this point in the history
…-group-resource

New resource: vsphere_host_port_group
  • Loading branch information
vancluever authored Aug 31, 2017
2 parents ae75db1 + 55de1a4 commit c8761af
Show file tree
Hide file tree
Showing 8 changed files with 733 additions and 2 deletions.
21 changes: 21 additions & 0 deletions vsphere/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/hashicorp/terraform/terraform"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/vim25/types"
)

// testCheckVariables bundles common variables needed by various test checkers.
Expand Down Expand Up @@ -49,3 +50,23 @@ func testAccSkipIfNotEsxi(t *testing.T) {
t.Skip("set VSPHERE_TEST_ESXI to run ESXi-specific acceptance tests")
}
}

// testGetPortGroup is a convenience method to fetch a static port group
// resource for testing.
func testGetPortGroup(s *terraform.State, resourceName string) (*types.HostPortGroup, error) {
tVars, err := testClientVariablesForResource(s, fmt.Sprintf("vsphere_host_port_group.%s", resourceName))
if err != nil {
return nil, err
}

hsID, name, err := splitHostPortGroupID(tVars.resourceID)
if err != nil {
return nil, err
}
ns, err := hostNetworkSystemFromHostSystemID(tVars.client, hsID)
if err != nil {
return nil, fmt.Errorf("error loading host network system: %s", err)
}

return hostPortGroupFromName(tVars.client, ns, name)
}
24 changes: 22 additions & 2 deletions vsphere/host_network_system_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ func hostNetworkSystemFromHostSystemID(client *govmomi.Client, hsID string) (*ob
return hostNetworkSystemFromHostSystem(hs)
}

// hostVSwitchFromName locates a virtual switch on the supplied HostSystem by
// name.
// hostVSwitchFromName locates a virtual switch on the supplied
// HostNetworkSystem by name.
func hostVSwitchFromName(client *govmomi.Client, ns *object.HostNetworkSystem, name string) (*types.HostVirtualSwitch, error) {
var mns mo.HostNetworkSystem
pc := client.PropertyCollector()
Expand All @@ -47,3 +47,23 @@ func hostVSwitchFromName(client *govmomi.Client, ns *object.HostNetworkSystem, n

return nil, fmt.Errorf("could not find virtual switch %s", name)
}

// hostPortGroupFromName locates a port group on the supplied HostNetworkSystem
// by name.
func hostPortGroupFromName(client *govmomi.Client, ns *object.HostNetworkSystem, name string) (*types.HostPortGroup, error) {
var mns mo.HostNetworkSystem
pc := client.PropertyCollector()
ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer cancel()
if err := pc.RetrieveOne(ctx, ns.Reference(), []string{"networkInfo.portgroup"}, &mns); err != nil {
return nil, fmt.Errorf("error fetching host network properties: %s", err)
}

for _, pg := range mns.NetworkInfo.Portgroup {
if pg.Spec.Name == name {
return &pg, nil
}
}

return nil, fmt.Errorf("could not find port group %s", name)
}
143 changes: 143 additions & 0 deletions vsphere/host_port_group_structure.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package vsphere

import (
"fmt"
"strings"

"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"
"github.com/hashicorp/terraform/terraform"
"github.com/vmware/govmomi/vim25/types"
)

const hostPortGroupIDPrefix = "tf-HostPortGroup"

// schemaHostPortGroupSpec returns schema items for resources that
// need to work with HostPortGroupSpec, such as port groups.
func schemaHostPortGroupSpec() map[string]*schema.Schema {
s := map[string]*schema.Schema{
// HostPortGroupSpec
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "The name of the port group.",
ForceNew: true,
},
"vlan_id": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Description: "The VLAN ID/trunk mode for this port group. An ID of 0 denotes no tagging, an ID of 1-4094 tags with the specific ID, and an ID of 4095 enables trunk mode, allowing the guest to manage its own tagging.",
Default: 0,
ValidateFunc: validation.IntBetween(0, 4095),
},
"virtual_switch_name": &schema.Schema{
Type: schema.TypeString,
Required: true,
Description: "The name of the virtual switch to bind this port group to.",
ForceNew: true,
},
}
mergeSchema(s, schemaHostNetworkPolicy())
return s
}

// expandHostPortGroupSpec reads certain ResourceData keys and returns a
// HostPortGroupSpec.
func expandHostPortGroupSpec(d *schema.ResourceData) *types.HostPortGroupSpec {
obj := &types.HostPortGroupSpec{
Name: d.Get("name").(string),
VlanId: int32(d.Get("vlan_id").(int)),
VswitchName: d.Get("virtual_switch_name").(string),
Policy: *expandHostNetworkPolicy(d),
}
return obj
}

// flattenHostPortGroupSpec reads various fields from a HostPortGroupSpec into
// the passed in ResourceData.
func flattenHostPortGroupSpec(d *schema.ResourceData, obj *types.HostPortGroupSpec) error {
d.Set("vlan_id", obj.VlanId)
if err := flattenHostNetworkPolicy(d, &obj.Policy); err != nil {
return err
}
return nil
}

// calculateComputedPolicy is a utility function to compute a map of state
// attributes for the port group's effective policy. It uses a bit of a
// roundabout way to set the attributes, but allows us to utilize our
// functional deep reading helpers to perform this task, versus having to
// re-write code.
//
// This function relies a bit on some of the lower-level utility functionality
// of helper/schema, so it may need to change in the future.
func calculateComputedPolicy(policy types.HostNetworkPolicy) (map[string]string, error) {
cpr := &schema.Resource{Schema: schemaHostNetworkPolicy()}
cpd := cpr.Data(&terraform.InstanceState{})
cpd.SetId("effectivepolicy")
if err := flattenHostNetworkPolicy(cpd, &policy); err != nil {
return nil, fmt.Errorf("error setting effective policy data: %s", err)
}
cpm := cpd.State().Attributes
delete(cpm, "id")
return cpm, nil
}

// calculatePorts is a utility function that returns a set of port data.
func calculatePorts(ports []types.HostPortGroupPort) *schema.Set {
s := make([]interface{}, 0)
for _, port := range ports {
m := make(map[string]interface{})
m["key"] = port.Key
m["mac_addresses"] = sliceStringsToInterfaces(port.Mac)
m["type"] = port.Type
s = append(s, m)
}
return schema.NewSet(schema.HashResource(portGroupPortSchema()), s)
}

// portGroupPortSchema returns a sub-schema for a port group's connected ports.
func portGroupPortSchema() *schema.Resource {
return &schema.Resource{
Schema: map[string]*schema.Schema{
"key": &schema.Schema{
Type: schema.TypeString,
Description: "The linkable identifier for this port entry.",
Computed: true,
},
"mac_addresses": &schema.Schema{
Type: schema.TypeList,
Description: "The MAC addresses of the network service of the virtual machine connected on this port.",
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},
"type": &schema.Schema{
Type: schema.TypeString,
Description: "Type type of the entity connected on this port. Possible values are host (VMKkernel), systemManagement (service console), virtualMachine, or unknown.",
Computed: true,
},
},
}
}

// saveHostPortGroupID sets a special ID for a host virtual switch, composed of
// the MOID for the concerned HostSystem and the port group's key.
func saveHostPortGroupID(d *schema.ResourceData, hsID, name string) {
d.SetId(fmt.Sprintf("%s:%s:%s", hostPortGroupIDPrefix, hsID, name))
}

// splitHostPortGroupID splits a vsphere_host_port_group resource ID into its
// counterparts: the prefix, the HostSystem ID, and the port group name.
func splitHostPortGroupID(raw string) (string, string, error) {
s := strings.SplitN(raw, ":", 3)
if len(s) != 3 || s[0] != hostPortGroupIDPrefix || s[1] == "" || s[2] == "" {
return "", "", fmt.Errorf("corrupt ID: %s", raw)
}
return s[1], s[2], nil
}

// portGroupIDsFromResourceID passes a resource's ID through
// splitHostPortGroupID.
func portGroupIDsFromResourceID(d *schema.ResourceData) (string, string, error) {
return splitHostPortGroupID(d.Id())
}
1 change: 1 addition & 0 deletions vsphere/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ func Provider() terraform.ResourceProvider {
"vsphere_datacenter": resourceVSphereDatacenter(),
"vsphere_file": resourceVSphereFile(),
"vsphere_folder": resourceVSphereFolder(),
"vsphere_host_port_group": resourceVSphereHostPortGroup(),
"vsphere_host_virtual_switch": resourceVSphereHostVirtualSwitch(),
"vsphere_license": resourceVSphereLicense(),
"vsphere_virtual_disk": resourceVSphereVirtualDisk(),
Expand Down
148 changes: 148 additions & 0 deletions vsphere/resource_vsphere_host_port_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package vsphere

import (
"fmt"

"context"

"github.com/hashicorp/terraform/helper/schema"
"github.com/vmware/govmomi"
)

func resourceVSphereHostPortGroup() *schema.Resource {
s := map[string]*schema.Schema{
"host_system_id": &schema.Schema{
Type: schema.TypeString,
Description: "The managed object ID of the host to set the virtual switch up on.",
Required: true,
ForceNew: true,
},
"computed_policy": &schema.Schema{
Type: schema.TypeMap,
Description: "The effective network policy after inheritance. Note that this will look similar to, but is not the same, as the policy attributes defined in this resource.",
Computed: true,
},
"key": &schema.Schema{
Type: schema.TypeString,
Description: "The linkable identifier for this port group.",
Computed: true,
},
"ports": &schema.Schema{
Type: schema.TypeSet,
Description: "The ports that currently exist and are used on this port group.",
Computed: true,
MaxItems: 1,
Elem: portGroupPortSchema(),
},
}
mergeSchema(s, schemaHostPortGroupSpec())

// Transform any necessary fields in the schema that need to be updated
// specifically for this resource.
s["active_nics"].Optional = true
s["standby_nics"].Optional = true

return &schema.Resource{
Create: resourceVSphereHostPortGroupCreate,
Read: resourceVSphereHostPortGroupRead,
Update: resourceVSphereHostPortGroupUpdate,
Delete: resourceVSphereHostPortGroupDelete,
Schema: s,
}
}

func resourceVSphereHostPortGroupCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*govmomi.Client)
name := d.Get("name").(string)
hsID := d.Get("host_system_id").(string)
ns, err := hostNetworkSystemFromHostSystemID(client, hsID)
if err != nil {
return fmt.Errorf("error loading network system: %s", err)
}

ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer cancel()
spec := expandHostPortGroupSpec(d)
if err := ns.AddPortGroup(ctx, *spec); err != nil {
return fmt.Errorf("error adding port group: %s", err)
}

saveHostPortGroupID(d, hsID, name)
return resourceVSphereHostPortGroupRead(d, meta)
}

func resourceVSphereHostPortGroupRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*govmomi.Client)
hsID, name, err := portGroupIDsFromResourceID(d)
if err != nil {
return err
}
ns, err := hostNetworkSystemFromHostSystemID(client, hsID)
if err != nil {
return fmt.Errorf("error loading host network system: %s", err)
}

pg, err := hostPortGroupFromName(meta.(*govmomi.Client), ns, name)
if err != nil {
return fmt.Errorf("error fetching port group data: %s", err)
}

if err := flattenHostPortGroupSpec(d, &pg.Spec); err != nil {
return fmt.Errorf("error setting resource data: %s", err)
}

d.Set("key", pg.Key)
cpm, err := calculateComputedPolicy(pg.ComputedPolicy)
if err != nil {
return err
}
if err := d.Set("computed_policy", cpm); err != nil {
return fmt.Errorf("error saving effective policy to state: %s", err)
}
if err := d.Set("ports", calculatePorts(pg.Port)); err != nil {
return fmt.Errorf("error setting port list: %s", err)
}

return nil
}

func resourceVSphereHostPortGroupUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*govmomi.Client)
hsID, name, err := portGroupIDsFromResourceID(d)
if err != nil {
return err
}
ns, err := hostNetworkSystemFromHostSystemID(client, hsID)
if err != nil {
return fmt.Errorf("error loading host network system: %s", err)
}

ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer cancel()
spec := expandHostPortGroupSpec(d)
if err := ns.UpdatePortGroup(ctx, name, *spec); err != nil {
return fmt.Errorf("error updating port group: %s", err)
}

return resourceVSphereHostPortGroupRead(d, meta)
}

func resourceVSphereHostPortGroupDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*govmomi.Client)
hsID, name, err := portGroupIDsFromResourceID(d)
if err != nil {
return err
}
ns, err := hostNetworkSystemFromHostSystemID(client, hsID)
if err != nil {
return fmt.Errorf("error loading host network system: %s", err)
}

ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer cancel()
if err := ns.RemovePortGroup(ctx, name); err != nil {
return fmt.Errorf("error deleting port group: %s", err)
}

return nil
}
Loading

0 comments on commit c8761af

Please sign in to comment.