Skip to content

Commit

Permalink
feat: vmware_distributed_virtual_switch_pvlan_mapping resource (#2291)
Browse files Browse the repository at this point in the history
  • Loading branch information
GCHQDeveloper609 authored Nov 7, 2024
1 parent 122c732 commit da5dd22
Show file tree
Hide file tree
Showing 8 changed files with 335 additions and 48 deletions.
31 changes: 31 additions & 0 deletions vsphere/distributed_virtual_switch_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,34 @@ func enableDVSNetworkResourceManagement(client *govmomi.Client, dvs *object.Vmwa

return nil
}

func updateDVSPvlanMappings(dvs *object.VmwareDistributedVirtualSwitch, pvlanConfig []types.VMwareDVSPvlanConfigSpec) error {
// Load current properties, required to get the 'config version' to provide back when updating
props, err := dvsProperties(dvs)
if err != nil {
return fmt.Errorf("cannot read properties of distributed_virtual_switch: %s", err)
}
dvsConfig := props.Config.(*types.VMwareDVSConfigInfo)

updateSpec := types.VMwareDVSConfigSpec{
DVSConfigSpec: types.DVSConfigSpec{
ConfigVersion: dvsConfig.ConfigVersion,
},
PvlanConfigSpec: pvlanConfig,
}

// Start ReconfigureDvs_Task
ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer cancel()
task, err := dvs.Reconfigure(ctx, &updateSpec)
if err != nil {
return fmt.Errorf("error reconfiguring DVS: %s", err)
}

// Wait for ReconfigureDvs_Task to finish
tctx, tcancel := context.WithTimeout(context.Background(), defaultAPITimeout)
defer tcancel()
task.Wait(tctx)

return nil
}
2 changes: 1 addition & 1 deletion vsphere/distributed_virtual_switch_structure.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func schemaVMwareDVSConfigSpec() map[string]*schema.Schema {
"ignore_other_pvlan_mappings": {
Type: schema.TypeBool,
Optional: true,
Description: "Whether to ignore existing PVLAN mappings not managed by this resource. Defaults to false.",
Description: "Whether to ignore existing PVLAN mappings not managed by this resource.",
Default: false,
},

Expand Down
87 changes: 44 additions & 43 deletions vsphere/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,49 +102,50 @@ func Provider() *schema.Provider {
},

ResourcesMap: map[string]*schema.Resource{
"vsphere_compute_cluster": resourceVSphereComputeCluster(),
"vsphere_compute_cluster_host_group": resourceVSphereComputeClusterHostGroup(),
"vsphere_compute_cluster_vm_affinity_rule": resourceVSphereComputeClusterVMAffinityRule(),
"vsphere_compute_cluster_vm_anti_affinity_rule": resourceVSphereComputeClusterVMAntiAffinityRule(),
"vsphere_compute_cluster_vm_dependency_rule": resourceVSphereComputeClusterVMDependencyRule(),
"vsphere_compute_cluster_vm_group": resourceVSphereComputeClusterVMGroup(),
"vsphere_compute_cluster_vm_host_rule": resourceVSphereComputeClusterVMHostRule(),
"vsphere_content_library": resourceVSphereContentLibrary(),
"vsphere_content_library_item": resourceVSphereContentLibraryItem(),
"vsphere_custom_attribute": resourceVSphereCustomAttribute(),
"vsphere_datacenter": resourceVSphereDatacenter(),
"vsphere_datastore_cluster": resourceVSphereDatastoreCluster(),
"vsphere_datastore_cluster_vm_anti_affinity_rule": resourceVSphereDatastoreClusterVMAntiAffinityRule(),
"vsphere_distributed_port_group": resourceVSphereDistributedPortGroup(),
"vsphere_distributed_virtual_switch": resourceVSphereDistributedVirtualSwitch(),
"vsphere_drs_vm_override": resourceVSphereDRSVMOverride(),
"vsphere_dpm_host_override": resourceVSphereDPMHostOverride(),
"vsphere_file": resourceVSphereFile(),
"vsphere_folder": resourceVSphereFolder(),
"vsphere_ha_vm_override": resourceVSphereHAVMOverride(),
"vsphere_host_port_group": resourceVSphereHostPortGroup(),
"vsphere_host_virtual_switch": resourceVSphereHostVirtualSwitch(),
"vsphere_license": resourceVSphereLicense(),
"vsphere_offline_software_depot": resourceVsphereOfflineSoftwareDepot(),
"vsphere_resource_pool": resourceVSphereResourcePool(),
"vsphere_tag": resourceVSphereTag(),
"vsphere_tag_category": resourceVSphereTagCategory(),
"vsphere_virtual_disk": resourceVSphereVirtualDisk(),
"vsphere_virtual_machine": resourceVSphereVirtualMachine(),
"vsphere_virtual_machine_class": resourceVsphereVmClass(),
"vsphere_virtual_machine_snapshot": resourceVSphereVirtualMachineSnapshot(),
"vsphere_nas_datastore": resourceVSphereNasDatastore(),
"vsphere_storage_drs_vm_override": resourceVSphereStorageDrsVMOverride(),
"vsphere_supervisor": resourceVsphereSupervisor(),
"vsphere_vapp_container": resourceVSphereVAppContainer(),
"vsphere_vapp_entity": resourceVSphereVAppEntity(),
"vsphere_vmfs_datastore": resourceVSphereVmfsDatastore(),
"vsphere_host": resourceVsphereHost(),
"vsphere_vnic": resourceVsphereNic(),
"vsphere_vm_storage_policy": resourceVMStoragePolicy(),
"vsphere_role": resourceVsphereRole(),
"vsphere_entity_permissions": resourceVsphereEntityPermissions(),
"vsphere_guest_os_customization": resourceVSphereGuestOsCustomization(),
"vsphere_compute_cluster": resourceVSphereComputeCluster(),
"vsphere_compute_cluster_host_group": resourceVSphereComputeClusterHostGroup(),
"vsphere_compute_cluster_vm_affinity_rule": resourceVSphereComputeClusterVMAffinityRule(),
"vsphere_compute_cluster_vm_anti_affinity_rule": resourceVSphereComputeClusterVMAntiAffinityRule(),
"vsphere_compute_cluster_vm_dependency_rule": resourceVSphereComputeClusterVMDependencyRule(),
"vsphere_compute_cluster_vm_group": resourceVSphereComputeClusterVMGroup(),
"vsphere_compute_cluster_vm_host_rule": resourceVSphereComputeClusterVMHostRule(),
"vsphere_content_library": resourceVSphereContentLibrary(),
"vsphere_content_library_item": resourceVSphereContentLibraryItem(),
"vsphere_custom_attribute": resourceVSphereCustomAttribute(),
"vsphere_datacenter": resourceVSphereDatacenter(),
"vsphere_datastore_cluster": resourceVSphereDatastoreCluster(),
"vsphere_datastore_cluster_vm_anti_affinity_rule": resourceVSphereDatastoreClusterVMAntiAffinityRule(),
"vsphere_distributed_port_group": resourceVSphereDistributedPortGroup(),
"vsphere_distributed_virtual_switch": resourceVSphereDistributedVirtualSwitch(),
"vsphere_distributed_virtual_switch_pvlan_mapping": resourceVSphereDistributedVirtualSwitchPvlanMapping(),
"vsphere_drs_vm_override": resourceVSphereDRSVMOverride(),
"vsphere_dpm_host_override": resourceVSphereDPMHostOverride(),
"vsphere_file": resourceVSphereFile(),
"vsphere_folder": resourceVSphereFolder(),
"vsphere_ha_vm_override": resourceVSphereHAVMOverride(),
"vsphere_host_port_group": resourceVSphereHostPortGroup(),
"vsphere_host_virtual_switch": resourceVSphereHostVirtualSwitch(),
"vsphere_license": resourceVSphereLicense(),
"vsphere_offline_software_depot": resourceVsphereOfflineSoftwareDepot(),
"vsphere_resource_pool": resourceVSphereResourcePool(),
"vsphere_tag": resourceVSphereTag(),
"vsphere_tag_category": resourceVSphereTagCategory(),
"vsphere_virtual_disk": resourceVSphereVirtualDisk(),
"vsphere_virtual_machine": resourceVSphereVirtualMachine(),
"vsphere_virtual_machine_class": resourceVsphereVmClass(),
"vsphere_virtual_machine_snapshot": resourceVSphereVirtualMachineSnapshot(),
"vsphere_nas_datastore": resourceVSphereNasDatastore(),
"vsphere_storage_drs_vm_override": resourceVSphereStorageDrsVMOverride(),
"vsphere_supervisor": resourceVsphereSupervisor(),
"vsphere_vapp_container": resourceVSphereVAppContainer(),
"vsphere_vapp_entity": resourceVSphereVAppEntity(),
"vsphere_vmfs_datastore": resourceVSphereVmfsDatastore(),
"vsphere_host": resourceVsphereHost(),
"vsphere_vnic": resourceVsphereNic(),
"vsphere_vm_storage_policy": resourceVMStoragePolicy(),
"vsphere_role": resourceVsphereRole(),
"vsphere_entity_permissions": resourceVsphereEntityPermissions(),
"vsphere_guest_os_customization": resourceVSphereGuestOsCustomization(),
},

DataSourcesMap: map[string]*schema.Resource{
Expand Down
5 changes: 5 additions & 0 deletions vsphere/resource_vsphere_distributed_virtual_switch.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ func resourceVSphereDistributedVirtualSwitchRead(d *schema.ResourceData, meta in
if err := viapi.ValidateVirtualCenter(client); err != nil {
return err
}

id := d.Id()
dvs, err := dvsFromUUID(client, id)
if err != nil {
Expand Down Expand Up @@ -195,6 +196,8 @@ func resourceVSphereDistributedVirtualSwitchUpdate(d *schema.ResourceData, meta
return err
}

vsphereDistributedVirtualSwitchModificationMutex.Lock()

id := d.Id()
dvs, err := dvsFromUUID(client, id)
if err != nil {
Expand Down Expand Up @@ -256,6 +259,8 @@ func resourceVSphereDistributedVirtualSwitchUpdate(d *schema.ResourceData, meta
}
}

vsphereDistributedVirtualSwitchModificationMutex.Unlock()

return resourceVSphereDistributedVirtualSwitchRead(d, meta)
}

Expand Down
142 changes: 142 additions & 0 deletions vsphere/resource_vsphere_distributed_virtual_switch_pvlan_mapping.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package vsphere

import (
"fmt"
"sync"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-provider-vsphere/vsphere/internal/helper/viapi"
"github.com/vmware/govmomi/vim25/types"
)

var vsphereDistributedVirtualSwitchModificationMutex sync.Mutex

func resourceVSphereDistributedVirtualSwitchPvlanMapping() *schema.Resource {
s := map[string]*schema.Schema{
"distributed_virtual_switch_id": {
Type: schema.TypeString,
Description: "The ID of the distributed virtual switch to attach this mapping to.",
Required: true,
ForceNew: true,
},
"primary_vlan_id": {
Type: schema.TypeInt,
Required: true,
Description: "The primary VLAN ID. The VLAN IDs of 0 and 4095 are reserved and cannot be used in this property.",
ValidateFunc: validation.IntBetween(1, 4094),
ForceNew: true,
},
"secondary_vlan_id": {
Type: schema.TypeInt,
Required: true,
Description: "The secondary VLAN ID. The VLAN IDs of 0 and 4095 are reserved and cannot be used in this property.",
ValidateFunc: validation.IntBetween(1, 4094),
ForceNew: true,
},
"pvlan_type": {
Type: schema.TypeString,
Required: true,
Description: "The private VLAN type. Valid values are promiscuous, community and isolated.",
ValidateFunc: validation.StringInSlice(privateVLANTypeAllowedValues, false),
ForceNew: true,
},
}

return &schema.Resource{
Create: resourceVSphereDistributedVirtualSwitchPvlanMappingCreate,
Read: resourceVSphereDistributedVirtualSwitchPvlanMappingRead,
Delete: resourceVSphereDistributedVirtualSwitchPvlanMappingDelete,
Schema: s,
}
}

func resourceVSphereDistributedVirtualSwitchPvlanMappingOperation(operation types.ConfigSpecOperation, d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client).vimClient
if err := viapi.ValidateVirtualCenter(client); err != nil {
return err
}

// vSphere only allows one modification operation at a time, so lock locally
// to avoid errors if multiple pvlan mappings are created at once.
vsphereDistributedVirtualSwitchModificationMutex.Lock()

// Fetch the target dvswitch
dvs, err := dvsFromUUID(client, d.Get("distributed_virtual_switch_id").(string))
if err != nil {
return fmt.Errorf("cannot locate distributed_virtual_switch: %s", err)
}

// Perform operation
entry := types.VMwareDVSPvlanMapEntry{
PrimaryVlanId: int32(d.Get("primary_vlan_id").(int)),
SecondaryVlanId: int32(d.Get("secondary_vlan_id").(int)),
PvlanType: d.Get("pvlan_type").(string),
}

pvlanConfig := []types.VMwareDVSPvlanConfigSpec{
{
Operation: string(operation),
PvlanEntry: entry,
},
}

err = updateDVSPvlanMappings(dvs, pvlanConfig)
if err != nil {
return fmt.Errorf("cannot reconfigure distributed virtual switch: %s", err)
}

vsphereDistributedVirtualSwitchModificationMutex.Unlock()
return nil
}

func resourceVSphereDistributedVirtualSwitchPvlanMappingCreate(d *schema.ResourceData, meta interface{}) error {
err := resourceVSphereDistributedVirtualSwitchPvlanMappingOperation(types.ConfigSpecOperationAdd, d, meta)
if err != nil {
return err
}

// Try to read the mapping back, which will also generate the ID.
return resourceVSphereDistributedVirtualSwitchPvlanMappingRead(d, meta)
}

func resourceVSphereDistributedVirtualSwitchPvlanMappingDelete(d *schema.ResourceData, meta interface{}) error {
return resourceVSphereDistributedVirtualSwitchPvlanMappingOperation(types.ConfigSpecOperationRemove, d, meta)
}

func resourceVSphereDistributedVirtualSwitchPvlanMappingRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client).vimClient
if err := viapi.ValidateVirtualCenter(client); err != nil {
return err
}

// Ensure the DVSwitch still exists
dvs, err := dvsFromUUID(client, d.Get("distributed_virtual_switch_id").(string))
if err != nil {
return fmt.Errorf("cannot locate distributed_virtual_switch: %s", err)
}

// Get its properties
props, err := dvsProperties(dvs)
if err != nil {
return fmt.Errorf("cannot read properties of distributed_virtual_switch: %s", err)
}
d.Set("distributed_virtual_switch_id", props.Uuid)

// Loop through the existing mappings on the switch to try and find one matching our spec
for _, mapping := range props.Config.(*types.VMwareDVSConfigInfo).PvlanConfig {
// Check if the existing mapping matches the one specified by the resource
if mapping.PrimaryVlanId == int32(d.Get("primary_vlan_id").(int)) && mapping.SecondaryVlanId == int32(d.Get("secondary_vlan_id").(int)) && mapping.PvlanType == d.Get("pvlan_type").(string) {
d.SetId(fmt.Sprintf("dvswitch-%s-mapping-%d-%d-%s", props.Config.(*types.VMwareDVSConfigInfo).Uuid, mapping.PrimaryVlanId, mapping.SecondaryVlanId, mapping.PvlanType))
return nil
}
}

// If we don't find a mapping on the switch matching the current spec, then tell Terraform
// that the resource no longer exists
d.SetId("")
return nil
}
Loading

0 comments on commit da5dd22

Please sign in to comment.