| #!/usr/bin/perl -w |
| ############################################################################### |
| # $Id$ |
| ############################################################################### |
| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You under the Apache License, Version 2.0 |
| # (the "License"); you may not use this file except in compliance with |
| # the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| ############################################################################### |
| |
| =head1 NAME |
| |
| VCL::Module::OS::Linux.pm - VCL module to support Linux operating systems |
| |
| =head1 SYNOPSIS |
| |
| Needs to be written |
| |
| =head1 DESCRIPTION |
| |
| This module provides VCL support for Linux operating systems. |
| |
| =cut |
| |
| ############################################################################### |
| package VCL::Module::OS::Linux; |
| |
| # Specify the lib path using FindBin |
| use FindBin; |
| use lib "$FindBin::Bin/../../.."; |
| |
| # Configure inheritance |
| use base qw(VCL::Module::OS); |
| |
| # Specify the version of this module |
| our $VERSION = '2.5.1'; |
| |
| # Specify the version of Perl to use |
| use 5.008000; |
| |
| use strict; |
| use warnings; |
| use diagnostics; |
| no warnings 'redefine'; |
| |
| use VCL::utils; |
| use English qw(-no_match_vars); |
| use Net::Netmask; |
| use File::Basename; |
| use File::Temp qw(tempfile mktemp); |
| |
| ############################################################################### |
| |
| =head1 CLASS VARIABLES |
| |
| =cut |
| |
| =head2 $SOURCE_CONFIGURATION_DIRECTORY |
| |
| Data type : String |
| Description : Location on the management node of the files specific to this OS |
| module which are needed to configure the loaded OS on a computer. |
| This is normally the directory under 'tools' named after this OS |
| module. |
| |
| Example: |
| /usr/local/vcl/tools/Linux |
| |
| =cut |
| |
| our $SOURCE_CONFIGURATION_DIRECTORY = "$TOOLS/Linux"; |
| |
| =head2 $NODE_CONFIGURATION_DIRECTORY |
| |
| Data type : String |
| Description : Location on computer loaded with a VCL image where configuration |
| files and scripts reside. |
| |
| =cut |
| |
| our $NODE_CONFIGURATION_DIRECTORY = '/root/VCL'; |
| |
| =head2 $CAPTURE_DELETE_FILE_PATHS |
| |
| Data type : Array |
| Description : List of files to be deleted during the image capture process. |
| |
| =cut |
| |
| our $CAPTURE_DELETE_FILE_PATHS = [ |
| '/root/.ssh/id_rsa', |
| '/root/.ssh/id_rsa.pub', |
| '/root/*-v*.xml', |
| '/etc/sysconfig/iptables*old*', |
| '/etc/sysconfig/iptables_pre*', |
| '/etc/udev/rules.d/70-persistent-net.rules', |
| '/tmp/*', |
| '/var/log/*.0*', |
| '/var/log/*.1*', |
| '/var/log/*-20*', |
| '/var/log/*.gz', |
| '/var/log/*.old', |
| ]; |
| |
| =head2 $CAPTURE_CLEAR_FILE_PATHS |
| |
| Data type : Array |
| Description : List of files to be cleared during the image capture process. |
| |
| =cut |
| |
| our $CAPTURE_CLEAR_FILE_PATHS = [ |
| '/etc/hostname', |
| '/var/log/audit/audit.log', |
| '/var/log/auth.log', |
| '/var/log/boot.log', |
| '/var/log/kern.log', |
| '/var/log/lastlog', |
| '/var/log/maillog', |
| '/var/log/messages', |
| '/var/log/secure', |
| '/var/log/syslog', |
| '/var/log/udev', |
| '/var/log/ufw.log', |
| '/var/log/wtmp', |
| ]; |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_node_configuration_directory |
| |
| Parameters : none |
| Returns : string |
| Description : Retrieves the $NODE_CONFIGURATION_DIRECTORY variable value for |
| the OS. This is the path on the computer's hard drive where image |
| configuration files and scripts are copied. |
| |
| =cut |
| |
| sub get_node_configuration_directory { |
| return $NODE_CONFIGURATION_DIRECTORY; |
| } |
| |
| ############################################################################### |
| |
| =head1 OBJECT METHODS |
| |
| =cut |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_init_modules |
| |
| Parameters : none |
| Returns : array of Linux init module references |
| Description : Determines the Linux init daemon being used by the computer |
| (SysV, systemd, etc.) and creates an object. The default is SysV |
| if no other modules in the lib/VCL/Module/OS/Linux/init directory |
| match the init daemon on the computer. The init module is mainly |
| used to control services on the computer. |
| |
| =cut |
| |
| sub get_init_modules { |
| 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->{init_modules}} if $self->{init_modules}; |
| |
| notify($ERRORS{'DEBUG'}, 0, "beginning Linux init daemon module initialization"); |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Get the absolute path of the init module directory |
| my $init_directory_path = "$FindBin::Bin/../lib/VCL/Module/OS/Linux/init"; |
| notify($ERRORS{'DEBUG'}, 0, "Linux init module directory path: $init_directory_path"); |
| |
| # Get a list of all *.pm files in the init module directory |
| my @init_module_paths = $self->mn_os->find_files($init_directory_path, '*.pm'); |
| |
| # Attempt to create an initialize an object for each init module |
| my %init_module_hash; |
| INIT_MODULE: for my $init_module_path (@init_module_paths) { |
| my $init_name = fileparse($init_module_path, qr/\.pm$/i); |
| my $init_perl_package = "VCL::Module::OS::Linux::init::$init_name"; |
| |
| # Attempt to load the init module |
| notify($ERRORS{'DEBUG'}, 0, "attempting to load $init_name init module: $init_perl_package"); |
| eval "use $init_perl_package"; |
| if ($EVAL_ERROR || $@) { |
| notify($ERRORS{'CRITICAL'}, 0, "failed to load $init_name init module: $init_perl_package, error: $EVAL_ERROR"); |
| next INIT_MODULE; |
| } |
| |
| # Attempt to create an init module object |
| # The 'new' constructor will automatically call the module's initialize subroutine |
| # initialize will check the computer to determine if it contains the corresponding Linux init daemon installed |
| # If not installed, the constructor will return false |
| my $init; |
| eval { $init = ($init_perl_package)->new({ |
| data_structure => $self->data, |
| os => $self, |
| mn_os => $self->mn_os, |
| init_modules => $self->{init_modules}, |
| }) }; |
| if ($init) { |
| my @required_commands = eval "@" . $init_perl_package . "::REQUIRED_COMMANDS"; |
| if ($EVAL_ERROR) { |
| notify($ERRORS{'CRITICAL'}, 0, "\@REQUIRED_COMMANDS variable is not defined in the $init_perl_package Linux init daemon module"); |
| next INIT_MODULE; |
| } |
| if (@required_commands) { |
| for my $command (@required_commands) { |
| if (!$self->command_exists($command)) { |
| next INIT_MODULE; |
| } |
| } |
| } |
| |
| my @prohibited_commands = eval "@" . $init_perl_package . "::PROHIBITED_COMMANDS"; |
| if (@prohibited_commands) { |
| for my $command (@prohibited_commands) { |
| if ($self->command_exists($command)) { |
| notify($ERRORS{'DEBUG'}, 0, "ignoring $init_perl_package Linux init daemon module, '$command' command exists on $computer_node_name"); |
| next INIT_MODULE; |
| } |
| } |
| } |
| |
| |
| # init object successfully created, retrieve the module's $INIT_DAEMON_ORDER variable |
| # An OS may have/support multiple Linux init daemons, services may be registered under different init daemons |
| # In some cases, need to try multple init modules to control a service |
| # This $INIT_DAEMON_ORDER integer determines the order in which the modules are tried |
| my $init_daemon_order = eval '$' . $init_perl_package . '::INIT_DAEMON_ORDER'; |
| if ($EVAL_ERROR) { |
| notify($ERRORS{'CRITICAL'}, 0, "\$INIT_DAEMON_ORDER variable is not defined in the $init_perl_package Linux init daemon module"); |
| next INIT_MODULE; |
| } |
| elsif ($init_module_hash{$init_daemon_order}) { |
| notify($ERRORS{'CRITICAL'}, 0, "multiple Linux init daemon modules are configured to use \$INIT_DAEMON_ORDER=$init_daemon_order: " . ref($init_module_hash{$init_daemon_order}) . ", " . ref($init) . ", the value of this variable must be unique"); |
| next INIT_MODULE; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "$init_name init object created and initialized to control $computer_node_name, order: $init_daemon_order"); |
| $init_module_hash{$init_daemon_order} = $init; |
| } |
| } |
| elsif ($EVAL_ERROR) { |
| notify($ERRORS{'WARNING'}, 0, "$init_perl_package init object could not be created, error:\n$EVAL_ERROR"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "$init_name init object could not be initialized to control $computer_node_name"); |
| } |
| } |
| |
| # Make sure at least 1 init module object was successfully initialized |
| if (!%init_module_hash) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create Linux init daemon module"); |
| return; |
| } |
| |
| # Construct an array of init module objects from highest to lowest $INIT_DAEMON_ORDER |
| $self->{init_modules} = []; |
| my $init_module_order_string; |
| for my $init_daemon_order (sort {$a <=> $b} keys %init_module_hash) { |
| push @{$self->{init_modules}}, $init_module_hash{$init_daemon_order}; |
| $init_module_order_string .= "$init_daemon_order: " . ref($init_module_hash{$init_daemon_order}) . "\n"; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "constructed array containing init module objects which may be used to control $computer_node_name:\n$init_module_order_string"); |
| return @{$self->{init_modules}}; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 firewall |
| |
| Parameters : none |
| Returns : Linux firewall module reference |
| Description : Determines the Linux firewall module to use and creates an |
| object. |
| |
| =cut |
| |
| sub firewall { |
| 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->{firewall} if $self->{firewall}; |
| |
| notify($ERRORS{'DEBUG'}, 0, "beginning Linux firewall daemon module initialization"); |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Get the absolute path of the init module directory |
| my $firewall_directory_path = "$FindBin::Bin/../lib/VCL/Module/OS/Linux/firewall"; |
| notify($ERRORS{'DEBUG'}, 0, "Linux firewall module directory path: $firewall_directory_path"); |
| |
| # Get a list of all *.pm files in the firewall module directory |
| my @firewall_module_paths = $self->mn_os->find_files($firewall_directory_path, '*.pm'); |
| |
| # Attempt to create an initialize an object for each firewall module |
| my %firewall_module_hash; |
| FIREWALL_MODULE: for my $firewall_module_path (@firewall_module_paths) { |
| my $firewall_name = fileparse($firewall_module_path, qr/\.pm$/i); |
| my $firewall_perl_package = "VCL::Module::OS::Linux::firewall::$firewall_name"; |
| |
| # Attempt to load the module |
| eval "use $firewall_perl_package"; |
| if ($EVAL_ERROR) { |
| notify($ERRORS{'WARNING'}, 0, "$firewall_perl_package module could not be loaded, error:\n" . $EVAL_ERROR); |
| return; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "$firewall_perl_package module loaded"); |
| |
| # Attempt to create the object |
| my $firewall_object; |
| eval { |
| $firewall_object = ($firewall_perl_package)->new({ |
| data_structure => $self->data, |
| os => $self, |
| mn_os => $self->mn_os, |
| }) |
| }; |
| |
| if ($EVAL_ERROR) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create $firewall_perl_package object, error: $EVAL_ERROR"); |
| } |
| elsif (!$firewall_object) { |
| notify($ERRORS{'DEBUG'}, 0, "$firewall_perl_package object could not be initialized"); |
| } |
| else { |
| $self->{firewall} = $firewall_object; |
| my $linux_address = sprintf('%x', $self); |
| my $firewall_object_address = sprintf('%x', $firewall_object); |
| my $self_firewall_address = sprintf('%x', $self->{firewall}); |
| notify($ERRORS{'DEBUG'}, 0, "$firewall_perl_package object created for $computer_node_name, Linux object address: $linux_address, firewall object address: $firewall_object_address, \$self->{firewall} address: $self_firewall_address"); |
| return $firewall_object; |
| } |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "unable to initialize suitable specific firewall module, returning generic VCL::Module::OS::Linux::firewall object"); |
| return bless {}, 'VCL::Module::OS::Linux::firewall'; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 pre_capture |
| |
| Parameters : none |
| Returns : boolean |
| Description : |
| |
| =cut |
| |
| sub pre_capture { |
| my $self = shift; |
| my $args = 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; |
| } |
| |
| # Check if end_state argument was passed |
| if (defined $args->{end_state}) { |
| $self->{end_state} = $args->{end_state}; |
| } |
| else { |
| $self->{end_state} = 'off'; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Call OS::pre_capture to perform the pre-capture tasks common to all OS's |
| if (!$self->SUPER::pre_capture($args)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute parent class pre_capture() subroutine"); |
| return; |
| } |
| |
| notify($ERRORS{'OK'}, 0, "beginning Linux-specific image capture preparation tasks"); |
| |
| if (!$self->generate_exclude_list_sample()) { |
| notify($ERRORS{'DEBUG'}, 0, "could not create /root/.vclcontrol/vcl_exclude_list.sample"); |
| } |
| |
| # Force user off computer |
| if (!$self->logoff_user()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to log user off $computer_node_name"); |
| } |
| |
| # Attempt to unmount NFS shares configured for the management node (Site Configuration > NFS Mounts) |
| $self->unmount_nfs_shares() || return; |
| $self->remove_matching_fstab_lines('Added by VCL'); |
| |
| # Remove user accounts |
| if ($self->delete_user_accounts()) { |
| notify($ERRORS{'OK'}, 0, "deleted user accounts added by VCL from $computer_node_name"); |
| } |
| |
| # Attempt to set the root password to a known value |
| # This is useful for troubleshooting image problems |
| $self->set_password("root", $WINDOWS_ROOT_PASSWORD); |
| |
| # Prevent the "Text Mode Setup Utility" - "Choose a Tool" screen from appearing |
| if ($self->service_exists('firstboot')) { |
| $self->disable_service('firstboot'); |
| } |
| |
| if (!$self->configure_default_sshd()) { |
| return; |
| } |
| |
| if (!$self->configure_rc_local()) { |
| return; |
| } |
| |
| if ($self->can('firewall') && $self->firewall->can('process_pre_capture')) { |
| $self->firewall->process_pre_capture() || return; |
| } |
| |
| if (!$self->clean_known_files()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to clean known files"); |
| } |
| |
| # Configure the private and public interfaces to use DHCP |
| my $private_interface_name = $self->get_private_interface_name(); |
| my $public_interface_name = $self->get_public_interface_name(); |
| |
| if (!$self->enable_dhcp($private_interface_name)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to enable DHCP on the private interface"); |
| return; |
| } |
| if (!$self->enable_dhcp($public_interface_name)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to enable DHCP on the public interface"); |
| return; |
| } |
| |
| # Delete route files if they exist for either the private or public interface |
| $self->delete_file("/etc/sysconfig/network-scripts/route-$private_interface_name"); |
| $self->delete_file("/etc/sysconfig/network-scripts/route-$public_interface_name"); |
| |
| # Remove computer/reservation specific lines from network file |
| $self->remove_lines_from_file('/etc/sysconfig/network', 'HOSTNAME'); |
| $self->remove_lines_from_file('/etc/sysconfig/network', 'GATEWAY'); |
| |
| # Shut the computer down |
| if ($self->{end_state} =~ /off/i) { |
| notify($ERRORS{'DEBUG'}, 0, "shutting down $computer_node_name, provisioning module specified end state: $self->{end_state}"); |
| if (!$self->shutdown()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to shut down $computer_node_name"); |
| return; |
| } |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "$computer_node_name not shut down, provisioning module specified end state: $self->{end_state}"); |
| } |
| |
| notify($ERRORS{'OK'}, 0, "Linux pre-capture steps complete"); |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 post_load |
| |
| Parameters : none |
| Returns : boolean |
| Description : |
| |
| =cut |
| |
| sub post_load { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $image_name = $self->data->get_image_name(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| my $image_os_install_type = $self->data->get_image_os_install_type(); |
| |
| notify($ERRORS{'OK'}, 0, "beginning Linux post_load tasks, image: $image_name, computer: $computer_node_name"); |
| |
| # Wait for computer to respond to SSH |
| if (!$self->wait_for_response(5, 600, 5)) { |
| notify($ERRORS{'WARNING'}, 0, "$computer_node_name never responded to SSH"); |
| return; |
| } |
| |
| # Attempt to generate ifcfg-eth* files and start any interfaces which the file does not exist |
| $self->activate_interfaces(); |
| |
| # Configure the firewall to allow SSH traffic only from the management node |
| if ($self->can('firewall') && $self->firewall->can('process_post_load')) { |
| $self->firewall->process_post_load() || return; |
| } |
| |
| # Make sure the public IP address assigned to the computer matches the database |
| if (!$self->update_public_ip_address()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to update public IP address"); |
| return; |
| } |
| |
| # Configure sshd to only listen on the private interface and add ext_sshd service listening on the public interface |
| # This locks down sshd so that it isn't listening on the public interface -- ext_sshd isn't started yet |
| if (!$self->configure_ext_sshd()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to configure ext_sshd on $computer_node_name"); |
| return 0; |
| } |
| |
| # Remove commands from rc.local added by previous versions of VCL |
| $self->configure_rc_local(); |
| |
| # Kickstart installations likely won't have currentimage.txt, generate it |
| if ($image_os_install_type eq "kickstart") { |
| notify($ERRORS{'OK'}, 0, "detected kickstart install on $computer_node_name, writing current_image.txt"); |
| if (!$self->create_currentimage_txt()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create currentimage.txt on $computer_node_name"); |
| } |
| } |
| |
| # Update time and ntpservers |
| if (!$self->synchronize_time()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to synchroinze date and time on $computer_node_name"); |
| } |
| |
| # Change password |
| if (!$self->set_password("root")) { |
| notify($ERRORS{'OK'}, 0, "failed to edit root password on $computer_node_name"); |
| } |
| |
| # Clear ssh idenity keys from /root/.ssh |
| if (!$self->clear_private_keys()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to clear known identity keys"); |
| } |
| |
| # Update computer hostname if imagemeta.sethostname is not set to 0 |
| my $set_hostname = $self->data->get_imagemeta_sethostname(0); |
| if (defined($set_hostname) && $set_hostname =~ /0/) { |
| notify($ERRORS{'DEBUG'}, 0, "not setting computer hostname, imagemeta.sethostname = $set_hostname"); |
| } |
| else { |
| $self->update_public_hostname(); |
| } |
| |
| # Run the vcl_post_load script if it exists in the image |
| my @post_load_script_paths = ('/usr/local/vcl/vcl_post_load', '/etc/init.d/vcl_post_load'); |
| |
| foreach my $script_path (@post_load_script_paths) { |
| notify($ERRORS{'DEBUG'}, 0, "script_path $script_path"); |
| if ($self->file_exists($script_path)) { |
| my $result = $self->run_script($script_path, '1', '300', '1'); |
| if (!defined($result)) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred running $script_path"); |
| } |
| elsif ($result == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "$script_path does not exist in image: $image_name"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "ran $script_path"); |
| } |
| } |
| } |
| |
| return $self->SUPER::post_load(); |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 post_reserve |
| |
| Parameters : none |
| Returns : boolean |
| Description : |
| |
| =cut |
| |
| sub post_reserve { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| my $reservation_id = $self->data->get_reservation_id(); |
| my $image_name = $self->data->get_image_name(); |
| my $computer_short_name = $self->data->get_computer_short_name(); |
| |
| |
| # User supplied data |
| #check if variable is set |
| #get variable from variable table related to server reservation id ‘userdata|<reservation id>’ |
| # write contents to local temp file /tmp/resrvationid_post_reserve_userdata |
| # scp tmpfile to ‘/root/.vclcontrol/post_reserve_userdata’ |
| # assumes the image has the call in vcl_post_reserve to import/read the user data file |
| my $variable_name = "userdata|$reservation_id"; |
| my $variable_data; |
| my $target_location = "/root/.vclcontrol/post_reserve_userdata"; |
| if ($self->data->is_variable_set($variable_name)) { |
| $variable_data = get_variable($variable_name); |
| |
| #write to local temp file |
| my $tmpfile = "/tmp/$reservation_id" ."_post_reserve_userdata"; |
| if (open(TMP, ">$tmpfile")) { |
| print TMP $variable_data; |
| close(TMP); |
| |
| if ($self->copy_file_to($tmpfile, $target_location)) { |
| notify($ERRORS{'DEBUG'}, 0, "copied $tmpfile to $target_location on $computer_short_name"); |
| } |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to open $tmpfile for writing userdata"); |
| } |
| #Clean variable from variable table |
| if (delete_variable($variable_name)) { |
| notify($ERRORS{'DEBUG'}, 0, "deleted variable_name $variable_name from variable table"); |
| } |
| } |
| |
| # Check if script exists |
| my @post_reserve_script_paths = ('/usr/local/vcl/vcl_post_reserve', '/etc/init.d/vcl_post_reserve'); |
| foreach my $script_path (@post_reserve_script_paths) { |
| if ($self->file_exists($script_path)) { |
| # If post_reserve script exists, assume it does user or reservation-specific actions |
| # If the user never connects and the reservation times out, there's no way to revert these actions in order to clean the computer for another user |
| # Tag the image as tainted so it is reloaded |
| $self->set_tainted_status('post-reserve scripts residing in the image executed'); |
| |
| # Run the vcl_post_reserve script if it exists in the image |
| my $result = $self->run_script($script_path, '1', '300', '1'); |
| if (!defined($result)) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred running $script_path"); |
| } |
| elsif ($result == 0) { |
| notify($ERRORS{'DEBUG'}, 0, "$script_path does not exist in image: $image_name"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "ran $script_path"); |
| } |
| } |
| } |
| |
| return $self->SUPER::post_reserve(); |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 post_reservation |
| |
| Parameters : none |
| Returns : boolean |
| Description : Checks for and runs vcl_post_reservation script at the end of a |
| reservation. |
| |
| =cut |
| |
| sub post_reservation { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| my $script_path = '/usr/local/vcl/vcl_post_reservation'; |
| |
| # Check if script exists |
| if ($self->file_exists($script_path)) { |
| # Run the vcl_post_reserve script if it exists in the image |
| $self->run_script($script_path, '1', '300', '1'); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "script does NOT exist: $script_path"); |
| } |
| |
| return $self->SUPER::post_reservation(); |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 update_public_hostname |
| |
| Parameters : none |
| Returns : boolean |
| Description : Retrieves the public IP address being used on the Linux computer. |
| Determines the hostname the IP address resolves to. Sets the |
| hostname on the Linux computer. |
| |
| =cut |
| |
| sub update_public_hostname { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $public_hostname = shift; |
| if (!$public_hostname) { |
| # Get the IP address of the public adapter |
| my $public_ip_address = $self->get_public_ip_address(); |
| if (!$public_ip_address) { |
| notify($ERRORS{'WARNING'}, 0, "hostname cannot be set, unable to determine public IP address"); |
| return; |
| } |
| notify($ERRORS{'DEBUG'}, 0, "retrieved public IP address of $computer_node_name: $public_ip_address"); |
| |
| # Get the hostname for the public IP address |
| $public_hostname = ip_address_to_hostname($public_ip_address) || $computer_node_name; |
| } |
| |
| my $error_occurred = 0; |
| |
| # Check if hostname file exists and update if necessary |
| my $hostname_file_path = '/etc/hostname'; |
| if ($self->file_exists($hostname_file_path)) { |
| if ($self->create_text_file($hostname_file_path, $public_hostname)) { |
| notify($ERRORS{'DEBUG'}, 0, "updated $hostname_file_path on $computer_node_name with hostname '$public_hostname'"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to update $hostname_file_path on $computer_node_name with '$public_hostname'"); |
| $error_occurred = 1; |
| } |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "$hostname_file_path not updated on $computer_node_name because the file does not exist"); |
| } |
| |
| |
| # Check if network file exists and update if necessary |
| my $network_file_path = '/etc/sysconfig/network'; |
| if ($self->file_exists($network_file_path)) { |
| my $sed_command = "sed -i -e \"/^HOSTNAME=/d\" $network_file_path; echo \"HOSTNAME=$public_hostname\" >> $network_file_path"; |
| my ($sed_exit_status, $sed_output) = $self->execute($sed_command); |
| if (!defined($sed_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to update hostname in $network_file_path on $computer_node_name"); |
| return; |
| } |
| elsif ($sed_exit_status != 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to update hostname in $network_file_path on $computer_node_name, exit status: $sed_exit_status, output:\n" . join("\n", @$sed_output)); |
| $error_occurred = 1; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "updated hostname in $network_file_path on $computer_node_name to '$public_hostname'"); |
| } |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "$network_file_path not updated on $computer_node_name because the file does not exist"); |
| } |
| |
| # Check if hostnamectl exists, this is provided by systemd on CentOS/RHEL 7+ |
| if ($self->command_exists('hostnamectl')) { |
| my $hostnamectl_command = "hostnamectl set-hostname $public_hostname"; |
| my ($hostnamectl_exit_status, $hostnamectl_output) = $self->execute($hostnamectl_command); |
| if (!defined($hostnamectl_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to set hostname using hostnamectl command on $computer_node_name to $public_hostname"); |
| return; |
| } |
| elsif ($hostnamectl_exit_status != 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set hostname using hostnamectl command on $computer_node_name to $public_hostname, exit status: $hostnamectl_exit_status, command: '$hostnamectl_command', output:\n" . join("\n", @$hostnamectl_output)); |
| $error_occurred = 1; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "set hostname using hostnamectl command on $computer_node_name to $public_hostname"); |
| } |
| } |
| else { |
| my $hostname_command = "hostname $public_hostname"; |
| my ($hostname_exit_status, $hostname_output) = $self->execute($hostname_command); |
| if (!defined($hostname_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to set hostname using hostname command on $computer_node_name to $public_hostname"); |
| return; |
| } |
| elsif ($hostname_exit_status != 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set hostname using hostname command on $computer_node_name to $public_hostname, exit status: $hostname_exit_status, command: '$hostname_command', output:\n" . join("\n", @$hostname_output)); |
| $error_occurred = 1; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "set hostname using hostname command on $computer_node_name to $public_hostname"); |
| } |
| } |
| |
| return !$error_occurred; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 clear_private_keys |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub clear_private_keys { |
| my $self = shift; |
| unless (ref($self) && $self->isa('VCL::Module')) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called as a VCL::Module module object method"); |
| return; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "perparing to clear known identity keys"); |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_short_name = $self->data->get_computer_short_name(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Clear ssh idenity keys from /root/.ssh |
| my $clear_private_keys = "/bin/rm -f /root/.ssh/id_rsa /root/.ssh/id_rsa.pub"; |
| if ($self->execute($clear_private_keys)) { |
| notify($ERRORS{'DEBUG'}, 0, "cleared any id_rsa keys from /root/.ssh"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'CRITICAL'}, 0, "failed to clear any id_rsa keys from /root/.ssh"); |
| return 0; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_static_public_address |
| |
| Parameters : none |
| Returns : boolean |
| Description : Configures the public interface with a static IP address. |
| |
| =cut |
| |
| sub set_static_public_address { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| my $computer_name = $self->data->get_computer_short_name(); |
| my $ip_configuration = $self->data->get_management_node_public_ip_configuration(); |
| my $public_ip_address = $self->data->get_computer_public_ip_address(); |
| my $subnet_mask = $self->data->get_management_node_public_subnet_mask(); |
| my @dns_servers = $self->data->get_management_node_public_dns_servers(); |
| |
| # TODO: Get this out of here. OS modules shouldn't have to figure this out. $self->data should always return correct value. |
| my $server_request_fixed_ip = $self->data->get_server_request_fixed_ip(); |
| if ($server_request_fixed_ip) { |
| $public_ip_address = $server_request_fixed_ip; |
| $subnet_mask = $self->data->get_server_request_netmask(); |
| @dns_servers = $self->data->get_server_request_dns_servers(); |
| } |
| |
| # Make sure public IP configuration is static or this is a server request |
| if ($ip_configuration !~ /static/i && !$server_request_fixed_ip) { |
| notify($ERRORS{'WARNING'}, 0, "management node IP configuration is $ip_configuration, static public IP address can only be set if the IP configuration is static or if a fixed IP was requested"); |
| return; |
| } |
| elsif (!$public_ip_address) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve public IP address to assign to $computer_name"); |
| return; |
| } |
| elsif (!$subnet_mask) { |
| notify($ERRORS{'WARNING'}, 0, "failed to retrieve public subnet mask to assign to $computer_name"); |
| return; |
| } |
| |
| # Determine the public interface name |
| my $public_interface_name = $self->get_public_interface_name(); |
| if (!$public_interface_name) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set static public IP address, public interface name could not be determined"); |
| return; |
| } |
| |
| # Get the current public IP address being used by the computer |
| # Use cached data if available (0), ignore errors (1) |
| my $current_public_ip_address = $self->get_public_ip_address(0, 1); |
| if ($current_public_ip_address && $current_public_ip_address eq $public_ip_address) { |
| notify($ERRORS{'DEBUG'}, 0, "static public IP address does not need to be set, $computer_name is already configured to use $current_public_ip_address"); |
| } |
| else { |
| if ($current_public_ip_address) { |
| notify($ERRORS{'DEBUG'}, 0, "static public IP address needs to be set, public IP address currently being used by $computer_name $current_public_ip_address does NOT match correct public IP address: $public_ip_address"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "static public IP address needs to be set, unable to determine public IP address currently in use on $computer_name"); |
| } |
| |
| |
| # Try to ping address to make sure it's available |
| # FIXME -- need to add other tests for checking ip_address is or is not available. |
| if (_pingnode($public_ip_address)) { |
| notify($ERRORS{'CRITICAL'}, 0, "ip_address $public_ip_address is pingable, can not assign to $computer_name "); |
| return; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "attempting to set static public IP address on $computer_name:\n" . |
| "interface: $public_interface_name\n" . |
| "IP address: $public_ip_address\n" . |
| "subnet mask: $subnet_mask" |
| ); |
| |
| my $ifcfg_parameters = { |
| bootproto => 'static', |
| ipaddr => $public_ip_address, |
| netmask => $subnet_mask, |
| }; |
| |
| if (!$self->generate_ifcfg_file($public_interface_name, $ifcfg_parameters)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set static public IP address on $computer_name, ifcfg file could not be created"); |
| return; |
| } |
| |
| # Restart the interface |
| if (!$self->restart_network_interface($public_interface_name)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to restart public interface $public_interface_name on $computer_name"); |
| return; |
| } |
| } |
| |
| # Set default gateway |
| if (!$self->set_static_default_gateway()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set static public IP address on $computer_name, default gateway could not be set"); |
| return; |
| } |
| |
| # Update resolv.conf if DNS server address is configured for the management node |
| if (@dns_servers) { |
| if (!$self->update_resolv_conf(@dns_servers)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to set static public IP address on $computer_name, DNS servers could not be configured"); |
| return; |
| } |
| } |
| |
| # Delete cached network configuration info - forces next call to get_network_configuration to retrieve changed network info from computer |
| delete $self->{network_configuration}; |
| |
| notify($ERRORS{'OK'}, 0, "set static public IP address on $computer_name"); |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 start_network_interface |
| |
| Parameters : $interface_name |
| Returns : boolean |
| Description : Calls ifup on the network interface. |
| |
| =cut |
| |
| sub start_network_interface { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| my $interface_name = shift; |
| if (!$interface_name) { |
| notify($ERRORS{'WARNING'}, 0, "unable to start network interface, interface name argument was not supplied"); |
| return; |
| } |
| |
| my $computer_name = $self->data->get_computer_short_name(); |
| |
| notify($ERRORS{'DEBUG'}, 0, "attempting to start network interface $interface_name on $computer_name"); |
| |
| my $command = "/sbin/ifup $interface_name"; |
| my ($exit_status, $output) = $self->execute($command, 0); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to start $interface_name interface on $computer_name"); |
| return; |
| } |
| elsif (grep(/already configured/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "$interface_name interface on $computer_name is already started, output:\n" . join("\n", @$output)); |
| } |
| elsif ($exit_status == 0 || grep(/done/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "started $interface_name interface on $computer_name, " . (@$output ? "output:\n" . join("\n", @$output) : 'no output')); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to start $interface_name interface on $computer_name, exit status: $exit_status, command: '$command', output:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 stop_network_interface |
| |
| Parameters : $interface_name |
| Returns : boolean |
| Description : Calls ifdown on the network interface. |
| |
| =cut |
| |
| sub stop_network_interface { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| my $interface_name = shift; |
| if (!$interface_name) { |
| notify($ERRORS{'WARNING'}, 0, "unable to stop network interface, interface name argument was not supplied"); |
| return; |
| } |
| |
| my $computer_name = $self->data->get_computer_short_name(); |
| |
| notify($ERRORS{'DEBUG'}, 0, "attempting to stop network interface $interface_name on $computer_name"); |
| |
| my $command = "/sbin/ifdown $interface_name"; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to stop $interface_name interface on $computer_name"); |
| return; |
| } |
| elsif (grep(/not configured/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "$interface_name interface on $computer_name is already stopped, output:\n" . join("\n", @$output)); |
| return 1; |
| } |
| elsif ($exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to stop $interface_name interface on $computer_name, exit status: $exit_status, command: '$command', output:\n" . join("\n", @$output)); |
| return; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "stopped $interface_name interface on $computer_name, output:\n" . join("\n", @$output)); |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 restart_network_interface |
| |
| Parameters : $interface_name |
| Returns : boolean |
| Description : Calls ifdown and then ifup on the network interface. |
| |
| =cut |
| |
| sub restart_network_interface { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| my $interface_name = shift; |
| if (!$interface_name) { |
| notify($ERRORS{'WARNING'}, 0, "unable to restart network interface, interface name argument was not supplied"); |
| return; |
| } |
| |
| my $computer_name = $self->data->get_computer_short_name(); |
| |
| notify($ERRORS{'DEBUG'}, 0, "attempting to restart network interface $interface_name on $computer_name"); |
| |
| my $command = "/sbin/ifdown $interface_name ; /sbin/ifup $interface_name"; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to restart $interface_name interface on $computer_name"); |
| return; |
| } |
| elsif ($exit_status) { |
| notify($ERRORS{'WARNING'}, 0, "failed to restart $interface_name interface on $computer_name, exit status: $exit_status, command: '$command', output:\n" . join("\n", @$output)); |
| return; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "restarted $interface_name interface on $computer_name, output:\n" . join("\n", @$output)); |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_default_gateway |
| |
| Parameters : none |
| Returns : boolean |
| Description : Deletes the existing default gateway from the routing table. |
| |
| =cut |
| |
| sub delete_default_gateway { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| my $computer_name = $self->data->get_computer_short_name(); |
| |
| my $command = "/sbin/route del default"; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to delete default gateway on $computer_name: $command"); |
| return; |
| } |
| elsif (grep(/No such process/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "default gateway not set on $computer_name"); |
| } |
| elsif ($exit_status ne '0') { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete default gateway on $computer_name, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output)); |
| return 0; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "deleted default gateway on $computer_name"); |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_static_default_gateway |
| |
| Parameters : none |
| Returns : boolean |
| Description : Sets the default route. |
| |
| =cut |
| |
| sub set_static_default_gateway { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| my $computer_name = $self->data->get_computer_short_name(); |
| |
| my $default_gateway = $self->get_correct_default_gateway(); |
| if (!$default_gateway) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set static default gateway on $computer_name, correct default gateway IP address could not be determined"); |
| return; |
| } |
| |
| my $current_default_gateway = $self->get_public_default_gateway(); |
| if ($current_default_gateway && $current_default_gateway eq $default_gateway) { |
| notify($ERRORS{'OK'}, 0, "default gateway on $computer_name is already set to $current_default_gateway"); |
| return 1; |
| } |
| |
| my $interface_name = $self->get_public_interface_name(); |
| if (!$interface_name) { |
| notify($ERRORS{'WARNING'}, 0, "unable to set static default gateway on $computer_name, public interface name could not be determined"); |
| return; |
| } |
| |
| # Delete existing default gateway or else error will occur: SIOCADDRT: File exists |
| $self->delete_default_gateway(); |
| |
| my $command = "/sbin/route add default gw $default_gateway metric 0 $interface_name"; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to set default gateway on $computer_name: $command"); |
| return; |
| } |
| elsif ($exit_status ne '0') { |
| notify($ERRORS{'WARNING'}, 0, "failed to set default gateway on $computer_name to $default_gateway, interface: $interface_name, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output)); |
| return 0; |
| } |
| |
| # Create a route file so default route persists across reboots |
| my $route_file_path = "/etc/sysconfig/network-scripts/route-$interface_name"; |
| # For testing: |
| #$self->delete_file($route_file_path); |
| my $route_file_contents = "default via $default_gateway dev $interface_name"; |
| $self->create_text_file($route_file_path, $route_file_contents); |
| |
| # Adding a route-* file does not prevent computer from obtaining a default route via DHCP |
| # Add a 'DEFROUTE=no' line to the ifcfg-<interface> file |
| my $interface_file = "/etc/sysconfig/network-scripts/ifcfg-$interface_name"; |
| # For testing: |
| #$self->remove_lines_from_file($interface_file, 'DEFROUTE'); |
| if ($self->file_exists($interface_file)) { |
| $self->set_config_file_parameter($interface_file, 'DEFROUTE', '=', 'no'); |
| } |
| |
| # Note: leave for future reference, this doesn't seem to work on CentOS/RHEL 7 |
| # Add a 'GATEWAY=' line to /etc/sysconfig/network |
| #my $network_file = "/etc/sysconfig/network"; |
| # For testing: $self->remove_lines_from_file($network_file, 'GATEWAY'); |
| #$self->set_config_file_parameter($network_file, 'GATEWAY', '=', $default_gateway); |
| |
| notify($ERRORS{'OK'}, 0, "set default gateway on $computer_name to $default_gateway, interface: $interface_name"); |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 logoff_user |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub logoff_user { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| # Make sure the user login ID was passed |
| my $user_login_id = shift || $self->data->get_user_login_id(); |
| if (!$user_login_id) { |
| notify($ERRORS{'WARNING'}, 0, "user could not be determined"); |
| return 0; |
| } |
| |
| # Make sure the user login ID was passed |
| my $computer_node_name = shift || $self->data->get_computer_node_name(); |
| if (!$computer_node_name) { |
| notify($ERRORS{'WARNING'}, 0, "computer node name could not be determined"); |
| return 0; |
| } |
| |
| my $logoff_cmd = "pkill -KILL -u $user_login_id"; |
| my ($exit_status, $output) = $self->execute($logoff_cmd); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to log off $user_login_id from $computer_node_name"); |
| return; |
| } |
| elsif (grep(/invalid user name/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "user $user_login_id does not exist on $computer_node_name"); |
| return 1; |
| } |
| elsif ($exit_status ne '0' && $exit_status ne '1') { |
| # pkill will exit with status = 1 if one or more processes were killed, and 1 if no processes matched |
| notify($ERRORS{'WARNING'}, 0, "error occurred attempting to log off $user_login_id from $computer_node_name, exit status: $exit_status, output:\n" . join("\n", @$output)); |
| return; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "logged off $user_login_id from $computer_node_name"); |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reserve |
| |
| Parameters : none |
| Returns : boolean |
| Description : Performs the steps necessary to reserve a computer for a user. |
| A "vcl" user group is added. |
| |
| =cut |
| |
| sub reserve { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| notify($ERRORS{'OK'}, 0, "beginning Linux reserve tasks"); |
| |
| # Add a local vcl user group if it doesn't already exist |
| # Do this before OS.pm::reserve calls add_user_accounts |
| $self->add_vcl_usergroup(); |
| |
| # Configure sshd to only listen on the private interface and add ext_sshd service listening on the public interface |
| # This needs to be done after update_public_ip_address is called from OS.pm::reserve |
| $self->configure_ext_sshd() || return; |
| |
| # Call OS.pm's reserve subroutine |
| $self->SUPER::reserve() || return; |
| |
| # Attempt to mount NFS shares configured for the management node (Site Configuration > NFS Mounts) |
| $self->mount_nfs_shares(); |
| |
| notify($ERRORS{'OK'}, 0, "Linux reserve tasks complete"); |
| return 1; |
| } ## end sub reserve |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 grant_access |
| |
| Parameters : none |
| Returns : boolean |
| Description : adds username to external_sshd_config and and starts sshd with |
| custom config |
| |
| =cut |
| |
| sub grant_access { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| if ($self->can('firewall') && $self->firewall->can('process_reserved')) { |
| if (!$self->firewall->process_reserved()) { |
| notify($ERRORS{'WARNING'}, 0, "failed to grant access to $computer_node_name, firewall configuration failed"); |
| return; |
| } |
| } |
| |
| # Process the connection methods, allow firewall access from any address |
| if ($self->process_connect_methods("", 1)) { |
| notify($ERRORS{'DEBUG'}, 0, "granted access to $computer_node_name by processing the connection methods"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to grant access to $computer_node_name by processing the connection methods"); |
| return; |
| } |
| } ## end sub grant_access |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 synchronize_time |
| |
| Parameters : none |
| Returns : boolean |
| Description : |
| |
| =cut |
| |
| sub synchronize_time { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| my $management_node_hostname = $self->data->get_management_node_hostname(); |
| |
| my $variable_name = "timesource|$management_node_hostname"; |
| my $variable_name_global = "timesource|global"; |
| |
| my $time_source_variable; |
| if (is_variable_set($variable_name)) { |
| $time_source_variable = get_variable($variable_name); |
| notify($ERRORS{'DEBUG'}, 0, "retrieved time source variable '$variable_name': $time_source_variable"); |
| } |
| elsif (is_variable_set($variable_name_global)) { |
| $time_source_variable = get_variable($variable_name_global); |
| notify($ERRORS{'DEBUG'}, 0, "retrieved global time source variable '$variable_name_global': $time_source_variable"); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "unable to sync time, neither '$variable_name' or '$variable_name_global' time source variable is set in database"); |
| return; |
| } |
| |
| # Split the time source variable into an array |
| my @time_sources = split(/[,; ]+/, $time_source_variable); |
| |
| # Assemble the rdate command |
| # Ubuntu doesn't accept multiple servers in a single command |
| my $rdate_command; |
| for my $time_source (@time_sources) { |
| $rdate_command .= "rdate -t 3 -s $time_source || "; |
| } |
| $rdate_command =~ s/[ \|]+$//g; |
| my ($rdate_exit_status, $rdate_output) = $self->execute($rdate_command, 0, 180); |
| if (!defined($rdate_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute rdate command to synchronize time on $computer_node_name"); |
| return; |
| } |
| elsif (grep(/not found/i, @$rdate_output)) { |
| notify($ERRORS{'DEBUG'}, 0, "unable to synchronize time on $computer_node_name, rdate is not installed"); |
| } |
| elsif ($rdate_exit_status > 0) { |
| notify($ERRORS{'WARNING'}, 0, "failed to synchronize time on $computer_node_name using rdate, exit status: $rdate_exit_status, command:\n$rdate_command\noutput:\n" . join("\n", @$rdate_output)); |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "synchronized time on $computer_node_name using rdate"); |
| } |
| |
| # Check if the ntpd service exists before attempting to configure it |
| if (!$self->service_exists('ntpd')) { |
| notify($ERRORS{'DEBUG'}, 0, "skipping ntpd configuration, ntpd service does not exist"); |
| return 1; |
| } |
| |
| # Update ntpservers file |
| my $ntpservers_contents = join("\n", @time_sources); |
| if (!$self->create_text_file('/etc/ntp/ntpservers', $ntpservers_contents)) { |
| return; |
| } |
| |
| return $self->restart_service('ntpd'); |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_password |
| |
| Parameters : $username, $password (optional) |
| Returns : boolean |
| Description : Sets password for the account specified by the username argument. |
| If no password argument is supplied, a random password is |
| generated. |
| |
| =cut |
| |
| sub set_password { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| my $username = shift; |
| my $password = shift; |
| |
| if (!$username) { |
| notify($ERRORS{'WARNING'}, 0, "username argument was not provided"); |
| return; |
| } |
| |
| if (!$password) { |
| $password = getpw(15); |
| } |
| |
| my $command = "echo -e '"; |
| $command .= qq[$password]; |
| $command .= "' \| /usr/bin/passwd -f $username --stdin"; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to set password for $username"); |
| return; |
| } |
| elsif (grep(/(unknown user|warning|error)/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to change password for $username to '$password', command: '$command', output:\n" . join("\n", @$output)); |
| return; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "changed password for $username to '$password', output:\n" . join("\n", @$output)); |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 sanitize |
| |
| Parameters : |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub sanitize { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Make sure user is not connected |
| if ($self->is_connected()) { |
| notify($ERRORS{'WARNING'}, 0, "unable to sanitize $computer_node_name, user is connected"); |
| return 0; |
| } |
| |
| if ($self->can('firewall') && $self->firewall->can('process_sanitize')) { |
| $self->firewall->process_sanitize() || return; |
| } |
| |
| # Call process_connect_methods with the overwrite flag to remove firewall exceptions |
| $self->process_connect_methods() || return; |
| |
| # Attempt to unmount NFS shares configured for the management node (Site Configuration > NFS Mounts) |
| $self->unmount_nfs_shares() || return; |
| $self->remove_matching_fstab_lines('Added by VCL'); |
| |
| # Delete all user associated with the reservation |
| $self->delete_user_accounts() || return; |
| |
| # Make sure ext_sshd is stopped |
| $self->stop_external_sshd() || return; |
| |
| notify($ERRORS{'OK'}, 0, "$computer_node_name has been sanitized"); |
| return 1; |
| } ## end sub sanitize |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 add_vcl_usergroup |
| |
| Parameters : |
| Returns : 1 |
| Description : step to add a user group to avoid group errors from useradd cmd |
| |
| =cut |
| |
| sub add_vcl_usergroup { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| if ($self->execute("groupadd vcl")) { |
| notify($ERRORS{'DEBUG'}, 0, "successfully added the vcl user group to $computer_node_name"); |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 is_connected |
| |
| Parameters : none |
| Returns : boolean, undefined if error occurred |
| Description : Checks if a connection on port 22 is established to the |
| computer's public IP address. |
| |
| =cut |
| |
| sub is_connected { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $computer_public_ip_address = $self->data->get_computer_public_ip_address(); |
| if (!$computer_public_ip_address) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine if connection exists to $computer_node_name, public IP address could not be determined"); |
| return; |
| } |
| |
| my $command = "netstat -an | grep ESTABLISHED"; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command on $computer_node_name: $command"); |
| return; |
| } |
| |
| if (grep(/(Warning|Connection refused)/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine if connection exists to $computer_public_ip_address on $computer_node_name, output:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif (my ($line) = grep(/tcp\s+([0-9]*)\s+([0-9]*)\s($computer_public_ip_address:22)\s+([.0-9]*):([0-9]*)(.*)(ESTABLISHED)/, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "connection exists to $computer_public_ip_address on $computer_node_name:\n$line"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "connection does not exist to $computer_public_ip_address on $computer_node_name"); |
| return 0; |
| } |
| } ## end sub is_connected |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 run_script |
| |
| Parameters : script path |
| Returns : boolean |
| Description : Checks if script exists on the Linux node and attempts to run it. |
| |
| =cut |
| |
| sub run_script { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get the script path argument |
| my $script_path = shift; |
| if (!$script_path) { |
| notify($ERRORS{'WARNING'}, 0, "script path argument was not specified"); |
| return; |
| } |
| my $display_output = shift || 0; |
| my $timeout_seconds = shift || 60; |
| my $max_attempts = shift || 3; |
| |
| # Check if script exists |
| if ($self->file_exists($script_path)) { |
| notify($ERRORS{'DEBUG'}, 0, "script exists: $script_path"); |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "script does NOT exist: $script_path"); |
| return 0; |
| } |
| |
| # Determine the script name |
| my ($script_name) = $script_path =~ /\/([^\/]+)$/; |
| notify($ERRORS{'DEBUG'}, 0, "script name: $script_name"); |
| |
| # Get the node configuration directory, make sure it exists, create if necessary |
| my $node_log_directory = $self->get_node_configuration_directory() . '/Logs'; |
| if (!$self->create_directory($node_log_directory)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create node log file directory: $node_log_directory"); |
| return; |
| } |
| |
| my $management_node_keys = $self->data->get_management_node_keys(); |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Assemble the log file path |
| my $log_file_path = $node_log_directory . "/$script_name.log"; |
| notify($ERRORS{'DEBUG'}, 0, "script log file path: $log_file_path"); |
| |
| # Assemble the command |
| my $command = "chmod +rx \"$script_path\" && \"$script_path\" >> \"$log_file_path\" 2>&1"; |
| |
| # Execute the command |
| my ($exit_status, $output) = $self->execute($command, $display_output, $timeout_seconds, $max_attempts); |
| if (defined($exit_status) && $exit_status == 0) { |
| notify($ERRORS{'OK'}, 0, "executed $script_path, exit status: $exit_status"); |
| } |
| elsif (defined($exit_status)) { |
| notify($ERRORS{'WARNING'}, 0, "$script_path returned a non-zero exit status: $exit_status, command: '$command'"); |
| return; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to run SSH command to execute $script_path"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 file_exists |
| |
| Parameters : $file_path, $display_output (optional) |
| Returns : boolean |
| Description : Checks if a file or directory exists on the Linux computer. |
| |
| =cut |
| |
| sub file_exists { |
| my $self = shift; |
| if (ref($self) !~ /module/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| # Get the path from the subroutine arguments and make sure it was passed |
| my $file_path = shift; |
| if (!$file_path) { |
| notify($ERRORS{'WARNING'}, 0, "path argument was not specified"); |
| return 0; |
| } |
| |
| my $display_output = shift; |
| if (!defined($display_output)) { |
| $display_output = 1; |
| } |
| |
| # Remove any quotes from the beginning and end of the path |
| $file_path = normalize_file_path($file_path); |
| |
| # Escape all spaces in the path |
| my $escaped_path = escape_file_path($file_path); |
| |
| my $computer_short_name = $self->data->get_computer_short_name(); |
| |
| # Check if the file or directory exists |
| # Do not enclose the path in quotes or else wildcards won't work |
| my $command = "stat $escaped_path"; |
| my ($exit_status, $output) = $self->execute($command, 0); |
| if (!defined($output)) { |
| notify($ERRORS{'DEBUG'}, 0, "failed to run command to determine if file or directory exists on $computer_short_name:\npath: '$file_path'\ncommand: '$command'"); |
| return 0; |
| } |
| elsif (grep(/no such file/i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "file or directory does not exist on $computer_short_name: '$file_path'") if $display_output; |
| return 0; |
| } |
| elsif (grep(/stat: /i, @$output)) { |
| notify($ERRORS{'DEBUG'}, 0, "failed to determine if file or directory exists on $computer_short_name:\npath: '$file_path'\ncommand: '$command'\nexit status: $exit_status, output:\n" . join("\n", @$output)); |
| return 0; |
| } |
| |
| # Count the lines beginning with "Size:" and ending with "file", "directory", or "link" to determine how many files and/or directories were found |
| my $files_found = grep(/^\s*Size:.*file$/i, @$output); |
| my $directories_found = grep(/^\s*Size:.*directory$/i, @$output); |
| my $links_found = grep(/^\s*Size:.*link$/i, @$output); |
| |
| if ($files_found || $directories_found || $links_found) { |
| notify($ERRORS{'DEBUG'}, 0, "'$file_path' exists on $computer_short_name, files: $files_found, directories: $directories_found, links: $links_found") if $display_output; |
| return 1; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "unexpected output returned while attempting to determine if file or directory exists on $computer_short_name: '$file_path'\ncommand: '$command'\nexit status: $exit_status, output:\n" . join("\n", @$output)); |
| return 0; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_file |
| |
| Parameters : $path |
| Returns : boolean |
| Description : Deletes files or directories on the Linux computer. |
| |
| =cut |
| |
| sub delete_file { |
| my $self = shift; |
| if (ref($self) !~ /module/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get the path argument |
| my $path = shift; |
| if (!$path) { |
| notify($ERRORS{'WARNING'}, 0, "path argument were not specified"); |
| return; |
| } |
| |
| # Remove any quotes from the beginning and end of the path |
| $path = normalize_file_path($path); |
| |
| # Escape all spaces in the path |
| my $escaped_path = escape_file_path($path); |
| |
| my $computer_short_name = $self->data->get_computer_short_name(); |
| |
| # Delete the file |
| my $command = "rm -rfv $escaped_path"; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to delete file or directory on $computer_short_name:\npath: '$path'\ncommand: '$command'"); |
| return; |
| } |
| elsif (grep(/(cannot access|no such file)/i, @$output)) { |
| notify($ERRORS{'OK'}, 0, "file or directory not deleted because it does not exist on $computer_short_name: $path"); |
| } |
| elsif (grep(/rm: /i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred attempting to delete file or directory on $computer_short_name: '$path':\ncommand: '$command'\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "deleted '$path' on $computer_short_name"); |
| } |
| |
| # Make sure the path does not exist |
| my $file_exists = $self->file_exists($path, 0); |
| if (!defined($file_exists)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to confirm file doesn't exist on $computer_short_name: '$path'"); |
| return; |
| } |
| elsif ($file_exists) { |
| notify($ERRORS{'WARNING'}, 0, "file was not deleted, it still exists on $computer_short_name: '$path'"); |
| return; |
| } |
| else { |
| #notify($ERRORS{'DEBUG'}, 0, "confirmed file does not exist on $computer_short_name: '$path'"); |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 clear_file |
| |
| Parameters : $file_path |
| Returns : boolean |
| Description : Clears a file on the computer via 'cat /dev/null'. If the file |
| doesn't exist it is not created and true is returned. |
| |
| =cut |
| |
| sub clear_file { |
| my $self = shift; |
| if (ref($self) !~ /module/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get the path argument |
| my $file_path = shift; |
| if (!$file_path) { |
| notify($ERRORS{'WARNING'}, 0, "file path argument was not specified"); |
| return; |
| } |
| |
| my $computer_short_name = $self->data->get_computer_short_name(); |
| |
| # Check if the file exists |
| if (!$self->file_exists($file_path, 0)) { |
| notify($ERRORS{'DEBUG'}, 0, "file not cleared on $computer_short_name because it doesn't exist: $file_path"); |
| return 1; |
| } |
| |
| # Remove any quotes from the beginning and end of the path |
| $file_path = normalize_file_path($file_path); |
| |
| # Escape all spaces in the path |
| my $escaped_file_path = escape_file_path($file_path); |
| |
| # Clear the file |
| my $command = "cat /dev/null > $escaped_file_path"; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to clear file on $computer_short_name: '$file_path'"); |
| return; |
| } |
| elsif ($exit_status ne 0) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred attempting to clear file on $computer_short_name: '$file_path', exit status: $exit_status, command: '$command', output:\n" . join("\n", @$output)); |
| return; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "cleared file on $computer_short_name: '$file_path'"); |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 create_directory |
| |
| Parameters : $directory_path |
| Returns : boolean |
| Description : Creates a directory on the Linux computer as indicated by the |
| $directory_path argument. |
| |
| =cut |
| |
| sub create_directory { |
| my $self = shift; |
| if (ref($self) !~ /module/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get the directory path argument |
| my $directory_path = shift; |
| if (!$directory_path) { |
| notify($ERRORS{'WARNING'}, 0, "directory path argument was not supplied"); |
| return; |
| } |
| |
| # Remove any quotes from the beginning and end of the path |
| $directory_path = normalize_file_path($directory_path); |
| |
| # If ~ is passed as the directory path, skip directory creation attempt |
| # The command will create a /root/~ directory since the path is enclosed in quotes |
| return 1 if $directory_path eq '~'; |
| |
| my $computer_short_name = $self->data->get_computer_short_name(); |
| |
| # Attempt to create the directory |
| my $command = "ls -d --color=never \"$directory_path\" 2>/dev/null || (mkdir -p \"$directory_path\" 2>&1 && ls -d --color=never \"$directory_path\")"; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to create directory on $computer_short_name:\npath: '$directory_path'\ncommand: '$command'"); |
| return; |
| } |
| elsif (grep(/mkdir:/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred attempting to create directory on $computer_short_name: '$directory_path':\ncommand: '$command'\nexit status: $exit_status\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif (grep(/^\s*$directory_path\s*$/, @$output)) { |
| if (grep(/ls:/, @$output)) { |
| notify($ERRORS{'OK'}, 0, "directory created on $computer_short_name: '$directory_path'"); |
| } |
| else { |
| #notify($ERRORS{'OK'}, 0, "directory already exists on $computer_short_name: '$directory_path'"); |
| } |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output returned from command to create directory on $computer_short_name: '$directory_path':\ncommand: '$command'\nexit status: $exit_status\noutput:\n" . join("\n", @$output) . "\nlast line:\n" . string_to_ascii(@$output[-1])); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 move_file |
| |
| Parameters : $source_path, $destination_path |
| Returns : boolean |
| Description : Moves or renames a file on a Linux computer. |
| |
| =cut |
| |
| sub move_file { |
| my $self = shift; |
| if (ref($self) !~ /module/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get the path arguments |
| my $source_path = shift; |
| my $destination_path = shift; |
| if (!$source_path || !$destination_path) { |
| notify($ERRORS{'WARNING'}, 0, "source and destination path arguments were not specified"); |
| return; |
| } |
| |
| # Remove any quotes from the beginning and end of the path |
| $source_path = normalize_file_path($source_path); |
| $destination_path = normalize_file_path($destination_path); |
| |
| # Escape all spaces in the path |
| my $escaped_source_path = escape_file_path($source_path); |
| my $escaped_destination_path = escape_file_path($destination_path); |
| |
| my $computer_short_name = $self->data->get_computer_short_name(); |
| |
| # Execute the command to move the file |
| my $command = "mv -f $escaped_source_path $escaped_destination_path"; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to move file on $computer_short_name:\nsource path: '$source_path'\ndestination path: '$destination_path'\ncommand: '$command'"); |
| return; |
| } |
| elsif (grep(/^mv: /i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to move file on $computer_short_name:\nsource path: '$source_path'\ndestination path: '$destination_path'\ncommand: '$command'\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "moved file on $computer_short_name:\n'$source_path' --> '$destination_path'"); |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_available_space |
| |
| Parameters : $path |
| Returns : If successful: integer |
| If failed: undefined |
| Description : Returns the bytes available in the path specified by the |
| argument. 0 is returned if no space is available. Undefined is |
| returned if an error occurred. |
| |
| =cut |
| |
| sub get_available_space { |
| my $self = shift; |
| if (ref($self) !~ /module/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get the path argument |
| my $path = shift; |
| if (!$path) { |
| notify($ERRORS{'WARNING'}, 0, "path argument was not specified"); |
| return; |
| } |
| |
| my $computer_short_name = $self->data->get_computer_short_name(); |
| |
| # Run stat -f specifying the path as an argument |
| # Don't use df because you can't specify a path under ESX and parsing would be difficult |
| my $command = "stat -f \"$path\""; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to determine available space on $computer_short_name, command: $command"); |
| return; |
| } |
| elsif (grep(/^stat: /i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred running command to determine available space on $computer_short_name\ncommand: $command\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Create an output string from the array of lines for easier regex parsing |
| my $output_string = join("\n", @$output); |
| |
| # Extract the block size value |
| # Search case sensitive for 'Block size:' because the line may also contain "Fundamental block size:" |
| # Some versions of Linux may not display a "Size:" value instead of "Block size:" |
| # Blocks: Total: 8720776 Free: 8288943 Available: 7845951 Size: 4096 |
| my ($block_size) = $output_string =~ /(?:Block size|Size): (\d+)/; |
| if (!$block_size) { |
| notify($ERRORS{'WARNING'}, 0, "unable to locate 'Block size:' or 'Size:' value in stat output:\ncommand: $command\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Extract the blocks free value |
| my ($blocks_available) = $output_string =~ /Blocks:[^\n]*Available: (\d+)/; |
| if (!defined($blocks_available)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to locate blocks available value in stat output:\ncommand: $command\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Calculate the bytes available |
| my $bytes_available = ($block_size * $blocks_available); |
| my $mb_available = format_number(($bytes_available / 1024 / 1024), 2); |
| my $gb_available = format_number(($bytes_available / 1024 / 1024 / 1024), 1); |
| |
| notify($ERRORS{'DEBUG'}, 0, "space available on volume on $computer_short_name containing '$path': " . get_file_size_info_string($bytes_available)); |
| return $bytes_available; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_total_space |
| |
| Parameters : $path |
| Returns : If successful: integer |
| If failed: undefined |
| Description : Returns the total size in bytes of the volume where the path |
| resides specified by the argument. Undefined is returned if an |
| error occurred. |
| |
| =cut |
| |
| sub get_total_space { |
| my $self = shift; |
| if (ref($self) !~ /module/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get the path argument |
| my $path = shift; |
| if (!$path) { |
| notify($ERRORS{'WARNING'}, 0, "path argument was not specified"); |
| return; |
| } |
| |
| my $computer_short_name = $self->data->get_computer_short_name(); |
| |
| # Run stat -f specifying the path as an argument |
| # Don't use df because you can't specify a path under ESX and parsing would be difficult |
| my $command = "stat -f \"$path\""; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to determine available space on $computer_short_name, command: $command"); |
| return; |
| } |
| elsif (grep(/^stat: /i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred running command to determine available space on $computer_short_name\ncommand: $command\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Create an output string from the array of lines for easier regex parsing |
| my $output_string = join("\n", @$output); |
| |
| # Extract the block size value |
| # Search case sensitive for 'Block size:' because the line may also contain "Fundamental block size:" |
| # Some versions of Linux may not display a "Size:" value instead of "Block size:" |
| # Blocks: Total: 8720776 Free: 8288943 Available: 7845951 Size: 4096 |
| my ($block_size) = $output_string =~ /(?:Block size|Size): (\d+)/; |
| if (!$block_size) { |
| notify($ERRORS{'WARNING'}, 0, "unable to locate 'Block size:' or 'Size:' value in stat output:\ncommand: $command\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Extract the blocks total value |
| my ($blocks_total) = $output_string =~ /Blocks:[^\n]*Total: (\d+)/; |
| if (!defined($blocks_total)) { |
| notify($ERRORS{'WARNING'}, 0, "unable to locate blocks total value in stat output:\ncommand: $command\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Calculate the bytes free |
| my $bytes_total = ($block_size * $blocks_total); |
| my $mb_total = format_number(($bytes_total / 1024 / 1024), 2); |
| my $gb_total = format_number(($bytes_total / 1024 / 1024 / 1024), 1); |
| |
| notify($ERRORS{'DEBUG'}, 0, "total size of volume on $computer_short_name containing '$path': " . get_file_size_info_string($bytes_total)); |
| return $bytes_total; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 copy_file_from |
| |
| Parameters : $source_file_path, $destination_file_path |
| Returns : boolean |
| Description : Copies file(s) from the Linux computer to the management node. |
| |
| =cut |
| |
| sub copy_file_from { |
| my $self = shift; |
| if (ref($self) !~ /VCL::Module/) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| # Get the source and destination arguments |
| my ($source_file_path, $destination_file_path) = @_; |
| if (!$source_file_path || !$destination_file_path) { |
| notify($ERRORS{'WARNING'}, 0, "source and destination file path arguments were not specified"); |
| return; |
| } |
| |
| # Get the computer name |
| my $computer_node_name = $self->data->get_computer_node_name() || return; |
| |
| # Get the destination parent directory path and create the directory on the management node |
| my $destination_directory_path = parent_directory_path($destination_file_path); |
| if (!$destination_directory_path) { |
| notify($ERRORS{'WARNING'}, 0, "unable to determine destination parent directory path: $destination_file_path"); |
| return; |
| } |
| create_management_node_directory($destination_directory_path) || return; |
| |
| # Get the identity keys used by the management node |
| my $management_node_keys = $self->data->get_management_node_keys() || ''; |
| |
| # Run the SCP command |
| if (run_scp_command("$computer_node_name:\"$source_file_path\"", $destination_file_path, $management_node_keys)) { |
| notify($ERRORS{'DEBUG'}, 0, "copied file from $computer_node_name to management node: $computer_node_name:'$source_file_path' --> '$destination_file_path'"); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to copy file from $computer_node_name to management node: $computer_node_name:'$source_file_path' --> '$destination_file_path'"); |
| return; |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_file_size |
| |
| Parameters : @file_paths |
| Returns : integer or array |
| Description : Determines the size of the file specified by the file path |
| argument in bytes. The file path argument may be a directory or |
| contain wildcards. Directories are processed recursively. |
| |
| When called in sclar context, the actual bytes used on the disk by the file |
| is returned. This correlates to the size reported by the `du` |
| command. This value is not the same as what is reported by the `ls` |
| command. This is important when determining the size of |
| compressed files or thinly-provisioned virtual disk images. |
| |
| When called in array context, 3 values are returned: |
| [0] bytes used (`du` size) |
| [1] bytes reserved (`ls` size) |
| [2] file count |
| |
| =cut |
| |
| sub get_file_size { |
| 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 $calling_sub = (caller(1))[3] || ''; |
| |
| # Get the path argument |
| my @file_paths = @_; |
| if (!@file_paths) { |
| notify($ERRORS{'WARNING'}, 0, "file paths argument was not specified"); |
| return; |
| } |
| |
| # Get the computer name |
| my $computer_node_name = $self->data->get_computer_node_name() || return; |
| |
| my $file_count = 0; |
| my $total_bytes_reserved = 0; |
| my $total_bytes_used = 0; |
| |
| for my $file_path (@file_paths) { |
| # Normalize the file path |
| $file_path = normalize_file_path($file_path); |
| |
| # Escape all spaces in the path |
| my $escaped_file_path = escape_file_path($file_path); |
| |
| # Run stat rather than du because du is not available on VMware ESX |
| # -L Dereference links |
| # %F File type |
| # %n File name |
| # %b Number of blocks allocated (see %B) |
| # %B The size in bytes of each block reported by %b |
| # %s Total size, in bytes |
| |
| my $command = 'stat -L -c "%F:%n:%s:%b:%B" ' . $escaped_file_path; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to determine file size on $computer_node_name: $file_path\ncommand: '$command'"); |
| return; |
| } |
| elsif (grep(/no such file/i, @$output)) { |
| if ($calling_sub !~ /get_file_size/) { |
| notify($ERRORS{'DEBUG'}, 0, "unable to determine size of file on $computer_node_name because it does not exist: $file_path\ncommand: '$command'"); |
| } |
| return; |
| } |
| elsif (grep(/^stat:/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred attempting to determine file size on $computer_node_name: $file_path\ncommand: $command\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Loop through the stat output lines |
| for my $line (@$output) { |
| # Take the stat output line apart |
| my ($type, $path, $file_bytes, $file_blocks, $block_size) = split(/:/, $line); |
| if (!defined($type) || !defined($file_bytes) || !defined($file_blocks) || !defined($block_size) || !defined($path)) { |
| notify($ERRORS{'WARNING'}, 0, "unexpected output returned from stat, line: $line\ncommand: $command\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Add the size to the total if the type is file |
| if ($type =~ /file/) { |
| $file_count++; |
| |
| my $file_bytes_allocated = ($file_blocks * $block_size); |
| |
| $total_bytes_used += $file_bytes_allocated; |
| $total_bytes_reserved += $file_bytes; |
| } |
| elsif ($type =~ /directory/) { |
| $path =~ s/[\\\/\*]+$//g; |
| #notify($ERRORS{'DEBUG'}, 0, "recursively retrieving size of files under directory: '$path'"); |
| my ($subdirectory_bytes_allocated, $subdirectory_bytes_used, $subdirectory_file_count) = $self->get_file_size("$path/*"); |
| |
| # Values will be null if there are no files under the subdirectory |
| if (!defined($subdirectory_bytes_allocated)) { |
| next; |
| } |
| |
| $file_count += $subdirectory_file_count; |
| $total_bytes_reserved += $subdirectory_bytes_used; |
| $total_bytes_used += $subdirectory_bytes_allocated; |
| } |
| } |
| } |
| |
| if ($calling_sub !~ /get_file_size/) { |
| notify($ERRORS{'DEBUG'}, 0, "size of " . join(", ", @file_paths) . " on $computer_node_name:\n" . |
| "file count: $file_count\n" . |
| "reserved: " . get_file_size_info_string($total_bytes_reserved) . "\n" . |
| "used: " . get_file_size_info_string($total_bytes_used)); |
| } |
| |
| if (wantarray) { |
| return ($total_bytes_used, $total_bytes_reserved, $file_count); |
| } |
| else { |
| return $total_bytes_used; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_file_permissions |
| |
| Parameters : $file_path, $chmod_mode, $recursive (optional) |
| Returns : boolean |
| Description : Calls chmod to set the file permissions on the Linux computer. |
| The $chmod_mode argument may be any valid chmod mode (+rw, 0755, |
| etc). The $recursive argument is optional. The default is false. |
| |
| =cut |
| |
| sub set_file_permissions { |
| 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 arguments |
| my $path = shift; |
| if (!defined($path)) { |
| notify($ERRORS{'WARNING'}, 0, "path argument was not specified"); |
| return; |
| } |
| |
| # Escape the file path in case it contains spaces |
| $path = escape_file_path($path); |
| |
| my $chmod_mode = shift; |
| if (!defined($chmod_mode)) { |
| notify($ERRORS{'WARNING'}, 0, "chmod mode argument was not specified"); |
| return; |
| } |
| |
| my $recursive = shift; |
| my $recursive_string = ''; |
| $recursive_string = "recursively " if $recursive; |
| |
| # Get the computer short and hostname |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Run the chmod command |
| my $command = "chmod "; |
| $command .= "-R " if $recursive; |
| $command .= "$chmod_mode $path"; |
| |
| my ($exit_status, $output) = $self->execute($command, 0); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to " . $recursive_string . "set file permissions on $computer_node_name: '$command'"); |
| return; |
| } |
| elsif (grep(/No such file or directory/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to " . $recursive_string . "set permissions of '$path' to '$chmod_mode' on $computer_node_name because the file does not exist, command: '$command', output:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif (grep(/^chmod:/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred attempting to " . $recursive_string . "set permissions of '$path' to '$chmod_mode' on $computer_node_name, command: '$command'\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, $recursive_string . "set permissions of '$path' to '$chmod_mode' on $computer_node_name"); |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 set_file_owner |
| |
| Parameters : $file_path, $owner, $group, $recursive (optional) |
| Returns : boolean |
| Description : Calls chown to set the owner of a file or directory. |
| |
| =cut |
| |
| sub set_file_owner { |
| 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 arguments |
| my $path = shift; |
| if (!defined($path)) { |
| notify($ERRORS{'WARNING'}, 0, "path argument was not specified"); |
| return; |
| } |
| |
| # Escape the file path in case it contains spaces |
| $path = escape_file_path($path); |
| |
| my $owner = shift; |
| if (!defined($owner)) { |
| notify($ERRORS{'WARNING'}, 0, "owner argument was not specified"); |
| return; |
| } |
| |
| my $group = shift; |
| $owner .= ":$group" if $group; |
| |
| my $recursive = shift; |
| $recursive = 1 if !defined($recursive); |
| |
| my $recursive_string = ''; |
| $recursive_string = "recursively " if $recursive; |
| |
| # Get the computer short and hostname |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Run the chown command |
| my $command = "chown "; |
| $command .= "-R " if $recursive; |
| $command .= "$owner $path"; |
| |
| my ($exit_status, $output) = $self->execute($command, 0); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to " . $recursive_string . "set file owner on $computer_node_name: '$command'"); |
| return; |
| } |
| elsif (grep(/No such file or directory/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to " . $recursive_string . "set owner of '$path' to '$owner' on $computer_node_name because the file does not exist, command: '$command', output:\n" . join("\n", @$output)); |
| return; |
| } |
| elsif (grep(/^chown:/i, @$output)) { |
| notify($ERRORS{'WARNING'}, 0, "error occurred attempting to " . $recursive_string . "set owner of '$path' to '$owner' on $computer_node_name, command: '$command'\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, $recursive_string . "set owner of '$path' to '$owner' on $computer_node_name"); |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 activate_interfaces |
| |
| Parameters : none |
| Returns : true |
| Description : Finds all networking interfaces with an active link. Checks if an |
| ifcfg-eth* file exists for the interface. An ifcfg-eth* file is |
| generated if it does not exist using DHCP and the interface is |
| brought up. This is useful if additional interfaces are added by |
| the provisioning module when an image is loaded. |
| |
| =cut |
| |
| sub activate_interfaces { |
| 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 0; |
| } |
| |
| # Run 'ip link' to find all interfaces with links |
| my $command = "ip link"; |
| notify($ERRORS{'DEBUG'}, 0, "attempting to find network interfaces with an active link"); |
| my ($exit_status, $output) = $self->execute($command, 1); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to find network interfaces with an active link:\n$command"); |
| return; |
| } |
| |
| # Extract the interface names from the 'ip link' output |
| my @interface_names = grep {/^\d+:\s+(eth\d+)/; $_ = $1} @$output; |
| notify($ERRORS{'DEBUG'}, 0, "found interface names:\n" . join("\n", @interface_names)); |
| |
| # Find existing ifcfg-eth* files |
| my $ifcfg_directory = '/etc/sysconfig/network-scripts'; |
| my @ifcfg_paths = $self->find_files($ifcfg_directory, 'ifcfg-eth*'); |
| notify($ERRORS{'DEBUG'}, 0, "found existing ifcfg-eth* files:\n" . join("\n", @ifcfg_paths)); |
| |
| # Loop through the linked interfaces |
| for my $interface_name (@interface_names) { |
| my $ifcfg_path = "$ifcfg_directory/ifcfg-$interface_name"; |
| |
| # Check if an ifcfg-eth* file already exists for the interface |
| if (grep(/$ifcfg_path/, @ifcfg_paths)) { |
| notify($ERRORS{'DEBUG'}, 0, "ifcfg file already exists for $interface_name"); |
| next; |
| } |
| |
| notify($ERRORS{'DEBUG'}, 0, "ifcfg file does not exist for $interface_name"); |
| |
| # Assemble the contents of the ifcfg-eth* file for the interface |
| my $ifcfg_contents = <<EOF; |
| DEVICE=$interface_name |
| BOOTPROTO=dhcp |
| STARTMODE=onboot |
| ONBOOT=yes |
| EOF |
| |
| # Create the ifcfg file |
| if (!$self->create_text_file($ifcfg_path, $ifcfg_contents)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create $ifcfg_path for interface: $interface_name"); |
| return; |
| } |
| |
| $self->start_network_interface($interface_name); |
| } |
| |
| return 1; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 get_network_configuration |
| |
| Parameters : $no_cache (optional) |
| Returns : hash reference |
| Description : Retrieves the network configuration on the Linux computer and |
| constructs a hash. The hash reference returned is formatted as |
| follows: |
| { |
| "eth0" => { |
| "broadcast_address" => "10.25.15.255", |
| "ip_address" => { |
| "10.25.10.194" => "255.255.240.0" |
| }, |
| "name" => "eth0", |
| "physical_address" => "00:50:56:23:00:bc" |
| }, |
| "eth1" => { |
| "name" => "eth1", |
| "physical_address" => "00:50:56:23:00:bd" |
| }, |
| "lo" => { |
| "name" => "lo" |
| }, |
| "xbr1" => { |
| "bridge" => { |
| "bridge_id" => "8000.0050562300bd", |
| "interfaces" => [ |
| "eth1" |
| ], |
| "stp_enabled" => "8000.0050562300bd" |
| }, |
| "broadcast_address" => "192.168.53.255", |
| "default_gateway" => "192.168.53.254", |
| "ip_address" => { |
| "152.46.18.135" => "255.255.248.0" |
| }, |
| "name" => "xbr1", |
| "physical_address" => "00:50:56:23:00:bd" |
| } |
| } |
| |
| =cut |
| |
| sub get_network_configuration { |
| 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 $no_cache = shift || 0; |
| notify($ERRORS{'DEBUG'}, 0, "attempting to retrieve network configuration, no cache: $no_cache"); |
| |
| # Delete previously retrieved data if $no_cache was specified |
| if ($no_cache) { |
| delete $self->{network_configuration}; |
| } |
| elsif ($self->{network_configuration}) { |
| return $self->{network_configuration} |
| } |
| |
| # Run ipconfig |
| my $ifconfig_command = "/sbin/ifconfig -a"; |
| my ($ifconfig_exit_status, $ifconfig_output) = $self->execute($ifconfig_command); |
| if (!defined($ifconfig_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to retrieve network configuration: $ifconfig_command"); |
| return; |
| } |
| #notify($ERRORS{'DEBUG'}, 0, "ifconfig output:\n" . join("\n", @$ifconfig_output)); |
| |
| # Loop through the ifconfig output lines |
| my $network_configuration; |
| my $interface_name; |
| for my $ifconfig_line (@$ifconfig_output) { |
| # Extract the interface name from the Link line: |
| # eth2 Link encap:Ethernet HWaddr 00:0C:29:78:77:AB |
| #eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 |
| #if ($ifconfig_line =~ /^([^\s]+).*Link/) { |
| if ($ifconfig_line =~ /^([^\s:]+).*(Link|flags)/) { |
| $interface_name = $1; |
| $network_configuration->{$interface_name}{name} = $interface_name; |
| } |
| |
| # Skip to the next line if the interface name has not been determined yet |
| next if !$interface_name; |
| |
| # Parse the HWaddr line: |
| # eth2 Link encap:Ethernet HWaddr 00:0C:29:78:77:AB |
| #if ($ifconfig_line =~ /HWaddr\s+([\w:]+)/) { |
| if ($ifconfig_line =~ /(ether|HWaddr)\s+([\w:]+)/) { |
| $network_configuration->{$interface_name}{physical_address} = lc($2); |
| } |
| |
| # Parse the IP address line: |
| # inet addr:10.10.4.35 Bcast:10.10.15.255 Mask:255.255.240.0 |
| if ($ifconfig_line =~ /inet addr:([\d\.]+)\s+Bcast:([\d\.]+)\s+Mask:([\d\.]+)/) { |
| $network_configuration->{$interface_name}{ip_address}{$1} = $3; |
| $network_configuration->{$interface_name}{broadcast_address} = $2; |
| } |
| |
| # inet 10.25.14.3 netmask 255.255.240.0 broadcast 10.25.15.255 |
| if ($ifconfig_line =~ /inet\s+([\d\.]+)\s+netmask\s+([\d\.]+)\s+broadcast\s+([\d\.]+)/) { |
| $network_configuration->{$interface_name}{ip_address}{$1} = $2; |
| $network_configuration->{$interface_name}{broadcast_address} = $3; |
| } |
| } |
| |
| |
| # Run route |
| my $route_command = "/sbin/route -n"; |
| my ($route_exit_status, $route_output) = $self->execute($route_command); |
| if (!defined($route_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to run command to retrieve routing configuration: $route_command"); |
| return; |
| } |
| |
| # Loop through the route output lines |
| for my $route_line (@$route_output) { |
| my ($default_gateway, $interface_name) = $route_line =~ /^0\.0\.0\.0\s+([\d\.]+).*\s([^\s]+)$/g; |
| |
| if (!defined($interface_name) || !defined($default_gateway)) { |
| #notify($ERRORS{'DEBUG'}, 0, "route output line does not contain a default gateway: '$route_line'"); |
| } |
| elsif (!defined($network_configuration->{$interface_name})) { |
| notify($ERRORS{'WARNING'}, 0, "found default gateway for '$interface_name' interface but the network configuration for '$interface_name' was not previously retrieved, route output:\n" . join("\n", @$route_output) . "\nnetwork configuation:\n" . format_data($network_configuration)); |
| } |
| elsif (defined($network_configuration->{$interface_name}{default_gateway}) && $default_gateway ne $network_configuration->{$interface_name}{default_gateway}) { |
| notify($ERRORS{'WARNING'}, 0, "multiple default gateways are configured for '$interface_name' interface, route output:\n" . join("\n", @$route_output)); |
| } |
| else { |
| $network_configuration->{$interface_name}{default_gateway} = $default_gateway; |
| notify($ERRORS{'DEBUG'}, 0, "found default route configured for '$interface_name' interface: $default_gateway"); |
| } |
| } |
| |
| # Check if bridge is configured |
| my $network_bridge_info = $self->get_network_bridge_info(); |
| for my $bridge_name (keys %$network_bridge_info) { |
| # Add bridge info under 'bridge' key for the bridge |
| if (defined($network_configuration->{$bridge_name})) { |
| $network_configuration->{$bridge_name}{bridge} = $network_bridge_info->{$bridge_name}; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "'$bridge_name' bridge was not found in 'ifconfig' output:" . |
| "ifconfig output:\n" . join("\n", @$ifconfig_output) . "\n" . |
| "network bridge info:\n" . format_data($network_bridge_info) |
| ); |
| } |
| |
| # Add name of bridge to 'master' key for the physical interface |
| for my $bridge_interface_name (@{$network_bridge_info->{$bridge_name}{interfaces}}) { |
| if (defined($network_configuration->{$bridge_interface_name})) { |
| $network_configuration->{$bridge_interface_name}{master} = $bridge_name; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "'$bridge_name' bridge contains '$bridge_interface_name' interface but '$bridge_interface_name' was not found in 'ifconfig' output:\n" . |
| "ifconfig output:\n" . join("\n", @$ifconfig_output) . "\n" . |
| "network bridge info:\n" . format_data($network_bridge_info) |
| ); |
| } |
| } |
| } |
| |
| $self->{network_configuration} = $network_configuration; |
| #can produce large output, if you need to monitor the configuration setting uncomment the below output statement |
| notify($ERRORS{'DEBUG'}, 0, "retrieved network configuration:\n" . format_data($self->{network_configuration})); |
| return $self->{network_configuration}; |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 reboot |
| |
| Parameters : none |
| Returns : boolean |
| Description : Attempts to gracefully reboot the computer by executing |
| 'shutdown -r now' command. Attempts to detect reboot began and |
| completed. If this fails or if the computer is not responding to |
| SSH, the provisioning module will attempt to forcefully perform a |
| hard reset of the computer. |
| |
| =cut |
| |
| sub reboot { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| notify($ERRORS{'DEBUG'}, 0, "rebooting $computer_node_name and waiting for SSH to become active"); |
| |
| my $reboot_start_time = time(); |
| |
| # Check if computer responds to ssh before preparing for reboot |
| if ($self->wait_for_ssh(0)) { |
| my $reboot_command = '/sbin/shutdown -r now &'; |
| notify($ERRORS{'DEBUG'}, 0, "attempting to gracefully reboot $computer_node_name by executing '$reboot_command'"); |
| my ($reboot_exit_status, $reboot_output) = $self->execute( |
| { |
| command => $reboot_command, |
| timeout => 30, |
| max_attempts => 1, |
| display_output => 0, |
| } |
| ); |
| |
| if ($self->wait_for_reboot()) { |
| my $reboot_duration = (time() - $reboot_start_time); |
| notify($ERRORS{'OK'}, 0, "gracefully rebooted $computer_node_name, took $reboot_duration seconds"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "did not detect $computer_node_name rebooting after executing '$reboot_command', attempting hard reset using the provisioning module"); |
| } |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "$computer_node_name is not responding to SSH, graceful reboot cannot be performed, attempting hard reset using the provisioning module"); |
| } |
| |
| $self->provisioner->power_reset() || return; |
| if ($self->wait_for_reboot()) { |
| my $reboot_duration = (time() - $reboot_start_time); |
| notify($ERRORS{'OK'}, 0, "hard reset of $computer_node_name complete, took $reboot_duration seconds"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "$computer_node_name may not have rebooted, did not detect reboot after attempting hard reset using the provisioning module"); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 shutdown |
| |
| Parameters : none |
| Returns : boolean |
| Description : Attempts to gracefully shut down the computer by executing the |
| shutdown command. Waits for provisioning module to report that |
| the computer is off. If this fails or if the computer is not |
| responding to SSH, the provisioning module will attempt to |
| forcefully power off the computer. |
| |
| =cut |
| |
| sub shutdown { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Check if computer responds to ssh before preparing for shut down |
| if ($self->wait_for_ssh(0)) { |
| my $shutdown_command = '/sbin/shutdown -h now &'; |
| notify($ERRORS{'DEBUG'}, 0, "attempting to gracefully shut down $computer_node_name by executing '$shutdown_command'"); |
| my ($exit_status, $output) = $self->execute( |
| { |
| command => $shutdown_command, |
| timeout => 30, |
| max_attempts => 1, |
| display_output => 0, |
| } |
| ); |
| |
| if ($self->provisioner->wait_for_power_off(300, 10)) { |
| notify($ERRORS{'OK'}, 0, "gracefully shut down $computer_node_name by executing the OS's shutdown command"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "$computer_node_name is still on after executing shutdown command, attempting to power off the computer using the provisioning module"); |
| } |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "$computer_node_name is NOT responding to SSH, attempting to power off the computer using the provisioning module"); |
| } |
| |
| $self->provisioner->power_off() || return; |
| if ($self->provisioner->wait_for_power_off(300, 10)) { |
| notify($ERRORS{'OK'}, 0, "forcefully powered off $computer_node_name using the provisioning module"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to shut down $computer_node_name, computer is still on after attempting to power off the computer using the provisioning module"); |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 hibernate |
| |
| Parameters : none |
| Returns : boolean |
| Description : Hibernates the computer. |
| |
| =cut |
| |
| sub hibernate { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $command = 'echo disk > /sys/power/state &'; |
| my ($exit_status, $output) = $self->execute($command); |
| if (!defined($output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to hibernate $computer_node_name"); |
| return; |
| } |
| elsif ($exit_status eq 0) { |
| notify($ERRORS{'OK'}, 0, "executed command to hibernate $computer_node_name: $command" . (scalar(@$output) ? "\noutput:\n" . join("\n", @$output) : '')); |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to hibernate $computer_node_name, exit status: $exit_status, command:\n$command\noutput:\n" . join("\n", @$output)); |
| return; |
| } |
| |
| # Wait for computer to power off |
| my $power_off = $self->provisioner->wait_for_power_off(300, 5); |
| if (!defined($power_off)) { |
| # wait_for_power_off result will be undefined if the provisioning module doesn't implement a power_status subroutine |
| notify($ERRORS{'OK'}, 0, "unable to determine power status of $computer_node_name from provisioning module, sleeping 1 minute to allow computer time to hibernate"); |
| sleep 60; |
| return 1; |
| } |
| elsif (!$power_off) { |
| notify($ERRORS{'WARNING'}, 0, "$computer_node_name never powered off after executing hibernate command: $command"); |
| return; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "$computer_node_name powered off after executing hibernate command"); |
| return 1; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 create_user |
| |
| Parameters : $argument_hash_ref |
| Returns : boolean |
| Description : Creates a user on the computer. The argument hash reference |
| should be constructed as follows: |
| { |
| username => $username, |
| password => $password, (optional) |
| root_access => $root_access, |
| uid => $uid, (optional) |
| ssh_public_keys => $ssh_public_keys, (optional) |
| }); |
| |
| =cut |
| |
| sub create_user { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| my $user_parameters = shift; |
| if (!$user_parameters) { |
| notify($ERRORS{'WARNING'}, 0, "unable to create user, user parameters argument was not provided"); |
| return; |
| } |
| elsif (!ref($user_parameters) || ref($user_parameters) ne 'HASH') { |
| notify($ERRORS{'WARNING'}, 0, "unable to create user, argument provided is not a hash reference"); |
| return; |
| } |
| |
| my $username = $user_parameters->{username}; |
| if (!defined($username)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create user on $computer_node_name, argument hash does not contain a 'username' key:\n" . format_data($user_parameters)); |
| return; |
| } |
| |
| my $root_access = $user_parameters->{root_access}; |
| if (!defined($root_access)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to create user on $computer_node_name, argument hash does not contain a 'root_access' key:\n" . format_data($user_parameters)); |
| return; |
| } |
| |
| my $password = $user_parameters->{password}; |
| my $uid = $user_parameters->{uid}; |
| my $ssh_public_keys = $user_parameters->{ssh_public_keys}; |
| |
| # If user account does not already exist - create it, then |
| # -- Set password if using local authentication |
| # -- update sudoers file if root access allowed |
| # -- process connect_methods_access |
| |
| if (!$self->user_exists($username)) { |
| |
| notify($ERRORS{'DEBUG'}, 0, "creating user on $computer_node_name:\n" . |
| "username: $username\n" . |
| "password: " . (defined($password) ? $password : '<not set>') . "\n" . |
| "UID: " . ($uid ? $uid : '<not set>') . "\n" . |
| "root access: " . ($root_access ? 'yes' : 'no') . "\n" . |
| "SSH public keys: " . (defined($ssh_public_keys) ? $ssh_public_keys : '<not set>') |
| ); |
| |
| my $home_directory_root = "/home"; |
| my $home_directory_path = "$home_directory_root/$username"; |
| my $home_directory_on_local_disk = $self->is_file_on_local_disk($home_directory_root); |
| if ($home_directory_on_local_disk) { |
| my $useradd_command = "/usr/sbin/useradd -s /bin/bash -m -d /home/$username -g vcl"; |
| $useradd_command .= " -u $uid" if ($uid); |
| $useradd_command .= " $username"; |
| |
| my ($useradd_exit_status, $useradd_output) = $self->execute($useradd_command); |
| if (!defined($useradd_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to add user '$username' to $computer_node_name: '$useradd_command'"); |
| return; |
| } |
| elsif (grep(/^useradd: /, @$useradd_output)) { |
| notify($ERRORS{'WARNING'}, 0, "warning detected on add user '$username' to $computer_node_name\ncommand: '$useradd_command'\noutput:\n" . join("\n", @$useradd_output)); |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "added user '$username' to $computer_node_name, output:" . (scalar(@$useradd_output) ? "\n" . join("\n", @$useradd_output) : ' <none>')); |
| } |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "$home_directory_path is NOT on local disk, skipping useradd attempt"); |
| } |
| } |
| |
| # Set the password |
| if ($password) { |
| # Set password |
| if (!$self->set_password($username, $password)) { |
| notify($ERRORS{'CRITICAL'}, 0, "failed to set password of user '$username' on $computer_node_name"); |
| return; |
| } |
| } |
| |
| # Process connect_methods |
| if ($self->can("grant_connect_method_access")) { |
| if (!$self->grant_connect_method_access({ |
| username => $username, |
| uid => $uid, |
| ssh_public_keys => $ssh_public_keys, |
| })) { |
| notify($ERRORS{'WARNING'}, 0, "failed to process grant_connect_method_access for $username"); |
| } |
| } |
| |
| # Add user to sudoers if necessary |
| if ($root_access) { |
| if (!$self->grant_administrative_access($username)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to process grant_administrative_access for $username"); |
| return; |
| } |
| } |
| else { |
| # Make sure user does not have root access |
| $self->revoke_administrative_access($username); |
| } |
| |
| return 1; |
| } ## end sub create_user |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 grant_administrative_access |
| |
| Parameters : $username |
| Returns : boolean |
| Description : Adds the user to the sudoers file. |
| |
| =cut |
| |
| sub grant_administrative_access { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $username = shift; |
| if (!defined($username)) { |
| notify($ERRORS{'WARNING'}, 0, "username argument was not supplied"); |
| return; |
| } |
| |
| my $timestamp = makedatestring(); |
| |
| my $sudoers_file_path = '/etc/sudoers'; |
| |
| my @existing_lines = $self->get_file_contents($sudoers_file_path); |
| my @matching_lines; |
| for my $line (@existing_lines) { |
| if ($line =~ /^\s*$username\s/) { |
| push @matching_lines, $line; |
| } |
| } |
| if (@matching_lines) { |
| notify($ERRORS{'DEBUG'}, 0, "$username was previously added to $sudoers_file_path:\n" . join("\n", @matching_lines)); |
| return 1; |
| } |
| |
| my $sudoers_line = "$username ALL= NOPASSWD: ALL\t# Added by VCL, ($timestamp)"; |
| if ($self->append_text_file($sudoers_file_path, $sudoers_line)) { |
| notify($ERRORS{'DEBUG'}, 0, "appended line to $sudoers_file_path: '$sudoers_line'"); |
| return 1; |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "failed to append line to $sudoers_file_path: '$sudoers_line'"); |
| return 0; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 revoke_administrative_access |
| |
| Parameters : $username |
| Returns : boolean |
| Description : Removes all entries from the sudoers file for the user. |
| |
| =cut |
| |
| sub revoke_administrative_access { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return; |
| } |
| |
| my $username = shift; |
| if (!defined($username)) { |
| notify($ERRORS{'WARNING'}, 0, "username argument was not supplied"); |
| return; |
| } |
| |
| my $sudoers_file_path = '/etc/sudoers'; |
| |
| # Remove lines from sudoers |
| if (defined($self->remove_lines_from_file($sudoers_file_path, "^[\\s#]*$username\\s"))) { |
| return 1; |
| } |
| else { |
| return; |
| } |
| } |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 delete_user |
| |
| Parameters : $username |
| Returns : |
| Description : |
| |
| =cut |
| |
| sub delete_user { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| # Make sure the user login ID was passed |
| my $username = shift; |
| $username = $self->data->get_user_login_id() if (!$username); |
| if (!$username) { |
| notify($ERRORS{'WARNING'}, 0, "user could not be determined"); |
| return; |
| } |
| |
| my $computer_node_name = $self->data->get_computer_node_name(); |
| |
| # Make sure the user exists |
| if (!$self->user_exists($username)) { |
| notify($ERRORS{'DEBUG'}, 0, "user NOT deleted from $computer_node_name because it does not exist: $username"); |
| |
| # Make sure user does not exist in sudoers |
| $self->revoke_administrative_access($username); |
| |
| return 1; |
| } |
| |
| # Check if the user is logged in |
| if ($self->user_logged_in($username)) { |
| if (!$self->logoff_user($username)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete user $username from $computer_node_name, user appears to be logged in but could NOT be logged off"); |
| return; |
| } |
| } |
| |
| # Determine if home directory is on a local device or network share |
| my $home_directory_path = "/home/$username"; |
| my $home_directory_on_local_disk = $self->is_file_on_local_disk($home_directory_path); |
| |
| # Assemble the userdel command |
| my $userdel_command = "/usr/sbin/userdel"; |
| |
| my $delete_home_directory = 0; |
| |
| if ($home_directory_on_local_disk) { |
| $delete_home_directory = 1; |
| |
| # Fetch exclude_list |
| my @exclude_list = $self->get_exclude_list(); |
| if ((grep(/\/home\/$username/, @exclude_list))) { |
| notify($ERRORS{'DEBUG'}, 0, "home directory will NOT be deleted: $home_directory_path"); |
| $delete_home_directory = 0; |
| } |
| else { |
| # Make sure no NFS shares are mounted under home directory |
| my @nfs_mount_strings = $self->get_nfs_mount_strings(); |
| for my $nfs_mount_string (@nfs_mount_strings) { |
| my ($nfs_remote_host, $nfs_remote_path, $nfs_local_path) = $nfs_mount_string =~ |
| / |
| ^ |
| ([^:]+) # Remote hostname or IP address |
| : |
| (\/.+) # Remote path |
| \s+ |
| (\/.+) # Local path |
| \s+ |
| nfs\d* # ' nfs ' or ' nfs4 ' |
| \s+ |
| /gx; |
| |
| if ($nfs_local_path) { |
| if ($nfs_local_path =~ /^$home_directory_path/) { |
| notify($ERRORS{'WARNING'}, 0, "home directory will NOT be deleted, NFS share is mounted under it\n" . |
| "NFS mount string : $nfs_mount_string\n" . |
| "home directory path : $home_directory_path\n" . |
| "local mount path : $nfs_local_path" |
| ); |
| $delete_home_directory = 0; |
| last; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "NFS share is NOT mounted under home directory\n" . |
| "NFS mount string : $nfs_mount_string\n" . |
| "home directory path : $home_directory_path\n" . |
| "local mount path : $nfs_local_path" |
| ); |
| } |
| } |
| else { |
| notify($ERRORS{'WARNING'}, 0, "home directory will NOT be deleted: $home_directory_path, failed to parse NFS mount string: $nfs_mount_string"); |
| $delete_home_directory = 0; |
| last; |
| } |
| } |
| } |
| } |
| |
| if ($delete_home_directory) { |
| notify($ERRORS{'DEBUG'}, 0, "home directory will be deleted: $home_directory_path"); |
| $userdel_command .= ' -r'; |
| } |
| $userdel_command .= " $username"; |
| |
| # Call userdel to delete the user |
| my ($userdel_exit_status, $userdel_output) = $self->execute($userdel_command); |
| if (!defined($userdel_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to delete user from $computer_node_name: $username"); |
| return; |
| } |
| elsif (grep(/does not exist/i, @$userdel_output)) { |
| notify($ERRORS{'DEBUG'}, 0, "user '$username' NOT deleted from $computer_node_name because it does not exist"); |
| } |
| elsif (grep(/not found/i, @$userdel_output)) { |
| notify($ERRORS{'DEBUG'}, 0, "userdel warning '$username' $computer_node_name :\n" . join("\n", @$userdel_output)); |
| } |
| elsif (grep(/userdel: /i, @$userdel_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete user '$username' from $computer_node_name, command: '$userdel_command', exit status: $userdel_exit_status, output:\n" . join("\n", @$userdel_output)); |
| return; |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "deleted user '$username' from $computer_node_name"); |
| } |
| |
| # Call groupdel to delete the user's group |
| my $groupdel_command = "/usr/sbin/groupdel $username"; |
| my ($groupdel_exit_status, $groupdel_output) = $self->execute($groupdel_command); |
| if (!defined($groupdel_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to execute command to delete group from $computer_node_name: $username"); |
| return; |
| } |
| elsif (grep(/does not exist/i, @$groupdel_output)) { |
| notify($ERRORS{'DEBUG'}, 0, "group '$username' NOT deleted from $computer_node_name because it does not exist"); |
| } |
| elsif (grep(/groupdel: /i, @$groupdel_output)) { |
| notify($ERRORS{'WARNING'}, 0, "failed to delete group '$username' from $computer_node_name, command: '$groupdel_command', output:\n" . join("\n", @$groupdel_output)); |
| } |
| else { |
| notify($ERRORS{'OK'}, 0, "deleted group '$username' from $computer_node_name"); |
| } |
| |
| # Remove username from AllowUsers lines in ssh/external_sshd_config |
| my $external_sshd_config_file_path = '/etc/ssh/external_sshd_config'; |
| my @original_lines = $self->get_file_contents($external_sshd_config_file_path); |
| my @modified_lines; |
| my $new_file_contents; |
| for my $line (@original_lines) { |
| if ($line =~ /AllowUsers.*\s$username(\s|$)/) { |
| push @modified_lines, $line; |
| $line =~ s/\s*$username//g; |
| # If user was only username listed on line, don't add empty AllowUsers line back to file |
| if ($line !~ /AllowUsers\s+\w/) { |
| next; |
| } |
| } |
| $new_file_contents .= "$line\n"; |
| } |
| if (@modified_lines) { |
| notify($ERRORS{'OK'}, 0, "removing or modifying AllowUsers lines in $external_sshd_config_file_path:\n" . join("\n", @modified_lines)); |
| $self->create_text_file($external_sshd_config_file_path, $new_file_contents) || return; |
| } |
| else { |
| notify($ERRORS{'DEBUG'}, 0, "no AllowUsers lines were found in $external_sshd_config_file_path containing '$username'"); |
| } |
| |
| # Remove lines from sudoers |
| $self->revoke_administrative_access($username); |
| |
| return 1; |
| } ## end sub delete_user |
| |
| #////////////////////////////////////////////////////////////////////////////// |
| |
| =head2 is_file_on_local_disk |
| |
| Parameters : $file_path |
| Returns : boolean |
| Description : Determines if the file or directory is located on a local disk or |
| network share. |
| |
| =cut |
| |
| sub is_file_on_local_disk { |
| my $self = shift; |
| if (ref($self) !~ /linux/i) { |
| notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method"); |
| return 0; |
| } |
| |
| my $file_path = shift; |
| if (!$file_path) { |
| notify($ERRORS{'WARNING'}, 0, "file path argument was not specified"); |
| return; |
| } |
| |
| my $computer_name = $self->data->get_computer_short_name(); |
| |
| # Run df to determine if file is on a local device or network share |
| my $df_command = "df -T -P $file_path"; |
| my ($df_exit_status |