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__


Reply via email to