| perfSONAR_PS-Services-MA-CircuitStatus documentation | Contained in the perfSONAR_PS-Services-MA-CircuitStatus distribution. |
perfSONAR_PS::Services::MA::CircuitStatus - A module that provides methods for an E2EMon Compatible MP.
This module aims to offer simple methods for dealing with requests for information, and the related tasks of interacting with backend storage.
use perfSONAR_PS::Services::MA::CircuitStatus;
my %conf = readConfiguration();
my %ns = ( nmwg => "http://ggf.org/ns/nmwg/base/2.0/", ifevt => "http://ggf.org/ns/nmwg/event/status/base/2.0/", nmtm => "http://ggf.org/ns/nmwg/time/2.0/", nmwgtopo3 => "http://ggf.org/ns/nmwg/topology/base/3.0/", nmtl2 => "http://ggf.org/ns/nmwg/topology/l2/3.0/", nmtl3 => "http://ggf.org/ns/nmwg/topology/l3/3.0/", );
my $ma = perfSONAR_PS::Services::MA::CircuitStatus->new(\%conf, \%ns);
# or # $ma = perfSONAR_PS::Services::MA::CircuitStatus->new; # $ma->setConf(\%conf); # $ma->setNamespaces(\%ns);
if ($ma->init != 0) { print "Error: couldn't initialize measurement archive\n"; exit(-1); }
while(1) { my $request = $ma->receive; $ma->handleRequest($request); }
The offered API is simple, but offers the key functions we need in a measurement archive.
Initializes the MP and validates or fills in entries in the
configuration file. Returns 0 on success and -1 on failure.
Grabs an incoming message from transport object to begin processing. It
completes the processing if the message was handled by a lower layer.
If not, it returns the Request structure.
Handles the specified request returned from receive()
Validates that the message is one that we can handle, calls the
appropriate function for the message type and builds the response
message.
Goes through each metadata/data pair, extracting the eventType and
calling the function associated with that eventType.
Performs the required steps to handle a path status message: contacts
the topology service to resolve node information, contacts the LS if
needed to find the link status service, contacts the link status
service and munges the results.
Takes the set of nodes and outputs them in an E2EMon compatiable
format.
Takes the set of links and outputs them in an E2EMon compatiable
format.
Parses the links configuration file. It returns an array containg up to
five values. The first value is the status and can be one of 0 or -1.
If it is -1, parsing the configuration file failed and the error
message is in the next value. If the status is 0, the next 4 values are
the domain name, a pointer to the set of links, a pointer to a hash
containg the set of nodes to lookup in the topology service and a
pointer to a hash containing the set of links to lookup in the status
service.
Parses the output from the topology service and fills in the details
for the nodes. The domain name is passed so that when a node has no
name specified in the configuration file, it can be constructd based on
the domain name and the node's name in the topology service.
perfSONAR_PS::Services::Base, perfSONAR_PS::Services::MA::General, perfSONAR_PS::Common, perfSONAR_PS::Messages, perfSONAR_PS::Transport, perfSONAR_PS::Client::Status::MA, perfSONAR_PS::Client::Topology::MA
To join the 'perfSONAR-PS' mailing list, please visit:
https://mail.internet2.edu/wws/info/i2-perfsonar
The perfSONAR-PS subversion repository is located at:
https://svn.internet2.edu/svn/perfSONAR-PS
Questions and comments can be directed to the author, or the mailing list.
$Id:$
Aaron Brown, aaron@internet2.edu
You should have received a copy of the Internet2 Intellectual Property Framework along with this software. If not, see <http://www.internet2.edu/membership/ip.html>
Copyright (c) 2004-2007, Internet2 and the University of Delaware
All rights reserved.
| perfSONAR_PS-Services-MA-CircuitStatus documentation | Contained in the perfSONAR_PS-Services-MA-CircuitStatus distribution. |
package perfSONAR_PS::Services::MA::CircuitStatus; use base 'perfSONAR_PS::Services::Base'; use fields 'LOCAL_MA_CLIENT', 'TOPOLOGY_CLIENT', 'STORE', 'LS', 'DOMAIN', 'CIRCUITS', 'INCOMPLETE_NODES', 'TOPOLOGY_LINKS', 'NODES', 'LOGGER'; use warnings; use strict; use Log::Log4perl qw(get_logger); use Module::Load; use Fcntl qw (:flock); use Fcntl; use Params::Validate qw(:all); use perfSONAR_PS::Services::Base; use perfSONAR_PS::Services::MA::General; use perfSONAR_PS::Common; use perfSONAR_PS::Messages; use perfSONAR_PS::Transport; use perfSONAR_PS::Time; use perfSONAR_PS::Error_compat qw/:try/; use perfSONAR_PS::Client::LS::Remote; use perfSONAR_PS::Client::Status::MA; use perfSONAR_PS::Client::Topology::MA; our $VERSION = 0.09; sub init { my ($self, $handler) = @_; $self->{LOGGER} = get_logger("perfSONAR_PS::Services::MA::CircuitStatus"); if (not defined $self->{CONF}->{"circuitstatus"}->{"status_ma_type"} or $self->{CONF}->{"circuitstatus"}->{"status_ma_type"} eq q{}) { if (not defined $self->{CONF}->{"circuitstatus"}->{"ls_instance"} or $self->{CONF}->{"circuitstatus"}->{"ls_instance"} eq q{}) { $self->{LOGGER}->error("No LS nor Status MA specified"); return -1; } else { $self->{CONF}->{"circuitstatus"}->{"status_ma_type"} = "ls"; } } if (lc($self->{CONF}->{"circuitstatus"}->{"status_ma_type"}) eq "ls") { my ($host, $port, $endpoint) = &perfSONAR_PS::Transport::splitURI($self->{CONF}->{"circuitstatus"}->{"ls_instance"}); if (not $host or not $port or not $endpoint) { $self->{LOGGER}->error("Specified LS is not a URI: ".$self->{CONF}->{"circuitstatus"}->{"ls_instance"}); return -1; } $self->{LS} = $self->{CONF}->{"circuitstatus"}->{"ls_instance"}; } elsif (lc($self->{CONF}->{"circuitstatus"}->{"status_ma_type"}) eq "ma") { if (not defined $self->{CONF}->{"circuitstatus"}->{"status_ma_uri"} or $self->{CONF}->{"circuitstatus"}->{"status_ma_uri"} eq q{}) { $self->{LOGGER}->error("You specified an MA for the status, but did not specify the URI(status_ma_uri)"); return -1; } } elsif (lc($self->{CONF}->{"circuitstatus"}->{"status_ma_type"}) eq "sqlite") { load perfSONAR_PS::Client::Status::SQL; if (not defined $self->{CONF}->{"circuitstatus"}->{"status_ma_file"} or $self->{CONF}->{"circuitstatus"}->{"status_ma_file"} eq q{}) { $self->{LOGGER}->error("You specified a SQLite Database, but then did not specify a database file(status_ma_file)"); return -1; } my $file = $self->{CONF}->{"circuitstatus"}->{"status_ma_file"}; if (defined $self->{DIRECTORY}) { if (!($file =~ "^/")) { $file = $self->{DIRECTORY}."/".$file; } } $self->{LOCAL_MA_CLIENT} = perfSONAR_PS::Client::Status::SQL->new("DBI:SQLite:dbname=".$file, $self->{CONF}->{"circuitstatus"}->{"status_ma_table"}); if (not defined $self->{LOCAL_MA_CLIENT}) { my $msg = "No database to dump"; $self->{LOGGER}->error($msg); return -1; } } elsif (lc($self->{CONF}->{"circuitstatus"}->{"status_ma_type"}) eq "mysql") { load perfSONAR_PS::Client::Status::SQL; my $dbi_string = "dbi:mysql"; if (not defined $self->{CONF}->{"circuitstatus"}->{"status_ma_name"} or $self->{CONF}->{"circuitstatus"}->{"status_ma_name"} eq q{}) { $self->{LOGGER}->error("You specified a MySQL Database, but did not specify the database (status_ma_name)"); return -1; } $dbi_string .= ":".$self->{CONF}->{"circuitstatus"}->{"status_ma_name"}; if (not defined $self->{CONF}->{"circuitstatus"}->{"status_ma_host"} or $self->{CONF}->{"circuitstatus"}->{"status_ma_host"} eq q{}) { $self->{LOGGER}->error("You specified a MySQL Database, but did not specify the database host (status_ma_host)"); return -1; } $dbi_string .= ":".$self->{CONF}->{"circuitstatus"}->{"status_ma_host"}; if (defined $self->{CONF}->{"circuitstatus"}->{"status_ma_port"} and $self->{CONF}->{"circuitstatus"}->{"status_ma_port"} ne q{}) { $dbi_string .= ":".$self->{CONF}->{"circuitstatus"}->{"status_ma_port"}; } $self->{LOCAL_MA_CLIENT} = perfSONAR_PS::Client::Status::SQL->new($dbi_string, $self->{CONF}->{"circuitstatus"}->{"status_ma_username"}, $self->{CONF}->{"circuitstatus"}->{"status_ma_password"}); if (not defined $self->{LOCAL_MA_CLIENT}) { my $msg = "Couldn't create SQL client"; $self->{LOGGER}->error($msg); return -1; } } else { $self->{LOGGER}->error("Invalid MA type specified"); return -1; } if (not defined $self->{CONF}->{"circuitstatus"}->{"circuits_file_type"} or $self->{CONF}->{"circuitstatus"}->{"circuits_file_type"} eq q{}) { $self->{LOGGER}->error("No circuits file type specified"); return -1; } if($self->{CONF}->{"circuitstatus"}->{"circuits_file_type"} eq "file") { if (not defined $self->{CONF}->{"circuitstatus"}->{"circuits_file"} or $self->{CONF}->{"circuitstatus"}->{"circuits_file"} eq q{}) { $self->{LOGGER}->error("No circuits file specified"); return -1; } try { ($self->{DOMAIN}, $self->{CIRCUITS}, $self->{INCOMPLETE_NODES}, $self->{TOPOLOGY_LINKS}, $self->{NODES}) = $self->parseCircuitsFile($self->{CONF}->{"circuitstatus"}->{"circuits_file"}); } catch perfSONAR_PS::Error_compat with { my $ex = shift; my $msg = "Error parsing circuits file: $ex"; $self->{LOGGER}->error($msg); return -1; }; if ($self->{"CONF"}->{"circuitstatus"}->{"topology_ma_type"} eq "none" and scalar keys %{ $self->{INCOMPLETE_NODES} } > 0) { my $msg = "You specified no topology MA, but there are incomplete nodes"; $self->{LOGGER}->error($msg); return -1; } } else { $self->{LOGGER}->error("Invalid circuits file type specified: ".$self->{CONF}->{"circuitstatus"}->{"link_file_type"}); return -1; } if (not defined $self->{CONF}->{"circuitstatus"}->{"topology_ma_type"} or $self->{CONF}->{"circuitstatus"}->{"topology_ma_type"} eq q{}) { $self->{LOGGER}->error("No topology MA type specified"); return -1; } elsif (lc($self->{CONF}->{"circuitstatus"}->{"topology_ma_type"}) eq "xml") { load perfSONAR_PS::Client::Topology::XMLDB; if (not defined $self->{CONF}->{"circuitstatus"}->{"topology_ma_file"} or $self->{CONF}->{"circuitstatus"}->{"topology_ma_file"} eq q{}) { $self->{LOGGER}->error("You specified a Sleepycat XML DB Database, but then did not specify a database file(topology_ma_file)"); return -1; } if (not defined $self->{CONF}->{"circuitstatus"}->{"topology_ma_environment"} or $self->{CONF}->{"circuitstatus"}->{"topology_ma_environment"} eq q{}) { $self->{LOGGER}->error("You specified a Sleepycat XML DB Database, but then did not specify a database name(topology_ma_environment)"); return -1; } my $environment = $self->{CONF}->{"circuitstatus"}->{"topology_ma_environment"}; if (defined $self->{DIRECTORY}) { if (!($environment =~ "^/")) { $environment = $self->{DIRECTORY}."/".$environment; } } my $file = $self->{CONF}->{"circuitstatus"}->{"topology_ma_file"}; my %ns = &perfSONAR_PS::Topology::Common::getTopologyNamespaces(); $self->{TOPOLOGY_CLIENT} = perfSONAR_PS::Client::Topology::XMLDB->new($environment, $file, \%ns, 1); if (not defined $self->{TOPOLOGY_CLIENT}) { $self->{LOGGER}->error("Couldn't initialize topology client"); return -1; } } elsif (lc($self->{CONF}->{"circuitstatus"}->{"topology_ma_type"}) eq "none") { $self->{LOGGER}->warn("Ignoring the topology MA. Everything must be specified explicitly in the circuits.conf file"); } elsif (lc($self->{CONF}->{"circuitstatus"}->{"topology_ma_type"}) eq "ma") { if (not defined $self->{CONF}->{"circuitstatus"}->{"topology_ma_uri"} or $self->{CONF}->{"circuitstatus"}->{"topology_ma_uri"} eq q{}) { $self->{LOGGER}->error("You specified that you want a Topology MA, but did not specify the URI (topology_ma_uri)"); return -1; } $self->{TOPOLOGY_CLIENT} = perfSONAR_PS::Client::Topology::MA->new($self->{CONF}->{"circuitstatus"}->{"topology_ma_uri"}); } else { $self->{LOGGER}->error("Invalid database type specified: ".lc($self->{CONF}->{"circuitstatus"}->{"topology_ma_type"}) ); return -1; } if (lc($self->{CONF}->{"circuitstatus"}->{"topology_ma_type"}) ne "none" and defined $self->{INCOMPLETE_NODES} and keys %{ $self->{INCOMPLETE_NODES} } != 0) { my ($status, $res); ($status, $res) = $self->{TOPOLOGY_CLIENT}->open; if ($status != 0) { my $msg = "Problem opening topology MA: $res"; $self->{LOGGER}->error($msg); return -1; } ($status, $res) = $self->{TOPOLOGY_CLIENT}->getAll; if ($status != 0) { my $msg = "Error getting topology information: $res"; $self->{LOGGER}->error($msg); return -1; } my $topology = $res; ($status, $res) = $self->parseTopology($topology, $self->{INCOMPLETE_NODES}, $self->{DOMAIN}); if ($status ne q{}) { my $msg = "Error parsing topology: $res"; $self->{LOGGER}->error($msg); return -1; } } $self->{STORE} = $self->createMetadataStore($self->{NODES}, $self->{CIRCUITS}); $self->{LOGGER}->debug("Store: ".$self->{STORE}->toString); if (defined $self->{CONF}->{"circuitstatus"}->{"cache_length"} and $self->{CONF}->{"circuitstatus"}->{"cache_length"} > 0) { if (not defined $self->{CONF}->{"circuitstatus"}->{"cache_file"} or $self->{CONF}->{"circuitstatus"}->{"cache_file"} eq q{}) { my $msg = "If you specify a cache time period, you need to specify a file to cache to \"cache_file\""; $self->{LOGGER}->error($msg); return -1; } my $file = $self->{CONF}->{"circuitstatus"}->{"cache_file"}; if (defined $self->{DIRECTORY}) { if (!($file =~ "^/")) { $file = $self->{DIRECTORY}."/".$file; } } $self->{CONF}->{"circuitstatus"}->{"cache_file"} = $file; $self->{LOGGER}->debug("Using \"$file\" to cache current results"); } $handler->registerEventHandler("SetupDataRequest", "Path.Status", $self); $handler->registerEventHandler_Regex("SetupDataRequest", ".*select.*", $self); $handler->registerEventHandler("MetadataKeyRequest", "Path.Status", $self); return 0; } sub needLS { return 0; } sub handleEvent { my ($self, @args) = @_; my $parameters = validate(@args, { output => 1, messageId => 1, messageType => 1, messageParameters => 1, eventType => 1, subject => 1, filterChain => 1, data => 1, rawRequest => 1, doOutputMetadata => 1, }); my $output = $parameters->{"output"}; my $messageId = $parameters->{"messageId"}; my $messageType = $parameters->{"messageType"}; my $message_parameters = $parameters->{"messageParameters"}; my $eventType = $parameters->{"eventType"}; my $d = $parameters->{"data"}; my $raw_request = $parameters->{"rawRequest"}; my @subjects = @{ $parameters->{"subject"} }; my $doOutputMetadata = $parameters->{doOutputMetadata}; my $md = $subjects[0]; ${$doOutputMetadata} = 0; my ($status, $res1, $res2); # This could be wrapped in try/catch ($res1, $res2) = $self->resolveSelectChain($md, $raw_request); my $selectTime = $res1; my $subject_md = $res2; $eventType = undef; my $eventTypes = find($subject_md, "./nmwg:eventType", 0); foreach my $e ($eventTypes->get_nodelist) { my $value = extract($e, 1); $self->{LOGGER}->debug("Found: \"$value\""); if ($value eq "Path.Status") { $eventType = $value; last; } } if (not defined $eventType) { throw perfSONAR_PS::Error_compat("error.ma.event_type", "No supported event types for message of type \"$messageType\""); } if (defined $selectTime and $selectTime->getType("point") and $selectTime->getTime() eq "now") { $selectTime = undef; } my @circuits; if (find($subject_md, "./nmwg:key", 1)) { my $circuit_name = findvalue(find($subject_md, "./nmwg:key", 1), "./nmwg:parameters/nmwg:parameter[\@name=\"linkId\"]"); if (not defined $circuit_name or not defined $self->{CIRCUITS}->{$circuit_name}) { my $msg = "The specified key is invalid"; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat ("error.ma.invalid_key", "The specified key is invalid"); } push @circuits, $circuit_name; } elsif (find($subject_md, "./nmwg:subject", 1)) { # this could get wrapped in try/catch my $circuit_name; try { $circuit_name = $self->compatParseSubject(find($subject_md, "./nmwg:subject", 1)); } catch perfSONAR_PS::Error_compat with { my $ex = shift; $self->{LOGGER}->error("Error parsing subject: ".$ex->errorMessage()); $ex->rethrow(); }; push @circuits, $circuit_name; } else { @circuits = keys %{ $self->{CIRCUITS} }; } $self->handlePathStatus($output, \@circuits, $selectTime); return (q{}, q{}); } sub compatParseSubject { my ($self, $subject) = @_; my $circuit_name; if (!find($subject, "./nmtl2:link", 1)) { throw perfSONAR_PS::Error_compat("error.ma.invalid_subject", "The specified subject does not contain a link element"); } $circuit_name = findvalue($subject, "./nmtl2:link/nmtl2:name"); if (defined $circuit_name and defined $self->{CIRCUITS}->{$circuit_name}) { return $circuit_name; } my $nodes = find($subject, "./nmtl2:link/nmwgtopo3:node", 0); my $count = 0; my ($node1, $node2); foreach my $node ($nodes->get_nodelist) { my $node_name = findvalue($node, "./nmwgtopo3:name"); if (not defined $node_name) { throw perfSONAR_PS::Error_compat("error.ma.invalid_subject", "The specified subject contains an unfinished node"); } } return $circuit_name; } sub generateMDXpath { my ($subject) = @_; return q{}; } sub createMetadataStore { my ($self, $nodes, $circuits) = @_; my $doc = perfSONAR_PS::XML::Document_string->new(); $doc->startElement(prefix => "nmwg", tag => "store", namespace => "http://ggf.org/ns/nmwg/base/2.0/"); foreach my $node_id (keys %{ $nodes }) { my $node = $nodes->{$node_id}; $self->outputNodeElement($doc, $node); } foreach my $circuit_id (keys %{ $circuits }) { my $circuit = $circuits->{$circuit_id}; $self->outputCircuitElement($doc, $circuit); } $doc->endElement("store"); my $parser = XML::LibXML->new(); my $xmlDoc; eval { $xmlDoc = $parser->parse_string($doc->getValue); }; if ($@ or not defined $xmlDoc) { my $msg = "Couldn't parse metadata store: $@"; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat("error.configuration", $msg); } return $xmlDoc->documentElement; } sub resolveSelectChain { my ($self, $md, $request) = @_; if (!$request->getNamespaces()->{"http://ggf.org/ns/nmwg/ops/select/2.0/"}) { $self->{LOGGER}->debug("No select namespace means there is no select chain"); } if (!find($md, "./select:subject", 1)) { $self->{LOGGER}->debug("No select subject means there is no select chain"); } if ($request->getNamespaces()->{"http://ggf.org/ns/nmwg/ops/select/2.0/"} and find($md, "./select:subject", 1)) { my $other_md = find($request->getRequestDOM(), "//nmwg:metadata[\@id=\"".find($md, "./select:subject", 1)->getAttribute("metadataIdRef")."\"]", 1); if(!$other_md) { throw perfSONAR_PS::Error_compat("error.ma.chaining", "Cannot resolve supposed subject chain in metadata."); } if (!find($md, "./select:subject/select:parameters", 1)) { throw perfSONAR_PS::Error_compat ("error.ma.select", "No select parameters specified in given chain."); } my $time = findvalue($md, "./select:subject/select:parameters/select:parameter[\@name=\"time\"]"); my $startTime = findvalue($md, "./select:subject/select:parameters/select:parameter[\@name=\"startTime\"]"); my $endTime = findvalue($md, "./select:subject/select:parameters/select:parameter[\@name=\"endTime\"]"); my $duration = findvalue($md, "./select:subject/select:parameters/select:parameter[\@name=\"duration\"]"); if (defined $time and (defined $startTime or defined $endTime or defined $duration)) { throw perfSONAR_PS::Error_compat ("error.ma.select", "Ambiguous select parameters"); } if (defined $time) { return (perfSONAR_PS::Time->new("point", $time), $other_md); } if (not defined $startTime) { throw perfSONAR_PS::Error_compat ("error.ma.select", "No start time specified"); } elsif (not defined $endTime and not defined $duration) { throw perfSONAR_PS::Error_compat ("error.ma.select", "No end time specified"); } elsif (defined $endTime) { return (perfSONAR_PS::Time->new("range", $startTime, $endTime), $other_md); } else { return (perfSONAR_PS::Time->new("duration", $startTime, $duration), $other_md); } } else { # No select subject means they didn't specify one which results in "now" $self->{LOGGER}->debug("No select chain"); my $ret_time; my $time = findvalue($md, "./nmwg:parameters/nmwg:parameter[\@name=\"time\"]"); if (defined $time and lc($time) ne "now" and $time ne q{}) { $ret_time = perfSONAR_PS::Time->new("point", $time); } return ($ret_time, $md); } } sub getLinkStatus { my ($self, $link_ids, $time) = @_; my %clients = (); if (lc($self->{CONF}->{"circuitstatus"}->{"status_ma_type"}) eq "ma") { my %client; my @children; foreach my $link_id (keys %{ $self->{TOPOLOGY_LINKS} }) { push @children, $link_id; } $client{"CLIENT"} = perfSONAR_PS::Client::Status::MA->new($self->{CONF}->{"circuitstatus"}->{"status_ma_uri"}); $client{"LINKS"} = $link_ids; my ($status, $res) = $client{"CLIENT"}->open; if ($status != 0) { my $msg = "Problem opening status MA ".$self->{CONF}->{"circuitstatus"}->{"status_ma_uri"}.": $res"; $self->{LOGGER}->warn($msg); } else { $clients{$self->{CONF}->{"circuitstatus"}->{"status_ma_uri"}} = \%client; } } elsif (lc($self->{CONF}->{"circuitstatus"}->{"status_ma_type"}) eq "ls") { # Consult the LS to find the Status MA for each link my %queries = (); foreach my $link_id (@{ $link_ids }) { my $xquery = q{}; $xquery .= " declare namespace nmwg=\"http://ggf.org/ns/nmwg/base/2.0/\";\n"; $xquery .= " for \$data in /nmwg:store/nmwg:data\n"; $xquery .= " let \$metadata_id := \$data/\@metadataIdRef\n"; $xquery .= " where \$data//*:link[\@id=\"$link_id\"] and \$data//nmwg:eventType[text()=\"http://ggf.org/ns/nmwg/characteristic/link/status/20070809\"]\n"; $xquery.= " return /nmwg:store/nmwg:metadata[\@id=\$metadata_id]\n"; $queries{$link_id} = $xquery; } my $ls = perfSONAR_PS::Client::LS::Remote->new($self->{LS}); my ($status, $res) = $ls->query(\%queries); if ($status != 0) { my $msg = "Couldn't lookup Link Status MAs from LS: $res"; $self->{LOGGER}->warn($msg); } else { foreach my $link_id (@{ $link_ids }) { if (not defined $res->{$link_id}) { $self->{LOGGER}->warn("Couldn't find any information on link $link_id"); next; } my ($link_status, $link_res) = @{ $res->{$link_id} }; if ($link_status != 0) { $self->{LOGGER}->warn("Couldn't find any information on link $link_id"); next; } my $accessPoint; $accessPoint = findvalue($link_res, "./psservice:datum/nmwg:metadata/perfsonar:subject/psservice:service/psservice:accessPoint"); if (not defined $accessPoint or $accessPoint eq q{}) { my $msg = "Received response with no access point for link: $link_id"; $self->{LOGGER}->warn($msg); next; } if (not defined $clients{$accessPoint}) { my %client = (); my @children = (); my $new_client; push @children, $link_id; $client{"CLIENT"} = perfSONAR_PS::Client::Status::MA->new($accessPoint); $client{"LINKS"} = \@children; my ($status, $res) = $client{"CLIENT"}->open; if ($status != 0) { my $msg = "Problem opening status MA $accessPoint: $res"; $self->{LOGGER}->warn($msg); next; } $clients{$accessPoint} = \%client; } else { push @{ $clients{$accessPoint}->{"LINKS"} }, $link_id; } } } } else { my %client; $client{"CLIENT"} = $self->{LOCAL_MA_CLIENT}; $client{"LINKS"} = $link_ids; my ($status, $res) = $client{"CLIENT"}->open; if ($status != 0) { my $msg = "Problem opening status MA ".$self->{CONF}->{"circuitstatus"}->{"status_ma_uri"}.": $res"; $self->{LOGGER}->warn($msg); } else { $clients{"local"} = \%client; } } my %response = (); foreach my $ap_id (keys %clients) { my $ma = $clients{$ap_id}; my ($status, $res) = $ma->{"CLIENT"}->getLinkStatus($ma->{"LINKS"}, $time); if ($status != 0) { my $msg = "Error getting link status: $res"; $self->{LOGGER}->warn($msg); } else { foreach my $link_id (keys %{ $res }) { $response{$link_id} = $res->{$link_id}; } } } return \%response; } sub handlePathStatus { my ($self, $output, $circuits, $time) = @_; my ($status, $res); if (defined $self->{CONF}->{"circuitstatus"}->{"cache_length"} and $self->{CONF}->{"circuitstatus"}->{"cache_length"} > 0 and not defined $time) { my $mtime = (stat $self->{CONF}->{"circuitstatus"}->{"cache_file"})[9]; if (time - $mtime < $self->{CONF}->{"circuitstatus"}->{"cache_length"}) { $self->{LOGGER}->debug("Using cached results in ".$self->{CONF}->{"circuitstatus"}->{"cache_file"}); if (open(CACHEFILE, $self->{CONF}->{"circuitstatus"}->{"cache_file"})) { my $response; local $/; flock CACHEFILE, LOCK_SH; $response = <CACHEFILE>; close CACHEFILE; $output->addOpaque($response); return; } else { $self->{LOGGER}->warn("Unable to open cached results in ".$self->{CONF}->{"circuitstatus"}->{"cache_file"}); } } } # get the list of topology link IDs to lookup my %link_ids = (); foreach my $circuit_name (@{ $circuits }) { my $circuit = $self->{CIRCUITS}->{$circuit_name}; foreach my $sublink_id (@{ $circuit->{"sublinks"} }) { $link_ids{$sublink_id} = q{}; } } # Lookup the link status my @links = keys %link_ids; $res = $self->getLinkStatus(\@links, $time); # Fill in any missing links foreach my $link_id (@links) { if (not defined $res->{$link_id}) { my $msg = "Did not receive any information about link $link_id"; $self->{LOGGER}->warn($msg); my $link; if (not defined $time) { my $curr_time = time; $link = perfSONAR_PS::Status::Link->new($link_id, "full", $curr_time, $curr_time, "unknown", "unknown"); } else { $link = perfSONAR_PS::Status::Link->new($link_id, "full", $time->getStartTime(), $time->getEndTime(), "unknown", "unknown"); } $res->{$link_id} = [ $link ]; } } my %circuit_status = (); foreach my $circuit_name (@{ $circuits }) { my $circuit = $self->{CIRCUITS}->{$circuit_name}; my @data_points = (); if (defined $time and $time->getType() ne "point") { foreach my $sublink_id (@{ $circuit->{"sublinks"} }) { foreach my $link_status (@{ $res->{$sublink_id} }) { push @data_points, $link_status; } } } else { my $circuit_admin_value = "unknown"; my $circuit_oper_value = "unknown"; my $circuit_time; foreach my $sublink_id (@{ $circuit->{"sublinks"} }) { foreach my $link_status (@{ $res->{$sublink_id} }) { $self->{LOGGER}->debug("Sublink: $sublink_id"); my $oper_value = $link_status->getOperStatus; my $admin_value = $link_status->getAdminStatus; my $end_time = $link_status->getEndTime; $circuit_time = $end_time if (not defined $circuit_time or $end_time > $circuit_time); if ($circuit_oper_value eq "down" or $oper_value eq "down") { $circuit_oper_value = "down"; } elsif ($circuit_oper_value eq "degraded" or $oper_value eq "degraded") { $circuit_oper_value = "degraded"; } elsif ($circuit_oper_value eq "up" or $oper_value eq "up") { $circuit_oper_value = "up"; } else { $circuit_oper_value = "unknown"; } if ($circuit_admin_value eq "maintenance" or $admin_value eq "maintenance") { $circuit_admin_value = "maintenance"; } elsif ($circuit_admin_value eq "troubleshooting" or $admin_value eq "troubleshooting") { $circuit_admin_value = "troubleshooting"; } elsif ($circuit_admin_value eq "underrepair" or $admin_value eq "underrepair") { $circuit_admin_value = "underrepair"; } elsif ($circuit_admin_value eq "normaloperation" or $admin_value eq "normaloperation") { $circuit_admin_value = "normaloperation"; } else { $circuit_admin_value = "unknown"; } } } if (not defined $time and defined $self->{CONF}->{"circuitstatus"}->{"max_recent_age"} and $self->{CONF}->{"circuitstatus"}->{"max_recent_age"} ne q{}) { my $curr_time = time; if ($curr_time - $circuit_time > $self->{CONF}->{"circuitstatus"}->{"max_recent_age"}) { $self->{LOGGER}->debug("Old link time: $circuit_time Current Time: ".$curr_time.": ".($curr_time - $circuit_time)); $circuit_time = $curr_time; $circuit_oper_value = "unknown"; $circuit_admin_value = "unknown"; } } else { $circuit_time = $time->getTime(); } my $link = perfSONAR_PS::Status::Link->new(q{}, q{}, $circuit_time, $circuit_time, $circuit_oper_value, $circuit_admin_value); push @data_points, $link; } $circuit_status{$circuit_name} = \@data_points; } my $doc = perfSONAR_PS::XML::Document_string->new(); startParameters($doc, "params.0"); addParameter($doc, "DomainName", $self->{DOMAIN}); endParameters($doc); $self->outputResults($doc, \%circuit_status, $time); if (not defined $time and defined $self->{CONF}->{"circuitstatus"}->{"cache_length"} and $self->{CONF}->{"circuitstatus"}->{"cache_length"} > 0) { $self->{LOGGER}->debug("Caching results in ".$self->{CONF}->{"circuitstatus"}->{"cache_file"}); unlink($self->{CONF}->{"circuitstatus"}->{"cache_file"}); if (sysopen(CACHEFILE, $self->{CONF}->{"circuitstatus"}->{"cache_file"}, O_WRONLY | O_CREAT, 0600)) { flock CACHEFILE, LOCK_EX; print CACHEFILE $doc->getValue(); close CACHEFILE; } else { $self->{LOGGER}->warn("Unable to cache results"); } } $output->addOpaque($doc->getValue()); return; } sub outputResults { my ($self, $output, $results, $time) = @_; my %output_endpoints = (); my $i = 0; foreach my $circuit_name (keys %{ $results }) { my $circuit = $self->{CIRCUITS}->{$circuit_name}; foreach my $endpoint (@{ $circuit->{"endpoints"} }) { next if (not defined $self->{NODES}->{$endpoint->{name}}); next if (defined $output_endpoints{$endpoint->{name}}); startMetadata($output, "metadata.".genuid(), q{}, undef); $output->startElement(prefix => "nmwg", tag => "subject", namespace => "http://ggf.org/ns/nmwg/base/2.0/", attributes => { id => "sub-".$endpoint->{name} }); $self->outputNodeElement($output, $self->{NODES}->{$endpoint->{name}}); $output->endElement("subject"); endMetadata($output); $output_endpoints{$endpoint->{name}} = 1; } my $mdid = "metadata.".genuid(); startMetadata($output, $mdid, q{}, undef); $output->startElement(prefix => "nmwg", tag => "subject", namespace => "http://ggf.org/ns/nmwg/base/2.0/", attributes => { id => "sub$i" }); $self->outputCircuitElement($output, $circuit); $output->endElement("subject"); endMetadata($output); my @data = @{ $results->{$circuit_name} }; startData($output, "data.$i", $mdid, undef); foreach my $datum (@data) { my %attrs = (); $attrs{"timeType"} = "unix"; $attrs{"timeValue"} = $datum->getEndTime(); if (defined $time and $time ne "point") { $attrs{"startTime"} = $datum->getStartTime(); $attrs{"endTime"} = $datum->getEndTime(); } $output->startElement(prefix => "ifevt", tag => "datum", namespace => "http://ggf.org/ns/nmwg/event/status/base/2.0/", attributes => \%attrs); $output->createElement(prefix => "ifevt", tag => "stateAdmin", namespace => "http://ggf.org/ns/nmwg/event/status/base/2.0/", content => $datum->getAdminStatus); $output->createElement(prefix => "ifevt", tag => "stateOper", namespace => "http://ggf.org/ns/nmwg/event/status/base/2.0/", content => $datum->getOperStatus); $output->endElement("datum"); } endData($output); $i++; } return; } sub outputNodeElement { my ($self, $output, $node) = @_; $output->startElement(prefix => "nmwgtopo3", tag => "node", namespace => "http://ggf.org/ns/nmwg/topology/base/3.0/", attributes => { id => $self->{DOMAIN}."-".$node->{"name"} }); $output->createElement(prefix => "nmwgtopo3", tag => "type", namespace => "http://ggf.org/ns/nmwg/topology/base/3.0/", attributes => { type => "logical" }, content => "TopologyPoint"); $output->createElement(prefix => "nmwgtopo3", tag => "name", namespace => "http://ggf.org/ns/nmwg/topology/base/3.0/", attributes => { type => "logical" }, content => $node->{"name"}); if (defined $node->{"city"} and $node->{"city"} ne q{}) { $output->createElement(prefix => "nmwgtopo3", tag => "city", namespace => "http://ggf.org/ns/nmwg/topology/base/3.0/", content => $node->{"city"}); } if (defined $node->{"country"} and $node->{"country"} ne q{}) { $output->createElement(prefix => "nmwgtopo3", tag => "country", namespace => "http://ggf.org/ns/nmwg/topology/base/3.0/", content => $node->{"country"}); } if (defined $node->{"latitude"} and $node->{"latitude"} ne q{}) { $output->createElement(prefix => "nmwgtopo3", tag => "latitude", namespace => "http://ggf.org/ns/nmwg/topology/base/3.0/", content => $node->{"latitude"}); } if (defined $node->{"longitude"} and $node->{"longitude"} ne q{}) { $output->createElement(prefix => "nmwgtopo3", tag => "longitude", namespace => "http://ggf.org/ns/nmwg/topology/base/3.0/", content => $node->{"longitude"}); } if (defined $node->{"institution"} and $node->{"institution"} ne q{}) { $output->createElement(prefix => "nmwgtopo3", tag => "institution", namespace => "http://ggf.org/ns/nmwg/topology/base/3.0/", , content => $node->{"institution"}); } $output->endElement("node"); return; } sub outputCircuitElement { my ($self, $output, $circuit) = @_; $output->startElement(prefix => "nmtl2", tag => "link", namespace => "http://ggf.org/ns/nmwg/topology/l2/3.0/"); $output->createElement(prefix => "nmtl2", tag => "name", namespace => "http://ggf.org/ns/nmwg/topology/l2/3.0/", attributes => { type => "logical" }, content => $circuit->{"name"}); $output->createElement(prefix => "nmtl2", tag => "globalName", namespace => "http://ggf.org/ns/nmwg/topology/l2/3.0/", attributes => { type => "logical" }, content => $circuit->{"globalName"}); $output->createElement(prefix => "nmtl2", tag => "type", namespace => "http://ggf.org/ns/nmwg/topology/l2/3.0/", content => $circuit->{"type"}); foreach my $endpoint (@{ $circuit->{"endpoints"} }) { $output->startElement(prefix => "nmwgtopo3", tag => "node", namespace => "http://ggf.org/ns/nmwg/topology/base/3.0/", attributes => { nodeIdRef => $self->{DOMAIN}."-".$endpoint->{"name"} }); $output->createElement(prefix => "nmwgtopo3", tag => "role", namespace => "http://ggf.org/ns/nmwg/topology/base/3.0/", content => $endpoint->{"type"}); $output->endElement("node"); } # startParameters($output, "params.0"); # addParameter($output, "supportedEventType", "Path.Status"); # endParameters($output); $output->endElement("link"); return; } sub parseCircuitsFile { my ($self, $file) = @_; my %nodes = (); my %incomplete_nodes = (); my %topology_links = (); my %circuits = (); my $parser = XML::LibXML->new(); my $doc; eval { $doc = $parser->parse_file($file); }; if ($@ or not defined $doc) { my $msg = "Couldn't parse circuits file $file: $@"; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat ("error.configuration", $msg); } my $conf = $doc->documentElement; my $domain = findvalue($conf, "domain"); if (not defined $domain) { my $msg = "No domain specified in configuration"; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat ("error.configuration", $msg); } my $find_res; $find_res = find($conf, "./*[local-name()='node']", 0); if ($find_res) { foreach my $endpoint ($find_res->get_nodelist) { my $node_id = $endpoint->getAttribute("id"); my $node_type = $endpoint->getAttribute("type"); my $node_name = $endpoint->getAttribute("name"); my $city = findvalue($endpoint, "city"); my $country = findvalue($endpoint, "country"); my $longitude = findvalue($endpoint, "longitude"); my $institution = findvalue($endpoint, "institution"); my $latitude = findvalue($endpoint, "latitude"); if (not defined $node_name or $node_name eq q{}) { my $msg = "Node needs to have a name"; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat ("error.configuration", $msg); } $node_name =~ s/[^a-zA-Z0-9_]//g; $node_name = uc($node_name); if (defined $nodes{$node_name}) { my $msg = "Multiple endpoints have the name \"$node_name\""; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat ("error.configuration", $msg); } my %tmp = (); my $new_node = \%tmp; $new_node->{"id"} = $node_id if (defined $node_id and $node_id ne q{}); $new_node->{"name"} = $node_name if (defined $node_name and $node_name ne q{}); $new_node->{"city"} = $city if (defined $city and $city ne q{}); $new_node->{"country"} = $country if (defined $country and $country ne q{}); $new_node->{"longitude"} = $longitude if (defined $longitude and $longitude ne q{}); $new_node->{"latitude"} = $latitude if (defined $latitude and $latitude ne q{}); $new_node->{"institution"} = $institution if (defined $institution and $institution ne q{}); if (defined $node_id and (not defined $city or not defined $country or not defined $longitude or not defined $latitude or not defined $institution)) { $incomplete_nodes{$node_id} = $new_node; } $nodes{$node_name} = $new_node; } } $find_res = find($conf, "./*[local-name()='circuit']", 0); if ($find_res) { foreach my $circuit ($find_res->get_nodelist) { my $global_name = findvalue($circuit, "globalName"); my $local_name = findvalue($circuit, "localName"); my $knowledge = $circuit->getAttribute("knowledge"); my $circuit_type; if (not defined $global_name or $global_name eq q{}) { my $msg = "Circuit has no global name"; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat ("error.configuration", $msg); } if (not defined $knowledge or $knowledge eq q{}) { $self->{LOGGER}->warn("Don't know the knowledge level of circuit \"$global_name\". Assuming full"); $knowledge = "full"; } else { $knowledge = lc($knowledge); } if (not defined $local_name or $local_name eq q{}) { $local_name = $global_name; } my %sublinks = (); $find_res = find($circuit, "./*[local-name()='linkID']", 0); if ($find_res) { foreach my $topo_id ($find_res->get_nodelist) { my $id = $topo_id->textContent; if (defined $sublinks{$id}) { my $msg = "Link $id appears multiple times in circuit $global_name"; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat ("error.configuration", $msg); } $sublinks{$id} = q{}; $topology_links{$id} = q{}; } } my @endpoints = (); my $num_endpoints = 0; my $prev_domain; $find_res = find($circuit, "./*[local-name()='endpoint']", 0); if ($find_res) { foreach my $endpoint ($find_res->get_nodelist) { my $node_type = $endpoint->getAttribute("type"); my $node_name = $endpoint->getAttribute("name"); if (not defined $node_type or $node_type eq q{}) { my $msg = "Node with unspecified type found"; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat ("error.configuration", $msg); } if (not defined $node_name or $node_name eq q{}) { my $msg = "Endpint needs to specify a node name"; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat ("error.configuration", $msg); } $node_name =~ s/[^a-zA-Z0-9_]//g; $node_name = uc($node_name); if (lc($node_type) ne "demarcpoint" and lc($node_type) ne "endpoint") { my $msg = "Node found with invalid type $node_type. Must be \"DemarcPoint\" or \"EndPoint\""; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat ("error.configuration", $msg); } my ($domain, @junk) = split(/-/, $node_name); if (not defined $prev_domain) { $prev_domain = $domain; } elsif ($domain eq $prev_domain) { $circuit_type = "DOMAIN_Link"; } else { if ($knowledge eq "full") { $circuit_type = "ID_Link"; } else { $circuit_type = "ID_LinkPartialInfo"; } } my %new_endpoint = (); $new_endpoint{"type"} = $node_type; $new_endpoint{"name"} = $node_name; push @endpoints, \%new_endpoint; $num_endpoints++; } } if ($num_endpoints != 2) { my $msg = "Invalid number of endpoints, $num_endpoints, must be 2"; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat ("error.configuration", $msg); } my @sublinks = keys %sublinks; my %new_circuit = (); $new_circuit{"globalName"} = $global_name; $new_circuit{"name"} = $local_name; $new_circuit{"sublinks"} = \@sublinks; $new_circuit{"endpoints"} = \@endpoints; $new_circuit{"type"} = $circuit_type; if (defined $circuits{$local_name}) { my $msg = "Error: existing circuit of name $local_name"; $self->{LOGGER}->error($msg); throw perfSONAR_PS::Error_compat ("error.configuration", $msg); } else { $circuits{$local_name} = \%new_circuit; } } } return ($domain, \%circuits, \%incomplete_nodes, \%topology_links, \%nodes); } sub parseTopology { my ($self, $topology, $incomplete_nodes, $domain_name) = @_; my %ids = (); foreach my $node ($topology->getElementsByLocalName("node")) { my $id = $node->getAttribute("id"); $self->{LOGGER}->debug("node: ".$id); next if not defined $incomplete_nodes->{$id}; $self->{LOGGER}->debug("found node ".$id." in here"); my $longitude = findvalue($node, "./*[local-name()='longitude']"); $self->{LOGGER}->debug("searched for longitude"); my $institution = findvalue($node, "./*[local-name()='institution']"); $self->{LOGGER}->debug("searched for institution"); my $latitude = findvalue($node, "./*[local-name()='latitude']"); $self->{LOGGER}->debug("searched for latitude"); my $city = findvalue($node, "./*[local-name()='city']"); $self->{LOGGER}->debug("searched for city"); my $country = findvalue($node, "./*[local-name()='country']"); $self->{LOGGER}->debug("searched for country"); $incomplete_nodes->{$id}->{"type"} = "TopologyPoint"; if (not defined $incomplete_nodes->{$id}->{"longitude"} and defined $longitude and $longitude ne q{}) { # conversions may need to be made $incomplete_nodes->{$id}->{"longitude"} = $longitude; } if (not defined $incomplete_nodes->{$id}->{"latitude"} and defined $latitude and $latitude ne q{}) { # conversions may need to be made $incomplete_nodes->{$id}->{"latitude"} = $latitude; } if (not defined $incomplete_nodes->{$id}->{"institution"}) { if ( defined $institution and $institution ne q{}) { # conversions may need to be made $incomplete_nodes->{$id}->{"institution"} = $institution; } else { $incomplete_nodes->{$id}->{"institution"} = $domain_name; } } if (not defined $incomplete_nodes->{$id}->{"city"} and defined $city and $city ne q{}) { $incomplete_nodes->{$id}->{"city"} = $city; } if (not defined $incomplete_nodes->{$id}->{"country"} and defined $country and $country ne q{}) { $incomplete_nodes->{$id}->{"country"} = $country; } } return ("", undef); } 1; __END__
# vim: expandtab shiftwidth=4 tabstop=4