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 2017/03/07 20:28:49 UTC

svn commit: r1785881 - in /vcl/trunk/managementnode/lib/VCL: DataStructure.pm Module/OS/Windows.pm Module/Provisioning.pm utils.pm

Author: arkurth
Date: Tue Mar  7 20:28:49 2017
New Revision: 1785881

URL: http://svn.apache.org/viewvc?rev=1785881&view=rev
Log:
VCL-867
Made another attempt to update DataStructure.pm::_automethod to not display warnings when get_image_domain* is called and the data is not defined. The previous change wasn't catching all calls.

Added utils.pm::get_active_directory_domain_credentials. This is needed to determine the domain username and password in order to remove a computer from a domain after the preloaded image was reconfigured to not join a domain. The normal $self->data->get_image_domain* information is not available in this case.

Updated Provisioning.pm to check if OS module implements a node_status_os_check subroutine and call it if it does.

Added Windows.pm::ad_check. It contains all logic to determine if an image is configured for AD, if a computer is already joined to a domain, and calls the necessary subroutines to either unjoin the domain, join the domain, or both if the computer is joined to a different domain or located in the wrong OU. Removed similar logic from ad_join.

Replaced call from ad_join to ad_check in Windows.pm::post_load.

Added Windows.pm::node_status_os_check which simply returns the value of ad_check. Naming reason: ad_check is called elsewhere and Windows-specific. node_status_os_check is more general and may perform additional functions in the future.

Reworked Windows.pm::ad_unjoin to call wmic.exe rather than building Powershell script. Need to test this and determine which is better.

Updated Windows.pm::ad_search, ad_delete_computer, and ad_search_computer to accept domain DNS name argument. This is used when a computer is joined to a domain but shouldn't be (image was reconfigured after computer was loaded).


Modified:
    vcl/trunk/managementnode/lib/VCL/DataStructure.pm
    vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm
    vcl/trunk/managementnode/lib/VCL/Module/Provisioning.pm
    vcl/trunk/managementnode/lib/VCL/utils.pm

Modified: vcl/trunk/managementnode/lib/VCL/DataStructure.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/DataStructure.pm?rev=1785881&r1=1785880&r2=1785881&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/DataStructure.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/DataStructure.pm Tue Mar  7 20:28:49 2017
@@ -900,7 +900,7 @@ sub _automethod : Automethod {
 			$return_value = eval $hash_path;
 		}
 		elsif (!$key_defined) {
-			if ($show_warnings && $hash_path !~ /(serverrequest|image_domain)/) {
+			if ($show_warnings && $hash_path !~ /(serverrequest|domain)/) {
 				notify($ERRORS{'WARNING'}, 0, "corresponding data has not been initialized for $method_name: $hash_path", $self->request_data);
 			}
 			return sub { };

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm?rev=1785881&r1=1785880&r2=1785881&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Windows.pm Tue Mar  7 20:28:49 2017
@@ -947,7 +947,7 @@ sub post_load {
 =cut
 
 	if ($imagedomain_domaindnsname) {
-		if (!$self->ad_join()) {
+		if (!$self->ad_check()) {
 			notify($ERRORS{'WARNING'}, 0, "failed to join Active Directory domain");
 			return 0;
 		}
@@ -13502,6 +13502,149 @@ sub ad_join_prepare {
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 node_status_os_check
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Called from provisioning module's node_status subroutine. This
+               checks if the loaded computer's Active Directory configuration is
+               correct if image is configured to join an AD domain.
+
+=cut
+
+sub node_status_os_check {
+	my $self = shift;	
+	if (ref($self) !~ /windows/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	# Check if computer AD configuration is correct if image is configured for AD
+	# Returning false indicates AD configuration could not be corrected and calling subroutine should reload the computer
+	return $self->ad_check();
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 ad_check
+
+ Parameters  : none
+ Returns     : boolean
+ Description : Checks if the computer is joined to an Active Directory domain
+               and located in the correct OU.
+
+=cut
+
+sub ad_check {
+	my $self = shift;	
+	if (ref($self) !~ /windows/i) {
+		notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+		return;
+	}
+	
+	my $computer_name	= $self->data->get_computer_short_name();
+	my $image_domain_dns_name = $self->data->get_image_domain_dns_name();
+	
+	# Check if the computer is joined to any AD domain
+	my $computer_current_domain_name = $self->ad_get_current_domain();
+	
+	if (!$image_domain_dns_name) {
+		# Computer should NOT be joined to an AD domain
+		if (!$computer_current_domain_name) {
+			notify($ERRORS{'OK'}, 0, "image is not configured for Active Directory and $computer_name is not joined to a domain, returning 1");
+			return 1;
+		}
+		
+		# Computer incorrectly joined to an AD domain, attempt to unjoin the domain
+		notify($ERRORS{'OK'}, 0, "$computer_name is joined to the $computer_current_domain_name domain but the image is not configured for Active Directory, attempting to unjoin the domain");
+		if ($self->ad_unjoin()) {
+			notify($ERRORS{'OK'}, 0, "image is not configured for Active Directory, unjoined $computer_name from $computer_current_domain_name domain, returning 1");
+			return 1;
+		}
+		else {
+			notify($ERRORS{'WARNING'}, 0, "image is not configured for Active Directory, failed to unjoin $computer_name from $computer_current_domain_name domain, returning undefined");
+			return;
+		}
+	}
+	
+	# Computer should be joined to AD domain
+	if (!$computer_current_domain_name) {
+		# Computer is not joined to an AD domain, return the result of attempting to join
+		notify($ERRORS{'OK'}, 0, "image is configured to join the $image_domain_dns_name domain, $computer_name is not joined to a domain, attempting to join the domain");
+		return $self->ad_join();
+	}
+	
+	
+	# Computer is joined to an AD domain, check if it's in the correct domain
+	if ($computer_current_domain_name ne $image_domain_dns_name) {
+		# Computer is not joined to the correct domain, attempt to unjoin and then rejoin
+		notify($ERRORS{'DEBUG'}, 0, "$computer_name is joined to the $computer_current_domain_name domain, image is configured to join the $image_domain_dns_name, attempting to unjoin then join the correct domain");
+		if (!$self->ad_unjoin()) {
+			notify($ERRORS{'WARNING'}, 0, "image is configured to join the $image_domain_dns_name, failed to unjoin $computer_name from the $computer_current_domain_name domain, returning undefined");
+			return;
+		}
+		elsif (!$self->ad_join()) {
+			notify($ERRORS{'WARNING'}, 0, "image is configured to join the $image_domain_dns_name, unjoined $computer_name from the incorrect $computer_current_domain_name domain but failed to rejoin the correct $image_domain_dns_name domain, returning undefined");
+			return;
+		}
+		else {
+			notify($ERRORS{'OK'}, 0, "unjoined $computer_name from the incorrect $computer_current_domain_name and rejoined to the correct domain: $image_domain_dns_name, returning 1");
+			return 1;
+		}
+	}
+	
+	# Computer is joined to the correct AD domain, make sure computer object is in the correct OU
+	
+	# Determine the OU configured for the image
+	my $image_ou_dn = $self->get_ad_computer_ou_dn();
+	if (!$image_ou_dn) {
+		notify($ERRORS{'WARNING'}, 0, "image is configured to join the $image_domain_dns_name domain but proper computer OU DN could not be determined, returning undefined");
+		return;
+	}
+	
+	# Get the computer's current OU
+	my $computer_current_dn = $self->ad_search_computer();
+	if (!$computer_current_dn) {
+		notify($ERRORS{'WARNING'}, 0, "$computer_name is joined to the correct $computer_current_domain_name domain but current OU could not be determined, assuming computer object is in the correct OU, returning 1");
+		return 1;
+	}
+	
+	# Extract the OU DN from the DN of the computer object
+	my ($computer_current_ou_dn) = $computer_current_dn =~ /^[^,]+,(OU=.+)$/;
+	if (!$computer_current_ou_dn) {
+		notify($ERRORS{'WARNING'}, 0, "$computer_name is joined to the correct $computer_current_domain_name domain but current OU DN could not be parsed from current computer object DN: '$computer_current_dn', assuming computer object is in the correct OU, returning 1");
+		return 1;
+	}
+	
+	if ($computer_current_ou_dn =~ /^$image_ou_dn$/i) {
+		notify($ERRORS{'OK'}, 0, "$computer_name is joined to the correct domain and in the correct OU, returning 1:\n" .
+			"current domain: $computer_current_domain_name\n" .
+			"computer object OU: $computer_current_ou_dn"
+		);
+		return 1;
+	}
+	
+	# Computer is in the wrong OU
+	notify($ERRORS{'OK'}, 0, "$computer_name is joined to the correct $computer_current_domain_name domain but located in the wrong OU, attempting to unjoin then rejoin the domain in the correct OU:\n" .
+		"OU configured for image    : $image_ou_dn\n" .
+		"current computer object OU : $computer_current_ou_dn"
+	);
+	if (!$self->ad_unjoin()) {
+		notify($ERRORS{'WARNING'}, 0, "failed to unjoin $computer_name from the $computer_current_domain_name domain in order to rejoin in the correct OU, returning undefined");
+		return;
+	}
+	elsif (!$self->ad_join()) {
+		notify($ERRORS{'WARNING'}, 0, "failed to rejoin $computer_name to the correct OU in the $image_domain_dns_name domain: '$image_ou_dn', returning undefined");
+		return;
+	}
+	else {
+		notify($ERRORS{'OK'}, 0, "rejoined $computer_name to the correct OU in the $image_domain_dns_name domain: '$image_ou_dn', returning 1");
+		return 1;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 =head2 ad_join
 
  Parameters  : none
@@ -13545,43 +13688,6 @@ sub ad_join {
 		return;
 	}
 	
-	# Make sure the computer is not already a member of a domain
-	# TODO: add logic to check if computer belongs to the correct domain in the correct OU
-	# If not, remove and rejoin
-	my $current_domain_name = $self->ad_get_current_domain();
-	if ($current_domain_name) {
-		if ($current_domain_name ne $domain_dns_name) {
-			notify($ERRORS{'WARNING'}, 0, "unable to add $computer_name to $domain_dns_name domain, it is already a member of a different domain: $current_domain_name");
-			return;
-		}
-		
-		# Search for the computer object in the domain
-		my $current_computer_dn = $self->ad_search_computer();
-		if (!$current_computer_dn) {
-			notify($ERRORS{'WARNING'}, 0, "unable to add $computer_name to $domain_dns_name domain, it appears to already a member of the domain but the current DN could not be determined");
-			return;
-		}
-		
-		# Extract the OU DN from the computer DN
-		my ($current_computer_ou_dn) = $current_computer_dn =~ /^[^,]+,(OU=.+)$/;
-		if (!$current_computer_ou_dn) {
-			notify($ERRORS{'WARNING'}, 0, "unable to add $computer_name to $domain_dns_name domain, failed to parse OU DN from current computer DN: $current_computer_dn");
-			return;
-		}
-		
-		if ($current_computer_ou_dn =~ /^$computer_ou_dn$/i) {
-			notify($ERRORS{'OK'}, 0, "$computer_name is already joined to $domain_dns_name domain and in the correct OU: $current_computer_ou_dn");
-			return 1;
-		}
-		else {
-			notify($ERRORS{'WARNING'}, 0, "$computer_name is already joined to $domain_dns_name domain but in the a different OU:\n" .
-				"correct OU: $computer_ou_dn\n" .
-				"current OU: $current_computer_ou_dn"
-			);
-			$self->ad_unjoin() || return;
-		}
-	}
-	
 	# Figure out/fix the computer OU and assemble optional section to add to PowerShell command
 	my $domain_computer_command_section = '';
 	if ($computer_ou_dn) {
@@ -13594,7 +13700,7 @@ sub ad_join {
 		"domain password: $domain_password\n" .
 		"domain computer OU DN: " . ($computer_ou_dn ? $computer_ou_dn : '<not configured>')
 	);
-
+	
 	# Perform preparation tasks
 	$self->ad_join_prepare() || return;
 	
@@ -13719,69 +13825,124 @@ sub ad_unjoin {
 	
 	my $computer_name	= $self->data->get_computer_short_name();
 	my $image_name	= $self->data->get_image_name();
+	my $system32_path = $self->get_system32_path() || return;
 	
-	my $domain_dns_name = $self->data->get_image_domain_dns_name();
-	my $domain_username = $self->data->get_image_domain_username();
-	my $domain_password = $self->data->get_image_domain_password();
+	my $computer_current_domain = $self->ad_get_current_domain();
+	if (!$computer_current_domain) {
+		notify($ERRORS{'DEBUG'}, 0, "$computer_name does not need to be removed from AD because it is not currently joined to a domain");
+		return 1;
+	}
 	
-	if (!defined($domain_dns_name)) {
-		notify($ERRORS{'WARNING'}, 0, "unable to remove $computer_name from AD, image $image_name is not assigned to a domain");
+	# Expected output:
+	# Executing (\\<COMPUTERNAME>\ROOT\CIMV2:Win32_ComputerSystem.Name="<COMPUTERNAME>")->UnJoinDomainOrWorkgroup()
+	# Method execution successful.s
+	# Out Parameters:
+	# instance of __PARAMETERS
+	# {
+	#       ReturnValue = 0;
+	# };
+	
+	# Assemble the unjoin command
+	my $unjoin_command = "echo | cmd.exe /c \"$system32_path/Wbem/wmic.exe /INTERACTIVE:OFF COMPUTERSYSTEM WHERE Name=\\\"%COMPUTERNAME%\\\" Call UnJoinDomainOrWorkgroup FUnjoinOptions=0\"";
+	notify($ERRORS{'DEBUG'}, 0, "attempting to unjoin $computer_name from $computer_current_domain Active Directory domain");
+	my ($unjoin_exit_status, $unjoin_output) = $self->execute($unjoin_command);
+	if (!defined($unjoin_output)) {
+		notify($ERRORS{'DEBUG'}, 0, "failed to execute command to unjoin the $computer_current_domain Active Directory domain");
 		return;
 	}
-	elsif (!defined($domain_username)) {
-		notify($ERRORS{'WARNING'}, 0, "unable to remove $computer_name from AD, user name is not configured for $domain_dns_name domain");
+	elsif (grep(/ERROR/i, @$unjoin_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to unjoin $computer_current_domain Active Directory domain, output:\n" . join("\n", @$unjoin_output));
 		return;
 	}
-	elsif (!defined($domain_password)) {
-		notify($ERRORS{'WARNING'}, 0, "unable to remove $computer_name from AD, password is not configured for $domain_dns_name domain");
+	elsif (grep(/ReturnValue\s+=\s+[1-9]/i, @$unjoin_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to unjoin $computer_current_domain Active Directory domain, return value is not 0, output:\n" . join("\n", @$unjoin_output));
 		return;
 	}
-	
-	if (!$self->ad_get_current_domain()) {
-		notify($ERRORS{'DEBUG'}, 0, "$computer_name does not need to be removed from AD because it is not currently joined to a domain");
-		return 1;
+	elsif (grep(/Method execution successful/i, @$unjoin_output)) {
+		notify($ERRORS{'OK'}, 0, "unjoined $computer_current_domain Active Directory domain, output:\n" . join("\n", @$unjoin_output));
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "unexpected output unjoining $computer_current_domain Active Directory domain, output:\n" . join("\n", @$unjoin_output));
 	}
 	
-	notify($ERRORS{'DEBUG'}, 0, "attempting to unjoin $computer_name from AD");
 	
-	# Assemble the PowerShell script
-	my $ad_powershell_script = <<EOF;
-\$Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size(5000, 500)
-\$ps_credential = New-Object System.Management.Automation.PsCredential("$domain_dns_name\\$domain_username", (ConvertTo-SecureString "$domain_password" -AsPlainText -Force))
-try {
-   Add-Computer -WorkgroupName VCL -Credential \$ps_credential -ErrorAction Stop
-}
-catch {
-   Write-Host "ERROR: failed to add computer to workgroup, error: \$(\$_.Exception.Message)"
-   exit 1
-}
-EOF
-
-	my ($exit_status, $output) = $self->run_powershell_as_script($ad_powershell_script, 1, 1);
-	if (!defined($output)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to execute PowerShell script to remove $computer_name from Active Directory domain");
+	# Assemble the join workgroup command
+	my $join_workgroup_command = "echo | cmd.exe /c \"$system32_path/Wbem/wmic.exe /INTERACTIVE:OFF COMPUTERSYSTEM WHERE Name=\\\"%COMPUTERNAME%\\\" Call JoinDomainOrWorkgroup name=VCL\"";
+	my ($join_workgroup_exit_status, $join_workgroup_output) = $self->execute($join_workgroup_command);
+	if (!defined($join_workgroup_output)) {
+		notify($ERRORS{'DEBUG'}, 0, "failed to execute command to join workgroup");
 		return;
 	}
-	elsif (grep(/ERROR/, @$output)) {
-		# Computer object was already or deleted or can't be found for some reason:
-		#   This command cannot be executed on target computer('') due to following error: No mapping between account names and security IDs was done.
-		if (grep(/No mapping between account names/, @$output)) {
-			notify($ERRORS{'WARNING'}, 0, "failed to remove $computer_name from Active Directory domain, the computer object may have been deleted from the domain, output:\n" . join("\n", @$output));
-		}
-		else {
-			notify($ERRORS{'WARNING'}, 0, "failed to remove $computer_name from Active Directory domain, output:\n" . join("\n", @$output));
-		}
-		return 0;
+	elsif (grep(/ERROR/i, @$join_workgroup_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to join workgroup, output:\n" . join("\n", @$join_workgroup_output));
 	}
-	
-	notify($ERRORS{'OK'}, 0, "removed $computer_name from Active Directory domain, output:\n" . join("\n", @$output));
-	
+	elsif (grep(/ReturnValue\s+=\s+[1-9]/i, @$join_workgroup_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to join workgroup, return value is not 0, output:\n" . join("\n", @$join_workgroup_output));
+	}
+	elsif (grep(/Method execution successful/i, @$join_workgroup_output)) {
+		notify($ERRORS{'OK'}, 0, "joined workgroup, output:\n" . join("\n", @$join_workgroup_output));
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "unexpected output joining workgroup, output:\n" . join("\n", @$join_workgroup_output));
+	}
+
 	if (!$self->reboot(300, 3, 1)) {
-		notify($ERRORS{'WARNING'}, 0, "failed to remove $computer_name from Active Directory domain, failed to reboot computer after unjoining domain");
+		notify($ERRORS{'WARNING'}, 0, "failed to unjoin $computer_name from Active Directory domain, failed to reboot computer after unjoining domain");
+		return;
+	}
+	
+	# Verify the computer no longer is joined to a domain
+	my $new_computer_current_domain = $self->ad_get_current_domain();
+	if ($new_computer_current_domain) {
+		notify($ERRORS{'WARNING'}, 0, "failed to unjoin $computer_name from Active Directory domain, it appears to still be a member of the $new_computer_current_domain domain");
 		return;
 	}
 	
-	$self->ad_delete_computer($computer_name);
+	#if (!defined($domain_dns_name)) {
+	#	notify($ERRORS{'WARNING'}, 0, "unable to remove $computer_name from AD, image $image_name is not assigned to a domain");
+	#	return;
+	#}
+	#elsif (!defined($domain_username)) {
+	#	notify($ERRORS{'WARNING'}, 0, "unable to remove $computer_name from AD, user name is not configured for $domain_dns_name domain");
+	#	return;
+	#}
+	#elsif (!defined($domain_password)) {
+	#	notify($ERRORS{'WARNING'}, 0, "unable to remove $computer_name from AD, password is not configured for $domain_dns_name domain");
+	#	return;
+	#}
+	#	# Assemble the PowerShell script
+	#	my $ad_powershell_script = <<EOF;
+	#\$Host.UI.RawUI.BufferSize = New-Object Management.Automation.Host.Size(5000, 500)
+	#\$ps_credential = New-Object System.Management.Automation.PsCredential("$domain_dns_name\\$domain_username", (ConvertTo-SecureString "$domain_password" -AsPlainText -Force))
+	#try {
+	#   Add-Computer -WorkgroupName VCL -Credential \$ps_credential -ErrorAction Stop
+	#}
+	#catch {
+	#   Write-Host "ERROR: failed to add computer to workgroup, error: \$(\$_.Exception.Message)"
+	#   exit 1
+	#}
+	#EOF
+	#
+	#	my ($exit_status, $output) = $self->run_powershell_as_script($ad_powershell_script, 1, 1);
+	#	if (!defined($output)) {
+	#		notify($ERRORS{'WARNING'}, 0, "failed to execute PowerShell script to remove $computer_name from Active Directory domain");
+	#		return;
+	#	}
+	#	elsif (grep(/ERROR/, @$output)) {
+	#		# Computer object was already or deleted or can't be found for some reason:
+	#		#   This command cannot be executed on target computer('') due to following error: No mapping between account names and security IDs was done.
+	#		if (grep(/No mapping between account names/, @$output)) {
+	#			notify($ERRORS{'WARNING'}, 0, "failed to remove $computer_name from Active Directory domain, the computer object may have been deleted from the domain, output:\n" . join("\n", @$output));
+	#		}
+	#		else {
+	#			notify($ERRORS{'WARNING'}, 0, "failed to remove $computer_name from Active Directory domain, output:\n" . join("\n", @$output));
+	#		}
+	#		return 0;
+	#	}
+	#	
+	#	notify($ERRORS{'OK'}, 0, "removed $computer_name from Active Directory domain, output:\n" . join("\n", @$output));
+	
+	$self->ad_delete_computer($computer_name, $computer_current_domain);
 	return 1;
 }
 
@@ -13792,6 +13953,14 @@ EOF
  Parameters  : none
  Returns     : boolean
  Description : Checks if the computer is joined to any Active Directory domain.
+               Returns the following:
+               * undefined - Error occurred, unable to determine if computer is
+                 joined to a domain.
+               * 0 - Computer is not joined to a domain.
+               * string - Computer is joined to a domain. The domain name is
+                 returned.
+               * 1 - Computer is joined to a domain but the domain name could
+                 not be determined.
 
 =cut
 
@@ -13860,7 +14029,11 @@ sub ad_search {
 		return;
 	}
 	
-	my ($ldap_filter_argument, $attempt_limit) = @_;
+	my $arguments = shift;
+	
+	my $computer_name	= $self->data->get_computer_short_name();
+	
+	my $ldap_filter_argument = $arguments->{ldap_filter};
 	if (!defined($ldap_filter_argument)) {
 		notify($ERRORS{'WARNING'}, 0, "LDAP filter hash reference argument was not supplied");
 		return;
@@ -13873,11 +14046,8 @@ sub ad_search {
 		notify($ERRORS{'WARNING'}, 0, "empty LDAP FILTER hash reference argument was supplied");
 		return;
 	}
-	
-	$attempt_limit = 3 unless $attempt_limit;
-	
-	# Make sure objectClass was specified
-	if (!defined($ldap_filter_argument->{objectClass})) {
+	elsif (!defined($ldap_filter_argument->{objectClass})) {
+		# Make sure objectClass was specified
 		notify($ERRORS{'WARNING'}, 0, "LDAP FILTER hash reference argument does not contain an objectClass value:\n" . format_data($ldap_filter_argument));
 		return;
 	}
@@ -13886,24 +14056,24 @@ sub ad_search {
 		return;
 	}
 	
-	# This sub handles both search and delete under very strict conditions
-	# This is somewhat ugly but was done to reduce code duplication - especially with the Powershell below
-	my $operation;
-	my $calling_subroutine = get_calling_subroutine();
-	if ($calling_subroutine =~ /(ad_delete_computer)/) {
-		$operation = 'delete';
+	my $domain_dns_name;
+	my $domain_username;
+	my $domain_password;
+	if (defined($arguments->{domain_dns_name})) {
+		$domain_dns_name = $arguments->{domain_dns_name};
+		($domain_username, $domain_password) = get_active_directory_domain_credentials($domain_dns_name);
+		if (!defined($domain_username) || !defined($domain_password)) {
+			notify($ERRORS{'WARNING'}, 0, "unable to search domain: $domain_dns_name, domain DNS name argument was specified but credentials could not be determined from existing 'addomain' table entries");
+			return;
+		}
 	}
 	else {
-		$operation = 'search for';
+		$domain_dns_name = $self->data->get_image_domain_dns_name();
+		$domain_username = $self->data->get_image_domain_username();
+		$domain_password = $self->data->get_image_domain_password();
 	}
-	
-	my $computer_name	= $self->data->get_computer_short_name();
-	
-	my $domain_dns_name = $self->data->get_image_domain_dns_name();
-	my $domain_username = $self->data->get_image_domain_username();
-	my $domain_password = $self->data->get_image_domain_password();
 	if (!defined($domain_dns_name)) {
-		notify($ERRORS{'WARNING'}, 0, "unable to determine if AD object exists on $computer_name, domain DNS name is not configured");
+		notify($ERRORS{'WARNING'}, 0, "unable to determine if AD object exists on $computer_name, domain DNS name is not configured for the image and was not passed as an argument");
 		return;
 	}
 	elsif (!defined($domain_username)) {
@@ -13915,6 +14085,19 @@ sub ad_search {
 		return;
 	}
 	
+	my $attempt_limit = $arguments->{attempt_limit} || 3;
+	
+	# This sub handles both search and delete under very strict conditions
+	# This is somewhat ugly but was done to reduce code duplication - especially with the Powershell below
+	my $operation;
+	my $calling_subroutine = get_calling_subroutine();
+	if ($calling_subroutine =~ /(ad_delete_computer)/) {
+		$operation = 'delete';
+	}
+	else {
+		$operation = 'search for';
+	}
+	
 	my $search_attribute_count = scalar(keys %$ldap_filter_argument);
 	
 	my $ldap_filter;
@@ -14064,7 +14247,7 @@ EOF
 
 =head2 ad_delete_computer
 
- Parameters  : $computer_samaccountname (optional)
+ Parameters  : $computer_samaccountname (optional), $domain_dns_name (optional)
  Returns     : boolean
  Description : Deletes a computer object from the active directory domain with a
                sAMAccountName attribute matching the argument. If no argument is
@@ -14084,23 +14267,34 @@ sub ad_delete_computer {
 		return;
 	}
 	
-	my $computer_samaccountname = shift || $self->data->get_computer_short_name();
+	my ($computer_samaccountname, $domain_dns_name) = @_;
 	
+	$computer_samaccountname = $self->data->get_computer_short_name() unless $computer_samaccountname;
+	
+	# Make sure computer samAccountName does not contain a trailing dollar sign
+	# A dollar sign will be present if retrieved directly from AD
 	$computer_samaccountname =~ s/\$*$/\$/g;
 	
-	return $self->ad_search(
-		{
+	my $ad_search_arguments = {
+		'ldap_filter' => {
 			'objectClass' => 'computer',
 			'sAMAccountName' => $computer_samaccountname,
-		},
-	);
+		}
+	};
+	
+	# If a specific domain was specified, retrieve the username and password for that domain
+	if ($domain_dns_name) {
+		$ad_search_arguments->{domain_dns_name} = $domain_dns_name;
+	}
+	
+	return $self->ad_search($ad_search_arguments);
 }
 
 #/////////////////////////////////////////////////////////////////////////////
 
 =head2 ad_search_computer
 
- Parameters  : $computer_samaccountname (optional)
+ Parameters  : $computer_samaccountname (optional), $domain_dns_name (optional)
  Returns     : string
  Description : Checks if a computer exists in the Active Directory domain with a
                sAMAccountName attribute matching the argument. If found, a
@@ -14115,16 +14309,27 @@ sub ad_search_computer {
 		return;
 	}
 	
-	my $computer_samaccountname = shift || $self->data->get_computer_short_name();
+	my ($computer_samaccountname, $domain_dns_name) = @_;
+	
+	$computer_samaccountname = $self->data->get_computer_short_name() unless $computer_samaccountname;
 	
+	# Make sure computer samAccountName does not contain a trailing dollar sign
+	# A dollar sign will be present if retrieved directly from AD
 	$computer_samaccountname =~ s/\$*$/\$/g;
 	
-	my @computer_dns = $self->ad_search(
-		{
+	my $ad_search_arguments = {
+		'ldap_filter' => {
 			'objectClass' => 'computer',
 			'sAMAccountName' => $computer_samaccountname,
 		}
-	);
+	};
+	
+	# If a specific domain was specified, retrieve the username and password for that domain
+	if ($domain_dns_name) {
+		$ad_search_arguments->{domain_dns_name} = $domain_dns_name;
+	}
+	
+	my @computer_dns = $self->ad_search($ad_search_arguments);
 	if (@computer_dns) {
 		return $computer_dns[0];
 	}
@@ -14174,8 +14379,10 @@ sub ad_search_ou {
 	
 	return $self->ad_search(
 		{
-			'objectClass' => 'organizationalUnit',
-			$attribute_name => $ou_identifier,
+			'ldap_filter' => {
+				'objectClass' => 'organizationalUnit',
+				$attribute_name => $ou_identifier,
+			}
 		}
 	);
 }
@@ -14216,8 +14423,10 @@ sub ad_user_exists {
 	
 	my @user_dns = $self->ad_search(
 		{
-			'objectClass' => 'user',
-			'sAMAccountName' => $user_samaccountname,
+			'ldap_filter' => {
+				'objectClass' => 'user',
+				'sAMAccountName' => $user_samaccountname,
+			}
 		}
 	);
 	

Modified: vcl/trunk/managementnode/lib/VCL/Module/Provisioning.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/Provisioning.pm?rev=1785881&r1=1785880&r2=1785881&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/Provisioning.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/Provisioning.pm Tue Mar  7 20:28:49 2017
@@ -128,14 +128,22 @@ sub node_status {
 	
 	# Check if the post-load tasks have been completed
 	my $post_load_status = $self->os->get_post_load_status();
-	if ($post_load_status) {
-		notify($ERRORS{'OK'}, 0, "OS module post_load tasks have been completed on $computer_name, returning 'READY'");
-		return 'READY';
-	}
-	else {
+	if (!$post_load_status) {
 		notify($ERRORS{'DEBUG'}, 0, "OS module post_load tasks have NOT been completed on $computer_name, returning 'POST_LOAD'");
 		return 'POST_LOAD';
 	}
+	
+	# Check if OS module implements a node_status_os_check subroutine
+	# Currently, this is only used by the Windows module to ensure the AD configuration is correct if an image's AD configuration is changed after a computer is loaded
+	if ($self->os->can('node_status_os_check')) {
+		if (!$self->os->node_status_os_check()) {
+			notify($ERRORS{'DEBUG'}, 0, "OS module's node_status_os_check returned false, returning 'RELOAD'");
+			return 'RELOAD';
+		}
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "general node status checks all succeeded, returning 'READY'");
+	return 'READY';
 }
 
 #/////////////////////////////////////////////////////////////////////////////

Modified: vcl/trunk/managementnode/lib/VCL/utils.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/utils.pm?rev=1785881&r1=1785880&r2=1785881&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/utils.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/utils.pm Tue Mar  7 20:28:49 2017
@@ -115,6 +115,7 @@ our @EXPORT = qw(
 	format_data
 	format_hash_keys
 	format_number
+	get_active_directory_domain_credentials
 	get_affiliation_info
 	get_array_intersection
 	get_block_request_image_info
@@ -14697,6 +14698,61 @@ EOF
 
 #/////////////////////////////////////////////////////////////////////////////
 
+=head2 get_active_directory_domain_credentials
+
+ Parameters  : $domain_dns_name, $no_cache (optional)
+ Returns     : ($username, $password)
+ Description : Attempts to retrieve the username and password for the domain
+               from the addomain table. This is used if a computer needs to be
+               removed from a domain but the reservation image is not configured
+               for Active Directory. When this occurs, the credentials are not
+               available from $self->data.
+
+=cut
+
+sub get_active_directory_domain_credentials {
+	my ($domain_dns_name, $no_cache) = @_;
+	if (!$domain_dns_name) {
+		notify($ERRORS{'WARNING'}, 0, "domain DNS name argument was not specified");
+		return;
+	}
+	
+	if (!$no_cache && defined($ENV{active_directory_domain_credentials}{$domain_dns_name})) {
+		notify($ERRORS{'DEBUG'}, 0, "returning cached Active Directory credentials for $domain_dns_name domain");
+		return @{$ENV{active_directory_domain_credentials}{$domain_dns_name}};
+	}
+	
+	# Construct the select statement
+	my $select_statement = <<EOF;
+SELECT DISTINCT
+username,
+password
+FROM
+addomain
+WHERE
+addomain.domainDNSName = '$domain_dns_name'
+EOF
+	
+	# Call the database select subroutine
+	my @selected_rows = database_select($select_statement);
+
+	# Check to make sure 1 row was returned
+	if (scalar @selected_rows == 0) {
+		notify($ERRORS{'DEBUG'}, 0, "Active Directory domain does not exist in the database: $domain_dns_name");
+		return ();
+	}
+
+	# Get the single row returned from the select statement
+	my $row = $selected_rows[0];
+	my $username = $row->{username};
+	my $password = $row->{password};
+	notify($ERRORS{'DEBUG'}, 0, "retrieved credentials for $domain_dns_name domain from database, username: '$username', password: '$password'");
+	$ENV{active_directory_domain_credentials}{$domain_dns_name} = [$username, $password];
+	return @{$ENV{active_directory_domain_credentials}{$domain_dns_name}};
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
 
 1;
 __END__