#!/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 "; 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, < di sbc postControlCmd $tag teardown EOF if (grep { $$_[1] != 1 or $$_[2] !~ m#(Accepted|Not found)# } @ret) { DEBUG "Failed to dispatch teardown request: " . join(", ", @ret); } } } INFO "Emergency mode successfully activated"; 1; # vim: set tabstop=4 expandtab: