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.
		
		
		
		
		
			
		
			
				
					
					
						
							261 lines
						
					
					
						
							8.4 KiB
						
					
					
				
			
		
		
	
	
							261 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 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:
 |