package perfSONAR_PS::Client::LS::Remote;
=head1 NAME
perfSONAR_PS::Client::LS::Remote - A module that provides a client API for an LS
=head1 DESCRIPTION
This module aims to offer simple methods for dealing with requests for information, and the
related tasks of interacting with backend storage.
=head1 SYNOPSIS
use perfSONAR_PS::Client::LS::Remote;
my %conf = ();
$conf{"SERVICE_ACCESSPOINT"} = "http://someorganization.org:8080/perfSONAR_PS/services/service";
$conf{"SERVICE_NAME"} = "Some Organization's Service MA"
$conf{"SERVICE_TYPE"} = "MA"
$conf{"SERVICE_DESCRIPTION"} = "Service MA"
my $ls = "http://someorganization.org:8080/perfSONAR_PS/services/LS";
my $ls_client = perfSONAR_PS::Client::LS::Remote->new($ls, \%conf, \%ns);
# or
# $ls_client = perfSONAR_PS::Client::LS::Remote->new;
# $ls_client->setURI($ls);
# $ls_client->setConf(\%conf);
# $ls_client->setNamespaces(\%ns);
$ls_client->registerStatic(\@data);
$ls_client->sendKeepalive($conf{"SERVICE_ACCESSPOINT"});
$ls_client->sendDeregister($conf{"SERVICE_ACCESSPOINT"});
my $ls2 = "http://otherorganization.org:8080/perfSONAR_PS/services/LS";
my $ls_client2 = perfSONAR_PS::Client::LS::Remote->new($ls2);
my %queries = ();
$queries{"req1"} = "";
$queries{"req1"} .= "declare namespace nmwg=\"http://ggf.org/ns/nmwg/base/2.0/\";\n";
$queries{"req1"} .= "for \$data in /nmwg:store/nmwg:data\n";
$queries{"req1"} .= " let \$metadata_id := \$data/\@metadataIdRef\n";
$queries{"req1"} .= " where \$data//*:link[\@id=\"link1\"] and \$data//nmwg:eventType[text()=\"http://ggf.org/ns/nmwg/characteristic/link/status/20070809\"]\n";
$queries{"req1"} .= " return /nmwg:store/nmwg:metadata[\@id=\$metadata_id]\n";
$queries{"req2"} = "";
$queries{"req2"} .= "declare namespace nmwg=\"http://ggf.org/ns/nmwg/base/2.0/\";\n";
$queries{"req2"} .= "for \$data in /nmwg:store/nmwg:data\n";
$queries{"req2"} .= " let \$metadata_id := \$data/\@metadataIdRef\n";
$queries{"req2"} .= " where \$data//*:link[\@id=\"link2\"] and \$data//nmwg:eventType[text()=\"http://ggf.org/ns/nmwg/characteristic/link/status/20070809\"]\n";
$queries{"req2"} .= " return /nmwg:store/nmwg:metadata[\@id=\$metadata_id]\n";
my ($status, $res) = $ls_client2->query(\%queries);
if ($status != 0 or not defined $res{"req1"} or not defined $res{"req2"}) {
print "Error: querying $ls2 failed\n";
exit(-1);
}
my ($query_status, $query_res);
($query_status, $query_res) = $res{"req1"};
if ($query_status != 0) {
print "Couldn't get information on query req1: ".$query_res."\n";
exit(-1);
} else {
print "Results for res1: ".$query_res->toString()."\n";
}
($query_status, $query_res) = $res{"req2"};
if ($query_status != 0) {
print "Couldn't get information on query req2: ".$query_res."\n";
exit(-1);
} else {
print "Results for res1: ".$query_res->toString()."\n";
}
=cut
use fields 'URI', 'CONF', 'CHUNK', 'ALIVE', 'FIRST';
use strict;
use warnings;
use Log::Log4perl qw(get_logger);
use perfSONAR_PS::Common;
use perfSONAR_PS::Transport;
use perfSONAR_PS::Messages;
use perfSONAR_PS::Client::Echo;
our $VERSION = 0.08;
=head1 API
The offered API is simple, but offers the key functions we need in a measurement archive.
=head2 new ($package, $uri, \%conf)
The parameters are the URI of the Lookup Service, a %conf describing the service for registration purposes.
The %conf can have 4 keys in it:
SERVICE_NAME - The name of the service registering data
SERVICE_ACCESSPOINT - The URL for the service registering data
SERVICE_TYPE - The type (MA, LS, etc) of the service registering data
SERVICE_DESCRIPTION - A description of the service registering data
=cut
sub new {
my ($package, $uri, $conf) = @_;
my $self = fields::new($package);
$self->{URI} = $uri;
if(defined $conf and $conf ne "") {
$self->{CONF} = \%{$conf};
}
$self->{CHUNK} = 50;
$self->{ALIVE} = 0;
$self->{FIRST} = 1;
return $self;
}
=head2 setURI ($self, $uri)
(Re-)Sets the value for the LS URI.
=cut
sub setURI {
my ($self, $uri) = @_;
my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
if(defined $uri and $uri ne "") {
$self->{URI} = $uri;
}
else {
$logger->error("Missing argument.");
}
return;
}
=head2 setConf ($self, \%conf)
(Re-)Sets the value for the 'conf' hash.
=cut
sub setConf {
my ($self, $conf) = @_;
my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
if(defined $conf and $conf ne "") {
$self->{CONF} = \%{$conf};
}
else {
$logger->error("Missing argument.");
}
return;
}
=head2 createKey ($self, $key)
Creates a 'key' value that is used to access the LS.
=cut
sub createKey {
my($self, $lsKey) = @_;
my $key = " \n";
$key = $key . " \n";
if (defined $lsKey and $lsKey ne "") {
$key = $key . " ".$lsKey."\n";
} else {
$key = $key . " ".$self->{CONF}->{"SERVICE_ACCESSPOINT"}."\n";
}
$key = $key . " \n";
$key = $key . " \n";
return $key;
}
=head2 createService ($self)
Creates the 'service' subject (description of the service) for LS registration.
=cut
sub createService {
my($self) = @_;
my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
my $service = " \n";
$service = $service . " \n";
$service = $service . " ".$self->{CONF}->{"SERVICE_NAME"}."\n" if (defined $self->{CONF}->{"SERVICE_NAME"});
$service = $service . " ".$self->{CONF}->{"SERVICE_ACCESSPOINT"}."\n" if (defined $self->{CONF}->{"SERVICE_ACCESSPOINT"});
$service = $service . " ".$self->{CONF}->{"SERVICE_TYPE"}."\n" if (defined $self->{CONF}->{"SERVICE_TYPE"});
$service = $service . " ".$self->{CONF}->{"SERVICE_DESCRIPTION"}."\n" if (defined $self->{CONF}->{"SERVICE_DESCRIPTION"});
$service = $service . " \n";
$service = $service . " \n";
return $service;
}
=head2 callLS ($self, $sender, $message)
Given a message and a sender, contact an LS and parse the results.
=cut
sub callLS {
my($self, $sender, $message) = @_;
my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
my $error;
my $responseContent = $sender->sendReceive(makeEnvelope($message), "", \$error);
if($error ne "") {
$logger->error("sendReceive failed: $error");
return -1;
}
my $parser = XML::LibXML->new();
if(defined $responseContent and $responseContent ne "" and
not ($responseContent =~ m/^\d+/x)) {
my $doc = "";
eval {
$doc = $parser->parse_string($responseContent);
};
if($@) {
$logger->error("Parser failed: ".$@);
return -1;
}
else {
my $msg = $doc->getDocumentElement->getElementsByTagNameNS("http://ggf.org/ns/nmwg/base/2.0/", "message")->get_node(1);
if($msg) {
my $eventType = findvalue($msg, "./nmwg:metadata/nmwg:eventType");
if(defined $eventType and $eventType =~ m/success/x) {
return 0;
}
}
}
}
return -1;
}
=head2 sendDeregister ($self, $key)
Deregisters the data with the specified key
=cut
sub sendDeregister {
my ($self, $key) = @_;
if (not defined $self->{URI}) {
return -1;
}
my ($host, $port, $endpoint) = &perfSONAR_PS::Transport::splitURI($self->{URI});
if (not defined $host and not defined $port and not defined $endpoint) {
return -1;
}
my $sender = new perfSONAR_PS::Transport($host, $port, $endpoint);
my $doc = perfSONAR_PS::XML::Document_string->new();
startMessage($doc, "message.".genuid(), "", "LSDeregisterRequest", "", {perfsonar=>"http://ggf.org/ns/nmwg/tools/org/perfsonar/1.0/", psservice=>"http://ggf.org/ns/nmwg/tools/org/perfsonar/service/1.0/"});
my $mdID = "metadata.".genuid();
createMetadata($doc, $mdID, "", createKey($self, $key), undef);
createData($doc, "data.".genuid(), $mdID, "", undef);
endMessage($doc);
return callLS($self, $sender, $doc->getValue());
}
=head2 sendKeepalive ($self, $key)
Sends a keepalive message for the data with the specified key
=cut
sub sendKeepalive {
my ($self, $key) = @_;
if (not defined $self->{URI}) {
return -1;
}
my ($host, $port, $endpoint) = &perfSONAR_PS::Transport::splitURI($self->{URI});
if (not defined $host and not defined $port and not defined $endpoint) {
return -1;
}
my $sender = new perfSONAR_PS::Transport($host, $port, $endpoint);
my $doc = perfSONAR_PS::XML::Document_string->new();
startMessage($doc, "message.".genuid(), "", "LSKeepaliveRequest", "", {perfsonar=>"http://ggf.org/ns/nmwg/tools/org/perfsonar/1.0/", psservice=>"http://ggf.org/ns/nmwg/tools/org/perfsonar/service/1.0/"});
my $mdID = "metadata.".genuid();
createMetadata($doc, $mdID, "", createKey($self, $key), undef);
createData($doc, "data.".genuid(), $mdID, "", undef);
endMessage($doc);
return callLS($self, $sender, $doc->getValue());
}
=head2 registerStatic ($self, \@data_ref)
Performs registration of 'static' data with an LS. Static in this sense
indicates that the data in the underlying storage DOES NOT change. This
function uses special messages that intend to simply keep the data alive,
not worrying at all if something comes in that is new or goes away that is
old.
=cut
sub registerStatic {
my($self, $data_ref) = @_;
my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
if (not defined $self->{URI}) {
return -1;
}
if(!$self->{ALIVE}) {
my $echo_service = perfSONAR_PS::Client::Echo->new($self->{URI});
my ($status, $res) = $echo_service->ping();
if ($status == -1) {
$logger->error("Ping to ".$self->{URI}." failed: $res");
return -1;
}
$self->{ALIVE} = 1;
}
if($self->{FIRST}) {
if ($self->sendDeregister($self->{CONF}->{"SERVICE_ACCESSPOINT"}) == 0) {
$logger->debug("Nothing registered.");
}
else {
$logger->debug("Removed old registration.");
}
my @resultsString = ();
@resultsString = @{$data_ref};
if($#resultsString != -1) {
my ($status, $res) = $self->__register(createService($self), $data_ref);
if ($status == -1) {
$logger->error("Unable to register data with LS.");
$self->{ALIVE} = 0;
}
}
}
else {
if ($self->sendKeepalive() == -1) {
my @resultsString = ();
@resultsString = @{$data_ref};
if($#resultsString != -1) {
my ($status, $res) = $self->__register(createService($self), $data_ref);
if ($status == -1) {
$logger->error("Unable to register data with LS.");
$self->{ALIVE} = 0;
return -1;
}
}
}
}
$self->{FIRST} = 0 if $self->{FIRST};
return 0;
}
=head2 __register ($self, $subject, $data_ref)
Performs the actual data registration. Unlike the above registration
functions, this function does not try to perform any of the
keepalive/deregister registration tricks. It simply registers the specified
data. As part of the registration, it splits the data into chunks and
registers each independently.
=cut
sub __register {
my ($self, $subject, $data_ref) = @_;
my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
if (not defined $self->{URI}) {
return -1
}
my ($host, $port, $endpoint) = &perfSONAR_PS::Transport::splitURI($self->{URI});
if (not defined $host and not defined $port and not defined $endpoint) {
return -1
}
my $sender = new perfSONAR_PS::Transport($host, $port, $endpoint);
my @data = @{ $data_ref };
my $iterations = int((($#data+1)/$self->{CHUNK}));
my $x = 0;
for(my $y = 1; $y <= ($iterations+1); $y++) {
my $doc = perfSONAR_PS::XML::Document_string->new();
startMessage($doc, "message.".genuid(), "", "LSRegisterRequest", "", {perfsonar=>"http://ggf.org/ns/nmwg/tools/org/perfsonar/1.0/", psservice=>"http://ggf.org/ns/nmwg/tools/org/perfsonar/service/1.0/"});
my $mdID = "metadata.".genuid();
createMetadata($doc, $mdID, "", createService($self), undef);
for(; $x < ($y*$self->{CHUNK}) and $x <= $#data; $x++) {
createData($doc, "data.".genuid(), $mdID, $data[$x], undef);
}
endMessage($doc);
unless(callLS($self, $sender, $doc->getValue()) == 0) {
$logger->error("Unable to register data with LS.");
return -1;
}
}
return 0;
}
=head2 registerDynamic ($self, \@data_ref)
Performs registration of 'dynamic' data with an LS. Dynamic in this sense
indicates that the data in the underlying storage DOES change. This
function uses special messages that will remove all old data and insert
everything brand new with each registration.
=cut
sub registerDynamic {
my($self, $data_ref) = @_;
my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
if (not defined $self->{URI}) {
return -1;
}
if(!$self->{ALIVE}) {
my $echo_service = perfSONAR_PS::Client::Echo->new($self->{URI});
my ($status, $res) = $echo_service->ping();
if ($status == -1) {
$logger->error("Ping to ".$self->{URI}." failed: $res");
return -1;
}
$self->{ALIVE} = 1;
}
if($self->{FIRST}) {
if ($self->sendDeregister($self->{CONF}->{"SERVICE_ACCESSPOINT"}) == 0) {
$logger->debug("Nothing registered.");
}
else {
$logger->debug("Removed old registration.");
}
my @resultsString = @{$data_ref};
if($#resultsString != -1) {
if ($self->__register(createService($self), $data_ref) == -1) {
$logger->error("Unable to register data with LS.");
$self->{ALIVE} = 0;
}
}
} else {
my @resultsString = @{$data_ref};
my $subject = "";
if ($self->sendKeepalive() == -1) {
$subject = createService($self);
}
else {
$subject = createKey($self, $self->{CONF}->{SERVICE_ACCESSPOINT})."\n".createService($self);
}
if($#resultsString != -1) {
if ($self->__register($subject, $data_ref) == -1) {
$logger->error("Unable to register data with LS.");
$self->{ALIVE} = 0;
return -1;
}
}
}
$self->{FIRST} = 0 if ($self->{FIRST});
return 0;
}
=head2 query ($self, \%queries)
This function sends the specified queries to the LS and returns the
results. The queries are given as a hash table with each key/value pair
being an identifier/a query. Each query gets executed and the returned
value is a hash containing the same identifiers as keys, but instead of
pointing to queries, they point to an array containing a status and a
result. The status is either 0 or -1. If it's 0, the result is a pointer to
the data element. If it's -1, the result is the error message.
=cut
sub query {
my ($self, $queries) = @_;
my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
if (not defined $self->{URI}) {
return -1;
}
my ($host, $port, $endpoint) = &perfSONAR_PS::Transport::splitURI($self->{URI});
if (not defined $host and not defined $port and not defined $endpoint) {
return -1;
}
my $request = "";
$request .= "\n";
foreach my $query_id (keys %{ $queries }) {
$request .= " \n";
$request .= " \n";
$request .= $queries->{$query_id};
$request .= " \n";
$request .= " http://ggf.org/ns/nmwg/tools/org/perfsonar/service/lookup/xquery/1.0\n";
$request .= " \n";
$request .= " native\n";
$request .= " \n";
$request .= " \n";
$request .= " \n";
}
$request .= "\n";
my ($status, $res) = consultArchive($host, $port, $endpoint, $request);
if ($status != 0) {
my $msg = "Error consulting LS: $res";
$logger->error($msg);
return -1;
}
$logger->debug("Response: ".$res->toString);
my %ret_structure = ();
foreach my $d ($res->getChildrenByTagName("nmwg:data")) {
foreach my $m ($res->getChildrenByTagName("nmwg:metadata")) {
my $md_id = $m->getAttribute("id");
my $md_idref = $m->getAttribute("metadataIdRef");
my $d_idref = $d->getAttribute("metadataIdRef");
if($md_id eq $d_idref) {
my $query_id;
my $eventType = findvalue($m, "nmwg:eventType");
if (defined $md_idref and $md_idref =~ /perfsonar_ps\.meta\.(.*)/x) {
$query_id = $1;
} elsif ($md_id =~ /perfsonar_ps\.meta\.(.*)/x) {
$query_id = $1;
} else {
my $msg = "Received unknown response: $md_id/$md_idref";
$logger->error($msg);
next;
}
my @retval;
if (defined $eventType and $eventType =~ /^error\./x) {
my $error_msg = findvalue($d, "./nmwgr:datum");
$error_msg = "Unknown error" if (not defined $error_msg or $error_msg eq "");
@retval = (-1, $error_msg);
} else {
@retval = (0, $d);
}
$ret_structure{$query_id} = \@retval;
}
}
}
return (0, \%ret_structure);
}
1;
__END__
=head1 SEE ALSO
L, L, L,
L, L
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. Bugs,
feature requests, and improvements can be directed here:
https://bugs.internet2.edu/jira/browse/PSPS
=head1 VERSION
$Id$
=head1 AUTHOR
Aaron Brown, aaron@internet2.edu
Jason Zurawski, zurawski@internet2.edu
=head1 LICENSE
You should have received a copy of the Internet2 Intellectual Property Framework along
with this software. If not, see
=head1 COPYRIGHT
Copyright (c) 2004-2008, Internet2 and the University of Delaware
All rights reserved.
=cut
# vim: expandtab shiftwidth=4 tabstop=4