You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by ar...@apache.org on 2012/10/02 20:16:00 UTC

svn commit: r1393075 - in /vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware: VMware.pm vSphere_SDK.pm

Author: arkurth
Date: Tue Oct  2 18:15:59 2012
New Revision: 1393075

URL: http://svn.apache.org/viewvc?rev=1393075&view=rev
Log:
VCL-638
Added get_host_network_info and add_ethernet_adapter to vSphere_SDK.pm. This code detects whether a dvSwitch or regular network switch is being used on the VM host and adds an ethernet adapter accordingly.

Added code to VMware.pm to check if the API object implements add_ethernet_adapter. If it does, the ethernet info isn't added to the .vmx file before it is registered. After being registered, add_ethernet_adapter is called.

Modified:
    vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm
    vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm

Modified: vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm?rev=1393075&r1=1393074&r2=1393075&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/VMware.pm Tue Oct  2 18:15:59 2012
@@ -504,6 +504,17 @@ sub load {
 		return;
 	}
 	
+	# If API implements 'add_ethernet_adapter' the adapters were not added to the vmx, add them now
+	if ($self->api->can('add_ethernet_adapter')) {
+		(my @vm_ethernet_adapter_configuration = $self->get_vm_ethernet_adapter_configuration()) || return;
+		for my $adapter (@vm_ethernet_adapter_configuration) {
+			if (!$self->api->add_ethernet_adapter($vmx_file_path, $adapter)) {
+				notify($ERRORS{'WARNING'}, 0, "failed to add ethernet adapter to VM $computer_name on VM host: $vmhost_name\n" . format_data($adapter));
+				return;
+			}
+		}
+	}
+	
 	# Create a snapshot of the VM
 	if (!$self->snapshot('register')) {
 		notify($ERRORS{'WARNING'}, 0, "failed to create snapshot before powering on VM $computer_name on VM host: $vmhost_name, attempting to delete VM to prevent the possibility of writing to the shared vmdk if the VM is powered on");
@@ -516,7 +527,6 @@ sub load {
 		return;
 	}
 	
-	
 	# Power on the VM
 	if (!$self->power_on($vmx_file_path)) {
 		notify($ERRORS{'WARNING'}, 0, "failed to power on VM $computer_name on VM host: $vmhost_name");
@@ -1835,107 +1845,59 @@ sub prepare_vmx {
 	# ide needed for boot
 	# usb needed for mouse
 	# monitor, ich7m, smc for darwin
-  	if ($image_os_type =~ /osx/i) {
-		  %vmx_parameters = (%vmx_parameters, (
-		  "ide1:0.clientDevice" => "TRUE",
-        "ide1:0.deviceType" => "atapi-cdrom",
-        "ide1:0.fileName" => "",           
-        "ide1:0.present" => "TRUE", 
-        "ide1:0.startConnected" => "FALSE",
-        "usb.present" => "TRUE",                              
-        "usb:1.deviceType" => "hub",      
-        "usb:1.present" => "TRUE",    
-        "usb:2.deviceType" => "mouse",    
-        "usb:2.present" => "TRUE", 
-        "monitor.virtual_exec" => "hardware",
-        "monitor.virtual_mmu" => "software",
-        "ich7m.present" => "TRUE",
-        "smc.present" => "FALSE",
-        "keyboard.vusb.enable" => "TRUE",
-        "mouse.vusb.enable" => "TRUE",
-	  ));
+	if ($image_os_type =~ /osx/i) {
+		%vmx_parameters = (%vmx_parameters, (
+			"ide1:0.clientDevice" => "TRUE",
+			"ide1:0.deviceType" => "atapi-cdrom",
+			"ide1:0.fileName" => "",           
+			"ide1:0.present" => "TRUE", 
+			"ide1:0.startConnected" => "FALSE",
+			"usb.present" => "TRUE",                              
+			"usb:1.deviceType" => "hub",      
+			"usb:1.present" => "TRUE",    
+			"usb:2.deviceType" => "mouse",    
+			"usb:2.present" => "TRUE", 
+			"monitor.virtual_exec" => "hardware",
+			"monitor.virtual_mmu" => "software",
+			"ich7m.present" => "TRUE",
+			"smc.present" => "FALSE",
+			"keyboard.vusb.enable" => "TRUE",
+			"mouse.vusb.enable" => "TRUE",
+		));
 	}
 
-	
-	# Get a list of all the network names configured on the VMware host
-	my @network_names = $self->api->get_network_names();
-	notify($ERRORS{'DEBUG'}, 0, "retrieved network names configured on the VM host: " . join(", ", @network_names));
-	
-	# Add ethernet adapter definitions to the hash
-	my $interface_index = 0;
-	
-	if ($virtual_switch_0) {
-		if (!grep(/^$virtual_switch_0$/, @network_names)) {
-			notify($ERRORS{'WARNING'}, 0, "VM network 0 '$virtual_switch_0' configured in the VM profile does not match any network names on the VM host:\n" . join("\n", sort @network_names));
-			return;
-		}
-		
-		if ($vm_eth0_generated) {
-			%vmx_parameters = (%vmx_parameters, %{$self->get_generated_ethernet_vmx_definition($interface_index, $virtual_switch_0)});
-		}
-		else {
-			my $vm_eth0_mac = $self->data->get_computer_eth0_mac_address();
-			%vmx_parameters = (%vmx_parameters, %{$self->get_static_ethernet_vmx_definition($interface_index, $virtual_switch_0, $vm_eth0_mac)});
-		}
-		$interface_index++;
-	}
-	
-	if ($virtual_switch_1) {
-		if (!grep(/^$virtual_switch_1$/, @network_names)) {
-			notify($ERRORS{'WARNING'}, 0, "VM network 1 '$virtual_switch_1' configured in the VM profile does not match any network names on the VM host:\n" . join("\n", sort @network_names));
-			return;
-		}
-		
-		if ($vm_eth1_generated) {
-			%vmx_parameters = (%vmx_parameters, %{$self->get_generated_ethernet_vmx_definition($interface_index, $virtual_switch_1)});
-		}
-		else {
-			my $vm_eth1_mac = $self->data->get_computer_eth1_mac_address();
-			%vmx_parameters = (%vmx_parameters, %{$self->get_static_ethernet_vmx_definition($interface_index, $virtual_switch_1, $vm_eth1_mac)});
-		}
-		$interface_index++;
-	}
-	
-	# If vmprofile.virtualswitch2 or vmprofile.virtualswitch3 are defined in the database, add autogenerated interfaces
-	if ($virtual_switch_2) {
-		if (!grep(/^$virtual_switch_2$/, @network_names)) {
-			notify($ERRORS{'WARNING'}, 0, "VM network 2 '$virtual_switch_2' configured in the VM profile does not match any network names on the VM host:\n" . join("\n", sort @network_names));
-			return;
-		}
-		
-		%vmx_parameters = (%vmx_parameters, %{$self->get_generated_ethernet_vmx_definition($interface_index, $virtual_switch_2)});
-		$interface_index++;
-	}
-	if ($virtual_switch_3) {
-		if (!grep(/^$virtual_switch_3$/, @network_names)) {
-			notify($ERRORS{'WARNING'}, 0, "VM network 3 '$virtual_switch_3' configured in the VM profile does not match any network names on the VM host:\n" . join("\n", sort @network_names));
-			return;
-		}
-		
-		%vmx_parameters = (%vmx_parameters, %{$self->get_generated_ethernet_vmx_definition($interface_index, $virtual_switch_3)});
-		$interface_index++;
-	}
-	
-	# Add additional Ethernet interfaces if the image project name is not vcl
-	if ($image_project !~ /^vcl$/i && $self->api->can('get_network_names')) {
-		notify($ERRORS{'DEBUG'}, 0, "image project is: $image_project, checking if additional network adapters should be configured");
-		
-		# Check each network name
-		# Begin the index at 2 for additional interfaces added because ethernet0 and ethernet1 have already been added
-		for my $network_name (@network_names) {
-			if ($network_name =~ /$image_project/i || $image_project =~ /$network_name/i) {
-				notify($ERRORS{'DEBUG'}, 0, "network name ($network_name) and image project name ($image_project) intersect, adding network interface to VM for network $network_name");
-				%vmx_parameters = (%vmx_parameters, %{$self->get_generated_ethernet_vmx_definition($interface_index, $network_name)});
-				$interface_index++;
+	# Check if the API implements 'add_ethernet_adapter'
+	# This is necessary if the host is using dvSwitches/dvPorts
+	# Adding the info to the vmx before it is registered will not work
+	if ($self->api->can('add_ethernet_adapter')) {
+		notify($ERRORS{'DEBUG'}, 0, "ethernet adapters not added to vmx file, they will be added after the VM is registered");
+	}
+	else {
+		(my @vm_ethernet_adapter_configuration = $self->get_vm_ethernet_adapter_configuration()) || return;
+		
+		my $interface_index = 0;
+		for my $adapter (@vm_ethernet_adapter_configuration) {
+			if ($adapter->{address_type} =~ /Manual/i) {
+				%vmx_parameters = (%vmx_parameters, (
+					"ethernet$interface_index.present" => "TRUE",
+					"ethernet$interface_index.connectionType" => "custom",
+					"ethernet$interface_index.virtualDev" => $adapter->{adapter_type},
+					"ethernet$interface_index.networkName" => $adapter->{network_name},
+					"ethernet$interface_index.addressType" => "static",
+					"ethernet$interface_index.address" => $adapter->{address},
+				));
 			}
 			else {
-				notify($ERRORS{'DEBUG'}, 0, "network name ($network_name) and image project name ($image_project) do not intersect, network interface will not be added to VM for network $network_name");
+				%vmx_parameters = (%vmx_parameters, (
+					"ethernet$interface_index.present" => "TRUE",
+					"ethernet$interface_index.connectionType" => "custom",
+					"ethernet$interface_index.virtualDev" => $adapter->{adapter_type},
+					"ethernet$interface_index.networkName" => $adapter->{network_name},
+					"ethernet$interface_index.addressType" => "generated",
+				));
 			}
+			$interface_index++;
 		}
-		
-	}
-	else {
-		notify($ERRORS{'DEBUG'}, 0, "image project is: $image_project, additional network adapters will not be configured");
 	}
 	
 	notify($ERRORS{'DEBUG'}, 0, "vmx parameters:\n" . format_data(\%vmx_parameters));
@@ -1976,96 +1938,110 @@ sub prepare_vmx {
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_static_ethernet_vmx_definition
+=head2 get_vm_ethernet_adapter_configuration
 
- Parameters  : $interface_index, $network_name, $mac_address
- Returns     : hash reference
- Description : Generates a hash containing keys and values which correlate to
-               the vmx file parameters necessary to define an ethernet interface
-               with a static MAC address.
+ Parameters  : none
+ Returns     : array of hashes
+ Description : Assembles a data structure containing the ethernet adapter
+ configuration for the VM.
 
 =cut
 
-sub get_static_ethernet_vmx_definition {
+sub get_vm_ethernet_adapter_configuration {
 	my $self = shift;
 	if (ref($self) !~ /vmware/i) {
 		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
 		return;
 	}
 	
-	my $interface_index = shift;
-	if (!defined($interface_index)) {
-		notify($ERRORS{'WARNING'}, 0, "interface index argument was not supplied");
-		return;
-	}
+	my $image_project = $self->data->get_image_project();
+	my $virtual_switch_0 = $self->data->get_vmhost_profile_virtualswitch0(0);
+	my $virtual_switch_1 = $self->data->get_vmhost_profile_virtualswitch1(0);
+	my $virtual_switch_2 = $self->data->get_vmhost_profile_virtualswitch2(0);
+	my $virtual_switch_3 = $self->data->get_vmhost_profile_virtualswitch3(0);
+	my $vm_ethernet_adapter_type = $self->get_vm_ethernet_adapter_type() || return;
 	
-	my $network_name = shift;
-	if (!defined($network_name)) {
-		notify($ERRORS{'WARNING'}, 0, "network name argument was not supplied");
+	# Get a list of all the network names configured on the VMware host
+	my @network_names = $self->api->get_network_names();
+	if (!@network_names) {
+		notify($ERRORS{'WARNING'}, 0, "unable to assemble ethernet adapter configuration, network names could not be retrieved from the VM host");
 		return;
 	}
 	
-	my $mac_address = shift;
-	if (!defined($mac_address)) {
-		notify($ERRORS{'WARNING'}, 0, "MAC address argument was not supplied");
-		return;
+	# Make sure all network names configured in the VM host profile actually exist on the host
+	for my $network_name ($virtual_switch_0, $virtual_switch_1, $virtual_switch_2, $virtual_switch_3) {
+		if ($network_name && !grep(/^$network_name$/, @network_names)) {
+			notify($ERRORS{'WARNING'}, 0, "unable to assemble ethernet adapter configuration, network name '$network_name' configured in the VM profile does not match any network names on the VM host:\n" . join("\n", sort @network_names));
+			return;
+		}
 	}
 	
-	my $vm_ethernet_adapter_type  = $self->get_vm_ethernet_adapter_type() || return;
-	
-	my %vmx_parameters = (
-		"ethernet$interface_index.present" => "TRUE",
-		"ethernet$interface_index.connectionType" => "custom",
-		"ethernet$interface_index.addressType" => "static",
-		"ethernet$interface_index.address" => "$mac_address",
-		"ethernet$interface_index.virtualDev" => "$vm_ethernet_adapter_type",
-		"ethernet$interface_index.networkName" => "$network_name",
-	);
+	my @adapters;
 	
-	return \%vmx_parameters;
-}
-
-#/////////////////////////////////////////////////////////////////////////////
-
-=head2 get_generated_ethernet_vmx_definition
-
- Parameters  : $interface_index, $network_name
- Returns     : hash reference
- Description : Generates a hash containing keys and values which correlate to
-               the vmx file parameters necessary to define an ethernet interface
-               with an auto-generated MAC address.
-
-=cut
-
-sub get_generated_ethernet_vmx_definition {
-	my $self = shift;
-	if (ref($self) !~ /vmware/i) {
-		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
-		return;
+	if ($virtual_switch_0) {
+		my $adapter_configuration = {
+			network_name => $virtual_switch_0,
+			adapter_type => $vm_ethernet_adapter_type,
+		};
+		if ($self->data->get_vmhost_profile_eth0generated(0)) {
+			$adapter_configuration->{address_type} = 'Generated',
+		}
+		else {
+			$adapter_configuration->{address_type} = 'Manual';
+			$adapter_configuration->{address} = $self->data->get_computer_eth0_mac_address();
+		}
+		push @adapters, $adapter_configuration;
 	}
 	
-	my $interface_index = shift;
-	if (!defined($interface_index)) {
-		notify($ERRORS{'WARNING'}, 0, "interface index argument was not supplied");
-		return;
+	if ($virtual_switch_1) {
+		my $adapter_configuration = {
+			network_name => $virtual_switch_1,
+			adapter_type => $vm_ethernet_adapter_type,
+		};
+		if ($self->data->get_vmhost_profile_eth1generated(0)) {
+			$adapter_configuration->{address_type} = 'Generated',
+		}
+		else {
+			$adapter_configuration->{address_type} = 'Manual';
+			$adapter_configuration->{address} = $self->data->get_computer_eth1_mac_address();
+		}
+		push @adapters, $adapter_configuration;
 	}
 	
-	my $network_name = shift;
-	if (!defined($interface_index)) {
-		notify($ERRORS{'WARNING'}, 0, "network name argument was not supplied");
-		return;
-	}
+	my @additional_network_names;
+	push @additional_network_names, $virtual_switch_2 if $virtual_switch_2;
+	push @additional_network_names, $virtual_switch_3 if $virtual_switch_3;
 	
-	my $vm_ethernet_adapter_type  = $self->get_vm_ethernet_adapter_type() || return;
+	# Add additional Ethernet interfaces if the image project name is not vcl
+	if ($image_project !~ /^vcl$/i) {
+		notify($ERRORS{'DEBUG'}, 0, "image project is: $image_project, checking if additional network adapters should be configured");
+		
+		# Check each network name
+		for my $network_name (@network_names) {
+			if ($network_name =~ /$image_project/i || $image_project =~ /$network_name/i) {
+				notify($ERRORS{'DEBUG'}, 0, "network name ($network_name) and image project name ($image_project) intersect, adding network interface to VM for network $network_name");
+				push @additional_network_names, $network_name;
+			}
+			else {
+				notify($ERRORS{'DEBUG'}, 0, "network name ($network_name) and image project name ($image_project) do not intersect, network interface will not be added to VM for network $network_name");
+			}
+		}
+	}
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "image project is: $image_project, no additional network adapters will be configured");
+	}
 	
-	my %vmx_parameters = (
-		"ethernet$interface_index.addressType" => "generated",
-		"ethernet$interface_index.present" => "TRUE",
-		"ethernet$interface_index.virtualDev" => "$vm_ethernet_adapter_type",
-		"ethernet$interface_index.networkName" => "$network_name",
-	);
+	for my $network_name (@additional_network_names) {
+		my $adapter_configuration = {
+			network_name => $network_name,
+			adapter_type => $vm_ethernet_adapter_type,
+			address_type => 'Generated',
+		};
+		push @adapters, $adapter_configuration;
+	}
 	
-	return \%vmx_parameters;
+	notify($ERRORS{'DEBUG'}, 0, "VM ethernet adapter configuration:\n" . format_data(\@adapters));
+	return @adapters;
 }
 
 #/////////////////////////////////////////////////////////////////////////////

Modified: vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm?rev=1393075&r1=1393074&r2=1393075&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/Provisioning/VMware/vSphere_SDK.pm Tue Oct  2 18:15:59 2012
@@ -1364,8 +1364,8 @@ sub get_virtual_disk_hardware_version {
  Returns     : string
  Description : Returns the full VMware product name installed on the VM host.
                Examples:
-					VMware Server 2.0.2 build-203138
-					VMware ESXi 4.0.0 build-208167
+               VMware Server 2.0.2 build-203138
+               VMware ESXi 4.0.0 build-208167
 
 =cut
 
@@ -1431,35 +1431,6 @@ sub get_vmware_product_version {
 
 #/////////////////////////////////////////////////////////////////////////////
 
-=head2 get_network_names
-
- Parameters  : none
- Returns     : array
- Description : Retrieves the network names configured on the VM host.
-
-=cut
-
-sub get_network_names {
-	my $self = shift;
-	if (ref($self) !~ /VCL::Module/i) {
-		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
-		return;
-	}
-	
-	my $datacenter_view = $self->_get_datacenter_view();
-	
-	# Retrieve the network info, check if each network is accessible
-	my @network_names;
-	for my $network (@{Vim::get_views(mo_ref_array => $datacenter_view->network)}) {
-		push @network_names, $network->name;
-	}
-	
-	notify($ERRORS{'DEBUG'}, 0, "retrieved network names:\n" . join("\n", @network_names));
-	return @network_names;
-}
-
-#/////////////////////////////////////////////////////////////////////////////
-
 =head2 is_restricted
 
  Parameters  : none
@@ -3470,6 +3441,321 @@ sub _is_vcenter {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 _mo_ref_to_string
+
+ Parameters  : $mo_ref
+ Returns     : string
+ Description : Formats the information in a managed object reference. Filters
+               out a lot of information which is contained in every mo_ref such
+               as the vim key.
+
+=cut
+
+sub _mo_ref_to_string {
+	my $mo_ref = shift;
+	
+	# Check the argument, either a mo_ref or view can be supplied
+	if (!ref($mo_ref)) {
+		notify($ERRORS{'WARNING'}, 0, "argument is not a reference: $mo_ref");
+		return;
+	}
+	elsif (ref($mo_ref) eq 'ManagedObjectReference') {
+		$mo_ref = Vim::get_view(mo_ref => $mo_ref)
+	}
+	
+	my $return_string = '';
+	for my $key (sort keys %$mo_ref) {
+		# Ignore keys which only contain general info
+		next if $key =~ /(vim|alarmActionsEnabled|permission|recentTask|triggeredAlarmState|declaredAlarmState|tag|overallStatus|availableField|configIssue|configStatus|customValue|disabledMethod)/;
+		
+		my $value = $mo_ref->{$key};
+		if (!defined($value)) {
+			$return_string .= "KEY '$key': <undefined>\n";
+		}
+		elsif (my $type = ref($value)) {
+			$return_string .=  "KEY '$key' <$type>:\n";
+			$return_string .= format_data($value) . "\n";
+		}
+		else {
+			$return_string .= "KEY '$key' <scalar>: '$value'\n";
+		}
+	}
+	return $return_string;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_host_network_info
+
+ Parameters  : none
+ Returns     : hash reference
+ Description : Retrieves information about the networks defined on the VM host.
+               A hash reference is returned. The hash keys are the network
+               names. The data contained in the hash differs based on whether
+               its a dvPortgroup or regular network.
+               {
+                  "dv-net-vlan2148" => {
+                     "portgroupKey" => "dvportgroup-125",
+                     "switchUuid" => "4d 12 08 50 01 69 a8 6b-01 9c 43 69 92 7e ad f1",
+                     "type" => "DistributedVirtualPortgroup",
+                     "value" => "dvportgroup-125"
+                  },
+                  "regular-net-public" => {
+                     "network" => bless( {
+                       "type" => "Network",
+                       "value" => "network-159"
+                     }, 'ManagedObjectReference' ),
+                     "type" => "Network",
+                     "value" => "network-159"
+                  }
+               }
+
+=cut
+
+sub get_host_network_info {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	return $self->{vm_network_info} if $self->{vm_network_info};
+	
+	my $vmhost_hostname = $self->data->get_vmhost_hostname();
+	
+	# Override the die handler
+	local $SIG{__DIE__} = sub{};
+	
+	# Get the datacenter view
+	my $datacenter_view = $self->_get_datacenter_view();
+	
+	# Get the NetworkFolder view
+	my $network_folder_view = Vim::get_view(mo_ref => $datacenter_view->networkFolder);
+	if (!$network_folder_view) {
+		notify($ERRORS{'WARNING'}, 0, "failed to retrieve networkFolder view from $vmhost_hostname");
+		return;
+	}
+	
+	my $child_array = $network_folder_view->{childEntity};
+	if (!$child_array) {
+		notify($ERRORS{'WARNING'}, 0, "networkFolder does not contain a 'childEntity' key:\n" . _mo_ref_to_string($network_folder_view));
+		return;
+	}
+	
+	my $host_network_info = {};
+	CHILD: for my $child (@$child_array) {
+		my $type = $child->{type};
+		my $value = $child->{value};
+		
+		# Get a view for the child entity
+		my $child_view = Vim::get_view(mo_ref => $child);
+		if (!$child_view) {
+			notify($ERRORS{'WARNING'}, 0, "failed to retrieve view for networkFolder child:\n" . format_data($child));
+			next CHILD;
+		}
+		
+		my $name = $child_view->{name};
+		if (!$name) {
+			notify($ERRORS{'WARNING'}, 0, "networkFolder child does not have a 'name' key:\n" . format_data($child_view));
+			next CHILD;
+		}
+		
+		$host_network_info->{$name}{type} = $type;
+		$host_network_info->{$name}{value} = $value;
+		
+		if ($type eq 'Network') {
+			$host_network_info->{$name}{network} = $child;
+		}
+		elsif ($type eq 'DistributedVirtualPortgroup') {
+			# Save the portgroup key to the hash, example: dvportgroup-361
+			$host_network_info->{$name}{portgroupKey} = $child_view->{key};
+			
+			# Each portgroup belongs to a distributed virtual switch
+			# The UUID of the switch is required when adding the portgroup to a VM
+			# Get the dvSwitch view
+			my $dv_switch_view = Vim::get_view(mo_ref => $child_view->config->distributedVirtualSwitch);
+			if (!$dv_switch_view) {
+				notify($ERRORS{'WARNING'}, 0, "failed to retrieve DistributedVirtualSwitch view for portgroup $name");
+				next CHILD;
+			}
+			
+			my $dv_switch_uuid = $dv_switch_view->{uuid};
+			$host_network_info->{$name}{switchUuid} = $dv_switch_uuid;
+		}
+		
+	}
+	
+	$self->{host_network_info} = $host_network_info;
+	notify($ERRORS{'DEBUG'}, 0, "retrieved network info from $vmhost_hostname:\n" . format_data($host_network_info));
+	return $host_network_info;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_network_names
+
+ Parameters  : none
+ Returns     : array
+ Description : Retrieves the network names configured on the VM host.
+
+=cut
+
+sub get_network_names {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my $host_network_info = $self->get_host_network_info();
+	if ($host_network_info) {
+		return sort keys %$host_network_info;
+	}
+	else {
+		return;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 add_ethernet_adapter
+
+ Parameters  : $vmx_path, $adapter_specification
+ Returns     : boolean
+ Description : Adds an ethernet adapter to the VM based on the adapter
+               specification argument.
+
+=cut
+
+sub add_ethernet_adapter {
+	my $self = shift;
+	if (ref($self) !~ /VCL::Module/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	# Get the vmx path argument and convert it to a datastore path
+	my $vmx_path = $self->_get_datastore_path(shift) || return;
+	
+	# Get the adapter spec argument and make sure required values are included
+	my $adapter_specification = shift;
+	if (!$adapter_specification) {
+		notify($ERRORS{'WARNING'}, 0, "adapter specification argument was not supplied");
+		return;
+	}
+	my $adapter_type = $adapter_specification->{adapter_type};
+	my $network_name = $adapter_specification->{network_name};
+	my $address_type = $adapter_specification->{address_type};
+	my $address = $adapter_specification->{address} || '';
+	if (!$adapter_type) {
+		notify($ERRORS{'WARNING'}, 0, "'adapter_type' is missing from adapter specification:\n" . format_data($adapter_specification));
+		return;
+	}
+	if (!$network_name) {
+		notify($ERRORS{'WARNING'}, 0, "'network_name' is missing from adapter specification:\n" . format_data($adapter_specification));
+		return;
+	}
+	if (!$address_type) {
+		notify($ERRORS{'WARNING'}, 0, "'address_type' is missing from adapter specification:\n" . format_data($adapter_specification));
+		return;
+	}
+	
+	# Get the VM host's network info
+	my $host_network_info = $self->get_host_network_info();
+	if (!$host_network_info) {
+		notify($ERRORS{'WARNING'}, 0, "unable to add ethernet adapter, VM host network info could not be retrieved");
+		return;
+	}
+	
+	# Make sure the network name provided in the adapter spec argument was found on the VM host
+	my $adapter_network_info = $host_network_info->{$network_name};
+	if (!$adapter_network_info) {
+		notify($ERRORS{'WARNING'}, 0, "unable to add ethernet adapter, VM host network info does not contain a network named '$network_name'");
+		return;
+	}
+	my $adapter_network_type = $adapter_network_info->{type};
+	
+	# Assemble the backing info
+	my $backing;
+	if ($adapter_network_type eq 'DistributedVirtualPortgroup') {
+		my $portgroup_key = $adapter_network_info->{portgroupKey};
+		my $switch_uuid = $adapter_network_info->{switchUuid};
+		
+		$backing = VirtualEthernetCardDistributedVirtualPortBackingInfo->new(
+			port => DistributedVirtualSwitchPortConnection->new(
+				portgroupKey => $portgroup_key,
+				switchUuid => $switch_uuid,
+			),
+		);
+	}
+	else {
+		my $network = $adapter_network_info->{network};
+		$backing = VirtualEthernetCardNetworkBackingInfo->new(
+			deviceName => $network_name,
+			network => $network,
+		);
+	}
+	
+	# Assemble the ethernet device
+	my $ethernet_device;
+	if ($adapter_type =~ /e1000/i) {
+		$ethernet_device = VirtualE1000->new(
+			key => '',
+			backing => $backing,
+			addressType => $address_type,
+			macAddress => $address,
+		);
+	}
+	elsif ($adapter_type =~ /vmxnet/i) {
+		$ethernet_device = VirtualVmxnet3->new(
+			key => '',
+			backing => $backing,
+			addressType => $address_type,
+			macAddress => $address,
+		);
+	}
+	else {
+		$ethernet_device = VirtualPCNet32->new(
+			key => '',
+			backing => $backing,
+			addressType => $address_type,
+			macAddress => $address,
+		);
+	}
+	
+	# Assemble the VM config spec
+	my $vm_config_spec = VirtualMachineConfigSpec->new(
+		deviceChange => [
+			VirtualDeviceConfigSpec->new(
+				operation =>	VirtualDeviceConfigSpecOperation->new('add'),
+				device => $ethernet_device,
+			),
+		],
+	);
+	
+	# Override the die handler
+	local $SIG{__DIE__} = sub{};
+	
+	my $vm = $self->_get_vm_view($vmx_path) || return;
+	
+	notify($ERRORS{'DEBUG'}, 0, "attempting to add ethernet adapter to VM: $vmx_path:\n" . format_data($adapter_specification));
+	eval {
+		$vm->ReconfigVM(
+			spec => $vm_config_spec,
+		);
+	};
+	if ($@) {
+		notify($ERRORS{'WARNING'}, 0, "failed to add ethernet adapter to VM: $vmx_path, adapter specification:\n" . format_data($adapter_specification) . "\nerror:\n$@");
+		return;
+	}
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "added ethernet adapter to VM: $vmx_path:\n" . format_data($adapter_specification));
+		return 1;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 1;
 __END__