#!/usr/bin/perl

use strict;
use warnings;
use feature qw(state);

use Getopt::Long qw(:config posix_default no_ignore_case);
use List::Util qw(any none);
use File::Spec::Functions qw(splitpath);
use File::Path qw(make_path remove_tree);
use Term::ANSIColor;
use NGCP::Log::Functions;
use NGCP::Service::IO;
use NGCP::Service::Meta;

sub action_skip
{
    my ($servdesc, $action, $opts) = @_;

    # We filter on platform, ngcp_type and group first, as these should come
    # before the 'stop' and 'status' unconditional allowance exception.
    if (not serv_in_platform($servdesc)) {
        return "Action $action skipped, service $servdesc->{name} is not for this platform";
    } elsif (not serv_in_ngcp_type($servdesc)) {
        my $host_ngcp_type = host_get_ngcp_type();

        return "Action $action skipped, service $servdesc->{name} is not for ngcp_type $host_ngcp_type";
    } elsif (not serv_in_group($servdesc, $opts->{group})) {
        return "Action $action skipped, service $servdesc->{name} is not for this group";
    } elsif ($servdesc->{enable} eq 'no') {
        return "Action $action skipped, service $servdesc->{name} is not enabled";
    } elsif (not serv_in_role($servdesc)) {
        return "Action $action skipped, service $servdesc->{name} is not for this role";
    } elsif (not serv_in_host($servdesc)) {
        return "Action $action skipped, service $servdesc->{name} is not for this node";
    } elsif (not $opts->{'skip-ha-state-check'} and
             not serv_in_status($servdesc)) {
        return "Action $action skipped, service $servdesc->{name} is not for the standby node";
    } elsif (serv_is_ha_managed($servdesc) == SERV_HA_INSTANCE and
             not serv_is_active($servdesc)) {
        return "Action $action skipped, instance $servdesc->{name} is not active on this node";
    }

    return;
}

sub set_command
{
    my ($servdesc, $servstate, $action, $cmds, $opts) = @_;
    my $manager = $servstate->{manager};
    my $service = $servdesc->{name};

    if (not exists $servdesc->{$manager}) {
        my $configfile = serv_get_config_name();
        error("$service\[$manager\] not defined at $configfile");
        return 1;
    }

    my $manager_pathname = host_get_process_handler_pathname($manager);
    if ($manager eq 'sysv') {
        push @{$cmds->{manager}{$manager}},
             [ $manager_pathname, $servdesc->{$manager}, $action ];
    } elsif ($manager eq 'monit') {
        push @{$cmds->{manager}{$manager}},
             [ $manager_pathname, $servdesc->{$manager}, $action ];
    } elsif ($manager eq 'systemd') {
        $cmds->{manager}{$manager} //= [ [
            $manager_pathname,
            $opts->{'async'} ? '--no-block' : (),
            $action,
        ] ];
        push @{@{$cmds->{manager}{$manager}}[0]}, $servdesc->{$manager};
    } else {
        error("unknown process handler $manager");
        return 1;
    }

    my $monit_pathname = host_get_process_handler_pathname('monit');
    if ($servstate->{unmonitor}) {
        push @{$cmds->{pre}},
             [ $monit_pathname, 'unmonitor', $servdesc->{monit} ];
    }

    if ($servstate->{monitor}) {
        push @{$cmds->{post}},
             [ $monit_pathname, 'monitor', $servdesc->{monit} ];
    }

    return 0;
}

sub action_is_managed
{
    my ($opts) = @_;
    my $rc = 0;

    $opts->{'skip-ha-state-check'} = 1;

    foreach my $service (@{$opts->{service}}) {
        my $servdesc = serv_get_desc_byname($service);
        $servdesc //= serv_get_desc_fallback_byname($service);
        if (not defined $servdesc) {
            error("No $service defined or found");
            $rc |= 1;
        } else {
            my $skip = action_skip($servdesc, $opts->{action}, $opts);
            if ($skip) {
                # For debugging purposes only, on verbose mode.
                info($skip);
                $rc |= 1;
            }
        }
    }

    return $rc;
}

sub do_action
{
    my ($opts) = @_;

    my $rc = 0;
    my $action = $opts->{action};

    my $process_handler = host_get_process_handler();
    debug("process_handler: '$process_handler'");

    my $cmds = {};
    foreach my $service (@{$opts->{service}}) {
        my $servdesc = serv_get_desc_byname($service);
        $servdesc //= serv_get_desc_fallback_byname($service);
        if (not defined $servdesc) {
            error("No $service defined or found");
            $rc |= 1;
            next;
        }

        my $servstate = serv_get_state($servdesc, $process_handler, $action);

        my $skip = action_skip($servdesc, $action, $opts);
        if ($skip) {
            # If we are selecting via groups or none of the following options,
            # we skip the action.
            if (exists $opts->{group} or
                none { $action eq $_ } qw(stop status reset-failed)) {
                info($skip);
                next;
            }

            # If we were supposed to skip, but let the action pass through
            # we should disable monit actions, as it will either not know
            # about or will not be monitoring the service right now.
            $servstate->{unmonitor} = 0;
            $servstate->{monitor} = 0;
        }

        $rc |= set_command($servdesc, $servstate, $action, $cmds, $opts);
    }

    foreach my $cmd (@{$cmds->{pre}}) {
        execute($cmd);
    }
    foreach my $manager (keys %{$cmds->{manager}}) {
        foreach my $cmd (@{$cmds->{manager}{$manager}}) {
            $rc |= execute($cmd, verbose => 1);
        }
    }
    foreach my $cmd (@{$cmds->{post}}) {
        execute($cmd);
    }

    return $rc;
}

sub queue_dir
{
    my $queue = shift;

    return get_root_dir() . "/run/ngcp-service/queue-$queue";
}

sub queue_start
{
    my $opts = shift;

    foreach my $queue (split /,/, $opts->{queue}) {
        make_path(queue_dir($queue));
    }
}

sub queue_clear
{
    my $opts = shift;

    foreach my $queue (split /,/, $opts->{queue}) {
        remove_tree(queue_dir($queue));
    }
}

sub queue_is_started
{
    my $queue = shift;
    usageerr('enqueuing can only act on one queue') if $queue =~ m/,/;

    state $queue_present = -d queue_dir($queue);

    return $queue_present;
}

sub enqueue_action
{
    my $opts = shift;
    my $action = $opts->{action};

    foreach my $service (@{$opts->{service}}) {
        my $queue = queue_dir($opts->{queue}) . "/$service";

        open my $fh, '>>', $queue or syserror("cannot open '$queue' queue");
        print { $fh } "$action\n" or syserror("cannot write '$queue' queue");
        close $fh or syserror("cannot write '$queue' queue");
    }
}

sub queue_synthesize
{
    my $opts = shift;
    my %services;
    my %action;

    foreach my $queue (glob queue_dir("{$opts->{queue}}") . "/*") {
        my $servraw = (splitpath($queue))[2];
        my $servdesc = serv_get_desc_byname($servraw);
        my $service = defined $servdesc ? $servdesc->{name} : $servraw;

        my @serv_actions;

        # Load service queue.
        open my $fh, '<', $queue or syserror("cannot open '$queue' queue");
        @serv_actions = <$fh>;
        close $fh;

        chomp @serv_actions;

        push @{$services{$service}}, @serv_actions;
    }

    foreach my $service (sort keys %services) {
        my $prev_action;

        # Initialize processed action, so we have something to compare against.
        $prev_action = shift @{$services{$service}};

        # Process actions, to reduce them to their minimal required operation.
        foreach my $action (@{$services{$service}}) {
            # The logic here is as follows:
            #
            # - duplicates get squashed.
            # - stop + start map to restart.
            # - start + reload map to restart. [0]
            # - restart + start map to restart.
            # - stop, start, restart override any previous reload.
            # - otherwise new action overrides previous one.
            #
            # [0] We cannot assume the service is not running, which would
            # make the start a no-op, and we would not do the reload. Mapping
            # to a restart while possibly heavy-weight, means we always get
            # the desired semantics.
            if (($prev_action eq 'stop' and $action eq 'start') or
                ($prev_action eq 'start' and $action eq 'reload') or
                ($prev_action eq 'restart' and $action eq 'start')) {
                # Equivalent to restart, reset the action.
                $prev_action = 'restart';
            } elsif ($prev_action ne $action and $action ne 'reload') {
                # Reset the action.
                $prev_action = $action;
            }

            # XXX: Should we track other options such as --async or --timeout
            # or --skip-ha-state-check too, or take them from the queue-run
            # invocation?
        }

        push @{$action{$prev_action}}, $service;

        debug("synthesized action=$prev_action for service=$service");
    }

    return \%action;
}

sub queue_show
{
    my $opts = shift;
    my $action_queue = queue_synthesize($opts);

    return unless %{$action_queue};

    foreach my $action (qw(stop start restart reload)) {
        my $services = $action_queue->{$action};

        next unless defined $services;

        print "The following services will $action:\n";
        foreach my $service (@{$services}) {
            print "  $service\n";
        }
    }
}

sub queue_run
{
    my $opts = shift;
    my $action_queue = queue_synthesize($opts);

    my $rc = 0;
    foreach my $action (qw(stop start restart reload)) {
        $opts->{action} = $action;
        $opts->{service} = $action_queue->{$action};
        $rc |= do_action($opts);
    }
    return $rc;
}

use constant {
    SUMMARY_COL_SERVICE => 30,
    SUMMARY_COL_MANAGED => 10,
    SUMMARY_COL_STARTED => 9,
    SUMMARY_COL_STATUS => 9,
    SUMMARY_COL_OK => 2,
};

sub summary_col
{
    my ($text, $color, $width) = @_;
    my $text_out = defined $color ? colored($text, $color) : $text;

    if (defined $width) {
        return ($width + length($text_out) - length($text), $text_out);
    } else {
        return $text_out;
    }
}

sub summary_row
{
    my ($ok, $ok_color, $service, $service_color, $managed, $managed_color,
        $started, $started_color, $status, $status_color) = @_;

    printf "%-*s %-*s %-*s %-*s %-*s\n",
        summary_col($ok, $ok_color, SUMMARY_COL_OK),
        summary_col($service, $service_color, SUMMARY_COL_SERVICE),
        summary_col($managed, $managed_color, SUMMARY_COL_MANAGED),
        summary_col($started, $started_color, SUMMARY_COL_STARTED),
        summary_col($status, $status_color, SUMMARY_COL_STATUS);
}

sub summary
{
    my $host_process_handler = host_get_process_handler();
    my $host_init_system = host_get_init_system();

    debug("request: summary");
    debug("process handler: $host_process_handler");

    if ($host_init_system eq 'systemd') {
        my $colored_output = -t STDOUT; ## no critic (InputOutput::ProhibitInteractiveTest)
        my $host_is_active = host_is_active();
        my $rc = 0;

        summary_row(
            'Ok', undef,
            'Service', undef,
            'Managed', undef,
            'Started', undef,
            'Status', undef,
        );
        summary_row(
            '-' x SUMMARY_COL_OK, undef,
            '-' x SUMMARY_COL_SERVICE, undef,
            '-' x SUMMARY_COL_MANAGED, undef,
            '-' x SUMMARY_COL_STARTED, undef,
            '-' x SUMMARY_COL_STATUS, undef,
        );

        # Check services current and expected status.
        foreach my $servdesc (@{serv_meta_with_systemd_props()}) {
            my $service = $servdesc->{name};
            my $service_color;
            my $started = 'on-boot';
            my $started_color;
            my $managed = 'unmanaged';
            my $managed_color;
            my $ok;

            # Check whether the service is relevant for this platform.
            next unless serv_in_platform($servdesc);

            # Check whether the service is relevant for this NGCP type.
            next unless serv_in_ngcp_type($servdesc);

            # Check how the service is HA managed.
            if (serv_is_ha_managed($servdesc)) {
                $started = 'by-ha';
            }

            # Check the expected status.
            my $exp_status = 'inactive';
            if ($servdesc->{enable} eq 'yes' and
                serv_in_role($servdesc) and
                serv_in_host($servdesc))
            {
                $managed = 'managed';
                $exp_status = serv_get_exp_ha_status($servdesc, $host_is_active);
            }

            # Check the current status.
            my $cur_status = $servdesc->{props}{systemd}{ActiveState};

            # Compare the current vs the expected status.
            my $status = $cur_status;
            my $status_color;
            if ($exp_status eq 'unknown') {
                $service_color = 'bright_cyan';
                $ok = "?";
            } elsif ($cur_status eq $exp_status) {
                $service_color = 'clear';
                $ok = " ";
            } elsif ($managed eq 'unmanaged' and $cur_status eq 'active') {
                $service_color = 'bright_yellow';
                $ok = "~";
            } else {
                $service_color = 'bright_red';
                $ok = "!";
            }

            if ($cur_status ne $exp_status) {
                $rc = 1;
            }

            if ($colored_output) {
                $started_color = $started eq 'on-boot' ? 'bright_blue' : 'bright_magenta';
                $managed_color = 'bright_white' if $managed eq 'managed';
                if ($cur_status eq 'active') {
                    $status_color = 'bright_green';
                } elsif ($cur_status eq 'inactive') {
                    $status_color = 'clear';
                } elsif ($cur_status eq 'failed') {
                    $status_color = 'bright_red';
                } else {
                    $status_color = 'bright_yellow';
                }
            } else {
                $service_color = undef;
            }

            summary_row(
                $ok, $service_color,
                $service, $service_color,
                $managed, $managed_color,
                $started, $started_color,
                $status, $status_color,
            );
        }

        return $rc;
    } elsif ($host_process_handler eq 'monit') {
        my @cmd = (host_get_process_handler_pathname('monit'), 'summary');
        my $rc = execute(\@cmd, verbose => 1);
        return $rc;
    } else {
        error("Unsupported 'summary' for process_handler $host_process_handler");
        return 1;
    }
}

sub sync_state
{
    my $opts = shift;

    my $host_process_handler = host_get_process_handler();
    my $host_init_system = host_get_init_system();

    debug("request: sync-state");
    debug("process handler: $host_process_handler");

    # We cannot reliably fetch status information from services.
    return 1 if host_get_init_system() ne 'systemd';

    my $host_is_active = host_is_active();
    my %services;

    # Check services current and expected status.
    foreach my $servdesc (@{serv_meta_with_systemd_props()}) {
        my $service = $servdesc->{name};

        if (host_get_maintenance() eq 'yes') {
            next if any { $_ eq 'ngcp-upgrade-ha' } @{$servdesc->{group}};
        }
        next if $servdesc->{props}{systemd}{Type} eq 'oneshot';

        # Check the expected status.
        my $exp_status = 'inactive';
        if ($servdesc->{enable} eq 'yes' and
            serv_in_platform($servdesc) and
            serv_in_ngcp_type($servdesc) and
            serv_in_role($servdesc))
        {
            $exp_status = serv_get_exp_ha_status($servdesc, $host_is_active);
        }

        # Check the current status.
        my $cur_status = $servdesc->{props}{systemd}{ActiveState};

        # Skip if state matches.
        next if $cur_status eq $exp_status;

        # Decide whether we need to reset failed states.
        if ($cur_status eq 'failed') {
            push @{$services{'reset-failed'}}, $service;
        }

        # Decide required action.
        my $action;
        if ($exp_status eq 'inactive') {
            $action = 'stop';
        } elsif ($exp_status eq 'active') {
            $action = 'start';
        }

        push @{$services{$action}}, $service;
    }

    my $rc = 0;
    foreach my $action (qw(reset-failed stop start)) {
        $opts->{action} = $action;
        $opts->{service} = $services{$action};
        $rc |= do_action($opts);
    }
    return $rc;
}

sub usage
{
    print <<"HELP";
Usage: ngcp-service [<option...>] <command> |
                    <action> <service>... |
                    <service> <action> |
                    --action=<action> [--service=<service1>...|--group=<group>]

Options:
      --skip-ha-state-check Do not check HA state before running the action.
      --timeout <timeout>   Timeout for the operation.
      --async               Perform asynchronous operations (do not wait).
      --enqueue             Add this action into the delayed actions queue.
      --queue <queue>[,...] Use the specified delayed actions queue(s).
  -a, --action <action>     Action to apply (see Actions section).
  -s, --service <service>   Service (can be given multiple times).
  -g, --group <group>       Service group we want to act on.
  -v, --verbose             Increase output verbosity (default).
      --quiet               Disable verbose output.
      --debug               Enable debug output.
  -h, --help                Show this help message and exit.

Commands:
  summary                   Print a summary of the services (default).
  queue-start               Start the delayed actions queue mode.
  queue-clear               Clear the delayed actions queue.
  queue-show                Show the delayed actions queue.
  queue-run                 Run the delayed actions queue.
  sync-state                Synchronize the current with the expected state.

Actions:
  is-managed                Check whether the service is managed on this node.
  status                    Print the service status.
  start                     Start the service.
  stop                      Stop the service.
  restart                   Restart the service.
  reload                    Reload the service configuration.
HELP
}

sub usageerr
{
    my $msg = shift;

    error("$msg\n\nUsage error, try --help.");
    exit 2;
}

sub args_is_action
{
    my $action = shift;
    my %actions = map { $_ => 1 } qw(
        is-managed
        status
        start
        stop
        restart
        reload
    );

    return exists $actions{$action};
}

sub args_validate_action
{
    my $action = shift;

    usageerr("unknown action: $action") if not args_is_action($action);
}

sub args_validate_command
{
    my $command = shift;
    my %commands = map { $_ => 1 } qw(
        summary
        queue-start
        queue-clear
        queue-show
        queue-run
        sync-state
    );

    usageerr("unknown command: $command") if not exists $commands{$command};
}

sub args_parse
{
    my %opts = (
        help => sub { usage(); exit 0; },
        queue => 'default',
        verbose => undef,
        debug => 0,
    );
    $opts{quiet} = sub { $opts{verbose} = 0 };
    my @opts_spec = (
        'verbose|v',
        'quiet',
        'debug',
        'timeout=i',
        'async',
        'enqueue',
        'queue=s',
        'skip-ha-state-check',
        'action|a=s',
        'service|s=s@',
        'group|g=s',
        'help|?',
    );

    create_logger(
        use_stdout => 0,
    );

    local $SIG{__WARN__} = sub { usageerr(shift) };
    GetOptions(\%opts, @opts_spec);

    # Special case the default command.
    if (@ARGV == 0 and not exists $opts{action} and
        not (exists $opts{service} or exists $opts{group})) {
        unshift @ARGV, 'summary';
    }

    if (@ARGV == 0) {
        if (not exists $opts{action}) {
            usageerr("missing action");
        }
        if (not (exists $opts{service} or exists $opts{group})) {
            usageerr("missing service(s) or group");
        }
        if (exists $opts{service} and exists $opts{group}) {
            usageerr("mutually exclusive service(s) and group options");
        }

        # If we are selecting via groups, we need to check all services.
        if (not exists $opts{service} and exists $opts{group}) {
            $opts{service} = [ sort keys %{serv_meta()} ];
        }

        args_validate_action($opts{action});
    } elsif (@ARGV == 1) {
        my $cmd = shift @ARGV;

        $opts{action} = $cmd;

        usageerr("action '$cmd' is missing arguments")
            if args_is_action($cmd);
        args_validate_command($cmd);
    } elsif (@ARGV == 2) {
        if (exists $opts{service} or exists $opts{action}) {
            usageerr("invalid call mode");
        }

        if (args_is_action($ARGV[0])) {
            $opts{action} = shift @ARGV;
            $opts{service} = [ shift @ARGV ];
        } else {
            # Legacy mode, supported for backwards compatibility.
            $opts{service} = [ shift @ARGV ];
            $opts{action} = shift @ARGV;

            args_validate_action($opts{action});
        }
    } elsif (@ARGV > 2) {
        if (exists $opts{service} or exists $opts{action}) {
            usageerr("invalid call mode");
        }

        $opts{action} = shift @ARGV;
        $opts{service} = [ @ARGV ];

        args_validate_action($opts{action});
    }

    # Set default if missing. We make is-managed default to quiet mode as
    # it's a query command, and printing information is rather confusing.
    $opts{verbose} //= $opts{action} eq 'is-managed' ? 0 : 1;

    if ($opts{debug}) {
        $opts{log_upto} = 'debug';
    } elsif ($opts{verbose}) {
        $opts{log_upto} = 'info';
    } else {
        $opts{log_upto} = 'notice';
    }

    return \%opts;
}

sub main
{
    my ($opts) = @_;
    my $rc = 0;

    create_logger(
        output => 'both',
        use_stdout => 0,
        upto => $opts->{log_upto},
    );

    if ($opts->{action} eq 'summary') {
        # This is an equivalent command to "monit summary", which should stop
        # being used as we are trying to get rid of monit, by replacing it
        # with native systemd support (see TT#18774).
        $rc = summary();
    } elsif ($opts->{action} eq 'sync-state') {
        $rc = sync_state($opts);
    } elsif ($opts->{action} eq 'is-managed') {
        $rc = action_is_managed($opts);
    } elsif ($opts->{action} eq 'queue-start') {
        queue_start($opts);
    } elsif ($opts->{action} eq 'queue-clear') {
        queue_clear($opts);
    } elsif ($opts->{action} eq 'queue-show') {
        queue_show($opts);
    } elsif ($opts->{action} eq 'queue-run') {
        queue_run($opts);
        queue_clear($opts);
    } elsif ($opts->{enqueue} and queue_is_started($opts->{queue})) {
        # Strictly speaking this is not an action, but we treat it as such.
        $rc = enqueue_action($opts);
    } else {
        warning("enqueueing requested but delayed actions queue not started")
            if $opts->{enqueue} and not queue_is_started($opts->{queue});

        $rc = do_action($opts);
    }

    exit $rc;
}

my $opts = args_parse();
main($opts);

__END__

=pod

=head1 NAME

ngcp-service - manage NGCP system services

=head1 SYNOPSIS

=over 4

=item B<ngcp-service> [I<option>...] I<command>

=item B<ngcp-service> [I<option>...] I<action> I<service>...

=item B<ngcp-service> [I<option>...] I<service> I<action>

=item B<ngcp-service> [I<option>...] B<--action>=I<action> B<--service>=I<service>...|B<--group>=I<group>

=back

=head1 DESCRIPTION

This program manages the NGCP system services, taking into account the current
init system, any process manager (such as B<monit>), the NGCP type of the
node, its role and whether its HA state if appropriate, and whether the
service has been enabled in the YAML configuration files.

The program has two major groups of operations, one with system-wide commands,
and another with service specific actions.

The preferred forms for the action operations are either with the action
first and then services or with the explicit B<--action> and B<--service>
or B<--group> options. The form with the service name first is kept for
backwards compatibility with older versions of the program and with the
B<service>(8) program, even though the usage of this form is discouraged.

The program has two main modes of operation when it comes to service actions.
The default immediate mode where the actions are performed right away.
And the deferred or queued mode, where actions are recorded but not acted on,
until later when they are all synthesized and dispatched together in a single
run.

To use the deferred or queued mode, the following steps need to be followed:

=over 4

=item * Start queue(s) with B<queue-start>

The queue(s) can be the B<default> one when not specified or a list as
specified with B<--queue>.
If there is a need to guarantee the queues are empty on start, a B<queue-clear>
can be used B<before> creating the queues.

=item * Enqueue service actions with B<--enqueue>

Use service actions and enqueue them into the desired queue (possibly via
B<--queue>).

=item * Listing the queues contents

Use the B<queue-show> to list the queues contents (possibly with B<--queue>).

=item * Flush the queue with B<queue-clear> or B<queue-run>

The queues can be flushed by either clearing them with B<queue-clear>, where
no actions will be performed, or by synthesizing and dispatching the actions
with B<queue-run>.
The queues to act on can also be selected with B<--queue>.

=back

=head1 OPTIONS

=over 4

=item B<-a>, B<--action> I<action>

Action to apply (see L<ACTIONS> section).

=item B<-s>, B<--service> I<service>

Service (can be given multiple times). When specifying services no group
can be specified.

=item B<-g>, B<--group> I<group>

Service group we want to act on. When specifying a group no services can
be specified.

=item B<--timeout> I<timeout>

Timeout for the operation.

=item B<--async>

Perform asynchronous operations, do not wait before returning.

=item B<--enqueue>

Add the specified action into the delayed actions queue.

=item B<--queue> I<queue>[,...]

Use the specified delayed actions queue(s) instead of using the queue
named B<default>.

=item B<--skip-ha-state-check>

Do not check the high-availability (HA) state before running the action.
This is mostly useful when using this program within HA controlled code,
to avoid loops and dead-locks.

=item B<-v>, B<--verbose>

Increase output verbosity (default).

=item B<--quiet>

Disable verbose output.

=item B<--debug>

Enable debug output.

=item B<-h>, B<--help>

Show this help message and exit.

=back

=head1 COMMANDS

The following are the system-wide commands:

=over 4

=item B<summary>

Print a summary of the services. The output is organized in the following
columns:

=over 4

=item B<Ok>

A single character showing the overall service status.
No character means the service is in a correct state (whether that means
running or stopped). A B<!> denotes an error. A B<~> denotes that the
service is running when it is not expected. A B<?> denotes that the state
is unknown.

=item B<Service>

The service name.

=item B<Managed>

Whether the service is managed by the platform on this node, because it
is intended for this NGCP type, role and is enabled in the configuration.

=item B<Started>

The entity responsible for starting the service. This can be either B<on-boot>
when the service is started during the boot process, or B<by-ha> when the
process only should be running on the active node in a high availability
setup.

=item B<Status>

The service status. This can be one of B<active> (when the service is running),
B<inactive> (when it is stopped), B<failed> (when there was some problem
while starting or stopping the service), among others.

=back

This is the default command when nothing is specified.

=item B<queue-start>

Start the delayed actions queue mode. After this command has been issued,
the next service actions will go into this queue for deferred processing.

=item B<queue-clear>

Clear the delayed actions queue.

=item B<queue-show>

Show the delayed actions queue.

=item B<queue-run>

Run the delayed actions queue. This command will synthesize the queued actions
for the services by reducing any redundant actions and execute any required
command needed in one go.

=item B<sync-state>

Synchronize the current state with the expected state. This will make sure
first that any service that is in a failed state gets it reset. Then it will
make sure that any services that is stopped that needs to be started and any
that is running and needs to be stopped gets to its intended state.

=back

=head1 ACTIONS

The following are the service-specific actions:

=over 4

=item  B<is-managed>

Check whether the service is managed on this node.

=item B<status>

Print the service status.

=item B<start>

Start the service.

=item B<stop>

Stop the service.

=item B<restart>

Restart the service.

=item B<reload>

Reload the service configuration.

=back

=head1 EXIT STATUS

=over 4

=item B<0>

Success.

=item B<1>

An error occurred with the commands or actions.

=item B<2>

A usage error occurred when invoking this program.

=back

=head1 SEE ALSO

B<service>(8), B<systemctl>(1), B<monit>(1).

=head1 BUGS AND LIMITATIONS

Please report problems you notice to the Sipwise
Development Team <support@sipwise.com>.

=head1 AUTHOR

Guillem Jover <gjover@sipwise.com>

=head1 LICENSE

Copyright (C) 2020 Sipwise GmbH, Austria

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
