File Coverage

blib/lib/perfSONAR_PS/Client/LS/Remote.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package perfSONAR_PS::Client::LS::Remote;
2              
3             =head1 NAME
4              
5             perfSONAR_PS::Client::LS::Remote - A module that provides a client API for an LS
6              
7             =head1 DESCRIPTION
8              
9             This module aims to offer simple methods for dealing with requests for information, and the
10             related tasks of interacting with backend storage.
11              
12             =head1 SYNOPSIS
13              
14             use perfSONAR_PS::Client::LS::Remote;
15              
16             my %conf = ();
17             $conf{"SERVICE_ACCESSPOINT"} = "http://someorganization.org:8080/perfSONAR_PS/services/service";
18             $conf{"SERVICE_NAME"} = "Some Organization's Service MA"
19             $conf{"SERVICE_TYPE"} = "MA"
20             $conf{"SERVICE_DESCRIPTION"} = "Service MA"
21              
22             my $ls = "http://someorganization.org:8080/perfSONAR_PS/services/LS";
23              
24             my $ls_client = perfSONAR_PS::Client::LS::Remote->new($ls, \%conf, \%ns);
25              
26             # or
27             # $ls_client = perfSONAR_PS::Client::LS::Remote->new;
28             # $ls_client->setURI($ls);
29             # $ls_client->setConf(\%conf);
30             # $ls_client->setNamespaces(\%ns);
31              
32             $ls_client->registerStatic(\@data);
33              
34             $ls_client->sendKeepalive($conf{"SERVICE_ACCESSPOINT"});
35              
36             $ls_client->sendDeregister($conf{"SERVICE_ACCESSPOINT"});
37              
38             my $ls2 = "http://otherorganization.org:8080/perfSONAR_PS/services/LS";
39              
40             my $ls_client2 = perfSONAR_PS::Client::LS::Remote->new($ls2);
41              
42             my %queries = ();
43              
44             $queries{"req1"} = "";
45             $queries{"req1"} .= "declare namespace nmwg=\"http://ggf.org/ns/nmwg/base/2.0/\";\n";
46             $queries{"req1"} .= "for \$data in /nmwg:store/nmwg:data\n";
47             $queries{"req1"} .= " let \$metadata_id := \$data/\@metadataIdRef\n";
48             $queries{"req1"} .= " where \$data//*:link[\@id=\"link1\"] and \$data//nmwg:eventType[text()=\"http://ggf.org/ns/nmwg/characteristic/link/status/20070809\"]\n";
49             $queries{"req1"} .= " return /nmwg:store/nmwg:metadata[\@id=\$metadata_id]\n";
50              
51             $queries{"req2"} = "";
52             $queries{"req2"} .= "declare namespace nmwg=\"http://ggf.org/ns/nmwg/base/2.0/\";\n";
53             $queries{"req2"} .= "for \$data in /nmwg:store/nmwg:data\n";
54             $queries{"req2"} .= " let \$metadata_id := \$data/\@metadataIdRef\n";
55             $queries{"req2"} .= " where \$data//*:link[\@id=\"link2\"] and \$data//nmwg:eventType[text()=\"http://ggf.org/ns/nmwg/characteristic/link/status/20070809\"]\n";
56             $queries{"req2"} .= " return /nmwg:store/nmwg:metadata[\@id=\$metadata_id]\n";
57              
58             my ($status, $res) = $ls_client2->query(\%queries);
59             if ($status != 0 or not defined $res{"req1"} or not defined $res{"req2"}) {
60             print "Error: querying $ls2 failed\n";
61             exit(-1);
62             }
63              
64             my ($query_status, $query_res);
65              
66             ($query_status, $query_res) = $res{"req1"};
67              
68             if ($query_status != 0) {
69             print "Couldn't get information on query req1: ".$query_res."\n";
70             exit(-1);
71             } else {
72             print "Results for res1: ".$query_res->toString()."\n";
73             }
74              
75             ($query_status, $query_res) = $res{"req2"};
76              
77             if ($query_status != 0) {
78             print "Couldn't get information on query req2: ".$query_res."\n";
79             exit(-1);
80             } else {
81             print "Results for res1: ".$query_res->toString()."\n";
82             }
83              
84             =cut
85              
86 1     1   57855 use fields 'URI', 'CONF', 'CHUNK', 'ALIVE', 'FIRST';
  1         1557  
  1         8  
87              
88 1     1   73 use strict;
  1         3  
  1         25  
89 1     1   5 use warnings;
  1         6  
  1         32  
90 1     1   1317 use Log::Log4perl qw(get_logger);
  1         58049  
  1         7  
91 1     1   1994 use perfSONAR_PS::Common;
  0            
  0            
92             use perfSONAR_PS::Transport;
93             use perfSONAR_PS::Messages;
94             use perfSONAR_PS::Client::Echo;
95              
96             our $VERSION = 0.09;
97              
98             =head1 API
99              
100             The offered API is simple, but offers the key functions we need in a measurement archive.
101              
102             =head2 new ($package, $uri, \%conf)
103              
104             The parameters are the URI of the Lookup Service, a %conf describing the service for registration purposes.
105              
106             The %conf can have 4 keys in it:
107              
108             SERVICE_NAME - The name of the service registering data
109             SERVICE_ACCESSPOINT - The URL for the service registering data
110             SERVICE_TYPE - The type (MA, LS, etc) of the service registering data
111             SERVICE_DESCRIPTION - A description of the service registering data
112              
113             =cut
114              
115             sub new {
116             my ($package, $uri, $conf) = @_;
117              
118             my $self = fields::new($package);
119              
120             $self->{URI} = $uri;
121              
122             if(defined $conf and $conf ne "") {
123             $self->{CONF} = \%{$conf};
124             }
125              
126             $self->{CHUNK} = 50;
127              
128             $self->{ALIVE} = 0;
129             $self->{FIRST} = 1;
130              
131             return $self;
132             }
133              
134             =head2 setURI ($self, $uri)
135             (Re-)Sets the value for the LS URI.
136             =cut
137              
138             sub setURI {
139             my ($self, $uri) = @_;
140             my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
141              
142             if(defined $uri and $uri ne "") {
143             $self->{URI} = $uri;
144             }
145             else {
146             $logger->error("Missing argument.");
147             }
148             return;
149              
150             }
151              
152             =head2 setConf ($self, \%conf)
153             (Re-)Sets the value for the 'conf' hash.
154             =cut
155             sub setConf {
156             my ($self, $conf) = @_;
157             my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
158              
159             if(defined $conf and $conf ne "") {
160             $self->{CONF} = \%{$conf};
161             }
162             else {
163             $logger->error("Missing argument.");
164             }
165             return;
166             }
167              
168             =head2 createKey ($self, $key)
169             Creates a 'key' value that is used to access the LS.
170             =cut
171             sub createKey {
172             my($self, $lsKey) = @_;
173             my $key = " \n";
174             $key = $key . " \n";
175             if (defined $lsKey and $lsKey ne "") {
176             $key = $key . " ".$lsKey."\n";
177             } else {
178             $key = $key . " ".$self->{CONF}->{"SERVICE_ACCESSPOINT"}."\n";
179             }
180             $key = $key . " \n";
181             $key = $key . " \n";
182             return $key;
183             }
184              
185             =head2 createService ($self)
186             Creates the 'service' subject (description of the service) for LS registration.
187             =cut
188             sub createService {
189             my($self) = @_;
190             my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
191             my $service = " \n";
192             $service = $service . " \n";
193             $service = $service . " ".$self->{CONF}->{"SERVICE_NAME"}."\n" if (defined $self->{CONF}->{"SERVICE_NAME"});
194             $service = $service . " ".$self->{CONF}->{"SERVICE_ACCESSPOINT"}."\n" if (defined $self->{CONF}->{"SERVICE_ACCESSPOINT"});
195             $service = $service . " ".$self->{CONF}->{"SERVICE_TYPE"}."\n" if (defined $self->{CONF}->{"SERVICE_TYPE"});
196             $service = $service . " ".$self->{CONF}->{"SERVICE_DESCRIPTION"}."\n" if (defined $self->{CONF}->{"SERVICE_DESCRIPTION"});
197             $service = $service . " \n";
198             $service = $service . " \n";
199             return $service;
200             }
201              
202             =head2 callLS ($self, $sender, $message)
203             Given a message and a sender, contact an LS and parse the results.
204             =cut
205             sub callLS {
206             my($self, $sender, $message) = @_;
207             my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
208             my $error;
209             my $responseContent = $sender->sendReceive(makeEnvelope($message), "", \$error);
210             if($error ne "") {
211             $logger->error("sendReceive failed: $error");
212             return -1;
213             }
214             my $parser = XML::LibXML->new();
215             if(defined $responseContent and $responseContent ne "" and
216             not ($responseContent =~ m/^\d+/x)) {
217             my $doc = "";
218             eval {
219             $doc = $parser->parse_string($responseContent);
220             };
221             if($@) {
222             $logger->error("Parser failed: ".$@);
223             return -1;
224             }
225             else {
226             my $msg = $doc->getDocumentElement->getElementsByTagNameNS("http://ggf.org/ns/nmwg/base/2.0/", "message")->get_node(1);
227             if($msg) {
228             my $eventType = findvalue($msg, "./nmwg:metadata/nmwg:eventType");
229             if(defined $eventType and $eventType =~ m/success/x) {
230             return 0;
231             }
232             }
233             }
234             }
235             return -1;
236             }
237              
238             =head2 sendDeregister ($self, $key)
239             Deregisters the data with the specified key
240             =cut
241             sub sendDeregister {
242             my ($self, $key) = @_;
243              
244             if (not defined $self->{URI}) {
245             return -1;
246             }
247              
248             my ($host, $port, $endpoint) = &perfSONAR_PS::Transport::splitURI($self->{URI});
249             if (not defined $host and not defined $port and not defined $endpoint) {
250             return -1;
251             }
252              
253             my $sender = new perfSONAR_PS::Transport($host, $port, $endpoint);
254              
255              
256             my $doc = perfSONAR_PS::XML::Document_string->new();
257             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/"});
258              
259             my $mdID = "metadata.".genuid();
260             createMetadata($doc, $mdID, "", createKey($self, $key), undef);
261             createData($doc, "data.".genuid(), $mdID, "", undef);
262             endMessage($doc);
263              
264             return callLS($self, $sender, $doc->getValue());
265             }
266              
267             =head2 sendKeepalive ($self, $key)
268             Sends a keepalive message for the data with the specified key
269             =cut
270             sub sendKeepalive {
271             my ($self, $key) = @_;
272              
273             if (not defined $self->{URI}) {
274             return -1;
275             }
276              
277             my ($host, $port, $endpoint) = &perfSONAR_PS::Transport::splitURI($self->{URI});
278             if (not defined $host and not defined $port and not defined $endpoint) {
279             return -1;
280             }
281              
282             my $sender = new perfSONAR_PS::Transport($host, $port, $endpoint);
283              
284              
285             my $doc = perfSONAR_PS::XML::Document_string->new();
286             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/"});
287              
288             my $mdID = "metadata.".genuid();
289             createMetadata($doc, $mdID, "", createKey($self, $key), undef);
290             createData($doc, "data.".genuid(), $mdID, "", undef);
291             endMessage($doc);
292              
293             return callLS($self, $sender, $doc->getValue());
294             }
295              
296             =head2 registerStatic ($self, \@data_ref)
297             Performs registration of 'static' data with an LS. Static in this sense
298             indicates that the data in the underlying storage DOES NOT change. This
299             function uses special messages that intend to simply keep the data alive,
300             not worrying at all if something comes in that is new or goes away that is
301             old.
302             =cut
303             sub registerStatic {
304             my($self, $data_ref) = @_;
305             my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
306              
307             if (not defined $self->{URI}) {
308             return -1;
309             }
310              
311             if(!$self->{ALIVE}) {
312             my $echo_service = perfSONAR_PS::Client::Echo->new($self->{URI});
313             my ($status, $res) = $echo_service->ping();
314             if ($status == -1) {
315             $logger->error("Ping to ".$self->{URI}." failed: $res");
316             return -1;
317             }
318             $self->{ALIVE} = 1;
319             }
320              
321             if($self->{FIRST}) {
322             if ($self->sendDeregister($self->{CONF}->{"SERVICE_ACCESSPOINT"}) == 0) {
323             $logger->debug("Nothing registered.");
324             }
325             else {
326             $logger->debug("Removed old registration.");
327             }
328              
329             my @resultsString = ();
330              
331             @resultsString = @{$data_ref};
332              
333             if($#resultsString != -1) {
334             my ($status, $res) = $self->__register(createService($self), $data_ref);
335             if ($status == -1) {
336             $logger->error("Unable to register data with LS.");
337             $self->{ALIVE} = 0;
338             }
339             }
340             }
341             else {
342             if ($self->sendKeepalive() == -1) {
343             my @resultsString = ();
344              
345             @resultsString = @{$data_ref};
346              
347             if($#resultsString != -1) {
348             my ($status, $res) = $self->__register(createService($self), $data_ref);
349             if ($status == -1) {
350             $logger->error("Unable to register data with LS.");
351             $self->{ALIVE} = 0;
352             return -1;
353             }
354             }
355             }
356             }
357              
358             $self->{FIRST} = 0 if $self->{FIRST};
359             return 0;
360             }
361              
362             =head2 __register ($self, $subject, $data_ref)
363             Performs the actual data registration. Unlike the above registration
364             functions, this function does not try to perform any of the
365             keepalive/deregister registration tricks. It simply registers the specified
366             data. As part of the registration, it splits the data into chunks and
367             registers each independently.
368             =cut
369             sub __register {
370             my ($self, $subject, $data_ref) = @_;
371             my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
372              
373             if (not defined $self->{URI}) {
374             return -1
375             }
376              
377             my ($host, $port, $endpoint) = &perfSONAR_PS::Transport::splitURI($self->{URI});
378             if (not defined $host and not defined $port and not defined $endpoint) {
379             return -1
380             }
381              
382             my $sender = new perfSONAR_PS::Transport($host, $port, $endpoint);
383              
384             my @data = @{ $data_ref };
385             my $iterations = int((($#data+1)/$self->{CHUNK}));
386             my $x = 0;
387              
388             for(my $y = 1; $y <= ($iterations+1); $y++) {
389             my $doc = perfSONAR_PS::XML::Document_string->new();
390             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/"});
391             my $mdID = "metadata.".genuid();
392             createMetadata($doc, $mdID, "", createService($self), undef);
393             for(; $x < ($y*$self->{CHUNK}) and $x <= $#data; $x++) {
394             createData($doc, "data.".genuid(), $mdID, $data[$x], undef);
395             }
396             endMessage($doc);
397             unless(callLS($self, $sender, $doc->getValue()) == 0) {
398             $logger->error("Unable to register data with LS.");
399             return -1;
400             }
401             }
402              
403             return 0;
404             }
405              
406             =head2 registerDynamic ($self, \@data_ref)
407             Performs registration of 'dynamic' data with an LS. Dynamic in this sense
408             indicates that the data in the underlying storage DOES change. This
409             function uses special messages that will remove all old data and insert
410             everything brand new with each registration.
411             =cut
412             sub registerDynamic {
413             my($self, $data_ref) = @_;
414             my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
415              
416             if (not defined $self->{URI}) {
417             return -1;
418             }
419              
420             if(!$self->{ALIVE}) {
421             my $echo_service = perfSONAR_PS::Client::Echo->new($self->{URI});
422             my ($status, $res) = $echo_service->ping();
423             if ($status == -1) {
424             $logger->error("Ping to ".$self->{URI}." failed: $res");
425             return -1;
426             }
427              
428             $self->{ALIVE} = 1;
429             }
430              
431             if($self->{FIRST}) {
432             if ($self->sendDeregister($self->{CONF}->{"SERVICE_ACCESSPOINT"}) == 0) {
433             $logger->debug("Nothing registered.");
434             }
435             else {
436             $logger->debug("Removed old registration.");
437             }
438              
439             my @resultsString = @{$data_ref};
440              
441             if($#resultsString != -1) {
442             if ($self->__register(createService($self), $data_ref) == -1) {
443             $logger->error("Unable to register data with LS.");
444             $self->{ALIVE} = 0;
445             }
446             }
447             } else {
448             my @resultsString = @{$data_ref};
449              
450             my $subject = "";
451             if ($self->sendKeepalive() == -1) {
452             $subject = createService($self);
453             }
454             else {
455             $subject = createKey($self, $self->{CONF}->{SERVICE_ACCESSPOINT})."\n".createService($self);
456             }
457              
458             if($#resultsString != -1) {
459             if ($self->__register($subject, $data_ref) == -1) {
460             $logger->error("Unable to register data with LS.");
461             $self->{ALIVE} = 0;
462             return -1;
463             }
464             }
465             }
466              
467             $self->{FIRST} = 0 if ($self->{FIRST});
468              
469             return 0;
470             }
471              
472             =head2 query ($self, \%queries)
473             This function sends the specified queries to the LS and returns the
474             results. The queries are given as a hash table with each key/value pair
475             being an identifier/a query. Each query gets executed and the returned
476             value is a hash containing the same identifiers as keys, but instead of
477             pointing to queries, they point to an array containing a status and a
478             result. The status is either 0 or -1. If it's 0, the result is a pointer to
479             the data element. If it's -1, the result is the error message.
480             =cut
481             sub query {
482             my ($self, $queries) = @_;
483             my $logger = get_logger("perfSONAR_PS::Client::LS::Remote");
484              
485             if (not defined $self->{URI}) {
486             return -1;
487             }
488              
489             my ($host, $port, $endpoint) = &perfSONAR_PS::Transport::splitURI($self->{URI});
490             if (not defined $host and not defined $port and not defined $endpoint) {
491             return -1;
492             }
493              
494             my $request = "";
495             $request .= "
496             $request .= " xmlns:nmwg=\"http://ggf.org/ns/nmwg/base/2.0/\"\n";
497             $request .= " xmlns:xquery=\"http://ggf.org/ns/nmwg/tools/org/perfsonar/service/lookup/xquery/1.0/\">\n";
498             foreach my $query_id (keys %{ $queries }) {
499             $request .= " \n";
500             $request .= " \n";
501             $request .= $queries->{$query_id};
502             $request .= " \n";
503             $request .= " http://ggf.org/ns/nmwg/tools/org/perfsonar/service/lookup/xquery/1.0\n";
504             $request .= " \n";
505             $request .= " native\n";
506             $request .= " \n";
507             $request .= " \n";
508             $request .= " \n";
509             }
510             $request .= "\n";
511              
512             my ($status, $res) = consultArchive($host, $port, $endpoint, $request);
513             if ($status != 0) {
514             my $msg = "Error consulting LS: $res";
515             $logger->error($msg);
516             return -1;
517             }
518              
519             $logger->debug("Response: ".$res->toString);
520              
521             my %ret_structure = ();
522              
523             foreach my $d ($res->getChildrenByTagName("nmwg:data")) {
524             foreach my $m ($res->getChildrenByTagName("nmwg:metadata")) {
525             my $md_id = $m->getAttribute("id");
526             my $md_idref = $m->getAttribute("metadataIdRef");
527             my $d_idref = $d->getAttribute("metadataIdRef");
528              
529             if($md_id eq $d_idref) {
530             my $query_id;
531             my $eventType = findvalue($m, "nmwg:eventType");
532              
533             if (defined $md_idref and $md_idref =~ /perfsonar_ps\.meta\.(.*)/x) {
534             $query_id = $1;
535             } elsif ($md_id =~ /perfsonar_ps\.meta\.(.*)/x) {
536             $query_id = $1;
537             } else {
538             my $msg = "Received unknown response: $md_id/$md_idref";
539             $logger->error($msg);
540             next;
541             }
542              
543             my @retval;
544             if (defined $eventType and $eventType =~ /^error\./x) {
545             my $error_msg = findvalue($d, "./nmwgr:datum");
546             $error_msg = "Unknown error" if (not defined $error_msg or $error_msg eq "");
547             @retval = (-1, $error_msg);
548             } else {
549             @retval = (0, $d);
550             }
551              
552             $ret_structure{$query_id} = \@retval;
553             }
554             }
555             }
556              
557             return (0, \%ret_structure);
558             }
559              
560             1;
561              
562              
563             __END__