You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ngcp-panel/tools_bin/ngcp-emergency-mode

262 lines
8.4 KiB

#!/usr/bin/perl
use warnings;
use strict;
use English;
my $config_path = '/etc/ngcp-emergency-mode/ngcp-emergency-mode.conf';
# required to use XMLDispatcher from ngcp-panel
{
package DummyLogger;
use Moose;
sub debug {};
sub info {};
## no critic (Subroutines::ProhibitBuiltinHomonyms)
sub warn {};
sub error {};
sub fatal {};
1;
}
{
package DummyController;
use Moose;
has 'log' => (
is => 'rw',
isa => 'DummyLogger',
default => sub { return DummyLogger->new; }
);
1;
}
use Redis;
use URI;
use NGCP::Panel::Utils::XMLDispatcher;
use NGCP::API::Client;
use Config::Simple;
use JSON;
use TryCatch;
use Sys::Syslog qw(:standard :macros);
use IO::Prompt::Tiny qw(prompt);
openlog($PROGRAM_NAME, "ndelay,pid", LOG_LOCAL0);
sub DEBUG {
my ($msg) = @_;
# only log debug to syslog to not clutter console
syslog(LOG_DEBUG, $msg);
}
sub INFO {
my ($msg) = @_;
print $msg, "\n";
syslog(LOG_INFO, $msg);
}
sub ERROR {
my ($msg) = @_;
print STDERR $msg, "\n";
syslog(LOG_ERR, $msg);
}
my $mode = shift @ARGV;
my @emergency_domains = @ARGV;
unless(defined $mode && ($mode eq "enable" || $mode eq "disable" || $mode eq "status") && @emergency_domains) {
ERROR "Usage: $PROGRAM_NAME <enable|disable|status> <all|[domain1 domain2 ...]>";
exit 1;
}
DEBUG "Emergency mode '$mode' requested for domains " . join(", ", @emergency_domains);
my $config = Config::Simple->new($config_path);
my $enabled = $config->param('ENABLED');
my @redis_hosts = split(/\s*,\s*/, $config->param('REDIS_IPS'));
my $redis_port = $config->param('REDIS_PORT');
my $redis_db = $config->param('REDIS_DB');
unless($enabled) {
ERROR "Emergency mode is globally disabled in config.yml, aborting!";
exit 1;
}
DEBUG "Using redis db $redis_db on port $redis_port for hosts " . join(", ", @redis_hosts);
my $client = NGCP::API::Client->new();
my $res = $client->request('GET', '/api/domains/');
unless($res->is_success) {
ERROR "Failed to fetch domains from API, aborting!";
exit 1;
}
my $ngcp_domains = $res->as_hash->{_embedded}->{'ngcp:domains'};
my %domain_names = map { ($_->{domain}, $_->{id}) } @{ $ngcp_domains };
my %domain_ids = map { ($_->{id}, $_->{domain}) } @{ $ngcp_domains };
my %emergency_domain_names = ();
foreach my $dom(@emergency_domains) {
if($dom eq 'all') {
# use all domains
%emergency_domain_names = %domain_names;
last;
}
if(!exists $domain_names{$dom}) {
ERROR "Domain $dom does not exist, aborting!";
exit 1;
}
$emergency_domain_names{$dom} = $domain_names{$dom};
}
unless ($mode eq "status") {
INFO "" . ($mode eq "enable" ? "A" : "Dea") . "ctivating emergency mode for domains " . join(", ", keys %emergency_domain_names);
DEBUG "Waiting for user confirmation...";
$res = prompt('Please confirm (yes/no):');
DEBUG "User entered '$res'";
if($res ne "yes") {
INFO "Aborting emergency mode $mode by user request!";
exit 0;
}
}
foreach my $domid(values %emergency_domain_names) {
$res = $client->request('GET', '/api/domainpreferences/'.$domid);
unless($res->is_success) {
ERROR "Failed to fetch preferences for domain $domain_ids{$domid}, skipping!";
next;
}
my $prefs = $res->as_hash;
if ($mode eq "status") {
INFO "domain $domain_ids{$domid} status: " . ($prefs->{emergency_mode_enabled} ? "enabled" : "disabled");
} elsif ($mode eq "enable" && exists $prefs->{emergency_mode_enabled} && $prefs->{emergency_mode_enabled} == 1) {
INFO "Emergency mode for domain $domain_ids{$domid} already active, skipping...";
} elsif ($mode eq "disable" && (!exists $prefs->{emergency_mode_enabled} || $prefs->{emergency_mode_enabled} == 0)) {
INFO "Emergency mode for domain $domain_ids{$domid} already inactive, skipping...";
} else {
$prefs->{emergency_mode_enabled} = ($mode eq "enable" ? JSON::true : JSON::false);
$res = $client->request('PUT', '/api/domainpreferences/'.$domid, $prefs);
unless($res->is_success) {
ERROR "Failed to $mode emergency mode for domain $domain_ids{$domid}, skipping...";
next;
}
}
}
if ($mode eq "status") {
exit 0;
} elsif ($mode eq "disable") {
INFO "Emergency mode disabled.";
exit 0;
}
DEBUG "Tearing down non-emergency calls of activated domains...";
foreach my $redis_host(@redis_hosts) {
DEBUG "Checking redis at $redis_host:$redis_port...";
my $redis;
try {
$redis = Redis->new(
server => "$redis_host:$redis_port",
cnx_timeout => 3,
read_timeout => 3,
on_connect => sub {
my ($redis) = @_;
$redis->select($redis_db);
});
} catch($e) {
DEBUG "Failed to connect to redis at $redis_host:$redis_port, skipping...";
}
next unless(defined $redis);
DEBUG "Fetching role of redis instance...";
my @role = $redis->role;
my $role = shift @role;
unless($role eq "master") {
DEBUG "Redis at $redis_host:$redis_port has role $role, skipping...";
next;
}
DEBUG "Processing call list on redis...";
my @tags = $redis->smembers('sems_calls');
my %priocalls = ();
my %nonpriocalls = ();
my %tag2cid = ();
foreach my $tag (@tags) {
my %call = $redis->hgetall($tag);
my $cid = $call{ci};
$cid =~ s/_b2b\-1//g;
$tag2cid{$tag} = $cid;
my $local_dom = URI->new($call{lp})->host;
my $remote_dom = URI->new($call{rp})->host;
if($call{ru} =~ /;ep[ab]=yes/) {
if(exists $priocalls{$cid}) {
push @{ $priocalls{$cid} }, { tag => $tag, local_dom => $local_dom, remote_dom => $remote_dom };
} else {
$priocalls{$cid} = [ { tag => $tag, local_dom => $local_dom, remote_dom => $remote_dom } ];
}
DEBUG "Call $cid has an emergency priorization leg on tag $tag, caching for emergency domain check...";
} else {
if(exists $nonpriocalls{$cid}) {
push @{ $nonpriocalls{$cid} }, { tag => $tag, local_dom => $local_dom, remote_dom => $remote_dom };
} else {
$nonpriocalls{$cid} = [ { tag => $tag, local_dom => $local_dom, remote_dom => $remote_dom } ];
}
DEBUG "Call $cid has no emergency priorization leg on tag $tag...";
}
}
my @nonpriotags = ();
foreach my $cid (keys %nonpriocalls) {
DEBUG "Cross checking call $cid for emergency domain...";
my $is_emergency_domain = 0;
my $is_emergency_call = 0;
foreach my $leg(@{ $nonpriocalls{$cid} }) {
if(exists $emergency_domain_names{$leg->{local_dom}} ||
exists $emergency_domain_names{$leg->{remote_dom}}) {
DEBUG "Non-priorized call $cid has leg in emergency domain, cross checking with priorized legs...";
$is_emergency_domain = 1;
}
}
foreach my $leg(@{ $priocalls{$cid} }) {
if($is_emergency_domain ||
exists $emergency_domain_names{$leg->{local_dom}} ||
exists $emergency_domain_names{$leg->{remote_dom}}) {
DEBUG "Priorized call $cid has leg in emergency domain, mark as priorized!";
$is_emergency_call = 1;
}
}
unless($is_emergency_call) {
DEBUG "Normal call $cid found...";
foreach my $leg(@{ $nonpriocalls{$cid} }) {
push @nonpriotags, $leg->{tag};
}
}
}
my $c = DummyController->new;
foreach my $tag(@nonpriotags) {
DEBUG "Tearing down normal call tag $tag for call id $tag2cid{$tag}...";
my @ret = NGCP::Panel::Utils::XMLDispatcher::dispatch($c, "appserver", 1, 1, <<EOF);
<?xml version="1.0" encoding="UTF-8"?>
<methodCall>
<methodName>di</methodName>
<params>
<param><value><string>sbc</string></value></param>
<param><value><string>postControlCmd</string></value></param>
<param><value><string>$tag</string></value></param>
<param><value><string>teardown</string></value></param>
</params>
</methodCall>
EOF
if (grep { $$_[1] != 1 or $$_[2] !~ m#<value>(Accepted|Not found)</value># } @ret) {
DEBUG "Failed to dispatch teardown request: " . join(", ", @ret);
}
}
}
INFO "Emergency mode successfully activated";
1;
# vim: set tabstop=4 expandtab: