File Coverage

blib/lib/Geneos/API.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             =head1 NAME
2              
3             Geneos::API - Handy Perl interface to ITRS Geneos XML-RPC Instrumentation API
4              
5             =head1 VERSION
6              
7             Version 0.12
8              
9             =head1 SYNOPSIS
10              
11             use Geneos::API;
12              
13             # open API to NetProbe running on host example.com and port 7036
14             my $api = Geneos::API->new("http://example.com:7036/xmlrpc");
15              
16             # get the sampler "Residents" in the managed entity "Zoo"
17             my $sampler = $api->get_sampler("Zoo", "Residents");
18              
19             # create view "Monkeys" in the group "Animals"
20             my $view = $sampler->create_view("Monkeys", "Animals");
21              
22             # prepare some data
23             my $monkeys = [
24             ["Name", "Type" ],
25             ["Funky", "Red-tailed monkey"],
26             ["Cheeky", "Tibetan macaque" ]
27             ];
28              
29             # populate the view
30             $view->update_entire_table($monkeys);
31              
32             # get stream "News" on sampler "Channels" in the managed entitity "Zoo"
33             my $stream = $api->get_sampler("Zoo","Channels")->get_stream("News");
34              
35             # add a message to the stream
36             $stream->add_message("Funky beats Cheeky in a chess boxing match!");
37              
38             =head1 DESCRIPTION
39              
40             C is a Perl module that implements ITRS XML-RPC Instrumentation API.
41             It can be used to create clients for both Geneos API and API Steams plug-ins.
42             The plug-ins act as an XML-RPC server.
43              
44             Geneos C, C and C are represented by instances of
45             C, C and C classes.
46             This provides easy to use building blocks for developing monitoring applications.
47              
48             This module comes with its own XML-RPC module based on XML::LibXML as ITRS implementation
49             of XML-RPC does not conform to the XML-RPC standard and therefore most of the available XML-RPC
50             modules cannot be used. The client uses C and gives access to all
51             the available constructor options provided by C.
52              
53             The module also provides customizable error and debug hanlders.
54              
55             =head1 METHODS
56              
57             =head2 Constructor
58              
59             =head3 $api->new($url, $options)
60              
61             C<$url> is required and must be in the format:
62              
63             C
64              
65             For example:
66              
67             my $api = Geneos::API->new("http://localhost:7036/xmlrpc");
68              
69             XML-RPC Client is initialized upon call to the API constructor
70              
71             Options
72              
73             The constructor accepts a reference to the options hash as optional second parameter:
74              
75             my $api = Geneos::API->new("http://localhost:7036/xmlrpc", {
76             api => {raise_error => 1,},
77             ua => {keep_alive=>10, timeout=>5,},
78             });
79              
80             =head4 api - XML-RPC options
81              
82             =over
83              
84             =item * C<< raise_error >>
85              
86             Force errors to raise exceptions via C
87              
88             =item * C<< print_error >>
89              
90             Force errors to raise warnings via C
91              
92             =item * C<< error_handler >>
93              
94             Custom error handler. See C section for more details.
95              
96             =item * C<< debug_handler >>
97              
98             Debug handler. See C section for more details.
99              
100             =back
101              
102             The order of presedence for error handling is as follows:
103              
104             =over
105              
106             =item * C<< error_handler >>
107              
108             =item * C<< raise_error >>
109              
110             =item * C<< print_error >>
111              
112             =back
113              
114             If neither is set, the errors won't be reported and C<$api-Eerror>
115             method will need to be called to check if the latest call generated an error or not.
116              
117             =head4 ua - UserAgent options
118              
119             =over
120              
121             =item * C<< any options supported by L >>
122              
123             =back
124              
125             =head2 Error handling
126              
127             =head3 $api->raise_error()
128              
129             Get the raise_error attribute value
130              
131             Returns true is the raise_error attribute is set or false otherwise
132              
133             If the raise_error attribute is set, errors generated by API calls will be passed to C
134              
135             =head3 $api->remove_raise_error()
136              
137             Remove the raise_error attribute
138              
139             =head3 $api->print_error()
140              
141             Get the print_error attribute value
142              
143             Returns true is the print_error attribute is set or false othersise
144              
145             If the print_error attribute is set, errors generated by API calls will be passed to C
146              
147             print_error attribute is ignored if raise_error is set.
148              
149             =head3 $api->remove_print_error()
150              
151             Remove the print_error attribute
152              
153             =head3 $api->status_line
154              
155             Returns the string " ". Returns undef if there is no error.
156              
157             =head3 $api->error
158              
159             Get the error produced by the last api call.
160              
161             Returns reference to the error hash or undef if the last call produced no error.
162             The hash contains three elements:
163              
164             =over
165              
166             =item * code
167              
168             HTTP or XML-RPC error code.
169              
170             =item * message
171              
172             Error string.
173              
174             =item * class
175              
176             The component that produced the error: C or C.
177              
178             =back
179              
180             =head3 Error handler
181              
182             Allows you to provide your own behaviour in case of errors.
183              
184             The handler must be passed as a reference to subroutine and it could be done as a constructor option:
185              
186             my $api = Geneos::API->new("http://localhost:7036/xmlrpc", {
187             api => { error_handler => \&my_error_handler, },
188             });
189              
190             or via a separate method:
191              
192             $api->error_handler(\&my_error_handler)
193              
194             The subroutine is called with two parameters: reference to the error hash and the api object itself.
195              
196             For example, to die with a full stack trace for any error:
197              
198             use Carp;
199             $api->error_handler( sub { confess("$_[0]->{code} $_[0]->{message}") } );
200              
201             Please note that the custom error handler overrides the raise_error and print_error settings.
202              
203             The error handler can be removed by calling:
204              
205             $api->remove_error_handler()
206              
207             =head2 API and API Streams Function Calls
208              
209             There are two classes that represent Samplers and Views. Samplers are represented by
210             the internal C class. First, a sampler object must be created:
211              
212             $sampler = $api->get_sampler($managed_entity, $sampler_name)
213              
214             This will create a Sampler object representing a sampler with the name C<$sampler_name> in the managed
215             entity C<$managed_entity>. You can call any method from the section "Sampler methods" on this object.
216              
217             Views are represented by the internal C class.
218             In order to create an instance of this class, you can use:
219              
220             # if the view already exists
221             $view = $sampler->get_view($view_name, $group_heading)
222              
223             # if the view does not exist yet and you want to create it
224             $view = $sampler->create_view($view_name, $group_heading)
225              
226             Once the view object is created, you can call any of the "View methods" on it.
227              
228             Streams are represented by the internal C class. In order to create
229             an instance of this class, you can use:
230              
231             $stream = $sampler->get_stream($stream_name)
232              
233             Once the object is created, you can call any of the "Stream methods" on it.
234              
235             =head2 Sampler methods
236              
237             =head3 $api->get_sampler($managed_entity, $sampler)
238              
239             This method doesn't check whether the sampler exists.
240              
241             Returns sampler object.
242              
243             =head3 $sampler->get_stream($stream_name)
244              
245             The stream must already exist. This method will NOT check that the stream extists or not.
246              
247             Returns an object representing the stream C<$stream_name>.
248              
249             =head3 $sampler->create_view($view_name, $group_heading)
250              
251             Creates a new, empty view C<$view_name> in the specified sampler under the specified C<$group_heading>.
252             This method will create a view and returns the object representing it. An error will be produced
253             if the view already exists.
254              
255             Returns OK on successful completion.
256              
257             =head3 $sampler->get_view($view_name, $group_heading)
258              
259             The view must already exist. This method will NOT check that the view extists or not.
260             Use C<$sampler-Eview_exists> method for that.
261              
262             Returns an object representing the view C<$view_name>.
263              
264             =head3 $sampler->view_exists($view_name, $group_heading)
265              
266             Checks whether a particular view exists in this sampler.
267              
268             Returns true if the view exists, false otherwise.
269              
270             =head3 $sampler->remove_view($view_name)
271              
272             Removes a view that has been created with create_view.
273              
274             Returns OK on successful completion.
275              
276             =head3 $sampler->get_parameter($parameter_name)
277              
278             Retrieves the value of a sampler parameter that has been defined in the gateway configuration.
279              
280             Returns the parameter text written in the gateway configuration.
281              
282             =head3 $sampler->sign_on($period)
283              
284             $period - The maximum time between updates before samplingStatus becomes FAILED
285              
286             Commits the API client to provide at least one heartbeat or update to the view within
287             the time period specified.
288              
289             Returns OK on successful completion.
290              
291             =head3 $sampler->sign_off()
292              
293             Cancels the commitment to provide updates to a view.
294              
295             Returns OK on successful completion.
296              
297             =head3 $sampler->heartbeat()
298              
299             Prevents the sampling status from becoming failed when no updates are needed to a view and
300             the client is signed on.
301              
302             Returns on successful completion.
303              
304             =head2 View methods
305              
306             =head3 $view->add_table_row($row_name,$data)
307              
308             Adds a new, table row to the specified view and populates it with data. Returns OK
309             on successful completion.
310              
311             =head3 $view->remove_table_row($row_name)
312              
313             Removes an existing row from the specified view.
314              
315             Returns OK on successful completion.
316              
317             =head3 $view->add_headline($headline_name)
318              
319             Adds a headline variable to the view.
320              
321             Returns OK on successful completion.
322              
323             =head3 $view->remove_headline($headline_name)
324              
325             Removes a headline variable from the view.
326              
327             Returns OK on successful completion.
328              
329             =head3 $view->update_variable($variable_name, $new_value)
330              
331             Can be used to update either a headline variable or a table cell.
332             If the variable name contains a period (.) then a cell is assumed,
333             otherwise a headline variable is assumed.
334              
335             Returns OK on successful completion.
336              
337             =head3 $view->update_headline($headline_name, $new_value)
338              
339             Updates a headline variable.
340              
341             Returns OK on successful completion.
342              
343             =head3 $view->update_table_cell($cell_name, $new_value))
344              
345             Updates a single cell in a table. The standard row.column format should be used
346             to reference a cell.
347              
348             Returns OK on successful completion.
349              
350             =head3 $view->update_table_row($row_name, $new_value))
351              
352             Updates an existing row from the specified view with the new values provided.
353              
354             Returns OK on successful completion.
355              
356             =head3 $view->add_table_column($column_name)
357              
358             Adds another column to the table.
359              
360             Returns OK on successful completion.
361              
362             =head3 $view->update_entire_table($new_table)
363              
364             Updates the entire table for a given view. This is useful if the entire table
365             will change at once or the table is being created for the first time.
366             The array passed should be two dimensional. The first row should be the column
367             headings and the first column of each subsequent row should be the name of the row.
368             The array should be at least 2 columns by 2 rows. Once table columns have been
369             defined, they cannot be changed by this method.
370              
371             Returns OK on successful completion.
372              
373             =head3 $view->column_exists($column_name)
374              
375             Check if the headline variable exists.
376              
377             Returns true if the column exists, false otherwise.
378              
379             =head3 $view->row_exists($row_name)
380              
381             Check if the headline variable exists.
382              
383             Returns true if the row exists, false otherwise.
384              
385             =head3 $view->headline_exists($headline_name)
386              
387             Check if the headline variable exists.
388              
389             Returns true if the headline variable exists, false otherwise.
390              
391             =head3 $view->get_column_count()
392              
393             Return the column count of the view.
394              
395             Returns the number of columns in the view. This includes the rowName column.
396              
397             =head3 $view->get_row_count()
398              
399             Return the headline count of the view.
400              
401             Returns the number of headlines in the view. This includes the samplingStatus headline.
402              
403             =head3 $view->get_headline_count()
404              
405             Returns the number of headlines in the view. This includes the samplingStatus headline.
406              
407             =head3 $view->get_column_names()
408              
409             Returns the names of existing columns in the view. This includes the rowNames column name.
410              
411             =head3 $view->get_row_names()
412              
413             Returns the names of existing rows in the view
414              
415             =head3 $view->get_headline_names()
416              
417             Returns the names of existing headlines in the view.
418             This includes the samplingStatus headline.
419              
420             =head3 $view->get_row_names_older_than($epoch)
421              
422             C<$epoch> - The timestamp against which to compare row update time.
423             The timestamp should be provided as Unix timestamp, i.e. number of seconds
424             elapsed since UNIX epoch.
425              
426             Returns the names of rows whose update time is older than the time provided.
427              
428             =head2 Stream methods
429              
430             =head3 $stream->add_message($message)
431              
432             Adds a new message to the end of the stream.
433              
434             Returns OK on successful completion.
435              
436             =head2 NetProbe Function Calls
437              
438             =head3 $api->managed_entity_exists($managed_entity)
439              
440             Checks whether a particular Managed Entity exists on this NetProbe
441             containing any API or API-Streams samplers.
442             Returns true if the Managed Entity exists, false otherwise
443              
444             =head3 $api->sampler_exists($managed_entity, $sampler)
445              
446             Checks whether a particular API or API-Streams sampler exists on this NetProbe
447             Returns true if sampler exists, false otherwise
448              
449             =head3 $api->gateway_connected()
450              
451             Checks whether the Gateway is connected to this NetProbe Returns true
452             if the Gateway is connected, false otherwise
453              
454             =head2 Gateway Function Calls
455              
456             =head3 $api->add_managed_entity($managed_entity, $data_section)
457              
458             Adds the managed entity to the particular data section Returns true on success, false otherwise
459              
460             =head2 Debugging
461              
462             The module comes with a debug handler. The handler must be passed as a reference
463             to subroutine and it could be done as a constructor option:
464              
465             my $api = Geneos::API->new("http://localhost:7036/xmlrpc", {
466             api => { debug_handler => \&my_debug_handler, },
467             });
468              
469             or via a separate method:
470              
471             $api->debug_handler(\&my_debug_handler)
472              
473             The subroutine is called with one parameter: C object.
474              
475             The following C methods might be useful for debugging purposes:
476              
477             =over 4
478              
479             =item * C
480              
481             Returns the time at the start of the request. It's captured using Time::HiRes::gettimeofday
482             method: C<$t0 = [gettimeofday]>
483              
484             =item * C
485              
486             Returns the C object.
487              
488             =item * C
489              
490             Returns the C object.
491              
492             =item * C
493              
494             Returns the C object. See C for more details.
495              
496             =item * C
497              
498             Returns the C object. See C for more details.
499              
500             =back
501              
502             The debug handler can be removed by calling:
503              
504             $api->remove_debug_handler()
505              
506             Example.
507              
508             The custom debug handler in this example will output the following stats:
509              
510             =over 4
511              
512             =item * Elapsed time
513              
514             =item * HTTP request headers
515              
516             =item * HTTP response headers
517              
518             =back
519              
520             use Time::HiRes qw(tv_interval);
521              
522             $api->debug_handler(\&custom_debug_handler);
523              
524             sub custom_debug_handler {
525             my $api_obj = shift;
526              
527             printf "# elapsed time: %f\n\n# request header:\n%s\n# response header:\n%s\n",
528             tv_interval($api_obj->t0),
529             $api_obj->http_request->headers_as_string,
530             $api_obj->http_response->headers_as_string;
531             }
532              
533             Upon execution, it will produce output similar to:
534              
535             # elapsed time: 0.002529
536              
537             # request header:
538             User-Agent: libwww-perl/6.04
539             Content-Type: text/xml
540              
541             # response header:
542             Connection: Keep-Alive
543             Server: GENEOS XML-RPC
544             Content-Length: 152
545             Content-Type: text/xml
546             Client-Date: Fri, 26 Dec 2014 16:18:10 GMT
547             Client-Peer: 127.0.0.1:9033
548             Client-Response-Num: 1
549              
550             =head1 ONLINE RESOURCES AND SUPPORT
551              
552             =over 4
553              
554             =item * L ITRS Group
555              
556             =item * L XML-RPC Specification
557              
558             =item * Drop me an email if you have any questions with Geneos::API in the subject
559              
560             =back
561              
562             =head1 BUGS
563              
564             Of course. Please raise a ticket via L
565              
566             =head1 AUTHOR
567              
568             Ivan Dmitriev, Etot@cpan.orgE
569              
570             =head1 COPYRIGHT AND LICENSE
571              
572             Copyright (C) 2014 by Ivan Dmitriev
573              
574             This library is free software; you can redistribute it and/or modify it
575             under the same terms as Perl itself.
576              
577             =cut
578              
579             ###############################################################
580             #
581             # package Geneos::API::XMLRPC::Response
582             #
583             # Parses XML-RPC response and converts it into Perl structure
584             #
585             ###############################################################
586              
587             package Geneos::API::XMLRPC::Response;
588              
589 1     1   20659 use strict;
  1         2  
  1         44  
590              
591 1     1   1987 use XML::LibXML qw(:libxml);
  0            
  0            
592              
593             sub new {
594             my $this = shift;
595             my $class = ref($this) || $this;
596             my $self = {
597             _response => {},
598             _error => undef,
599             };
600              
601             bless $self, $class;
602             $self->_init(@_);
603             }
604              
605             sub _init {
606             my ($self, $response) = @_;
607              
608             # Check if the HTTP request succeeded
609             if ($response->is_success) {
610              
611             my $dom = XML::LibXML->load_xml(string => $response->decoded_content);
612             process_node($self->{_response}, $dom->documentElement);
613              
614             if (exists $self->{_response}{fault}) {
615             my $code = exists $self->{_response}{fault}{faultCode}
616             ? $self->{_response}{fault}{faultCode}
617             : -1;
618              
619             my $str = exists $self->{_response}{fault}{faultString}
620             ? $self->{_response}{fault}{faultString}
621             : 'NO_ERROR_STRING';
622              
623             $self->error({class=>"XML-RPC", code=>$code, message=>$str,});
624             }
625              
626             }
627             else {
628             $self->error({class=>"HTTP", code=>$response->code, message=>$response->message,});
629             }
630              
631             return $self;
632             }
633              
634             sub is_success {!shift->error}
635              
636             sub params {shift->{_response}{params}}
637              
638             sub error {
639             my ($self, $error) = @_;
640             $self->{_error} = $error if $error;
641              
642             return $self->{_error};
643             }
644              
645             # ---------------
646             # Response parser
647              
648             sub process_node {
649             my ($r, $node) = @_;
650              
651             for my $child ($node->childNodes) {
652              
653             if ($child->nodeName eq "struct") {
654             process_struct($r, $child);
655             }
656             elsif ($child->nodeName eq "fault") {
657             process_fault($r, $child);
658             }
659             elsif ($child->nodeName eq "params") {
660             process_params($r, $child);
661             }
662             elsif ($child->nodeName eq "array") {
663             process_array($r, $child);
664             }
665             elsif ($child->nodeName =~ m/^i4|int|boolean|string|double|dateTime\.iso8601|base64$/) {
666             $$r = $child->textContent;
667             }
668             elsif ($child->nodeType == 3
669             && $node->nodeName eq "value"
670             && $node->childNodes->size == 1
671             ) {
672             $$r = $child->textContent;
673             }
674             else {
675             process_node($r, $child);
676             }
677             }
678             }
679              
680             sub process_fault {
681             my ($r, $node) = @_;
682              
683             my ($value) = $node->findnodes("./value");
684              
685             process_node(\$r->{fault}, $value);
686             }
687              
688             sub process_struct {
689             my ($r, $node) = @_;
690              
691             foreach my $member ( $node->findnodes("./member") ) {
692             my ($name) = $member->findnodes("./name");
693             my ($value) = $member->findnodes("./value");
694              
695             process_node(\$$r->{$name->textContent}, $value);
696             }
697             }
698              
699             sub process_array {
700             my ($r, $node) = @_;
701              
702             foreach my $value ( $node->findnodes("./data/value") ) {
703             process_node(\$$r->[++$#{$$r}], $value);
704             }
705             }
706              
707             sub process_params {
708             my ($r, $node) = @_;
709              
710             $r->{params} = [];
711              
712             foreach my $param ( $node->findnodes("./param") ) {
713             my ($value) = $param->findnodes("./value");
714             process_node(\$r->{params}[++$#{$r->{params}}], $value);
715             }
716             }
717              
718             ###########################################
719             #
720             # package Geneos::API::XMLRPC::Request
721             #
722             # Converts method and Perl data structure
723             # into an XML-RPC request
724             #
725             ###########################################
726              
727             package Geneos::API::XMLRPC::Request;
728              
729             use XML::LibXML;
730              
731             sub new {
732             my $this = shift;
733             my $class = ref($this) || $this;
734             my $self = {};
735             bless $self, $class;
736             $self->_init(@_);
737             }
738              
739             # Private methods
740              
741             sub _init {
742             my ($self, $method, @params) = @_;
743              
744             # remember the method and params
745             $self->{_method} = $method;
746             $self->{_params} = \@params;
747              
748             $self->{doc} = XML::LibXML::Document->new('1.0', 'utf-8');
749              
750             my $root = $self->{doc}->createElement("methodCall");
751             $self->{doc}->setDocumentElement($root);
752              
753             # ------------------
754             # Add the methodName
755              
756             my $methodName = $self->{doc}->createElement("methodName");
757             $methodName->appendTextNode($method);
758             $root->appendChild($methodName);
759              
760             # --------------
761             # Add the params
762             my $params = $self->{doc}->createElement("params");
763             $root->appendChild($params);
764              
765             # ---------------------
766             # Process the agruments
767             foreach (@params) {
768             my $param = $self->{doc}->createElement("param");
769             $params->addChild($param);
770             $self->parse($param, $_);
771             }
772              
773             return $self;
774             }
775              
776             # Public methods
777              
778             # accessor for the method
779             sub method {shift->{_method}}
780              
781             # accessor for the params
782             sub params {shift->{_params}}
783              
784             sub content {shift->{doc}->toString}
785              
786             sub parse {
787             my ($self, $node, $p) = @_;
788              
789             my $value = $self->{doc}->createElement("value");
790             $node->addChild($value);
791              
792             if ( ref($p) eq 'HASH' ) {
793             $self->parse_hash($value,$p);
794             }
795             elsif ( ref($p) eq 'ARRAY' ) {
796             $self->parse_array($value,$p);
797             }
798             elsif ( ref($p) eq 'CODE' ) {
799             $self->parse_code($value,$p);
800             }
801             else {
802             $self->parse_scalar($value,$p);
803             }
804             }
805              
806             # It seems that Geneos treats everything as a string
807             # no need for anything sophisticated here
808              
809             sub parse_scalar {
810             my ($self, $node, $scalar) = @_;
811              
812             $scalar ||= "";
813              
814             if (( $scalar =~ m/^[\-+]?\d+$/) && (abs($scalar) <= (0xffffffff >> 1))) {
815             my $i = $self->{doc}->createElement("i4");
816             $i->appendTextNode($scalar);
817             $node->appendChild($i);
818             }
819             elsif ( $scalar =~ m/^[\-+]?\d+\.\d+$/ ) {
820             my $d = $self->{doc}->createElement("double");
821             $d->appendTextNode($scalar);
822             $node->appendChild($d);
823             }
824             else {
825             my $s = $self->{doc}->createElement("string");
826             $s->appendTextNode($scalar);
827             $node->appendChild($s);
828             }
829             }
830              
831             sub parse_hash {
832             my ($self, $node, $hash) = @_;
833              
834             my $struct = $self->{doc}->createElement("struct");
835             $node->appendChild($struct);
836              
837             foreach (keys %$hash) {
838             my $member = $self->{doc}->createElement("member");
839             $struct->appendChild($member);
840              
841             my $name = $self->{doc}->createElement("name");
842             $name->appendTextNode($_);
843             $member->appendChild($name);
844              
845             $self->parse($member, $hash->{$_});
846             }
847             }
848              
849             sub parse_array {
850             my ($self, $node, $args) = @_;
851              
852             my $array = $self->{doc}->createElement("array");
853             $node->appendChild($array);
854              
855             my $data = $self->{doc}->createElement("data");
856             $array->appendChild($data);
857              
858             $self->parse($data, $_) for @$args;
859             }
860              
861             sub parse_code {
862             my ($self, $node, $code) = @_;
863              
864             my ($type, $data) = $code->();
865              
866             my $e = $self->{doc}->createElement($type);
867             $e->appendTextNode($data);
868             $node->appendChild($e);
869             }
870              
871             ########################################################################
872             #
873             # package Geneos::API::XMLRPC
874             #
875             # XML-RPC client
876             # The reason for yet another XML-RPC implementation is that
877             # because Geneos XML-RPC does not conform to the XML-RPC standard:
878             #
879             # * '-' is used in the method names
880             # * the values do not default to type 'string'
881             #
882             # Among other reasons, ensuring that HTTP1.1 is used to take advantage
883             # of the keep_alive feature supported by Geneos XML-RPC server
884             #
885             ########################################################################
886              
887             package Geneos::API::XMLRPC;
888              
889             use LWP::UserAgent;
890             use Time::HiRes qw(gettimeofday);
891              
892             # -----------
893             # Constructor
894              
895             sub new {
896             my $this = shift;
897              
898             my $class = ref($this) || $this;
899             my $self = {};
900             bless $self, $class;
901              
902             $self->_init(@_);
903             }
904              
905             # ---------------
906             # Private methods
907              
908             sub _init {
909             my ($self, $url, $opts) = @_;
910              
911             $self->{_url} = $url;
912              
913             $opts ||= {};
914             $opts->{ua} ||= {};
915              
916             # set up the UserAgent
917             $self->{_ua} = LWP::UserAgent->new(%{$opts->{ua}});
918              
919             return $self;
920             }
921              
922             # --------------
923             # Public methods
924              
925             sub request {
926             my ($self, $method, @params) = @_;
927              
928             # record the start time
929             $self->{_t0} = [gettimeofday];
930              
931             # prepare the XML-RPC request
932             $self->{_xmlrpc_request} = Geneos::API::XMLRPC::Request->new($method, @params);
933              
934             # create an http request
935             $self->{_http_request} = HTTP::Request->new("POST",$self->{_url});
936              
937             $self->{_http_request}->header('Content-Type' => 'text/xml');
938             $self->{_http_request}->add_content_utf8($self->{_xmlrpc_request}->content);
939              
940             # send the http request
941             $self->{_http_response} = $self->{_ua}->request($self->{_http_request});
942              
943             # parse the http response
944             $self->{_xmlrpc_response} = Geneos::API::XMLRPC::Response->new($self->{_http_response});
945             }
946              
947             # the LWP::UserAgent object
948             sub user_agent {shift->{_ua}}
949              
950             # --------------------------------------
951             # These methods are useful for debugging
952             #
953              
954             # request start time
955             sub t0 {shift->{_t0}}
956              
957             # xmlrpc/http requests/responses
958             sub xmlrpc_request {shift->{_xmlrpc_request}}
959             sub xmlrpc_response {shift->{_xmlrpc_response}}
960             sub http_request {shift->{_http_request}}
961             sub http_response {shift->{_http_response}}
962              
963             ###########################################################################
964             # package Geneos::Base #
965             # #
966             # This base class implements error handling and interface to #
967             # Geneos::API::XMLRPC that is used by Geneos::API, Geneos::API::Sampler, #
968             # Geneos::API::Sampler::View and Geneos::API::Sampler::Stream classes #
969             # #
970             ###########################################################################
971              
972             package Geneos::Base;
973              
974             use Carp;
975              
976             our $VERSION = '0.12';
977              
978             sub new {bless({_error=>undef,}, shift)->_init(@_)}
979              
980             sub status_line {
981             my $self = shift;
982              
983             if ($self->{_error}) {
984             my $code = $self->{_error}->{code} || '000';
985             my $message = $self->{_error}->{message} || 'Empty';
986             return "$code $message";
987             }
988             else {
989             return undef;
990             }
991             }
992              
993             sub call {
994             my ($self, $method, @params) = @_;
995              
996             $self->_reset_error;
997              
998             # send the XMLRPC request to the NetProbe
999             my $response = $self->api->request($self->_method($method), @params);
1000              
1001             # debug handler is passed the xmlrpc object
1002             $self->api->{_debug_handler}->($self->api->xmlrpc) if $self->api->{_debug_handler};
1003              
1004             # check the response
1005             if ($response->is_success) {
1006             $response->params->[0];
1007             }
1008             else {
1009             $self->_handle_error($response->error);
1010             }
1011             }
1012              
1013             sub error {shift->{_error}}
1014              
1015             sub _error {
1016             my ($self, $error) = @_;
1017             $self->{_error} = $error if $error;
1018              
1019             return $self->{_error};
1020             }
1021              
1022             sub _handle_error {
1023             my ($self, $error) = @_;
1024              
1025             # check if there is an error to handle
1026             unless (ref($error) eq 'HASH') {
1027             $error = {
1028             class => '_INTERNAL',
1029             code => '999',
1030             message => "Expected hashref but received '$error' instead",
1031             };
1032             }
1033              
1034             # record the error
1035             $self->_error($error);
1036              
1037             # execute the error handler code
1038             $self->api->{_error_handler}->($error, $self) if $self->api->{_error_handler};
1039              
1040             # always return undef
1041             return;
1042             }
1043              
1044             sub _reset_error {shift->{_error} = undef}
1045              
1046             #######################################
1047             #
1048             # package Geneos::API::Sampler::Stream
1049             #
1050             # Implements all Steam methods
1051             #
1052             #######################################
1053              
1054             package Geneos::API::Sampler::Stream;
1055              
1056             use base 'Geneos::Base';
1057              
1058             sub _init {
1059             my ($self, $sampler, $stream) = @_;
1060              
1061             $self->{_sampler} = $sampler;
1062             $self->{_stream} = $stream;
1063              
1064             return $self;
1065             }
1066              
1067             sub _method {
1068             my $self = shift;
1069             join(".", $self->{_sampler}->entity, $self->{_sampler}->sampler, "$self->{_stream}", @_);
1070             }
1071              
1072             sub api {shift->{_sampler}->api}
1073              
1074             # API Streams Function Calls
1075              
1076             sub add_message {shift->call("addMessage", @_)}
1077              
1078             ######################################
1079             #
1080             # package Geneos::API::Sampler::View
1081             #
1082             # Implements all View methods
1083             #
1084             ######################################
1085              
1086             package Geneos::API::Sampler::View;
1087              
1088             use base 'Geneos::Base';
1089              
1090             sub _init {
1091             my ($self, $sampler, $view, $group) = @_;
1092              
1093             $self->{_sampler} = $sampler;
1094             $self->{_view} = $view;
1095             $self->{_group} = $group;
1096              
1097             return $self;
1098             }
1099              
1100             sub _method {
1101             my $self = shift;
1102             join(".", $self->{_sampler}->entity, $self->{_sampler}->sampler, "$self->{_group}-$self->{_view}", @_);
1103             }
1104              
1105             sub api {shift->{_sampler}->api}
1106              
1107             # API calls
1108              
1109             # ---------------------------------------------
1110             # Combines addTableRow and updateTableRow calls
1111             #
1112             sub add_table_row {
1113             my ($self, $name, $data) = @_;
1114              
1115             return unless $self->_add_table_row($name);
1116              
1117             # if there is data - add it to the row
1118             $data ? $self->update_table_row($name, $data) : 1;
1119             }
1120              
1121             # -----------------------------------------------------
1122             # Each method below is an XML-RPC call to the NetProbe
1123             #
1124             # The first argument passed to the call method is the
1125             # XML-RPC method name. The rest are parameters passed
1126             # with that call to the XML-RPC server
1127             #
1128              
1129             sub _add_table_row {shift->call("addTableRow", @_)}
1130              
1131             sub remove_table_row {shift->call("removeTableRow", @_)}
1132              
1133             sub add_headline {shift->call("addHeadline", @_)}
1134              
1135             sub remove_headline {shift->call("removeHeadline", @_)}
1136              
1137             sub update_variable {shift->call("updateVariable", @_)}
1138              
1139             sub update_headline {shift->call("updateHeadline", @_)}
1140              
1141             sub update_table_cell {shift->call("updateTableCell", @_)}
1142              
1143             sub update_table_row {shift->call("updateTableRow", @_)}
1144              
1145             sub add_table_column {shift->call("addTableColumn", @_)}
1146              
1147             sub update_entire_table {shift->call("updateEntireTable", @_)}
1148              
1149             sub column_exists {shift->call("columnExists", @_)}
1150              
1151             sub row_exists {shift->call("rowExists", @_)}
1152              
1153             sub headline_exists {shift->call("headlineExists", @_)}
1154              
1155             sub get_column_count {shift->call("getColumnCount")}
1156              
1157             sub get_row_count {shift->call("getRowCount")}
1158              
1159             sub get_headline_count {shift->call("getHeadlineCount")}
1160              
1161             sub get_column_names {shift->call("getColumnNames")}
1162              
1163             sub get_row_names {shift->call("getRowNames")}
1164              
1165             sub get_headline_names {shift->call("getHeadlineNames")}
1166              
1167             sub get_row_names_older_than {shift->call("getRowNamesOlderThan", @_)}
1168              
1169             ##################################
1170             #
1171             # package Geneos::API::Sampler
1172             #
1173             # Implements all sampler methods
1174             #
1175             ##################################
1176              
1177             package Geneos::API::Sampler;
1178              
1179             use base 'Geneos::Base';
1180              
1181             sub _init {
1182             my ($self, $api, $entity, $sampler) = @_;
1183              
1184             $self->{_api} = $api;
1185             $self->{_entity} = $entity;
1186             $self->{_sampler} = $sampler;
1187              
1188             return $self;
1189             }
1190              
1191             # ---------------------------------------------------------
1192             # XML-RPC methodName for the sampler calls looks like this:
1193             # entity.sampler.action
1194             #
1195              
1196             sub _method {
1197             my $self = shift;
1198             join(".", $self->{_entity}, $self->{_sampler}, @_);
1199             }
1200              
1201             sub api {shift->{_api}}
1202             sub sampler {shift->{_sampler}}
1203             sub entity {shift->{_entity}}
1204              
1205             # -------------------------------------
1206             #
1207              
1208             sub get_stream {
1209             my $self = shift;
1210             $self->_reset_error;
1211             Geneos::API::Sampler::Stream->new($self, @_)
1212             }
1213              
1214             # -------------------------------------
1215             #
1216              
1217             sub get_view {
1218             my $self = shift;
1219             $self->_reset_error;
1220             Geneos::API::Sampler::View->new($self, @_)
1221             }
1222              
1223             #############
1224             # API calls #
1225             #############
1226              
1227             sub create_view {
1228             my $self = shift;
1229             $self->call("createView", @_) ? Geneos::API::Sampler::View->new($self, @_) : undef;
1230             }
1231              
1232             # -------------------------------------------------------
1233             # Checks whether a particular view exists in this sampler
1234             #
1235             # Returns true if the view exists, false otherwise
1236             #
1237              
1238             sub view_exists {
1239             my ($self, $view, $group) = @_;
1240             $self->call("viewExists", "${group}-${view}");
1241             }
1242              
1243             sub remove_view {shift->call("removeView", @_)}
1244              
1245             # -----------------------------------------------
1246             # Retrieves the value of a sampler parameter that
1247             # has been defined in the gateway configuration
1248             #
1249             # Returns the parameter text written in the gateway configuration
1250             #
1251              
1252             sub get_parameter {shift->call("getParameter", @_)}
1253              
1254             sub sign_on {shift->call("signOn", @_)}
1255              
1256             sub sign_off {shift->call("signOff")}
1257              
1258             sub heartbeat {shift->call("heartbeat")}
1259              
1260             ######################################
1261             #
1262             # package Geneos::API
1263             #
1264             # Implements the Geneos XML-RPC API
1265             #
1266             ######################################
1267              
1268             package Geneos::API;
1269              
1270             our $VERSION = '0.12';
1271              
1272             use base 'Geneos::Base';
1273             use Carp;
1274             use Time::HiRes qw(tv_interval);
1275              
1276             sub _init {
1277             my ($self, $url, $opts) = @_;
1278              
1279             # the url must be present
1280             croak "Geneos::API->new was called without url!" unless $url;
1281              
1282             # process the options
1283             $opts ||= {};
1284             $opts->{ua} ||= {};
1285             $opts->{api} ||= {};
1286              
1287             $self->{_xmlrpc} = Geneos::API::XMLRPC->new($url, $opts);
1288             $self->{_opts} = $opts;
1289              
1290             # ----------------------
1291             # init the error handler
1292              
1293             if (ref($opts->{api}{error_handler}) eq 'CODE') {
1294             $self->error_handler($opts->{api}{error_handler});
1295             }
1296             elsif ($opts->{api}{raise_error}) {
1297             $self->error_handler(
1298             sub {croak("$_[0]->{code} $_[0]->{message}")}
1299             );
1300             }
1301             elsif ($opts->{api}{print_error}) {
1302             $self->error_handler(
1303             sub {carp("$_[0]->{code} $_[0]->{message}")}
1304             );
1305             }
1306              
1307             # ----------------------
1308             # init the debug handler
1309              
1310             if ($opts->{api}{debug_handler}) {
1311             $self->debug_handler($opts->{api}{debug_handler});
1312             }
1313              
1314             return $self;
1315             }
1316              
1317             sub _method {shift;@_}
1318              
1319             # ---------------------
1320             # get/set error handler
1321              
1322             sub error_handler {
1323             my ($self, $handler) = @_;
1324              
1325             if (ref($handler) eq 'CODE') {
1326             $self->{_error_handler} = $handler;
1327             }
1328             elsif ($handler) {
1329             carp("argument for error_handler must be a coderef but got: ", ref($handler));
1330             }
1331              
1332             return $self->{_error_handler};
1333             }
1334              
1335             # --------------------
1336             # remove error handler
1337              
1338             sub remove_error_handler {shift->{_error_handler}=undef;}
1339              
1340             # ---------------------
1341             # get/set debug handler
1342              
1343             sub debug_handler {
1344             my ($self, $handler) = @_;
1345              
1346             if (ref($handler) eq 'CODE') {
1347             $self->{_debug_handler} = $handler;
1348             }
1349             elsif ($handler) {
1350             carp("argument for debug_handler must be a coderef but got: ", ref($handler));
1351             }
1352              
1353             return $self->{_debug_handler};
1354             }
1355              
1356             # --------------------
1357             # remove debug handler
1358              
1359             sub remove_debug_handler {shift->{_debug_handler}=undef;}
1360              
1361             sub raise_error {shift->{_opts}{api}{raise_error}}
1362              
1363             sub remove_raise_error {shift->{_opts}{api}{raise_error}=undef;}
1364              
1365             sub print_error {shift->{_opts}{api}{print_error}}
1366              
1367             sub remove_print_error {shift->{_opts}{api}{print_error}=undef;}
1368              
1369             sub api{shift}
1370              
1371             # send XMLRPC request
1372             sub request {shift->{_xmlrpc}->request(@_)}
1373              
1374             # LWP::UserAgent object
1375             sub user_agent {shift->{_xmlrpc}->user_agent}
1376              
1377             # Geneos::API::XMLPRC object
1378             sub xmlrpc {shift->{_xmlrpc}}
1379              
1380             #############
1381             # API calls #
1382             #############
1383              
1384             # ------------------------
1385             # Creates a sampler object
1386              
1387             sub get_sampler {
1388             my $self = shift;
1389             $self->_reset_error;
1390             Geneos::API::Sampler->new($self,@_)
1391             }
1392              
1393             # ------------------------------------------------------------------------------
1394             # Checks whether a particular API or API-Streams sampler exists on this NetProbe
1395             #
1396             # Returns true if the sampler exists, false otherwise
1397             #
1398              
1399             sub sampler_exists {
1400             my ($self, $me, $sampler) = @_;
1401             $self->call("_netprobe.samplerExists", "$me.$sampler");
1402             }
1403              
1404             # ---------------------------------------------------------
1405             # Checks whether the Gateway is connected to this NetProbe
1406             #
1407             # Returns true if the Gateway is connected, false otherwise
1408             #
1409              
1410             sub gateway_connected {shift->call("_netprobe.gatewayConnected")}
1411              
1412             # ------------------------------------------------------
1413             # Adds the managed entity to the particular data section
1414             #
1415             # Returns true on success, false otherwise
1416             #
1417              
1418             sub add_managed_entity {shift->call("_gateway.addManagedEntity", @_)}
1419              
1420             # ------------------------------------------------------------------
1421             # Checks whether a particular Managed Entity exists on this NetProbe
1422             # containing any API or API-Streams samplers
1423             #
1424             # Returns true if the Managed Entity exists, false otherwise
1425             #
1426              
1427             sub managed_entity_exists {shift->call("_netprobe.managedEntityExists", @_)}
1428              
1429             1;
1430              
1431             __END__