Author: arkurth
Date: Thu May 4 19:47:38 2017
New Revision: 1793863
URL: http://svn.apache.org/viewvc?rev=1793863&view=rev
Log:
VCL-966
Reworked Upstart.pm to be able to control some service control functions for
services that can't be controlled by initctl.
Added Upstart.pm::_get_service_info. Much of it was previously in
get_service_names. Added call to 'service --status-all' after 'initctl list' is
executed. The service command may show additional services which can't be
controlled by initctl such as xrdp. A hash is constructed containing service
name keys and the utility used to control the service (service/initctl) as the
values.
Added subroutines:
* Upstart.pm::_controlled_by_service_command
* Upstart.pm::_call_service_start
* Upstart.pm::_call_service_stop
* Upstart.pm::_call_service_restart
* Upstart.pm::_call_service_status
Added code to start_service, stop..., etc to check if the initctl or service
command should be used via _controlled_by_service_command. If service is used,
the corresponding _call_service_* subroutine is called.
Included code in _call_service_stop to overcome problems due to bugs in the
xrdp service. It doesn't always clean up the .pid file and this prevents the
service from being restarted.
Modified:
vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm
vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/Upstart.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=1793863&r1=1793862&r2=1793863&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux.pm Thu May 4 19:47:38 2017
@@ -158,7 +158,7 @@ sub get_node_configuration_directory {
=head2 get_init_modules
Parameters : none
- Returns : Linux init module reference
+ 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
Modified: vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/Upstart.pm
URL:
http://svn.apache.org/viewvc/vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/Upstart.pm?rev=1793863&r1=1793862&r2=1793863&view=diff
==============================================================================
--- vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/Upstart.pm (original)
+++ vcl/trunk/managementnode/lib/VCL/Module/OS/Linux/init/Upstart.pm Thu May 4
19:47:38 2017
@@ -118,33 +118,119 @@ sub get_service_names {
return;
}
+ my $service_info = $self->_get_service_info() || {};
+ return sort keys %$service_info;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 _get_service_info
+
+ Parameters : $use_cache (optional)
+ Returns : hash reference
+ Description : Calls 'initctl list' to retrieve the list of services controlled
+ by Upstart on the computer. Also calls 'service --status-all' to
+ determine SysV-style services controlled by Upstart. A hash
+ reference is returned. Hash keys are service names. Values are
+ either 'initctl' or 'service' indicating if Upstart's initctl
+ command can be used to control the service or the service
command
+ must be used:
+ {
+ "acpid" => "initctl",
+ "open-vm-tools" => "service",
+ "ssh" => "initctl",
+ "sshd" => "initctl",
+ "xrdp" => "service"
+ }
+ By default, the service info is retrieved every time this
+ subroutine is called. To use cached info, the $use_cache
argument
+ must be explicitely set to true.
+
+=cut
+
+sub _get_service_info {
+ 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 $use_cache = shift;
+ if ($use_cache && defined($self->{service_info})) {
+ return $self->{service_info};
+ }
+ else {
+ $self->{service_info} = {};
+ }
+
my $computer_node_name = $self->data->get_computer_node_name();
- my $service_info = {};
+ my %service_name_mappings_reversed = reverse %$SERVICE_NAME_MAPPINGS;
- my $command = "initctl list";
- my ($exit_status, $output) = $self->os->execute($command, 0);
- if (!defined($output)) {
+ my $initctl_command = "initctl list";
+ my ($initctl_exit_status, $initctl_output) =
$self->os->execute($initctl_command, 0);
+ if (!defined($initctl_output)) {
notify($ERRORS{'WARNING'}, 0, "failed to execute command to
list Upstart services on $computer_node_name");
return;
}
+ elsif ($initctl_exit_status ne '0') {
+ notify($ERRORS{'WARNING'}, 0, "failed to retrieve list of all
services on $computer_node_name using the initctl command, exit status:
$initctl_exit_status, command:\n$initctl_command\noutput:\n" . join("\n",
@$initctl_output));
+ return;
+ }
+ else {
+ # Format of initctl list output lines:
+ # splash-manager stop/waiting
+ # network-interface-security (network-interface/eth1)
start/running
+ # tty1 start/running, process 1400
+ for my $line (@$initctl_output) {
+ my ($service_name) = $line =~ /^([^\s\t]+)/;
+ if ($service_name) {
+ $self->{service_info}{$service_name} =
'initctl';
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to parse
service name from '$initctl_command' line: '$line'");
+ }
+ }
+ }
- # Format of initctl list output lines:
- # splash-manager stop/waiting
- # Add to hash then extract keys to remove duplicates
- my %service_name_hash;
- my %service_name_mappings_reversed = reverse %$SERVICE_NAME_MAPPINGS;
- for my $line (@$output) {
- my ($service_name) = $line =~ /^([^\s\t]+)/;
- next unless $service_name;
- $service_name_hash{$service_name} = 1 if $service_name;
- if (my $service_name_mapping =
$service_name_mappings_reversed{$service_name}) {
- $service_name_hash{$service_name_mapping} = 1;
+ # VCL-966
+ # Legacy SysV-style services are not reported by 'initctl list'
+ # The SysV.pm module cannot control these services becuase the
chkconfig command is not available on Ubuntu
+
+ my $service_command = "service --status-all";
+ my ($service_exit_status, $service_output) =
$self->os->execute($service_command);
+ if (!defined($service_output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute command to
retrieve list of all services on $computer_node_name using the service command:
$service_command");
+ return;
+ }
+ elsif ($service_exit_status ne '0') {
+ notify($ERRORS{'WARNING'}, 0, "failed to retrieve list of all
services on $computer_node_name using the service command, exit status:
$service_exit_status, command:\n$service_command\noutput:\n" . join("\n",
@$service_output));
+ }
+ else {
+ # Lines should be formatted as:
+ # [ + ] acpid
+ # [ ? ] apport
+ # [ - ] dbus
+ for my $line (@$service_output) {
+ my ($service_name) = $line =~ /(\S+)\s*$/;
+ if (!$service_name) {
+ notify($ERRORS{'WARNING'}, 0, "failed to parse
service name from '$service_command' line: '$line'");
+ }
+ elsif (!defined($self->{service_info}{$service_name})) {
+ $self->{service_info}{$service_name} =
'service';
+ }
+ }
+ }
+
+ for my $service_name (keys($self->{service_info})) {
+ my $service_name_mapping =
$service_name_mappings_reversed{$service_name};
+ if ($service_name_mapping) {
+ $self->{service_info}{$service_name_mapping} =
$self->{service_info}{$service_name};
}
}
- my @service_names = sort(keys %service_name_hash);
- notify($ERRORS{'DEBUG'}, 0, "retrieved Upstart service names from
$computer_node_name: " . join(", ", @service_names));
- return @service_names;
+
+ notify($ERRORS{'DEBUG'}, 0, "retrieved services info from
$computer_node_name:\n" . format_data($self->{service_info}));
+ return $self->{service_info};
}
#/////////////////////////////////////////////////////////////////////////////
@@ -218,6 +304,11 @@ sub start_service {
}
$service_name = $SERVICE_NAME_MAPPINGS->{$service_name} ||
$service_name;
+ # Check if initctl cannot be used to control service
+ if ($self->_controlled_by_service_command($service_name)) {
+ return $self->_call_service_start($service_name);
+ }
+
my $computer_node_name = $self->data->get_computer_node_name();
my $command = "initctl start $service_name";
@@ -264,47 +355,43 @@ sub stop_service {
return;
}
- my $service_name_argument = shift;
- if (!$service_name_argument) {
+ my $service_name = shift;
+ if (!$service_name) {
notify($ERRORS{'WARNING'}, 0, "service name argument was not
supplied");
return;
}
+ $service_name = $SERVICE_NAME_MAPPINGS->{$service_name} ||
$service_name;
- # Need to attempt to stop both the service with a name matching the
argument as well as the mapped service name
- my @service_names = ($service_name_argument);
-
- # If a mapped service name also exists, attempt to stop it as well
- if ($SERVICE_NAME_MAPPINGS->{$service_name_argument}) {
- push @service_names,
$SERVICE_NAME_MAPPINGS->{$service_name_argument};
+ # Check if initctl cannot be used to control service
+ if ($self->_controlled_by_service_command($service_name)) {
+ return $self->_call_service_stop($service_name);
}
my $computer_node_name = $self->data->get_computer_node_name();
- for my $service_name (@service_names) {
- my $command = "initctl stop $service_name";
- my ($exit_status, $output) = $self->os->execute($command);
- if (!defined($output)) {
- notify($ERRORS{'WARNING'}, 0, "failed to execute
command to stop '$service_name' service on $computer_node_name");
- return;
- }
- elsif (grep(/Unknown job/i, @$output)) {
- # Output if the service doesn't exist: 'initctl:
Unknown job: <service name>'
- notify($ERRORS{'DEBUG'}, 0, "'$service_name' service
does not exist on $computer_node_name");
- }
- elsif (grep(/Unknown instance/i, @$output)) {
- # Output if the service is not running: 'initctl:
Unknown instance:'
- notify($ERRORS{'DEBUG'}, 0, "'$service_name' is already
stopped on $computer_node_name");
- return 1;
- }
- elsif (grep(/ stop\//i, @$output)) {
- # Output if the service was stopped: '<service name>
stop/waiting'
- notify($ERRORS{'DEBUG'}, 0, "stopped '$service_name'
service on $computer_node_name");
- return 1;
- }
- else {
- notify($ERRORS{'WARNING'}, 0, "failed to stop
'$service_name' service on $computer_node_name, exit status: $exit_status,
command: '$command', output:\n" . join("\n", @$output));
- return;
- }
+ my $command = "initctl stop $service_name";
+ my ($exit_status, $output) = $self->os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute command to
stop '$service_name' service on $computer_node_name");
+ return;
+ }
+ elsif (grep(/Unknown job/i, @$output)) {
+ # Output if the service doesn't exist: 'initctl: Unknown job:
<service name>'
+ notify($ERRORS{'DEBUG'}, 0, "'$service_name' service does not
exist on $computer_node_name");
+ }
+ elsif (grep(/Unknown instance/i, @$output)) {
+ # Output if the service is not running: 'initctl: Unknown
instance:'
+ notify($ERRORS{'DEBUG'}, 0, "'$service_name' is already stopped
on $computer_node_name");
+ return 1;
+ }
+ elsif (grep(/ stop\//i, @$output)) {
+ # Output if the service was stopped: '<service name>
stop/waiting'
+ notify($ERRORS{'DEBUG'}, 0, "stopped '$service_name' service on
$computer_node_name");
+ return 1;
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to stop '$service_name'
service on $computer_node_name, exit status: $exit_status, command: '$command',
output:\n" . join("\n", @$output));
+ return;
}
return 1;
@@ -334,6 +421,11 @@ sub restart_service {
}
$service_name = $SERVICE_NAME_MAPPINGS->{$service_name} ||
$service_name;
+ # Check if initctl cannot be used to control service
+ if ($self->_controlled_by_service_command($service_name)) {
+ return $self->_call_service_restart($service_name);
+ }
+
my $computer_node_name = $self->data->get_computer_node_name();
my $command = "initctl restart $service_name";
@@ -446,6 +538,11 @@ sub service_running {
}
$service_name = $SERVICE_NAME_MAPPINGS->{$service_name} ||
$service_name;
+ # Check if initctl cannot be used to control service
+ if ($self->_controlled_by_service_command($service_name)) {
+ return $self->_call_service_status($service_name);
+ }
+
my $computer_node_name = $self->data->get_computer_node_name();
my $command = "initctl status $service_name";
@@ -625,6 +722,231 @@ sub disable_service {
return;
}
}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 _controlled_by_service_command
+
+ Parameters : $service_name
+ Returns : boolean
+ Description : Returns true if the service exists but cannot be controlled by
+ the 'initctl' command. The 'service' command must be used for
+ basic service control.
+
+=cut
+
+sub _controlled_by_service_command {
+ 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 $service_name = shift;
+ if (!$service_name) {
+ notify($ERRORS{'WARNING'}, 0, "service name argument was not
supplied");
+ return;
+ }
+
+ my $service_info = $self->_get_service_info(1) || return;
+ if ($service_info->{$service_name} && $service_info->{$service_name} eq
'service') {
+ notify($ERRORS{'DEBUG'}, 0, "'$service_name' service cannot be
controlled by the initctl command, the service command will be used");
+ return 1;
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "'$service_name' service will be
controlled by the initctl command");
+ return 0;
+ }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 _call_service_start
+
+ Parameters : $service_name
+ Returns : boolean
+ Description : Calls 'service <$service_name> start' to start a service that
+ can't be controlled by initctl.
+
+=cut
+
+sub _call_service_start {
+ 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 $service_name = shift;
+
+ my $computer_node_name = $self->data->get_computer_node_name();
+
+ my $service_command = "service $service_name start";
+ my ($service_exit_status, $service_output) =
$self->os->execute($service_command);
+ if (!defined($service_output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute command to
start '$service_name' service on $computer_node_name using the service command:
$service_command");
+ return;
+ }
+ elsif ($service_exit_status eq '0' || grep(/done/, @$service_output)) {
+ notify($ERRORS{'OK'}, 0, "started '$service_name' service on
$computer_node_name using the service command: '$service_command', output:\n" .
join("\n", @$service_output));
+ return 1;
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to start '$service_name'
service on $computer_node_name using the service command, exit status:
$service_exit_status, command:\n$service_command\noutput:\n" . join("\n",
@$service_output));
+ return;
+ }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 _call_service_stop
+
+ Parameters : $service_name
+ Returns : boolean
+ Description : Calls 'service <$service_name> stop' to stop a service that
+ can't be controlled by initctl.
+
+=cut
+
+sub _call_service_stop {
+ 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 $service_name = shift;
+
+ my $computer_node_name = $self->data->get_computer_node_name();
+
+ my $service_command = "service $service_name stop";
+ my ($service_exit_status, $service_output) =
$self->os->execute($service_command);
+ if (!defined($service_output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute command to
stop '$service_name' service on $computer_node_name using the service command:
$service_command");
+ return;
+ }
+ elsif ($service_exit_status eq '0' || grep(/done/, @$service_output)) {
+ notify($ERRORS{'OK'}, 0, "stopped '$service_name' service on
$computer_node_name using the service command: '$service_command', output:\n" .
join("\n", @$service_output));
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to stop '$service_name'
service on $computer_node_name using the service command, exit status:
$service_exit_status, command:\n$service_command\noutput:\n" . join("\n",
@$service_output));
+ return;
+ }
+
+ # Try to fix common xRDP bug on Ubuntu, service start/stop/restart
often leaves behind the .pid file
+ # Service can't be completly restarted until file is manually deleted
+ my @pid_files = $self->os->find_files('/var/run', "$service_name.pid",
1, 'f');
+ if (scalar(@pid_files) == 1) {
+ my $pid_file = $pid_files[0];
+ notify($ERRORS{'DEBUG'}, 0, "'$service_name' may not have
cleaned up .pid file when service was stopped, attempting to delete file:
$pid_file");
+ $self->os->delete_file($pid_file)
+ }
+
+ return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 _call_service_restart
+
+ Parameters : $service_name
+ Returns : boolean
+ Description : Calls 'service <$service_name> stop' and then
+ 'service <$service_name> start' to
restart a service that can't
+ be controlled by initctl.
+
+=cut
+
+sub _call_service_restart {
+ 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 $service_name = shift;
+
+ my $computer_node_name = $self->data->get_computer_node_name();
+
+ my $service_command = "service $service_name restart";
+ my ($service_exit_status, $service_output) =
$self->os->execute($service_command);
+ if (!defined($service_output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute command to
restart '$service_name' service on $computer_node_name using the service
command: $service_command");
+ return;
+ }
+ elsif ($service_exit_status eq '0' || grep(/done/, @$service_output)) {
+ notify($ERRORS{'OK'}, 0, "restarted '$service_name' service on
$computer_node_name using the service command: '$service_command', output:\n" .
join("\n", @$service_output));
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to restart
'$service_name' service on $computer_node_name using the service command, exit
status: $service_exit_status, command:\n$service_command\noutput:\n" .
join("\n", @$service_output));
+ return;
+ }
+
+ if ($self->_call_service_status($service_name)) {
+ return 1;
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "'$service_name' service does not
seem to be running after restarting it, attempting to stop and start service");
+ $self->_call_service_stop($service_name);
+ $self->_call_service_start($service_name);
+ return $self->_call_service_status($service_name);
+ }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 _call_service_status
+
+ Parameters : $service_name
+ Returns : boolean
+ Description : Calls 'service <$service_name> status' to get the status of a
+ service that can't be controlled by initctl.
+
+=cut
+
+sub _call_service_status {
+ 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 $service_name = shift;
+
+ my $computer_node_name = $self->data->get_computer_node_name();
+
+ my $service_command = "service $service_name status";
+ my ($service_exit_status, $service_output) =
$self->os->execute($service_command);
+ if (!defined($service_output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute command to
retrieve status of '$service_name' service on $computer_node_name using the
service command: $service_command");
+ return;
+ }
+
+ # Output if service is running:
+ # * Checking status of Remote Desktop Protocol server xrdp
+ # ...done.
+ # * Checking status of RDP Session Manager sesman
+ # ...done.
+
+ # Output if service is stopped:
+ # * Checking status of Remote Desktop Protocol server xrdp
+ # ...fail!
+ # * Checking status of RDP Session Manager sesman
+ # ...fail!
+
+ if (grep(/done/, @$service_output) && !grep(/fail/, @$service_output)) {
+ notify($ERRORS{'OK'}, 0, "'$service_name' service is running on
$computer_node_name, output of '$service_command':\n" . join("\n",
@$service_output));
+ return 1;
+ }
+ elsif (grep(/fail/, @$service_output)) {
+ notify($ERRORS{'OK'}, 0, "'$service_name' service is NOT
running on $computer_node_name, output of '$service_command':\n" . join("\n",
@$service_output));
+ return 0;
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to determine if
'$service_name' service is running on $computer_node_name using the service
command, output does not contain 'done' or 'fail', exit status:
$service_exit_status, command:\n$service_command\noutput:\n" . join("\n",
@$service_output));
+ return;
+ }
+}
#/////////////////////////////////////////////////////////////////////////////