diff --git a/.env.example b/.env.example new file mode 100644 index 000000000..9a4f2e13b --- /dev/null +++ b/.env.example @@ -0,0 +1,6 @@ +MYSQL_PASSWORD= +OESS_PASSWORD= +OESS_NETWORK_TYPE=vpn-mpls +NSO_HOST= +NSO_PASSWORD= +NSO_USERNAME= diff --git a/.gitignore b/.gitignore index 513d6ab88..3ecfcc91d 100644 --- a/.gitignore +++ b/.gitignore @@ -50,3 +50,5 @@ perl-lib/OESS/lib/OESS/pod2htmd.tmp perl-lib/OESS/lib/OESS/pod2htmi.tmp perl-lib/OESS/lib/OESS/t/conf/database.xml frontend/.htpasswd +*.DS_Store +.env diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 000000000..a7ef8df83 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,26 @@ +FROM centos:7 + +COPY globalnoc-public-el7.repo /etc/yum.repos.d/globalnoc-public-el7.repo +RUN curl -s https://packagecloud.io/install/repositories/rabbitmq/erlang/script.rpm.sh | bash +RUN curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.rpm.sh | bash + +RUN yum makecache +RUN yum -y install epel-release +RUN yum -y install perl httpd mariadb-server rabbitmq-server +RUN yum -y install perl-Carp-Always perl-Test-Deep perl-Test-Exception perl-Test-Pod perl-Test-Pod-Coverage perl-Devel-Cover perl-Net-AMQP-RabbitMQ perl-LWP-Protocol-https perl-AnyEvent-HTTP +RUN yum -y install perl-OESS oess-core oess-frontend yui2 + +COPY app/mpls/mpls_discovery.pl /usr/bin/mpls_discovery.pl +COPY app/mpls/mpls_fwdctl.pl /usr/bin/mpls_fwdctl.pl + +COPY frontend/conf/oe-ss.conf.example /etc/httpd/conf.d/oe-ss.conf +COPY app/etc/firmware.xml /etc/oess/firmware.xml +COPY perl-lib/OESS/t/conf/database.xml /etc/oess/database.xml +COPY perl-lib/OESS/t/conf/logging.conf /etc/oess/logging.conf +COPY perl-lib/OESS/t/conf/passwd.xml /etc/oess/.passwd.xml + +COPY perl-lib/OESS/entrypoint.dev.sh /entrypoint.sh +RUN chmod 777 /entrypoint.sh + +RUN touch /var/log/oess.log +RUN chmod 666 /var/log/oess.log diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..8201fafdc --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +OESS_VERSION=2.0.11 +OESS_NETWORK=oess + +container: + docker build -f Dockerfile.dev --tag oess:${OESS_VERSION} . +dev: + docker run -it \ + --env-file .env \ + --publish 8000:80 \ + --publish 5672:5672 \ + --network ${OESS_NETWORK} \ + --mount type=bind,src=${PWD}/perl-lib/OESS/lib/OESS,dst=/usr/share/perl5/vendor_perl/OESS \ + --mount type=bind,src=${PWD}/frontend,dst=/usr/share/oess-frontend \ + --mount type=bind,src=${PWD}/perl-lib/OESS/share,dst=/usr/share/doc/perl-OESS-${OESS_VERSION}/share \ + oess:${OESS_VERSION} /bin/bash diff --git a/app/Makefile b/app/Makefile index cc8946711..ad3702f6e 100644 --- a/app/Makefile +++ b/app/Makefile @@ -1,5 +1,5 @@ NAME= oess-core -VERSION = 2.0.11 +VERSION = 2.0.12 rpm: dist rpmbuild -ta dist/$(NAME)-$(VERSION).tar.gz diff --git a/app/etc/firmware.xml b/app/etc/firmware.xml index ff3963c90..ed1652f8c 100644 --- a/app/etc/firmware.xml +++ b/app/etc/firmware.xml @@ -4,4 +4,6 @@ + + diff --git a/app/mpls/mpls_discovery.pl b/app/mpls/mpls_discovery.pl index c8b281b76..f4b6d019b 100755 --- a/app/mpls/mpls_discovery.pl +++ b/app/mpls/mpls_discovery.pl @@ -9,16 +9,57 @@ use Proc::Daemon; use Data::Dumper; -use OESS::Database; +use GRNOC::WebService::Client; + +use OESS::Config; use OESS::MPLS::Discovery; +use OESS::NSO::Client; +use OESS::NSO::Discovery; my $pid_file = "/var/run/oess/mpls_discovery.pid"; +my $cnf_file = "/etc/oess/database.xml"; sub core{ - #basic init stuffs - Log::Log4perl::init_and_watch('/etc/oess/logging.conf',10); + Log::Log4perl::init_and_watch('/etc/oess/logging.conf', 10); + + my $config = new OESS::Config(config_filename => $cnf_file); + my $mpls_discovery; + my $nso_discovery; + my $nso_client = new OESS::NSO::Client(config_obj => $config); + my $tsds_client = new GRNOC::WebService::Client( + url => $config->tsds_url . "/push.cgi", + uid => $config->tsds_username, + passwd => $config->tsds_password, + realm => $config->tsds_realm, + usePost => 1 + ); + + if ($config->network_type eq 'nso') { + my $nso_discovery = OESS::NSO::Discovery->new( + config_obj => $config, + nso => $nso_client, + tsds => $tsds_client + ); + $nso_discovery->start; + } + elsif ($config->network_type eq 'vpn-mpls' || $config->network_type eq 'evpn-vxlan') { + $mpls_discovery = OESS::MPLS::Discovery->new(config_obj => $config); + } + elsif ($config->network_type eq 'nso+vpn-mpls') { + $nso_discovery = OESS::NSO::Discovery->new( + config_obj => $config, + nso => $nso_client, + tsds => $tsds_client + ); + $nso_discovery->start; + + $mpls_discovery = OESS::MPLS::Discovery->new(config_obj => $config); + } + else { + die "Unexpected network type configured."; + } - my $discovery = OESS::MPLS::Discovery->new(); + Log::Log4perl->get_logger('OESS.MPLS.Discovery.APP')->info("Starting OESS.MPLS.Discovery event loop."); AnyEvent->condvar->recv; } diff --git a/app/mpls/mpls_fwdctl.pl b/app/mpls/mpls_fwdctl.pl index d06c8c5d2..92cb43df7 100755 --- a/app/mpls/mpls_fwdctl.pl +++ b/app/mpls/mpls_fwdctl.pl @@ -2,7 +2,9 @@ use strict; use warnings; +use OESS::Config; use OESS::MPLS::FWDCTL; +use OESS::NSO::FWDCTLService; use English; use Data::Dumper; @@ -12,6 +14,7 @@ use XML::Simple; my $pid_file = "/var/run/oess/mpls_fwdctl.pid"; +my $cnf_file = "/etc/oess/database.xml"; sub get_diff_interval{ eval { @@ -27,11 +30,36 @@ sub get_diff_interval{ sub core{ Log::Log4perl::init_and_watch('/etc/oess/logging.conf', 10); - my $FWDCTL = OESS::MPLS::FWDCTL->new(); - my $reaper = AnyEvent->timer( after => 3600, interval => 3600, cb => sub { $FWDCTL->reap_old_events() } ); - my $status = AnyEvent->timer( after => 10, interval => 60, cb => sub { $FWDCTL->save_mpls_nodes_status() } ); - my $differ = AnyEvent->timer( after => 5, interval => get_diff_interval(), cb => sub { $FWDCTL->diff() } ); - + my $config = new OESS::Config(config_filename => $cnf_file); + my $mpls_fwdctl; + my $mpls_reaper; + my $mpls_status; + my $mpls_differ; + my $nso_fwdctl; + + if ($config->network_type eq 'nso') { + $nso_fwdctl = OESS::NSO::FWDCTLService->new(config_obj => $config); + $nso_fwdctl->start; + } + elsif ($config->network_type eq 'vpn-mpls' || $config->network_type eq 'evpn-vxlan') { + $mpls_fwdctl = OESS::MPLS::FWDCTL->new(config_obj => $config); + $mpls_reaper = AnyEvent->timer( after => 3600, interval => 3600, cb => sub { $mpls_fwdctl->reap_old_events() } ); + $mpls_status = AnyEvent->timer( after => 10, interval => 60, cb => sub { $mpls_fwdctl->save_mpls_nodes_status() } ); + $mpls_differ = AnyEvent->timer( after => 15, interval => get_diff_interval(), cb => sub { $mpls_fwdctl->diff() } ); + } + elsif ($config->network_type eq 'nso+vpn-mpls') { + $nso_fwdctl = OESS::NSO::FWDCTLService->new(config_obj => $config); + $nso_fwdctl->start; + + $mpls_fwdctl = OESS::MPLS::FWDCTL->new(config_obj => $config); + $mpls_reaper = AnyEvent->timer( after => 3600, interval => 3600, cb => sub { $mpls_fwdctl->reap_old_events() } ); + $mpls_status = AnyEvent->timer( after => 10, interval => 60, cb => sub { $mpls_fwdctl->save_mpls_nodes_status() } ); + $mpls_differ = AnyEvent->timer( after => 15, interval => get_diff_interval(), cb => sub { $mpls_fwdctl->diff() } ); + } + else { + die "Unexpected network type configured."; + } + Log::Log4perl->get_logger('OESS.MPLS.FWDCTL.APP')->info("Starting OESS.MPLS.FWDCTL event loop."); AnyEvent->condvar->recv; } @@ -42,8 +70,8 @@ sub main{ my $username; #remove the ready file - # This directory is auto-removed on reboot. Create the directory - # if not already created. + # This directory is auto-removed on reboot. Create the directory if not + # already created. This is used to store connection cache files. if (!-d "/var/run/oess/") { `/usr/bin/mkdir /var/run/oess`; `/usr/bin/chown _oess:_oess /var/run/oess`; diff --git a/app/nso/nso_discovery.pl b/app/nso/nso_discovery.pl new file mode 100755 index 000000000..2f1a5f4ba --- /dev/null +++ b/app/nso/nso_discovery.pl @@ -0,0 +1,104 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use AnyEvent; +use English; +use Getopt::Long; +use Proc::Daemon; + +use OESS::Config; +use OESS::NSO::Discovery; + +my $pid_file = "/var/run/oess/nso_discovery.pid"; +my $cnf_file = "/etc/oess/database.xml"; + +sub core{ + Log::Log4perl::init_and_watch('/etc/oess/logging.conf', 10); + + my $config = new OESS::Config(config_filename => $cnf_file); + if ($config->network_type eq 'nso') { + my $discovery = new OESS::NSO::Discovery(config_obj => $config); + $discovery->start; + AnyEvent->condvar->recv; + } else { + die "Unexpected network type configured."; + } + + Log::Log4perl->get_logger('OESS.NSO.Discovery.APP')->info("Starting OESS.NSO.Discovery event loop."); +} + +sub main{ + my $is_daemon = 0; + my $verbose; + my $username; + #remove the ready file + + #--- see if the pid file exists. if not then just continue running. + if(-e $pid_file){ + #--- read the file to get the PID + my $pid = `head -n 1 $pid_file`; + chomp $pid; + + my $run_test = `ps -p $pid | grep $pid`; + + #--- if run test is empty then the pid didn't exist. If it isn't empty then the process is already running. + if($run_test){ + print "Found $0 process: $pid already running. Aborting.\n"; + exit(0); + } + else{ + print "Pid File: $pid_file already exists but it looks like process $pid is dead. Continuing startup.\n"; + } + } + + my $result = GetOptions ( + "user|u=s" => \$username, + "verbose" => \$verbose, + "daemon|d" => \$is_daemon, + ); + + #now change username/ + if (defined $username) { + my $new_uid=getpwnam($username); + my $new_gid=getgrnam($username); + $EGID=$new_gid; + $EUID=$new_uid; + } + + if ($is_daemon != 0) { + my $daemon; + if ($verbose) { + $daemon = Proc::Daemon->new( + pid_file => $pid_file, + child_STDOUT => '/var/log/oess/nso_discovery.out', + child_STDERR => '/var/log/oess/nso_discovery.log', + ); + } else { + $daemon = Proc::Daemon->new(pid_file => $pid_file); + } + + # Init returns the PID (scalar) of the daemon to the parent, or + # the PIDs (array) of the daemons created if exec_command has + # more then one program to execute. + # + # Init returns 0 to the child (daemon). + my $kid_pid = $daemon->Init; + if ($kid_pid) { + `chmod 0644 $pid_file`; # How to wait until the child process is ready. + return; + } else { + core(); + } + } + #not a daemon, just run the core; + else { + $SIG{HUP} = sub{ exit(0); }; + core(); + } +} + +main(); + +1; diff --git a/app/nso/nso_fwdctl.pl b/app/nso/nso_fwdctl.pl new file mode 100755 index 000000000..8288f86ce --- /dev/null +++ b/app/nso/nso_fwdctl.pl @@ -0,0 +1,123 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use AnyEvent; +use English; +use Getopt::Long; +use Log::Log4perl; +use Proc::Daemon; +use XML::Simple; + +use OESS::Config; +use OESS::NSO::FWDCTLService; + +my $pid_file = "/var/run/oess/nso_fwdctl.pid"; +my $cnf_file = "/etc/oess/database.xml"; + +sub get_diff_interval{ + eval { + my $xml = XMLin('/etc/oess/fwdctl.xml'); + my $diff_interval = $xml->{diff}->{interval}; + die unless defined $diff_interval; + return $diff_interval; + } or do { + return 900; + } +} + +sub core{ + Log::Log4perl::init_and_watch('/etc/oess/logging.conf', 10); + + my $config = new OESS::Config(config_filename => $cnf_file); + if ($config->network_type eq 'nso') { + my $fwdctl = new OESS::NSO::FWDCTLService(config_obj => $config); + $fwdctl->start; + AnyEvent->condvar->recv; + } else { + die "Unexpected network type configured."; + } + Log::Log4perl->get_logger('OESS.NSO.FWDCTL.APP')->info("Starting OESS.NSO.FWDCTL event loop."); +} + +sub main{ + my $is_daemon = 0; + my $verbose; + my $username; + #remove the ready file + + # This directory is auto-removed on reboot. Create the directory if not + # already created. This is used to store connection cache files. + if (!-d "/var/run/oess/") { + `/usr/bin/mkdir /var/run/oess`; + `/usr/bin/chown _oess:_oess /var/run/oess`; + } + + #--- see if the pid file exists. if not then just continue running. + if(-e $pid_file){ + #--- read the file to get the PID + my $pid = `head -n 1 $pid_file`; + chomp $pid; + + my $run_test = `ps -p $pid | grep $pid`; + + #--- if run test is empty then the pid didn't exist. If it isn't empty then the process is already running. + if($run_test){ + print "Found $0 process: $pid already running. Aborting.\n"; + exit(0); + } + else{ + print "Pid File: $pid_file already exists but it looks like process $pid is dead. Continuing startup.\n"; + } + } + + my $result = GetOptions ( + "user|u=s" => \$username, + "verbose" => \$verbose, + "daemon|d" => \$is_daemon, + ); + + #now change username/ + if (defined $username) { + my $new_uid=getpwnam($username); + my $new_gid=getgrnam($username); + $EGID=$new_gid; + $EUID=$new_uid; + } + + if ($is_daemon != 0) { + my $daemon; + if ($verbose) { + $daemon = Proc::Daemon->new( + pid_file => $pid_file, + child_STDOUT => '/var/log/oess/nso_fwdctl.out', + child_STDERR => '/var/log/oess/nso_fwdctl.log', + ); + } else { + $daemon = Proc::Daemon->new(pid_file => $pid_file); + } + + # Init returns the PID (scalar) of the daemon to the parent, or + # the PIDs (array) of the daemons created if exec_command has + # more then one program to execute. + # + # Init returns 0 to the child (daemon). + my $kid_pid = $daemon->Init; + if ($kid_pid) { + `chmod 0644 $pid_file`; # How to wait until the child process is ready. + return; + } else { + core(); + } + } + #not a daemon, just run the core; + else { + $SIG{HUP} = sub{ exit(0); }; + core(); + } +} + +main(); + +1; diff --git a/app/oess-core.spec b/app/oess-core.spec index 4ed6a2338..5864293b4 100644 --- a/app/oess-core.spec +++ b/app/oess-core.spec @@ -1,6 +1,6 @@ Name: oess-core -Version: 2.0.11 -Release: 3%{?dist} +Version: 2.0.12 +Release: 1%{?dist} Summary: The core OESS service providers Group: Network @@ -20,7 +20,7 @@ Requires: /bin/bash Requires: /usr/bin/perl Requires: perl(base), perl(constant), perl(strict), perl(warnings) -Requires: perl-OESS >= 2.0.11 +Requires: perl-OESS >= 2.0.12 Requires: perl(AnyEvent), perl(AnyEvent::DBus), perl(AnyEvent::RabbitMQ) Requires: perl(CPAN), perl(CPAN::Shell) diff --git a/app/oess_pull_azure_interfaces.pl b/app/oess_pull_azure_interfaces.pl index 7f97e2981..86857b513 100755 --- a/app/oess_pull_azure_interfaces.pl +++ b/app/oess_pull_azure_interfaces.pl @@ -16,10 +16,11 @@ use OESS::DB::Endpoint; use OESS::Endpoint; -my $logger; +Log::Log4perl::init_and_watch('/etc/oess/logging.conf',10); + +my $logger = Log::Log4perl->get_logger('OESS.Cloud.Azure.Syncer');; sub main{ - my $logger = Log::Log4perl->get_logger('OESS.Cloud.Azure.Syncer'); my $config = OESS::Config->new(); my $db = OESS::DB->new(); @@ -31,10 +32,17 @@ sub main{ cloud_interconnect_type => 'azure-express-route' ); + foreach my $cloud (@azure_cloud_accounts_config) { - my $azure_connections = ($azure->expressRouteCrossConnections($cloud->{interconnect_id})); - reconcile_oess_endpoints($db, $endpoints, $azure_connections); + my $connectionsWithNoPeering = ($azure->expressRouteCrossConnections($cloud->{interconnect_id})); + my $azure_connections = []; + foreach my $conn (@$connectionsWithNoPeering) { + my $connWithPeering = $azure->expressRouteCrossConnection($cloud->{interconnect_id}, $conn->{name}); + push($azure_connections, $connWithPeering); + } + reconcile_oess_endpoints($db, $endpoints, $azure_connections, $cloud->{interconnect_id}); } + } =head2 get_connection_by_id @@ -65,6 +73,7 @@ sub reconcile_oess_endpoints { my $db = shift; my $endpoints = shift; my $azure_connections = shift; + my $cloud_interconnect_id = shift; foreach my $endpoint (@$endpoints) { my $azure_connection = get_connection_by_id( @@ -73,12 +82,26 @@ sub reconcile_oess_endpoints { ); next if (!defined $azure_connection); + my $ep = new OESS::Endpoint(db => $db, model => $endpoint); + $ep->load_peers(); + next if(! $cloud_interconnect_id eq $ep->cloud_interconnect_id()); + + my $could_account_id = $azure_connection->{name}; + my $azure_subnet = find_matching_azure_subnet($azure_connection, $cloud_interconnect_id); + my $endpoint_peer = get_endpoint_peer($ep, $cloud_interconnect_id, $could_account_id); + + next if(!defined $azure_subnet || !defined $endpoint_peer); + + if(increment_ip($endpoint_peer->{local_ip}, -1) ne $azure_subnet){ + $logger->info("MISMATCH: on could_account_id: $could_account_id cloud_interconnect_id: $cloud_interconnect_id azure_subnet: $azure_subnet but OESS is peering on $endpoint_peer->{local_ip}"); + update_endpoint_peer_ips($db, $azure_subnet, $endpoint_peer); + } + my $cloud_bandwidth = $azure_connection->{properties}->{bandwidthInMbps}; if (!$cloud_bandwidth || $endpoint->{bandwidth} eq $cloud_bandwidth) { next; } - my $ep = new OESS::Endpoint(db => $db, model => $endpoint); $ep->bandwidth($cloud_bandwidth); my $error = $ep->update_db; @@ -88,6 +111,68 @@ sub reconcile_oess_endpoints { } } +sub find_matching_azure_subnet{ + my $azure_connection_info = shift; + my $cloud_interconnect_id = shift; + my $azure_peering_subnet; + my $peering = $azure_connection_info->{properties}->{peerings}; + foreach my $ip (@$peering){ + if($cloud_interconnect_id =~ m/-SEC-/){ + $azure_peering_subnet = $ip->{properties}->{secondaryPeerAddresssubnet}; + }elsif($cloud_interconnect_id =~ m/-PRI-/){ + $azure_peering_subnet = $ip->{properties}->{primaryPeerAddresssubnet}; + }else{ + $azure_peering_subnet = undef; + } + } + return $azure_peering_subnet; +} + +sub get_endpoint_peer{ + my $endpoint = shift; + my $cloud_interconnect_id = shift; + my $cloud_account_id = shift; + my $peers = $endpoint->peers(); + my $endpoint_ips =[]; + if($endpoint->cloud_interconnect_id() eq $cloud_interconnect_id && $endpoint->cloud_account_id() eq $cloud_account_id){ + foreach my $peer (@$peers){ + my $cloud_connetions = $peer->{db}->{configuration}->{cloud}->{connection}; + foreach my $conn (@$cloud_connetions){ + if( $conn->{interconnect_id} eq $cloud_interconnect_id){ + return $peer; + } + } + } + } + return undef; +} + +sub update_endpoint_peer_ips{ + my $db = shift; + my $azure_subnet = shift; + my $peer = shift; + my $new_oess_ip = increment_ip($azure_subnet, 1); + my $new_azure_ip = increment_ip($azure_subnet, 2); + $logger->info("UPDATEING AZURE PEERING: changing $peer->{local_ip} to $new_oess_ip, and changing $peer->{peer_ip} to $new_azure_ip"); + $peer->{local_ip} = $new_oess_ip; + $peer->{peer_ip} = $new_azure_ip; + $peer->update; +} + +sub increment_ip{ + my $ip = shift; + my $increment = shift; + $ip =~ m/^(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)/; + my $firstOctet = $1; + my $secondOctet = $2; + my $thirdOctet = $3; + my $lastOctet = $4; + $ip =~ m/\/(\d\d?)$/; + my $subnet = $1; + $lastOctet = int($lastOctet) + $increment; + return "$firstOctet.$secondOctet.$thirdOctet.$lastOctet/$subnet"; +} + sub fetch_azure_cloud_account_configs{ my $config = shift; my @results = (); diff --git a/app/oess_setup.pl b/app/oess_setup.pl index 94b9754c8..54b21ed73 100755 --- a/app/oess_setup.pl +++ b/app/oess_setup.pl @@ -145,7 +145,7 @@ sub main{ my $tsds_password = required_parameter("TSDS Password: "); ReadMode('normal'); my $grafana_url = optional_parameter("Grafana URL", "https://localhost/grafana"); - my $third_party_mgmt = yes_or_no_parameter("Are you using third party User/Workgroup management? "); + my $third_party_management = yes_or_no_parameter("Are you using third party User/Workgroup management? "); #put all of this into a config file print "Creating Configuration file (/etc/oess/database.xml)\n"; open(FILE, "> /etc/oess/database.xml"); @@ -170,7 +170,7 @@ END print FILE " \n"; print FILE " \n"; print FILE " \n"; - print FILE " $third_party_management"; + print FILE " $third_party_management"; print FILE "\n"; close(FILE); diff --git a/frontend/Makefile b/frontend/Makefile index 4343d4b94..c5fda76c5 100644 --- a/frontend/Makefile +++ b/frontend/Makefile @@ -1,5 +1,5 @@ NAME= oess-frontend -VERSION = 2.0.11 +VERSION = 2.0.12 rpm: dist rpmbuild -ta dist/$(NAME)-$(VERSION).tar.gz diff --git a/frontend/conf/oe-ss.conf.example b/frontend/conf/oe-ss.conf.example index 00118ed92..99184099a 100644 --- a/frontend/conf/oe-ss.conf.example +++ b/frontend/conf/oe-ss.conf.example @@ -1,55 +1,51 @@ -Alias /oess/tiles /usr/share/nddi-tiles/ -Alias /oess/services /usr/share/oess-frontend/webservice -Alias /oess/yui/build /usr/share/yui2 -Alias /oess/notification-img/ /usr/share/oess-frontend/www/media/notification -Alias /oess /usr/share/oess-frontend/www -Alias /idc /usr/share/oess-frontend/webservice/idc +Alias /oess/tiles /usr/share/nddi-tiles/ +Alias /oess/services /usr/share/oess-frontend/webservice +Alias /oess/yui/build /usr/share/yui2 +Alias /oess/notification-img /usr/share/oess-frontend/www/media/notification +Alias /oess /usr/share/oess-frontend/www +Alias /idc /usr/share/oess-frontend/webservice/idc + - SSLRequireSSL - AddHandler cgi-script .cgi - DirectoryIndex index.cgi - Options ExecCGI - Order allow,deny - Allow from all - Satisfy any + AddHandler cgi-script .cgi + DirectoryIndex index.cgi + Options ExecCGI + Order allow,deny + Allow from all + Satisfy any - SSLRequireSSL - AddHandler cgi-script .cgi - DirectoryIndex index.cgi - Options ExecCGI - Order allow,deny - Allow from all - - AuthType Basic - AuthName "OESS" - AuthUserFile /usr/share/oess-frontend/www/.htpasswd - Require valid-user + AddHandler cgi-script .cgi + DirectoryIndex index.cgi + Options ExecCGI + Order allow,deny + Allow from all + + AuthType Basic + AuthName "OESS" + AuthUserFile /usr/share/oess-frontend/www/.htpasswd + Require valid-user + - SSLRequireSSL - Satisfy Any - Allow from all + Satisfy Any + Allow from all - SSLRequireSSL - AddHandler cgi-script .cgi - Options ExecCGI - Order allow,deny - Allow from all - - AuthType Basic - AuthName "OESS" - AuthUserFile /usr/share/oess-frontend/www/.htpasswd - Require valid-user + AddHandler cgi-script .cgi + Options ExecCGI + Order allow,deny + Allow from all + + AuthType Basic + AuthName "OESS" + AuthUserFile /usr/share/oess-frontend/www/.htpasswd + Require valid-user - - -Redirect 301 /oess/admin/admin_index.cgi /oess/admin \ No newline at end of file +Redirect 301 /oess/admin/admin_index.cgi /oess/admin diff --git a/frontend/oess-frontend.spec b/frontend/oess-frontend.spec index 92431403f..81e95ccde 100644 --- a/frontend/oess-frontend.spec +++ b/frontend/oess-frontend.spec @@ -1,6 +1,6 @@ Name: oess-frontend -Version: 2.0.11 -Release: 3%{?dist} +Version: 2.0.12 +Release: 1%{?dist} Summary: The OESS webservices and user interface Group: Network @@ -13,14 +13,14 @@ BuildRequires: perl BuildRequires: python >= 2.6, python-libs >= 2.6 BuildRequires: python-simplejson -Requires: oess-core >= 2.0.11 +Requires: oess-core >= 2.0.12 Requires: yui Requires: httpd, mod_ssl Requires: nddi-tiles Requires: perl-Crypt-SSLeay Requires: xmlsec1, xmlsec1-openssl -Requires: perl-OESS >= 2.0.11 +Requires: perl-OESS >= 2.0.12 Requires: perl(strict), perl(warnings) Requires: perl(AnyEvent) diff --git a/frontend/webservice/admin/admin.cgi b/frontend/webservice/admin/admin.cgi index a8324af20..07cfbf633 100755 --- a/frontend/webservice/admin/admin.cgi +++ b/frontend/webservice/admin/admin.cgi @@ -35,6 +35,7 @@ use Log::Log4perl; use GRNOC::WebService; +use OESS::Config; use OESS::Database; use OESS::DB; use OESS::DB::ACL; @@ -61,6 +62,7 @@ use constant PENDING_DIFF_ERROR => 2; Log::Log4perl::init('/etc/oess/logging.conf'); +my $config = new OESS::Config(); my $db = new OESS::Database(); my $db2 = new OESS::DB(); @@ -797,17 +799,21 @@ sub register_webservice_methods { pattern => $GRNOC::WebService::Regex::TEXT, required => 1, description => '' ); - $method->add_input_parameter( name => 'vendor', + $method->add_input_parameter( name => 'controller', pattern => $GRNOC::WebService::Regex::TEXT, required => 1, description => '' ); + $method->add_input_parameter( name => 'vendor', + pattern => $GRNOC::WebService::Regex::TEXT, + required => 0, + description => '' ); $method->add_input_parameter( name => 'model', pattern => $GRNOC::WebService::Regex::TEXT, - required => 1, + required => 0, description => '' ); $method->add_input_parameter( name => 'sw_ver', pattern => $GRNOC::WebService::Regex::TEXT, - required => 1, + required => 0, description => '' ); $svc->register_method($method); @@ -900,13 +906,25 @@ sub get_diff_text { return; } + my $node = new OESS::Node(db => $db2, node_id => $args->{node_id}{value}); + + my $fwdctl_topic = 'OF.FWDCTL.RPC'; + my $discovery_topic = 'OF.Discovery.RPC'; + if ($node->controller eq 'netconf') { + $fwdctl_topic = 'MPLS.FWDCTL.RPC'; + $discovery_topic = 'MPLS.Discovery.RPC'; + } + if ($node->controller eq 'nso') { + $fwdctl_topic = 'NSO.FWDCTL.RPC'; + $discovery_topic = 'NSO.Discovery.RPC'; + } + my $node_id = $args->{'node_id'}{'value'}; require OESS::RabbitMQ::Client; my $mq = OESS::RabbitMQ::Client->new( - topic => 'OF.FWDCTL.RPC', + topic => $fwdctl_topic, timeout => 60 ); - $mq->{'topic'} = "MPLS.FWDCTL.RPC"; my $cv = AnyEvent->condvar; $mq->get_diff_text( @@ -1192,35 +1210,37 @@ sub edit_remote_link { } +#Gets workgroups based on user_id given through parameter sub get_workgroups { my ($method, $args) = @_; - #my ($user, $err) = authorization(admin => 1, read_only => 1); my ($result, $err) = OESS::DB::User::has_system_access(db => $db2, username => $ENV{'REMOTE_USER'}, role=>'read-only'); if (defined $err) { $method->set_error($err); return; } - my %parameters = ( 'user_id' => $args->{'user_id'}{'value'} || undef ); - my $results; - my $workgroups; - - my $user = new OESS::User(db => $db2, username => $ENV{'REMOTE_USER'}); - $user->load_workgroups(); - $workgroups = $user->to_hash()->{workgroups}; - - if ( !defined $workgroups ) { - $results->{'error'} = $db->get_error(); - $results->{'results'} = []; + my $user; + if (defined $args->{user_id}{value}) { + $user = new OESS::User(db => $db2, user_id => $args->{user_id}{value}); + } else { + $user = new OESS::User(db => $db2, username => $ENV{REMOTE_USER}); } - else { - $results->{'results'} = $workgroups; + if (!defined $user) { + my $id = (defined $args->{user_id}{value}) ? $args->{user_id}{value} : $ENV{REMOTE_USER}; + $method->set_error("User $id was not found."); + return; } + $user->load_workgroups(); - return $results; + my $workgroups = $user->to_hash()->{workgroups}; + if (!defined $workgroups) { + $method->set_error("Couldn't load workgroups."); + return; + } + return { results => $workgroups }; } sub update_interface_owner { @@ -1797,35 +1817,68 @@ sub update_cache { use OESS::RabbitMQ::Client; - my $mq = OESS::RabbitMQ::Client->new( - topic => 'MPLS.FWDCTL.RPC', - timeout => 60 - ); - if (!defined $mq) { - $method->set_error("Couldn't create RabbitMQ client."); - return; - } + my $status = 0; - my $cv = AnyEvent->condvar; - $mq->update_cache( - node_id => $node_id, - async_callback => sub { - my $resultM = shift; - $cv->send($resultM); + if ($config->network_type eq 'vpn-mpls' || $config->network_type eq 'nso+vpn-mpls') { + my $mq = OESS::RabbitMQ::Client->new( + topic => 'MPLS.FWDCTL.RPC', + timeout => 60 + ); + if (!defined $mq) { + $method->set_error("Couldn't create RabbitMQ client."); + return; } - ); - my $resultC = $cv->recv(); - if (!defined $resultC) { - $method->set_error("Error while calling `update_cache` via RabbitMQ."); - return; + my $cv = AnyEvent->condvar; + $mq->update_cache( + node_id => $node_id, + async_callback => sub { + my $resultM = shift; + $cv->send($resultM); + } + ); + my $resultC = $cv->recv(); + if (!defined $resultC) { + $method->set_error("Error while calling `update_cache` via RabbitMQ."); + return; + } + if (defined $resultC->{'error'}) { + $method->set_error("Error while calling `update_cache`: $resultC->{error}"); + return; + } + $status = $resultC->{results}->{status}; } - if (defined $resultC->{'error'}) { - $method->set_error("Error while calling `update_cache`: $resultC->{error}"); - return; + + if ($config->network_type eq 'nso' || $config->network_type eq 'nso+vpn-mpls') { + my $mq = OESS::RabbitMQ::Client->new( + topic => 'NSO.FWDCTL.RPC', + timeout => 60 + ); + if (!defined $mq) { + $method->set_error("Couldn't create RabbitMQ client."); + return; + } + + my $cv = AnyEvent->condvar; + $mq->update_cache( + node_id => $node_id, + async_callback => sub { + my $resultM = shift; + $cv->send($resultM); + } + ); + my $resultC = $cv->recv(); + if (!defined $resultC) { + $method->set_error("Error while calling `update_cache` via RabbitMQ."); + return; + } + if (defined $resultC->{'error'}) { + $method->set_error("Error while calling `update_cache`: $resultC->{error}"); + return; + } + $status = $resultC->{results}->{status}; } - my $status = $resultC->{results}->{status}; return { results => [ { status => $status } ] }; } @@ -2686,6 +2739,7 @@ sub add_mpls_switch{ my $latitude = $args->{'latitude'}{'value'}; my $longitude = $args->{'longitude'}{'value'}; my $port = $args->{'port'}{'value'}; + my $controller = $args->{'controller'}{'value'}; my $vendor = $args->{'vendor'}{'value'}; my $model = $args->{'model'}{'value'}; my $sw_ver = $args->{'sw_ver'}{'value'}; @@ -2695,42 +2749,52 @@ sub add_mpls_switch{ return; } + my $node = $db->add_mpls_node( + name => $name, + short_name => $short_name, + ip => $ip_address, + lat => $latitude, + long => $longitude, + port => $port, + controller => $controller, + vendor => $vendor, + model => $model, + sw_ver => $sw_ver + ); + if (!defined $node) { + $method->set_error($db->get_error); + return; + } + require OESS::RabbitMQ::Client; - my $mq = OESS::RabbitMQ::Client->new( topic => 'OF.FWDCTL.RPC', - timeout => 60 ); + my $mq = OESS::RabbitMQ::Client->new( + topic => 'NSO.FWDCTL.RPC', + timeout => 60 + ); + if (!defined $mq) { + $method->set_error("Internal server error occurred. Message queue connection failed."); + return; + } - my $node = $db->add_mpls_node( name => $name, - short_name => $short_name, - ip => $ip_address, - lat => $latitude, - long => $longitude, - port => $port, - vendor => $vendor, - model => $model, - sw_ver => $sw_ver); + my $fwdctl_topic; + my $discovery_topic; - if(!defined($node)){ - return $db->get_error(); + if ($controller eq 'netconf') { + $fwdctl_topic = 'MPLS.FWDCTL.RPC'; + $discovery_topic = 'MPLS.Discovery.RPC'; } - - if (!defined $mq) { - my $results = {}; - $results->{'results'} = [ { - "error" => "Internal server error occurred. Message queue connection failed.", - "success" => 0 - } ]; - return $results; - } else { - $mq->{'topic'} = 'MPLS.FWDCTL.RPC'; + if ($controller eq 'nso') { + $fwdctl_topic = 'NSO.FWDCTL.RPC'; + $discovery_topic = 'NSO.Discovery.RPC'; } my $cv = AnyEvent->condvar; + $mq->{'topic'} = $fwdctl_topic; $mq->new_switch( node_id => $node->{'node_id'}, async_callback => sub { my $resultM = shift; - - $mq->{'topic'} = 'MPLS.Discovery.RPC'; + $mq->{'topic'} = $discovery_topic; $mq->new_switch( node_id => $node->{'node_id'}, async_callback => sub { @@ -2740,7 +2804,7 @@ sub add_mpls_switch{ ); } ); - my $res = $cv->recv(); + $cv->recv; return {results => [{success => 1, node_id => $node->{'node_id'}}]}; diff --git a/frontend/webservice/circuit.cgi b/frontend/webservice/circuit.cgi index c65fbd5b9..08d09d181 100755 --- a/frontend/webservice/circuit.cgi +++ b/frontend/webservice/circuit.cgi @@ -21,6 +21,7 @@ use OESS::DB::User; use OESS::Entity; use OESS::L2Circuit; use OESS::RabbitMQ::Client; +use OESS::RabbitMQ::Topic qw(fwdctl_topic_for_connection); use OESS::User; use OESS::VRF; @@ -125,6 +126,13 @@ my $provision = GRNOC::WebService::Method->new( callback => \&provision, description => 'Creates and provisions a new Circuit.' ); +$provision->add_input_parameter( + name => 'status', + pattern => '(active|reserved|confirmed|provisioned|released|decom)', + required => 0, + default => 'active', + description => 'Status of the Circuit (note mostly used for NSI integration)' + ); $provision->add_input_parameter( name => 'circuit_id', pattern => $GRNOC::WebService::Regex::INTEGER, @@ -246,7 +254,8 @@ sub provision { my $circuit = new OESS::L2Circuit( db => $db, model => { - name => $args->{description}->{value}, + status => $args->{status}->{value}, + name => $args->{description}->{value}, description => $args->{description}->{value}, remote_url => $args->{remote_url}->{value}, remote_requester => $args->{remote_requester}->{value}, @@ -265,6 +274,11 @@ sub provision { return; } + if (@{$args->{endpoint}->{value}} > 2) { + $method->set_error("Support for Multi-Point Layer 2 Connections is currently disabled. Please contact your OESS administrator for more information."); + return; + } + # Endpoint: { entity: 'entity name', bandwidth: 0, tag: 100, inner_tag: 100, peerings: [{ version: 4 }] } foreach my $value (@{$args->{endpoint}->{value}}) { my $ep; @@ -286,9 +300,10 @@ sub provision { node => $ep->{node} ); } - if (defined $interface && (!defined $interface->{cloud_interconnect_type} || $interface->{cloud_interconnect_type} eq 'aws-hosted-connection')) { - # Continue - } else { + # if (defined $interface && (!defined $interface->{cloud_interconnect_type} || $interface->{cloud_interconnect_type} eq 'aws-hosted-connection')) { + # # Continue + # } + else { $entity = new OESS::Entity(db => $db, name => $ep->{entity}); $interface = $entity->select_interface( inner_tag => $ep->{inner_tag}, @@ -310,6 +325,12 @@ sub provision { return; } + if(defined $interface->provisionable_bandwidth && ($ep->{bandwidth} + $interface->{utilized_bandwidth} > $interface->provisionable_bandwidth)){ + $method->set_error("Couldn't create Connnection: Specified bandwidth exceeds provisionable bandwidth for '$ep->{entity}'."); + $db->rollback; + return; + } + # Populate Endpoint modal with selected Interface details. $ep->{type} = 'circuit'; $ep->{entity_id} = $entity->{entity_id}; @@ -425,17 +446,20 @@ sub provision { #return {error => 1, error_text => 'lulz'}; $db->commit; - _send_update_cache($circuit->circuit_id); - _send_add_command($circuit->circuit_id); + # Ensure that endpoints' controller info loaded + $circuit->load_endpoints; + my $conn = $circuit->to_hash; + + _send_update_cache($conn); + _send_add_command($conn); _send_event( status => 'up', reason => 'provisioned', type => 'provisioned', - circuit => $circuit->to_hash + circuit => $conn ); - warn Dumper($circuit->to_hash); - return {success => 1, circuit_id => $circuit_id}; + return { success => 1, circuit_id => $conn->{circuit_id} }; } @@ -463,7 +487,7 @@ sub update { $circuit->load_paths; my $previous = $circuit->to_hash; - + $circuit->status($args->{status}->{value}); $circuit->description($args->{description}->{value}); $circuit->remote_url($args->{remote_url}->{value}); $circuit->remote_requester($args->{remote_requester}->{value}); @@ -485,6 +509,11 @@ sub update { my $add_endpoints = []; my $del_endpoints = []; + if (@{$args->{endpoint}->{value}} > 2) { + $method->set_error("Support for Multi-Point Layer 2 Connections is currently disabled. Please contact your OESS administrator for more information."); + return; + } + foreach my $value (@{$args->{endpoint}->{value}}) { my $ep; eval{ @@ -508,9 +537,10 @@ sub update { node => $ep->{node} ); } - if (defined $interface && (!defined $interface->{cloud_interconnect_type} || $interface->{cloud_interconnect_type} eq 'aws-hosted-connection')) { - # Continue - } else { + # if (defined $interface && (!defined $interface->{cloud_interconnect_type} || $interface->{cloud_interconnect_type} eq 'aws-hosted-connection')) { + # # Continue + # } + else { $entity = new OESS::Entity(db => $db, name => $ep->{entity}); $interface = $entity->select_interface( inner_tag => $ep->{inner_tag}, @@ -663,17 +693,41 @@ sub update { } } - $db->commit; + # Ensure that endpoints' controller info loaded. Required to + # choose correct topic. + $circuit->load_endpoints; my $pending = $circuit->to_hash; - _send_update_cache($circuit->circuit_id); - _send_modify_command($circuit->circuit_id, $previous, $pending); + my ($pending_topic, $t0_err) = fwdctl_topic_for_connection($pending); + my ($prev_topic, $t1_err) = fwdctl_topic_for_connection($previous); + + # No connection may be provisioned using multiple controllers. + if (defined $t0_err || defined $t1_err) { + $method->set_error("$t0_err $t1_err"); + return; + } + + # In the case where a connection is moved between controllers, we + # want the cache for both controllers updated. + if ($pending_topic ne $prev_topic) { + _send_remove_command($previous); + $db->commit; + _send_update_cache($previous); + + _send_update_cache($pending); + _send_add_command($pending); + } else { + $db->commit; + + _send_update_cache($pending); + _send_modify_command($circuit->circuit_id, $previous, $pending); + } _send_event( status => 'up', reason => 'edited', type => 'modified', - circuit => $circuit->to_hash + circuit => $pending ); return { success => 1, circuit_id => $circuit->circuit_id }; @@ -743,6 +797,8 @@ sub remove { $circuit->load_endpoints; $circuit->load_paths; + my $previous = $circuit->to_hash; + if (!$args->{skip_cloud_provisioning}->{value}) { eval { OESS::Cloud::cleanup_endpoints($circuit->endpoints); @@ -777,13 +833,11 @@ sub remove { return; } - _send_remove_command($args->{circuit_id}->{value}); - - # Put rollback in place for quick tests + _send_remove_command($previous); + # Move post _send_remove_commands and add rollback for quick tests # $db->rollback; $db->commit; - _send_update_cache($args->{circuit_id}->{value}); - + _send_update_cache($previous); _send_event( status => 'removed', reason => "removed by $ENV{REMOTE_USER}", @@ -795,33 +849,35 @@ sub remove { } sub _send_add_command { - my $circuit_id = shift; - - my $result = undef; - my $err = undef; + my $conn = shift; if (!defined $mq) { return (undef, "Couldn't create RabbitMQ Client."); } - $mq->{'topic'} = 'MPLS.FWDCTL.RPC'; + my ($topic, $err) = fwdctl_topic_for_connection($conn); + if (defined $err) { + warn $err; + return (undef, $err); + } + $mq->{'topic'} = $topic; my $cv = AnyEvent->condvar; $mq->addVlan( - circuit_id => int($circuit_id), + circuit_id => int($conn->{circuit_id}), async_callback => sub { my $result = shift; $cv->send($result); } ); - $result = $cv->recv(); + my $result = $cv->recv(); if (!defined $result) { - return ($result, "Error occurred while calling addVlan: Couldn't connect to RabbitMQ."); + return ($result, "Error occurred while calling $topic.addVlan: Couldn't connect to RabbitMQ."); } if (defined $result->{error}) { - return ($result, "Error occured while calling addVlan: $result->{error}"); + return ($result, "Error occured while calling $topic.addVlan: $result->{error}"); } - return ($result->{results}->{status}, $err); + return ($result->{results}->{status}, undef); } sub _send_modify_command { @@ -829,17 +885,22 @@ sub _send_modify_command { my $previous = shift; my $pending = shift; - my $result = undef; - my $err = undef; - if (!defined $mq) { return (undef, "Couldn't create RabbitMQ Client."); } - $mq->{'topic'} = 'MPLS.FWDCTL.RPC'; + + # IMPORTANT: It's assumed that $previous and $pending was/is + # managed by the same controller!!! + my ($topic, $err) = fwdctl_topic_for_connection($pending); + if (defined $err) { + warn $err; + return (undef, $err); + } + $mq->{'topic'} = $topic; my $cv = AnyEvent->condvar; $mq->modifyVlan( - circuit_id => int($circuit_id), + circuit_id => int($pending->{circuit_id}), previous => encode_json($previous), pending => encode_json($pending), async_callback => sub { @@ -847,75 +908,81 @@ sub _send_modify_command { $cv->send($result); } ); - $result = $cv->recv(); + my $result = $cv->recv(); if (!defined $result) { - return ($result, "Error occurred while calling modifyVlan: Couldn't connect to RabbitMQ."); + return ($result, "Error occurred while calling $topic.modifyVlan: Couldn't connect to RabbitMQ."); } if (defined $result->{error}) { - return ($result, "Error occured while calling modifyVlan: $result->{error}"); + return ($result, "Error occured while calling $topic.modifyVlan: $result->{error}"); } - return ($result->{results}->{status}, $err); + return ($result->{results}->{status}, undef); } sub _send_remove_command { - my $circuit_id = shift; - - my $result = undef; - my $err = undef; + my $conn = shift; if (!defined $mq) { return (undef, "Couldn't create RabbitMQ Client."); } - $mq->{'topic'} = 'MPLS.FWDCTL.RPC'; + + my ($topic, $err) = fwdctl_topic_for_connection($conn); + if (defined $err) { + warn $err; + return (undef, $err); + } + $mq->{'topic'} = $topic; my $cv = AnyEvent->condvar; $mq->deleteVlan( - circuit_id => int($circuit_id), + circuit_id => int($conn->{circuit_id}), async_callback => sub { my $result = shift; $cv->send($result); } ); - $result = $cv->recv(); + my $result = $cv->recv(); if (!defined $result) { - return ($result, "Error occurred while calling deleteVlan: Couldn't connect to RabbitMQ."); + return ($result, "Error occurred while calling $topic.deleteVlan: Couldn't connect to RabbitMQ."); } if (defined $result->{error}) { - return ($result, "Error occured while calling deleteVlan: $result->{error}"); + return ($result, "Error occured while calling $topic.deleteVlan: $result->{error}"); } - return ($result->{results}->{status}, $err); + return ($result->{results}->{status}, undef); } sub _send_update_cache { - my $circuit_id = shift || -1; - - my $result = undef; - my $err = undef; + my $conn = shift; if (!defined $mq) { return (undef, "Couldn't create RabbitMQ Client."); } - $mq->{'topic'} = 'MPLS.FWDCTL.RPC'; + + my ($topic, $err) = fwdctl_topic_for_connection($conn); + if (defined $err) { + warn $err; + return (undef, $err); + } + $mq->{topic} = $topic; my $cv = AnyEvent->condvar; $mq->update_cache( - circuit_id => int($circuit_id), + circuit_id => int($conn->{circuit_id}), async_callback => sub { my $result = shift; $cv->send($result); } ); - $result = $cv->recv(); + my $result = $cv->recv(); if (!defined $result) { - return ($result, "Error occurred while calling update_cache: Couldn't connect to RabbitMQ."); + return ($result, "Error occurred while calling $topic.update_cache: Couldn't connect to RabbitMQ."); } if (defined $result->{error}) { - return ($result, "Error occured while calling update_cache: $result->{error}"); + return ($result, "Error occured while calling $topic.update_cache: $result->{error}"); } - return ($result->{results}->{status}, $err); + return ($result->{results}->{status}, undef); } sub _send_event { diff --git a/frontend/webservice/data.cgi b/frontend/webservice/data.cgi index b1c373d2c..7770db1d5 100755 --- a/frontend/webservice/data.cgi +++ b/frontend/webservice/data.cgi @@ -765,7 +765,13 @@ sub get_circuits_by_interface_id { return; } else { - $results->{'results'} = $circuits; + my $circuit; + foreach(@{$circuits}){ + my ($ok, $err) = OESS::DB::User::has_circuit_permission(db => $db2, username => $ENV{'REMOTE_USER'}, circuit_id => $_->{'circuit_id'}, permission => 'read'); + if($ok){ + push($results->{'results'}, $_); + } + } } return $results; @@ -912,6 +918,11 @@ sub get_circuit_scheduled_events { my $results; my $circuit_id = $args->{'circuit_id'}{'value'}; + my ($ok, $err) = OESS::DB::User::has_circuit_permission(db => $db2, username => $ENV{'REMOTE_USER'}, circuit_id => $circuit_id, permission => 'read'); + if(!$ok){ + $results->{'error'} = $err; + return $results; + } my $events = $db->get_circuit_scheduled_events( circuit_id => $circuit_id ); @@ -932,6 +943,12 @@ sub get_circuit_history { my $results; my $circuit_id = $args->{'circuit_id'}{'value'}; + + my ($ok, $err) = OESS::DB::User::has_circuit_permission(db => $db2, username => $ENV{'REMOTE_USER'}, circuit_id => $circuit_id, permission => 'read'); + if(!$ok){ + $results->{'error'} = $err; + return $results; + } my $events = $db->get_circuit_history( circuit_id => $circuit_id ); @@ -951,7 +968,12 @@ sub get_circuit_details { my ( $method, $args ) = @_ ; my $circuit_id = $args->{'circuit_id'}{'value'}; - + + my ($ok, $err) = OESS::DB::User::has_circuit_permission(db => $db2, username => $ENV{'REMOTE_USER'}, circuit_id => $circuit_id, permission => 'read'); + if(!$ok){ + $results->{'error'} = $err; + return $results; + } my $ckt = OESS::Circuit->new( circuit_id => $circuit_id, db => $db); my $details = $ckt->get_details(); @@ -980,8 +1002,14 @@ sub get_circuit_details_by_external_identifier { $method->set_error( $db->get_error() ); return; } + my $circuit_id = $info->{'circuit_id'}; + my ($ok, $err) = OESS::DB::User::has_circuit_permission(db => $db2, username => $ENV{'REMOTE_USER'}, circuit_id => $circuit_id, permission => 'read'); + if(!$ok){ + $results->{'error'} = $err; + return $results; + } - my $ckt = OESS::Circuit->new( circuit_id => $info->{'circuit_id'}, db => $db); + my $ckt = OESS::Circuit->new( circuit_id => $circuit_id, db => $db); my $details = $ckt->get_details(); if ( !defined $details ) { @@ -1191,10 +1219,14 @@ sub generate_clr { my $results; my $circuit_id = $args->{'circuit_id'}{'value'}; - if ( !defined($circuit_id) ) { $method->set_error( "No Circuit ID Specified" ); } + my ($ok, $err) = OESS::DB::User::has_circuit_permission(db => $db2, username => $ENV{'REMOTE_USER'}, circuit_id => $circuit_id, permission => 'read'); + if(!$ok){ + $results->{'error'} = $err; + return $results; + } my $ckt = OESS::Circuit->new( circuit_id => $circuit_id, db => $db); diff --git a/frontend/webservice/interface.cgi b/frontend/webservice/interface.cgi index 34b7d98e7..524113bed 100755 --- a/frontend/webservice/interface.cgi +++ b/frontend/webservice/interface.cgi @@ -86,12 +86,63 @@ sub register_ro_methods{ $svc->register_method($method); + $method = GRNOC::WebService::Method->new( + name => "get_interfaces", + description => "returns all interfaces on a node or a specific interface if specified", + callback => sub { get_interfaces(@_) } + ); + $method->add_input_parameter( name => 'node', + pattern => $GRNOC::WebService::Regex::HOSTNAME, + required => 1, + description => "Node to fetch interfaces on"); + + $method->add_input_parameter(name => 'name', + pattern => $GRNOC::WebService::Regex::TEXT, + required => 0, + description => "Possible interface name to search for on specific node"); + $svc->register_method($method); + } sub register_rw_methods{ } +sub get_interfaces{ + my $method = shift; + my $params = shift; + + + my $node = $params->{'node'}{'value'}; + my $name = $params->{'name'}{'value'}; + +# warn Dumper($name); +# warn Dumper($node); + + my $n = OESS::Node->new( db => $db, name => $node); + +# warn Dumper($n); + if(defined($n)){ + my $interfaces = $n->interfaces(); + my @ints; + + if(defined($name)){ + foreach my $interface (@$interfaces){ + if($interface->name eq $name){ + return {results => [$interface->to_hash()]}; + } + } + }else{ + foreach my $interface( @$interfaces){ + push(@ints, $interface->to_hash()); + } + return {results => {interfaces => \@ints}}; + } + } + $method->set_error("Unable to find node: $node"); + return; +} + sub get_available_vlans{ my $method = shift; my $params = shift; diff --git a/frontend/webservice/measurement.cgi b/frontend/webservice/measurement.cgi index e0d3e5988..398d9d5d2 100755 --- a/frontend/webservice/measurement.cgi +++ b/frontend/webservice/measurement.cgi @@ -43,6 +43,7 @@ use OESS::Measurement qw(BUILDING_FILE); Log::Log4perl::init('/etc/oess/logging.conf'); my $db = new OESS::Database(); +my $db2 = new OESS::DB(); my $measurement = new OESS::Measurement(); #register web service dispatcher @@ -153,6 +154,12 @@ sub get_circuit_data { my $interface = $args->{'interface'}{'value'}; my $link = $args->{'link'}{'value'}; + my ($ok, $err); + my ($ok, $err) = OESS::DB::User::has_circuit_permission(db => $db2, username => $ENV{'REMOTE_USER'}, circuit_id => $circuit_id, permission => 'read'); + if(!$ok){ + $results->{'error'} = $err; + return $results; + } # if we were sent a link, pick one of the endpoints to use for gathering data if (defined $link){ diff --git a/frontend/webservice/vrf.cgi b/frontend/webservice/vrf.cgi index 6d7fcb647..fa05449af 100755 --- a/frontend/webservice/vrf.cgi +++ b/frontend/webservice/vrf.cgi @@ -12,6 +12,7 @@ use GRNOC::WebService::Method; use GRNOC::WebService::Dispatcher; use OESS::RabbitMQ::Client; +use OESS::RabbitMQ::Topic qw(fwdctl_topic_for_connection); use OESS::Cloud; use OESS::Config; use OESS::DB; @@ -146,12 +147,18 @@ sub register_rw_methods{ required => 1, description => "The workgroup_id with permission to build the vrf, the user must be a member of this workgroup." ); - $method->add_input_parameter( name => 'vrf_id', pattern => $GRNOC::WebService::Regex::INTEGER, required => 1, description => 'the ID of the VRF to remove from the network'); + $method->add_input_parameter( + name => 'skip_cloud_provisioning', + pattern => $GRNOC::WebService::Regex::INTEGER, + required => 0, + default => 0, + description => "If set to 1 cloud provider configurations will not be performed." + ); $svc->register_method($method); @@ -164,7 +171,7 @@ sub get_vrf_details{ my $vrf_id = $params->{'vrf_id'}{'value'}; - if ($config->network_type ne 'vpn-mpls') { + if ($config->network_type ne 'vpn-mpls' && $config->network_type ne 'nso' && $config->network_type ne 'nso+vpn-mpls') { $method->set_error("Support for Layer 3 Connections is currently disabled. Please contact your OESS administrator for more information."); return; } @@ -221,7 +228,7 @@ sub get_vrfs{ my $params = shift; my $ref = shift; - if ($config->network_type ne 'vpn-mpls') { + if ($config->network_type ne 'vpn-mpls' && $config->network_type ne 'nso' && $config->network_type ne 'nso+vpn-mpls') { $method->set_error("Support for Layer 3 Connections is currently disabled. Please contact your OESS administrator for more information."); return; } @@ -272,7 +279,7 @@ sub provision_vrf{ my $method = shift; my $params = shift; - if ($config->network_type ne 'vpn-mpls') { + if ($config->network_type ne 'vpn-mpls' && $config->network_type ne 'nso' && $config->network_type ne 'nso+vpn-mpls') { $method->set_error("Support for Layer 3 Connections is currently disabled. Please contact your OESS administrator for more information."); return; } @@ -322,7 +329,8 @@ sub provision_vrf{ my $previous_vrf = undef; if (defined $model->{'vrf_id'} && $model->{'vrf_id'} != -1) { - $vrf = OESS::VRF->new(db => $db, vrf_id => $model->{'vrf_id'}); + $vrf = OESS::VRF->new(db => $db, vrf_id => $model->{vrf_id}); + $vrf->description($params->{description}{value}); if (!defined $vrf) { $method->set_error("Couldn't load VRF"); $db->rollback; @@ -388,9 +396,10 @@ sub provision_vrf{ node => $ep->{node} ); } - if (defined $interface && (!defined $interface->{cloud_interconnect_type} || $interface->{cloud_interconnect_type} eq 'aws-hosted-connection')) { - # Continue - } else { + # if (defined $interface && (!defined $interface->{cloud_interconnect_type} || $interface->{cloud_interconnect_type} eq 'aws-hosted-connection')) { + # # Continue + # } + else { $entity = new OESS::Entity(db => $db, name => $ep->{entity}); $interface = $entity->select_interface( inner_tag => $ep->{inner_tag}, @@ -412,6 +421,12 @@ sub provision_vrf{ return; } + if(defined $interface->provisionable_bandwidth && ($ep->{bandwidth} + $interface->{utilized_bandwidth} > $interface->provisionable_bandwidth)){ + $method->set_error("Couldn't create Connnection: Specified bandwidth exceeds provisionable bandwidth for '$ep->{entity}'."); + $db->rollback; + return; + } + $ep->{type} = 'vrf'; $ep->{entity_id} = $entity->{entity_id}; $ep->{interface} = $interface->{name}; @@ -451,6 +466,28 @@ sub provision_vrf{ push @$add_endpoints, $endpoint; foreach my $peering (@{$ep->{peers}}) { + + if (defined $peerings->{"$endpoint->{node} $endpoint->{interface} $peering->{local_ip}"}) { + $method->set_error("Cannot have duplicate local addresses on an interface."); + $db->rollback; + return; + } + + # User defined or pre-defined (eg. azure) peering + if ($peering->{local_ip}) { + my $peer = new OESS::Peer(db => $db, model => $peering); + my ($peer_id, $peer_err) = $peer->create(vrf_ep_id => $endpoint->vrf_endpoint_id); + if (defined $peer_err) { + $method->set_error($peer_err); + $db->rollback; + return; + } + $endpoint->add_peer($peer); + + $peerings->{"$endpoint->{node} $endpoint->{interface} $peering->{local_ip}"} = 1; + next; + } + if ($interface->cloud_interconnect_type eq 'azure-express-route') { my $peer; if ($endpoint->cloud_interconnect_id =~ /PRI/) { @@ -481,25 +518,6 @@ sub provision_vrf{ ); } - my ($peer_id, $peer_err) = $peer->create(vrf_ep_id => $endpoint->vrf_endpoint_id); - if (defined $peer_err) { - $method->set_error($peer_err); - $db->rollback; - return; - } - $endpoint->add_peer($peer); - next; - } - - if (defined $peerings->{"$endpoint->{node} $endpoint->{interface} $peering->{local_ip}"}) { - $method->set_error("Cannot have duplicate local addresses on an interface."); - $db->rollback; - return; - } - - # User defined or pre-defined (eg. azure) peering - if ($peering->{local_ip}) { - my $peer = new OESS::Peer(db => $db, model => $peering); my ($peer_id, $peer_err) = $peer->create(vrf_ep_id => $endpoint->vrf_endpoint_id); if (defined $peer_err) { $method->set_error($peer_err); @@ -726,23 +744,67 @@ sub provision_vrf{ return; } - my $res = undef; + my $ok = 0; my $type = 'provisioned'; my $reason = "Created by $ENV{'REMOTE_USER'}"; + # Ensure that endpoints' controller info loaded + $vrf->load_endpoints; + foreach my $ep (@{$vrf->endpoints}) { + $ep->load_peers; + } + my $pending_vrf = $vrf->to_hash; + + # Valid vrf_id was passed in model which implies that the + # connection is being edited. if (defined $model->{'vrf_id'} && $model->{'vrf_id'} != -1) { - $db->commit; - _update_cache(vrf_id => $vrf_id); + my ($pending_topic, $t0_err) = fwdctl_topic_for_connection($pending_vrf); + my ($prev_topic, $t1_err) = fwdctl_topic_for_connection($previous_vrf); + + # No connection may be provisioned using multiple controllers. + if (defined $t0_err || defined $t1_err) { + $method->set_error("$t0_err $t1_err"); + return; + } + + # In the case where a connection is moved between controllers, + # we want the cache for both controllers updated. + if ($pending_topic ne $prev_topic) { + my ($dres, $derr) = vrf_del($previous_vrf); + if (defined $derr) { + warn $derr; + } + $db->commit; + _update_cache($previous_vrf); + + _update_cache($pending_vrf); + my ($ares, $aerr) = vrf_add($pending_vrf); + if (defined $aerr) { + warn $aerr; + } + } else { + $db->commit; - $res = vrf_modify(method => $method, vrf_id => $vrf_id, previous => $previous_vrf, pending => $vrf->to_hash); + _update_cache($pending_vrf); + my ($mres, $merr) = vrf_modify($vrf->vrf_id, $previous_vrf, $pending_vrf); + if (defined $merr) { + warn $merr; + } - $type = 'modified'; - $reason = "Updated by $ENV{'REMOTE_USER'}"; + $type = 'modified'; + $reason = "Updated by $ENV{'REMOTE_USER'}"; + } } else { $db->commit; - _update_cache(vrf_id => $vrf_id); + _update_cache($pending_vrf); + + my ($ares, $aerr) = vrf_add($pending_vrf); + if (defined $aerr) { + warn $aerr; + } - $res = vrf_add(method => $method, vrf_id => $vrf_id); + $type = 'provisioned'; + $reason = "Created by $ENV{'REMOTE_USER'}"; } eval { @@ -754,7 +816,7 @@ sub provision_vrf{ ); }; - return {results => $res}; + return { results => { success => 1, vrf_id => $vrf->vrf_id } }; } sub remove_vrf { @@ -762,7 +824,7 @@ sub remove_vrf { my $params = shift; my $ref = shift; - if ($config->network_type ne 'vpn-mpls') { + if ($config->network_type ne 'vpn-mpls' && $config->network_type ne 'nso' && $config->network_type ne 'nso+vpn-mpls') { $method->set_error("Support for Layer 3 Connections is currently disabled. Please contact your OESS administrator for more information."); return; } @@ -798,26 +860,35 @@ sub remove_vrf { $vrf->load_workgroup; $vrf->load_users; - my $result; + my $previous_vrf = $vrf->to_hash; + if(!$user->in_workgroup( $wg) && !$user->is_admin()){ $method->set_error("User " . $ENV{'REMOTE_USER'} . " is not in workgroup"); return {success => 0}; } - eval { - OESS::Cloud::cleanup_endpoints($vrf->endpoints); - }; - if ($@) { - $method->set_error("$@"); - return; + if (!$params->{skip_cloud_provisioning}->{value}) { + eval { + OESS::Cloud::cleanup_endpoints($vrf->endpoints); + }; + if ($@) { + $method->set_error("$@"); + return; + } } - my $res = vrf_del(method => $method, vrf_id => $vrf->vrf_id); + my $ok = 0; + my ($dres, $derr) = vrf_del($previous_vrf); + if (defined $derr) { + warn $derr; + } else { + $ok = 1; + } $vrf->decom(user_id => $user->user_id); $db->commit; - _update_cache(vrf_id => $vrf->vrf_id); + _update_cache($previous_vrf); eval { $log_client->vrf_notification( @@ -828,143 +899,160 @@ sub remove_vrf { ); }; - return {results => $res}; + return { results => { success => $ok, vrf => $vrf->vrf_id } }; } sub vrf_add{ - my %params = @_; - my $vrf_id = $params{'vrf_id'}; - my $method = $params{'method'}; - $mq->{'topic'} = 'MPLS.FWDCTL.RPC'; - - my $cv = AnyEvent->condvar; + my $conn = shift; - warn "_send_vrf_add_command: Calling addVrf on vrf $vrf_id"; - $mq->addVrf(vrf_id => int($vrf_id), async_callback => sub { - my $result = shift; - $cv->send($result); - }); - - my $result = $cv->recv(); - - if (defined $result->{'error'} || !defined $result->{'results'}){ - if (defined $result->{'error'}) { - $method->set_error($result->{'error'}); - } else { - $method->set_error("Did not get response from addVRF."); - } - return {success => 0, vrf_id => $vrf_id}; + if (!defined $mq) { + return (undef, "Couldn't create RabbitMQ Client."); + } + my ($topic, $err) = fwdctl_topic_for_connection($conn); + if (defined $err) { + warn $err; + return (undef, $err); } + $mq->{topic} = $topic; + + my $cv = AnyEvent->condvar; + $mq->addVrf( + vrf_id => int($conn->{vrf_id}), + async_callback => sub { + my $result = shift; + $cv->send($result); + } + ); + my $result = $cv->recv(); - return {success => 1, vrf_id => $vrf_id}; + if (!defined $result) { + return ($result, "Error occurred while calling $topic.addVrf: Couldn't connect to RabbitMQ."); + } + if (defined $result->{error}) { + return ($result, "Error occured while calling $topic.addVrf: $result->{error}"); + } + if (defined $result->{results}->{error}) { + return ($result, "Error occured while calling $topic.addVrf: " . $result->{results}->{error}); + } + return ($result->{results}->{status}, undef); } -sub vrf_del{ - my %params = @_; - my $vrf_id = $params{'vrf_id'}; - my $method = $params{'method'}; - $mq->{'topic'} = 'MPLS.FWDCTL.RPC'; +sub vrf_del { + my $conn = shift; - my $cv = AnyEvent->condvar; + if (!defined $mq) { + return (undef, "Couldn't create RabbitMQ Client."); + } - warn "_send_vrf_add_command: Calling delVrf on vrf $vrf_id"; - $mq->delVrf(vrf_id => int($vrf_id), async_callback => sub { - my $result = shift; - $cv->send($result); - }); - - my $result = $cv->recv(); - - if (defined $result->{'error'} || !defined $result->{'results'}){ - if (defined $result->{'error'}) { - $method->set_error($result->{'error'}); - } else { - $method->set_error("Did not get response from delVRF."); + my ($topic, $err) = fwdctl_topic_for_connection($conn); + if (defined $err) { + warn $err; + return (undef, $err); + } + $mq->{'topic'} = $topic; + + my $cv = AnyEvent->condvar; + $mq->delVrf( + vrf_id => int($conn->{vrf_id}), + async_callback => sub { + my $result = shift; + $cv->send($result); } - return {success => 0, vrf_id => $vrf_id}; + ); + my $result = $cv->recv(); + + if (!defined $result) { + return ($result, "Error occurred while calling $topic.delVrf: Couldn't connect to RabbitMQ."); } - - return {success => 1, vrf_id => $vrf_id}; + if (defined $result->{error}) { + return ($result, "Error occured while calling $topic.delVrf: $result->{error}"); + } + if (defined $result->{results}->{error}) { + return ($result, "Error occured while calling $topic.delVrf: " . $result->{results}->{error}); + } + return ($result->{results}->{status}, undef); } -sub vrf_modify{ - my %params = @_; - my $vrf_id = $params{vrf_id}; - my $method = $params{method}; - my $pending = $params{pending}; - my $previous = $params{previous}; +sub vrf_modify { + my $vrf_id = shift; + my $previous = shift; + my $pending = shift; - $mq->{topic} = 'MPLS.FWDCTL.RPC'; + if (!defined $mq) { + return (undef, "Couldn't create RabbitMQ Client."); + } - my $cv = AnyEvent->condvar; + # IMPORTANT: It's assumed that $previous and $pending was/is + # managed by the same controller!!! + my ($topic, $err) = fwdctl_topic_for_connection($pending); + if (defined $err) { + warn $err; + return (undef, $err); + } + $mq->{'topic'} = $topic; - warn "_send_vrf_modify_command: Calling modifyVrf on vrf $vrf_id"; + my $cv = AnyEvent->condvar; $mq->modifyVrf( - vrf_id => int($vrf_id), - pending => encode_json($pending), - previous => encode_json($previous), + vrf_id => int($pending->{vrf_id}), + pending => encode_json($pending), + previous => encode_json($previous), async_callback => sub { my $result = shift; $cv->send($result); } ); - my $result = $cv->recv; - if (defined $result->{error} || !defined $result->{results}) { - if (defined $result->{error}) { - $method->set_error($result->{error}); - } else { - $method->set_error("Did not get response from modifyVRF."); - } - return {success => 0, vrf_id => $vrf_id}; - } - return {success => 1, vrf_id => $vrf_id}; + if (!defined $result) { + return ($result, "Error occurred while calling $topic.modifyVrf: Couldn't connect to RabbitMQ."); + } + if (defined $result->{error}) { + return ($result, "Error occured while calling $topic.modifyVrf: $result->{error}"); + } + if (defined $result->{results}->{error}) { + return ($result, "Error occured while calling $topic.modifyVrf: " . $result->{results}->{error}); + } + return ($result->{results}->{status}, undef); } -sub _update_cache{ - my %args = @_; +sub _update_cache { + my $conn = shift; - if(!defined($args{'vrf_id'})){ - $args{'vrf_id'} = -1; + if (!defined $mq) { + return (undef, "Couldn't create RabbitMQ client."); } - my $err = undef; - - if (!defined $mq) { - $err = "Couldn't create RabbitMQ client."; - return; - } else { - $mq->{'topic'} = 'MPLS.FWDCTL.RPC'; + my ($topic, $err) = fwdctl_topic_for_connection($conn); + if (defined $err) { + warn $err; + return (undef, $err); } - my $cv = AnyEvent->condvar; - $mq->update_cache(vrf_id => $args{'vrf_id'}, - async_callback => sub { - my $result = shift; - $cv->send($result); - }); + $mq->{topic} = $topic; + my $cv = AnyEvent->condvar; + $mq->update_cache( + vrf_id => int($conn->{vrf_id}), + async_callback => sub { + my $result = shift; + $cv->send($result); + } + ); my $result = $cv->recv(); if (!defined $result) { - warn "Error occurred while calling update_cache: Couldn't contact MPLS.FWDCTL via RabbitMQ."; - return undef; + return ($result, "Error occurred while calling $topic.update_cache: Couldn't connect to RabbitMQ."); } - if (defined $result->{'error'}) { - warn "Error occurred while calling update_cache: $result->{'error'}"; - return undef; + if (defined $result->{error}) { + return ($result, "Error occured while calling $topic.update_cache: $result->{error}"); } - if (defined $result->{'results'}->{'error'}) { - warn "Error occured while calling update_cache: " . $result->{'results'}->{'error'}; - return undef; + if (defined $result->{results}->{error}) { + return ($result, "Error occured while calling $topic.update_cache: " . $result->{results}->{error}); } - - return $result->{'results'}->{'status'}; + return ($result->{results}->{status}, undef); } -sub main{ - +sub main { register_ro_methods(); register_rw_methods(); $svc->handle_request(); diff --git a/frontend/www/html_templates/base.html b/frontend/www/html_templates/base.html index dc46be16b..301863a48 100644 --- a/frontend/www/html_templates/base.html +++ b/frontend/www/html_templates/base.html @@ -32,7 +32,7 @@ - + Cloud Connect @@ -44,15 +44,15 @@ -
  • Explore
  • -
  • Workgroup
  • +
  • Explore
  • +
  • Workgroup