You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by ar...@apache.org on 2015/10/22 22:25:12 UTC

svn commit: r1710094 - /vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm

Author: arkurth
Date: Thu Oct 22 20:25:12 2015
New Revision: 1710094

URL: http://svn.apache.org/viewvc?rev=1710094&view=rev
Log:
VCL-915
Added the following subroutines to Linux.pm. These are not currently being called from elsewhere:
mount_nfs_share
unmount_nfs_share
is_nfs_share_mounted
get_nfs_mount_string
add_fstab_nfs_mount
remove_matching_fstab_lines

Other
Added check to Linux.pm::configure_default_sshd and configure_ext_sshd to check if the sshd service exists first.

Modified:
    vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm

Modified: vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm
URL: http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm?rev=1710094&r1=1710093&r2=1710094&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm Thu Oct 22 20:25:12 2015
@@ -4964,6 +4964,11 @@ sub configure_default_sshd {
 	
 	my $computer_node_name = $self->data->get_computer_node_name();
 	
+	if (!$self->service_exists('sshd')) {
+		notify($ERRORS{'DEBUG'}, 0, "skipping default sshd configuation, sshd service does not exist");
+		return 1;
+	}
+	
 	# Stop existing external sshd process if it is running
 	if (!$self->stop_external_sshd()) {
 		notify($ERRORS{'WARNING'}, 0, "unable to configure default sshd state, problem occurred attempting to kill external sshd process");
@@ -5014,6 +5019,11 @@ sub configure_ext_sshd {
 		return;
 	}
 	
+	if (!$self->service_exists('sshd')) {
+		notify($ERRORS{'DEBUG'}, 0, "skipping ext_sshd configuation, sshd service does not exist");
+		return 1;
+	}
+	
 	# Recreate the sshd_config file, set ListenAddress to the private IP address
 	if (!$self->configure_sshd_config_file({ListenAddress => $private_ip_address})) {
 		notify($ERRORS{'WARNING'}, 0, "unable to configure ext_sshd, failed to reconfigure sshd_config to only listen on private network on $computer_node_name");
@@ -6062,9 +6072,9 @@ sub get_ssh_public_key_string {
 		$public_key_string = $self->_get_ssh_public_key_string_helper($private_key_file_path, 'dropbearkey');
 	}
 	if ($public_key_string) {
-		if ($comment) {
-			$public_key_string =~ s/(ssh-[^\s]+\s[^\s=]+).*$/$1 $comment/g;
-		}
+		#if ($comment) {
+		#	$public_key_string =~ s/(ssh-[^\s]+\s[^\s=]+).*$/$1 $comment/g;
+		#}
 		return $public_key_string;
 	}
 	
@@ -6208,6 +6218,386 @@ sub install_package {
 	}
 }
 
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 mount_nfs_share
+
+ Parameters  : $remote_nfs_share, $local_mount_directory, $options
+ Returns     : boolean
+ Description : Mounts an NFS share on the computer. The $remote_nfs_share
+               argument must be in the for used by the mount command:
+               <hostname|IP>:/path-to-share
+               
+               The $local_mount_directory argument must specify a directory.
+               This directory will be created if it does not already exist.
+               
+               The $options argument allows NFS mount options to be specified
+               such as:
+               rsize=1048576,wsize=1048576,vers=3
+               
+               The $options string must be formatted correctly and is passed
+               directly to the mount command.
+
+=cut
+
+sub mount_nfs_share {
+	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 ($remote_nfs_share, $local_mount_directory, $options, $is_retry_attempt) = @_;
+	if (!defined($remote_nfs_share)) {
+		notify($ERRORS{'WARNING'}, 0, "remote target argument was not supplied");
+		return;
+	}
+	elsif (!defined($local_mount_directory)) {
+		notify($ERRORS{'WARNING'}, 0, "local directory path argument was not supplied");
+		return;
+	}
+	
+	my $computer_name = $self->data->get_computer_node_name();
+	
+	# Try to repair NFS client if 1st attempt failed
+	if ($is_retry_attempt) {
+		# Check if nfs-utils is installed, if not, try to install it
+		# Error looks like this if nfs-utils is not installed:
+		#    mount: wrong fs type, bad option, bad superblock on 10.1.2.3:/nfs,
+		#    missing codepage or helper program, or other error
+		#    (for several filesystems (e.g. nfs, cifs) you might
+		#    need a /sbin/mount.<type> helper program)
+		#    In some cases useful info is found in syslog - try
+		#    dmesg | tail  or so
+		if (!$self->command_exists('mount.nfs')) {
+			$self->install_package('nfs-utils');
+		}
+		
+		# Check if the rpcbind service exists, if not, try to install it
+		# Mount may fail if rpcbind service is not installed and running:
+		#    mount.nfs: rpc.statd is not running but is required for remote locking.
+		#    mount.nfs: Either use '-o nolock' to keep locks local, or start statd.
+		#    mount.nfs: an incorrect mount option was specified
+		if (!$self->service_exists('rpcbind')) {
+			$self->install_package('rpcbind');
+		}
+		
+		# Try to start the service
+		$self->start_service('rpcbind');
+	}
+	else {
+		# Create the local mount point directory if it does not exist
+		if (!$self->create_directory($local_mount_directory)) {
+			notify($ERRORS{'WARNING'}, 0, "unable to mount $remote_nfs_share on $computer_name, failed to create directory: $local_mount_directory");
+			return;
+		}
+	}
+	
+	my $mount_command = "mount -t nfs $remote_nfs_share \"$local_mount_directory\" -v";
+	if ($options) {
+		$mount_command .= " -o $options";
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "attempting to mount NFS share on $computer_name: $mount_command");
+	my ($mount_exit_status, $mount_output) = $self->execute({
+		command => $mount_command,
+		timeout_seconds => 10,
+		max_attempts => 2,
+	});
+	if (!defined($mount_exit_status)) {
+		notify($ERRORS{'CRITICAL'}, 0, "failed to execute command to mount NFS share on $computer_name: $mount_command");
+		return;
+	}
+	elsif ($mount_exit_status eq 0) {
+		notify($ERRORS{'OK'}, 0, "mounted NFS share on $computer_name: $remote_nfs_share --> $local_mount_directory");
+		return 1;
+	}
+	elsif (grep(/already mounted/, @$mount_output)) {
+		# mount.nfs: /mnt/mymountpoint is busy or already mounted
+		if ($self->is_nfs_share_mounted($remote_nfs_share, $local_mount_directory)) {
+			return 1;
+		}
+		else {
+			notify($ERRORS{'WARNING'}, 0, "failed to mount NFS share on $computer_name: $remote_nfs_share --> $local_mount_directory, mount command output indicates 'already mounted' but failed to verify mount in /proc/mounts, mount command: '$mount_command', exit status: $mount_exit_status, mount output:\n" . join("\n", @$mount_output));
+			return;
+		}
+	}
+	elsif (grep(/(Invalid argument|incorrect mount option|Usage:)/, @$mount_output)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to mount NFS share on $computer_name: $remote_nfs_share --> $local_mount_directory, command: '$mount_command', exit status: $mount_exit_status, output:\n" . join("\n", @$mount_output));
+		return;
+	}
+	else {
+		if ($is_retry_attempt) {
+			notify($ERRORS{'WARNING'}, 0, "failed to mount NFS share on $computer_name on 2nd attempt: $remote_nfs_share --> $local_mount_directory, command: '$mount_command', exit status: $mount_exit_status, output:\n" . join("\n", @$mount_output));
+			return;
+		}
+		else {
+			notify($ERRORS{'WARNING'}, 0, "failed to mount NFS share on $computer_name on 1st attempt: $remote_nfs_share --> $local_mount_directory, command: '$mount_command', exit status: $mount_exit_status, output:\n" . join("\n", @$mount_output));
+			
+			# Try to mount the NFS share again, set retry flag to avoid endless loop
+			return $self->mount_nfs_share($remote_nfs_share, $local_mount_directory, $options, 1);
+		}
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 unmount_nfs_share
+
+ Parameters  : $local_mount_directory
+ Returns     : boolean
+ Description : Unmounts an NFS share on the computer.
+
+=cut
+
+sub unmount_nfs_share {
+	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 ($local_mount_directory) = @_;
+	if (!defined($local_mount_directory)) {
+		notify($ERRORS{'WARNING'}, 0, "local directory path argument was not supplied");
+		return;
+	}
+	
+	my $computer_name = $self->data->get_computer_node_name();
+	
+	my $umount_command = "umount -v \"$local_mount_directory\" -v";
+	my ($umount_exit_status, $umount_output) = $self->execute({
+		command => $umount_command,
+		timeout_seconds => 30,
+		max_attempts => 2,
+	});
+	if (!defined($umount_exit_status)) {
+		notify($ERRORS{'CRITICAL'}, 0, "failed to execute command to umount NFS share on $computer_name: $umount_command");
+		return;
+	}
+	elsif ($umount_exit_status eq 0) {
+		notify($ERRORS{'OK'}, 0, "unmounted NFS share on $computer_name: $local_mount_directory, output:\n" . join("\n", @$umount_output));
+		return 1;
+	}
+	elsif (grep(/not mounted/, @$umount_output)) {
+		notify($ERRORS{'OK'}, 0, "NFS share is not mounted on $computer_name: $local_mount_directory");
+		return 1;
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to unmount NFS share on $computer_name: $local_mount_directory, command: '$umount_command', exit status: $umount_exit_status, output:\n" . join("\n", @$umount_output));
+		return;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 is_nfs_share_mounted
+
+ Parameters  : $remote_nfs_share, $local_mount_directory
+ Returns     : boolean
+ Description : Checks if an NFS share is mounted on the computer matching both
+               the remote NFS share path and local mount point directory
+               arguments.
+
+=cut
+
+sub is_nfs_share_mounted {
+	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 ($remote_nfs_share, $local_mount_directory) = @_;
+	if (!defined($remote_nfs_share)) {
+		notify($ERRORS{'WARNING'}, 0, "remote target argument was not supplied");
+		return;
+	}
+	elsif (!defined($local_mount_directory)) {
+		notify($ERRORS{'WARNING'}, 0, "local directory path argument was not supplied");
+		return;
+	}
+	
+	my $computer_name = $self->data->get_computer_node_name();
+	
+	if ($self->get_nfs_mount_string($remote_nfs_share, $local_mount_directory)) {
+		notify($ERRORS{'DEBUG'}, 0, "NFS share is mounted on $computer_name: $remote_nfs_share --> $local_mount_directory");
+		return 1;
+	}
+	else {
+		notify($ERRORS{'DEBUG'}, 0, "NFS share is NOT mounted on $computer_name: $remote_nfs_share --> $local_mount_directory");
+		return 0;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_nfs_mount_string
+
+ Parameters  : $remote_nfs_share, $local_mount_directory
+ Returns     : string
+ Description : Examines the contents of /proc/mounts and attempts to locate a
+               line matching the arguments. If found, the line is returned which
+               may be used in /etc/fstab. If not found, 0 is returned. Undefined
+               is returned if an error occurs.
+
+=cut
+
+sub get_nfs_mount_string {
+	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 ($remote_nfs_share, $local_mount_directory) = @_;
+	if (!defined($remote_nfs_share)) {
+		notify($ERRORS{'WARNING'}, 0, "remote target argument was not supplied");
+		return;
+	}
+	elsif (!defined($local_mount_directory)) {
+		notify($ERRORS{'WARNING'}, 0, "local directory path argument was not supplied");
+		return;
+	}
+	
+	# Remove trailing slashes for consistent comparison
+	$remote_nfs_share =~ s/\/+$//;
+	$local_mount_directory =~ s/\/+$//;
+	
+	# If the NFS share or local directory contain a space, the octal value will appear in /proc/mounts
+	$remote_nfs_share =~ s/ /\\\\040/g;
+	$local_mount_directory =~ s/ /\\\\040/g;
+	
+	my $computer_name = $self->data->get_computer_node_name();
+	
+	my $command = "cat /proc/mounts | grep ' nfs'";
+	my ($exit_status, $output) = $self->execute($command);
+	if (!defined($exit_status)) {
+		notify($ERRORS{'WARNING'}, 0, "failed to execute command on $computer_name: $command");
+		return;
+	}
+	
+	for my $line (@$output) {
+		if ($line =~ m|^$remote_nfs_share\/?\s+$local_mount_directory\/?\s|) {
+			notify($ERRORS{'DEBUG'}, 0, "found NFS share line in /proc/mounts on $computer_name: $remote_nfs_share --> $local_mount_directory\n$line");
+			return $line;
+		}
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "/proc/mounts on $computer_name does NOT contain a line matching NFS share: $remote_nfs_share --> $local_mount_directory\n" . join("\n", @$output));
+	return 0;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 add_fstab_nfs_mount
+
+ Parameters  : $remote_nfs_share, $local_mount_directory
+ Returns     : boolean
+ Description : Adds a line to /etc/fstab for an existing NFS mount. The share
+               must be mounted prior to calling this subroutine.
+
+=cut
+
+sub add_fstab_nfs_mount {
+	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 ($remote_nfs_share, $local_mount_directory) = @_;
+	if (!defined($remote_nfs_share)) {
+		notify($ERRORS{'WARNING'}, 0, "remote target argument was not supplied");
+		return;
+	}
+	elsif (!defined($local_mount_directory)) {
+		notify($ERRORS{'WARNING'}, 0, "local directory path argument was not supplied");
+		return;
+	}
+	
+	my $computer_name = $self->data->get_computer_node_name();
+	
+	my $nfs_mount_string = $self->get_nfs_mount_string($remote_nfs_share, $local_mount_directory);
+	if (!$nfs_mount_string) {
+		notify($ERRORS{'WARNING'}, 0, "unable to add NFS mount line to /etc/fstab, NFS share is not mounted: $remote_nfs_share --> $local_mount_directory");
+		return;
+	}
+	
+	# Remove existing line matching the local mount directory followed by "nfs" to avoid duplicate lines
+	$self->remove_matching_fstab_lines("$local_mount_directory nfs");
+	
+	my @fstab_lines = $self->get_file_contents('/etc/fstab');
+	push @fstab_lines, $nfs_mount_string;
+	my $new_fstab_contents = join("\n", @fstab_lines);
+	
+	my $timestamp = POSIX::strftime("%Y-%m-%d_%H-%M-%S\n", localtime);
+	$self->copy_file('/etc/fstab', "/tmp/fstab.$timestamp");
+	
+	if ($self->create_text_file('/etc/fstab', $new_fstab_contents)) {
+		notify($ERRORS{'OK'}, 0, "added line to /etc/fstab on $computer_name:\n$nfs_mount_string");
+		return 1;
+	}
+	else {
+		notify($ERRORS{'WARNING'}, 0, "failed to add line to /etc/fstab on $computer_name:\n$nfs_mount_string");
+		return;
+	}
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 remove_matching_fstab_lines
+
+ Parameters  : $regex_pattern
+ Returns     : boolean
+ Description : Removes all lines from /etc/fstab matching the pattern.
+
+=cut
+
+sub remove_matching_fstab_lines {
+	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 ($regex_pattern) = @_;
+	if (!defined($regex_pattern)) {
+		notify($ERRORS{'WARNING'}, 0, "pattern argument was not supplied");
+		return;
+	}
+	
+	my $computer_name = $self->data->get_computer_node_name();
+	
+	my $updated_fstab_contents;
+	
+	my @matching_lines;
+	my @fstab_lines = $self->get_file_contents('/etc/fstab');
+	for my $fstab_line (@fstab_lines) {
+		(my $fstab_line_cleaned = $fstab_line) =~ s/\\040/ /g;
+		
+		if ($fstab_line =~ m|$regex_pattern| || $fstab_line_cleaned =~ m|$regex_pattern|) {
+			push @matching_lines, $fstab_line;
+			notify($ERRORS{'DEBUG'}, 0, "removing line in /etc/fstab matching pattern: $regex_pattern\n$fstab_line");
+			next;
+		}
+		$updated_fstab_contents .= "$fstab_line\n";
+	}
+	
+	if (!@matching_lines) {
+		notify($ERRORS{'DEBUG'}, 0, "/etc/fstab does not contain any lines matching pattern: $regex_pattern");
+		return 1;
+	}
+	
+	notify($ERRORS{'DEBUG'}, 0, "removing " . scalar(@matching_lines) . " lines from /etc/fstab on $computer_name:\n" . join("\n", @matching_lines));
+	
+	# Save a backup
+	my $timestamp = POSIX::strftime("%Y-%m-%d_%H-%M-%S\n", localtime);
+	$self->copy_file('/etc/fstab', "/tmp/fstab.$timestamp");
+	
+	return $self->create_text_file('/etc/fstab', $updated_fstab_contents);
+}
+
 ##/////////////////////////////////////////////////////////////////////////////
 1;
 __END__