File Coverage

blib/lib/Net/OpenNebula/RPCClient.pm
Criterion Covered Total %
statement 22 29 75.8
branch n/a
condition 0 3 0.0
subroutine 8 9 88.8
pod n/a
total 30 41 73.1


line stmt bran cond sub pod time code
1 1     1   3 use strict;
  1         2  
  1         20  
2 1     1   3 use warnings;
  1         1  
  1         73  
3              
4             # packge the DummyLogger together with the RPCClient package
5             package Net::OpenNebula::DummyLogger; ## no critic
6             $Net::OpenNebula::DummyLogger::VERSION = '0.310.0';
7             sub new {
8 0     0     my $that = shift;
9 0   0       my $proto = ref($that) || $that;
10 0           my $self = { @_ };
11              
12 0           bless($self, $proto);
13              
14 0           return $self;
15             }
16              
17             # Mock basic methods of Log4Perl getLogger instance
18 1     1   3 no strict 'refs'; ## no critic
  1         1  
  1         55  
19             foreach my $i (qw(error warn info verbose debug)) {
20             *{$i} = sub {}
21             }
22 1     1   3 use strict 'refs';
  1         1  
  1         32  
23              
24              
25             package Net::OpenNebula::RPCClient;
26             $Net::OpenNebula::RPCClient::VERSION = '0.310.0';
27              
28 1     1   701 use RPC::XML;
  1         395789  
  1         77  
29 1     1   1022 use RPC::XML::Client;
  1         92923  
  1         32  
30 1     1   766 use Data::Dumper;
  1         4575  
  1         70  
31 1     1   304 use XML::Parser;
  0            
  0            
32              
33             use version;
34              
35             if(! defined($ENV{XML_SIMPLE_PREFERRED_PARSER})) {
36             $ENV{XML_SIMPLE_PREFERRED_PARSER} = 'XML::Parser';
37             };
38              
39             my $has_libxml;
40             eval "use XML::LibXML::Simple qw(XMLin);"; ## no critic (BuiltinFunctions::ProhibitStringyEval)
41             if ($@) {
42             use XML::Simple qw(XMLin XMLout);
43             $has_libxml = 0;
44             } else {
45             use XML::Simple qw(XMLout);
46             $has_libxml = 1;
47             };
48              
49             use RPC::XML::ParserFactory (class => $has_libxml ? 'XML::LibXML' : 'XML::Parser');
50              
51             # Caching
52             # data_cache
53             my $_cache = {};
54             my $_cache_methods = {};
55              
56             # options
57             # user: user to connect
58             # password: password for user
59             # url: the RPC url to use
60             # log: optional log4perl-like instance
61             # fail_on_rpc_fail: die on RPC error or not
62             # useragent: options passed to LWP::UserAgent (via RPC::XML useragent option)
63             # ca: CA file or dir, is passed as SSL_ca_file or SSL_ca_path via useragent
64             # does not override useragent settings; enables verify_hostname
65             sub new {
66             my $that = shift;
67             my $proto = ref($that) || $that;
68             my $self = { @_ };
69              
70             if (! exists($self->{log})) {
71             $self->{log} = Net::OpenNebula::DummyLogger->new();
72             }
73              
74             # legacy behaviour
75             if (! exists($self->{fail_on_rpc_fail})) {
76             $self->{fail_on_rpc_fail} = 1;
77             }
78              
79             bless($self, $proto);
80              
81             $self->{log}->debug(2, "Initialised with user $self->{user} and url $self->{url}");
82             $self->{log}->debug(2, ($has_libxml ? "U" : "Not u")."sing XML::LibXML(::Simple)");
83             $self->{log}->debug(2, "Using preferred XML::Simple parser $ENV{XML_SIMPLE_PREFERRED_PARSER}.");
84              
85             # Cache and test rpc
86             $self->version();
87              
88             return $self;
89             }
90              
91             # Enable the caching of all methods calls (cache is per method/args combo)
92             sub add_cache_method {
93             my ($self, $method) = @_;
94             $_cache_methods->{$method} = 1;
95             }
96              
97              
98             # Remove the caching method and cache
99             sub remove_cache_method {
100             my ($self, $method) = @_;
101             $_cache_methods->{$method} = 0;
102             $_cache->{$method} = {};
103             }
104              
105              
106             sub _rpc_args_to_txt {
107             my ($self, @args) = @_;
108              
109             my @txt;
110             foreach my $arg (@args) {
111             push(@txt, join(", ", @$arg));
112             };
113             my $args_txt = join("], [", @txt);
114              
115             return "[$args_txt]";
116             }
117              
118             sub _rpc {
119             my ($self, $meth, @params) = @_;
120              
121             my $req_txt = "method $meth args ".$self->_rpc_args_to_txt(@params);
122              
123             $self->debug(4, "_rpc called with $req_txt");
124              
125             if ($_cache_methods->{$meth}) {
126             if ($_cache->{$meth} && exists($_cache->{$meth}->{$req_txt})) {
127             $self->debug(1, "Returning cached data for $meth / $req_txt");
128             return $_cache->{$meth}->{$req_txt};
129             }
130             }
131              
132             my $cli = $self->{__cli};
133             my %opts;
134              
135             if (exists($self->{ca})) {
136             my $optname = "SSL_ca_" . (-f $self->{ca} ? 'file' : 'path');
137             my $set_verify_hostname = 1;
138             my $set_optname = 1;
139             if (exists($self->{useragent}) && exists ($self->{useragent}->{ssl_opts})) {
140             my $ssl_opts = $self->{useragent}->{ssl_opts};
141             $set_verify_hostname = ! exists($ssl_opts->{verify_hostname});
142             $set_optname = ! exists($ssl_opts->{$optname});
143             }
144             $self->{useragent}->{ssl_opts}->{verify_hostname} = 1 if $set_verify_hostname;
145             $self->{useragent}->{ssl_opts}->{$optname} = $self->{ca} if $set_optname;
146             }
147             # RPC::XML::Client expects that useragent is an arrayref, which is passed
148             # as an array to LWP::UserAgent, which interprets it as a hash
149             $opts{useragent} = [%{$self->{useragent}}] if exists($self->{useragent});
150              
151             if (! $cli) {
152             $self->{__cli} = RPC::XML::Client->new($self->{url}, %opts);
153             $cli = $self->{__cli};
154             };
155              
156             my @params_o = (RPC::XML::string->new($self->{user} . ":" . $self->{password}));
157             for my $p (@params) {
158             my $klass = "RPC::XML::" . $p->[0];
159             push(@params_o, $klass->new($p->[1]));
160             }
161              
162             my $req = RPC::XML::request->new($meth, @params_o);
163              
164             my $reqstring = $req->as_string();
165             my $password = XMLout($self->{password}, rootname => "x");
166             if ($password =~ m!^\s*<x>(.*)</x>\s*$!) {
167             $password = quotemeta $1;
168             $reqstring =~ s/$password/PASSWORD/g;
169             $self->debug(5, "_rpc RPC request $reqstring");
170             } else {
171             $self->debug(5, "_rpc RPC request not shown, failed to convert and replace password");
172             }
173              
174             my $resp = $cli->send_request($req);
175              
176             if(!ref($resp)) {
177             $self->error("_rpc send_request failed with message: $resp");
178             return;
179             }
180              
181             my $ret = $resp->value;
182              
183             if(ref($ret) ne "ARRAY") {
184             $self->error("_rpc failed to make request faultCode $ret->{faultCode} faultString $ret->{faultString} $req_txt");
185             return;
186             }
187              
188             elsif($ret->[0] == 1) {
189             $self->debug(5, "_rpc RPC answer $ret->[1]");
190             if($ret->[1] =~ m/^(\d|\.)+$/) {
191             my $parsed = $ret->[1];
192             if ($_cache_methods->{$meth}) {
193             $_cache->{$meth}->{$req_txt} = $parsed;
194             $self->debug(5, "Result for $meth / $req_txt cached");
195             };
196             return $parsed;
197             }
198             else {
199             my $opts = {
200             ForceArray => $has_libxml ? ['ID', 'NAME', 'STATE', qr{.}] : 1,
201             KeyAttr => [],
202             };
203              
204             my $parsed = XMLin($ret->[1], %$opts);
205             if ($_cache_methods->{$meth}) {
206             $_cache->{$meth}->{$req_txt} = $parsed;
207             $self->debug(5, "Result for $meth / $req_txt cached");
208             };
209              
210             return $parsed;
211             }
212             }
213              
214             else {
215             $self->error("_rpc Error sending request $req_txt: $ret->[1] (code $ret->[2])");
216             if( $self->{fail_on_rpc_fail}) {
217             die("error sending request.");
218             } else {
219             return;
220             }
221             }
222              
223             }
224              
225              
226             sub version {
227             my ($self) = @_;
228              
229             # cached value
230             if(exists($self->{_version})) {
231             return $self->{_version};
232             }
233             my $version = $self->_rpc("one.system.version");
234              
235             if(defined($version)) {
236             $self->verbose("Version $version found");
237             $self->{_version} = version->new($version);
238             return $self->{_version};
239             } else {
240             $self->error("Failed to retrieve version");
241             return;
242             }
243             }
244              
245              
246             # add logging shortcuts
247             no strict 'refs'; ## no critic
248             # The Log4Perl methods
249             foreach my $i (qw(error warn info debug)) {
250             *{$i} = sub {
251             my ($self, @args) = @_;
252             return $self->{log}->$i(@args);
253             };
254             };
255             # verbose fallback for Log4Perl
256             *{verbose} = sub {
257             my ($self, @args) = @_;
258             my $verbose = $self->{log}->can('verbose') ? 'verbose' : 'debug';
259             return $self->{log}->$verbose(@args);
260             };
261              
262             use strict 'refs';
263              
264             1;