File Coverage

blib/lib/Ovirt.pm
Criterion Covered Total %
statement 15 17 88.2
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 21 23 91.3


line stmt bran cond sub pod time code
1             package Ovirt;
2              
3 1     1   413 use v5.10;
  1         2  
4 1     1   501 use LWP::UserAgent;
  1         28502  
  1         68  
5 1     1   7 use Scalar::Util qw(looks_like_number);
  1         9  
  1         66  
6 1     1   3 use Carp;
  1         1  
  1         36  
7 1     1   4 use JSON;
  1         0  
  1         7  
8 1     1   300 use XML::LibXML;
  0            
  0            
9             use URI::Escape;
10             use Moo::Role;
11              
12             =head1 NAME
13              
14             Ovirt - Bindings for oVirt REST API
15              
16             =head1 VERSION
17              
18             Version 0.06
19              
20             =cut
21              
22             our $VERSION = '0.06';
23              
24             =head1 SYNOPSIS
25              
26             use Ovirt::VM;
27             use Ovirt::Template;
28             use Ovirt::Cluster;
29             use Ovirt::Host;
30             use Ovirt::Display;
31              
32             my %con = (
33             username => 'admin',
34             password => 'password',
35             manager => 'ovirt-mgr.example.com',
36             api_output => 'xml', # optional, default to json
37             vm_output_attrs => 'id,name,state,description', # optional
38             cluster_output_attrs => 'id,name,cpu_id,cpu_arch,description', # optional
39             );
40              
41             my $vm = Ovirt::VM ->new(%con);
42             my $cluster = Ovirt::Cluster ->new(%con);
43             my $template = Ovirt::Template ->new(%con);
44             my $host = Ovirt::Host ->new(%con);
45              
46             # return xml output / json
47             print $vm->list_xml;
48             print $vm->list_json;
49              
50             # list attributes based on 'vm_output_attrs'
51             print $vm ->list;
52             print $cluster ->list;
53             print $template->list;
54             print $host ->list;
55              
56             # create, remove vm
57             $vm->create('vm1','Default','CentOS7');
58             $vm->remove('2d83bb51-9a77-432d-939c-35be207017b9');
59            
60             # add/remove/list vm's nic and disk
61             $vm->add_disk('6efc0cfa-8495-4a96-93e5-ee490328cf48', # vm id
62             'virtio', # driver interface
63             'cow', # format
64             '1073741824', # size
65             'mydisk1', # disk name
66             '9b952bdc-b7ec-4673-84b0-477b48945a9a' # storage domain id
67             );
68              
69             $vm->add_nic('6efc0cfa-8495-4a96-93e5-ee490328cf48', # vm id
70             'virtio', # driver interface
71             'nic1', # nic name
72             'rhevm' # network name
73             );
74            
75             # start, stop, reboot, migrate vm
76             $vm->start ('b4738b0f-b73d-4a66-baa8-2ba465d63132');
77             $vm->stop ('b4738b0f-b73d-4a66-baa8-2ba465d63132');
78             $vm->reboot ('b4738b0f-b73d-4a66-baa8-2ba465d63132');
79             $vm->migrate ('b4738b0f-b73d-4a66-baa8-2ba465d63132');
80              
81             # the output also available in hash
82             # for example to print all vm name and state
83             my $hash = $vm->hash_output;
84             for my $array (keys $hash->{vm}) {
85             print $hash->{vm}[$array]->{name} . " " .
86             $hash->{vm}[$array]->{status}->{state};
87             }
88            
89             # we can also specify specific vm 'id' when initiating an object
90             # so we can direct access the element for specific vm
91             print $vm->hash_output->{name};
92             print $vm->hash_output->{cluster}->{id};
93            
94             # Generate display configuration for remote viewer
95             my $display = Ovirt::Display->new(%con);
96             print $display->generate();
97              
98             sample spice configuration output :
99             [virt-viewer]
100             type=spice
101             host=192.168.1.152
102             port=-1
103             password=+cnsq458Oq6T
104             # Password is valid for 300 seconds.
105             tls-port=5902
106             fullscreen=0
107             title=C1 : %d - Press SHIFT+F12 to Release Cursor
108             enable-smartcard=0
109             enable-usb-autoshare=1
110             delete-this-file=1
111             usb-filter=-1,-1,-1,-1,0
112             tls-ciphers=DEFAULT
113             host-subject=O=example.com,CN=192.168.1.152
114             ca=-----BEGIN CERTIFICATE-----\n -- output removed -- S2fE=\n-----END CERTIFICATE-----\n
115             toggle-fullscreen=shift+f11
116             release-cursor=shift+f12
117             secure-attention=ctrl+alt+end
118             secure-channels=main;inputs;cursor;playback;record;display;usbredir;smartcard
119              
120             you can save it to a file then use remote viewer to open it:
121             $ remote-viewer [your saved file].vv
122              
123             =head1 Attributes
124              
125             notes :
126             ro = read only, can be specified during initialization
127             rw = read write, user can set this attribute
128             rwp = read write protected, for internal class
129              
130             username = (ro, required) store Ovirt username
131             password = (ro, required) store Ovirt password
132             manager = (ro, required) store Ovirt Manager address
133             port = (ro) store Ovirt Manager's port (must be number)
134             id = (ro) store object id, if it's provided during initialization,
135             the rest api output will only contain attributes for this id
136             domain = (ro) store Ovirt Domain (default domain : internal)
137             ssl = (ro) if yes, use https (default is yes)
138             ssl_verify = (ro) disable host verification (default is no)
139             log_severity = (ro) store log severity level, valid value ERROR|OFF|FATAL|INFO|DEBUG|TRACE|ALL|WARN
140             (default is INFO)
141             api_output = (ro) json/xml, default is json
142             not_available = (rw) store undef or empty output string, default to 'N/A'
143             url = (rwp) store final url to be requested to Ovirt
144             root_url = (rwp) store url on each object
145             log = (rwp) store log from log4perl
146             xml_output = (rwp) store xml output from API output
147             json_output = (rwp) store json output from API output
148             hash_output = (rwp) store hash output converted from xml output
149              
150             =cut
151              
152             has [qw/url root_url xml_output json_output hash_output log/] => ( is => 'rwp' );
153             has [qw/id/] => ( is => 'ro' );
154             has [qw/username password manager/] => ( is => 'ro', required => 1 );
155              
156             has 'port' => ( is => 'ro',
157             isa =>
158             sub {
159             croak "$_[0] is not a number!" unless looks_like_number $_[0];
160             }
161             );
162            
163             has 'domain' => ( is => 'ro', default => 'internal' );
164             has 'ssl' => ( is => 'ro', default => 'yes' );
165             has 'ssl_verify' => ( is => 'ro',
166             isa => sub {
167             my $ssl_verify = $_[0];
168             $ssl_verify = lc ($ssl_verify);
169            
170             if ($ssl_verify eq 'yes') {
171             $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 1;
172             }
173             elsif ($ssl_verify eq 'no') {
174             $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0;
175             }
176             else {
177             croak "ssl_verify valid argument is yes/no";
178             }
179             },
180             default => sub { $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0; return 'no'; } );
181             has 'not_available' => ( is => 'rw', default => 'N/A' );
182              
183             has 'log_severity' => (is => 'ro',
184             isa => sub { croak "log severity value not valid\n"
185             unless $_[0] =~ /(ERROR|OFF|FATAL|INFO|DEBUG|TRACE|ALL|WARN)/;
186             },
187             default => 'INFO'
188             );
189              
190             has 'api_output' => (is => 'ro',
191             isa => sub { croak "valid api_output is xml/json\n"
192             unless $_[0] =~ /(xml|json)/;
193             },
194             default => 'json'
195             );
196              
197             =head1 SUBROUTINES/METHODS
198              
199             You may want to check :
200             - perldoc Ovirt::VM
201             - perldoc Ovirt::Template
202             - perldoc Ovirt::Cluster
203             - perldoc Ovirt::Host
204             - perldoc Ovirt::Display
205             - perldoc Ovirt::DataCenter
206             - perldoc Ovirt::Storage
207             - perldoc Ovirt::Network
208              
209             =head2 BUILD
210              
211             The Constructor, build logging, call pass_log_obj method
212             =cut
213              
214             sub BUILD
215             {
216             my $self = shift;
217            
218             $self->pass_log_obj();
219             }
220              
221             =head2 pass_log_obj
222              
223             it will build the log which stored to $self->log
224             you can assign the severity level by assigning the log_severity
225            
226             # output to console / screen
227             # format :
228             # %d = current date with yyyy/MM/dd hh:mm:ss format
229             # %p = Log Severity
230             # %P = pid of the current process
231             # %L = Line number within the file where the log statement was issued
232             # %M = Method or function where the logging request was issued
233             # %m = The message to be logged
234             # %n = Newline (OS-independent)
235            
236             =cut
237              
238             sub pass_log_obj
239             {
240             my $self = shift;
241            
242             # skip if already set
243             return if $self->log;
244            
245             my $severity = $self->log_severity;
246             my $log_conf =
247             qq /
248             log4perl.logger = $severity, Screen
249             log4perl.appender.Screen = Log::Log4perl::Appender::Screen
250             log4perl.appender.Screen.stderr = 0
251             log4perl.appender.Screen.layout = PatternLayout
252             log4perl.appender.Screen.layout.ConversionPattern = %d || %p || %P || %L || %M || %m%n
253             /;
254            
255             use Log::Log4perl;
256             Log::Log4perl::init(\$log_conf);
257             my $log = Log::Log4perl->get_logger();
258             $self->_set_log($log);
259             }
260              
261             =head2 base_url
262              
263             return the base url
264             =cut
265              
266             sub base_url
267             {
268             my $self = shift;
269            
270             # make sure the http encoding
271             # '%40' is '@'
272             my $username = uri_escape($self->username);
273             my $domain = uri_escape($self->domain);
274             my $password = uri_escape($self->password);
275            
276             my $url = $username . '%40'. $domain . ":" .$password .
277             "\@" . $self->manager;
278            
279             if ($self->port) {
280             $url = $username . '%40'. $domain . ":" .$password .
281             "\@" . $self->manager . ":" . $self->port;
282             }
283            
284             if ($self->ssl eq 'yes') {
285             $url = "https://" . $url;
286             }
287             elsif ($self->ssl eq 'no') {
288             $url = "http://" . $url;
289             }
290            
291             $self->log->debug($url);
292             return $url;
293             }
294              
295             =head2 api_url
296              
297             build the final url
298             =cut
299              
300             sub api_url
301             {
302             my $self = shift;
303            
304             # root_url is being set in each particular library
305             my $url = $self->base_url . $self->root_url;
306            
307             $self->log->debug("$url");
308             $self->_set_url($url);
309             }
310              
311             =head2 get_api_response
312              
313             get xml response, store to xml_output.
314             the xml output is also converted to hash and stored
315             at hash_output attribute.
316             xml2hash somehow complaining the xml declaration, so we
317             need to skip it and use 'toString' method on the xml string
318             parameter.
319             =cut
320              
321             sub get_api_response
322             {
323             my $self = shift;
324            
325             my $ua = LWP::UserAgent->new();
326            
327             if ($self->api_output eq 'json') {
328            
329             my $tx = $self->get_json_response($self->api_url);
330            
331             if ($tx->is_success) {
332             #store json output
333             my $json = $tx->decoded_content;
334             $self->_set_json_output($json);
335            
336             my $hash = from_json($json);
337             $self->_set_hash_output($hash);
338             }
339             else {
340             $self->set_lwp_error($tx);
341             }
342             }
343             elsif ($self->api_output eq 'xml') {
344             my $tx = $self->get_xml_response($self->api_url);
345            
346             if ($tx->is_success) {
347             use XML::Hash::XS qw();
348            
349             local $XML::LibXML::skipXMLDeclaration = 1;
350             my $parser = XML::LibXML->new();
351             my $xml_string = $parser->parse_string($tx->decoded_content);
352             $self->_set_xml_output($xml_string);
353            
354             #store to hash
355             my $conv = XML::Hash::XS->new(utf8 => 1, encoding => 'utf8');
356             my $hash = $conv->xml2hash($xml_string->toString, encoding => 'cp1251');
357             $self->_set_hash_output($hash);
358            
359             }
360             else {
361             $self->set_lwp_error($tx);
362             }
363             }
364             }
365              
366             =head2 set_lwp_error
367              
368             handle lwp if not success
369             required args ($tx)
370            
371             =cut
372              
373             sub set_lwp_error
374             {
375             my $self = shift;
376             my $tx = shift;
377            
378             my $err = $tx->status_line;
379             $self->log->debug("LWP Error : " . $err);
380             $self->log->debug("LWP Decoded Content :" . $tx->decoded_content);
381            
382             croak "LWP Status line : " . $err;
383             croak "LWP Decoded Content :" . $tx->decoded_content;
384             }
385              
386             =head2 get_api_output
387              
388             handle and return output based on $self->api_output
389              
390             =cut
391              
392             sub get_api_output
393             {
394             my $self = shift;
395             my $url = shift;
396            
397             if ($self->api_output eq 'json')
398             {
399             my $tx = $self->get_json_response($url);
400             if ($tx->is_success)
401             {
402             return $tx->decoded_content;
403             }
404             else
405             {
406             $self->set_lwp_error($tx);
407             }
408             }
409             else
410             {
411             my $tx = $self->get_xml_response($url);
412            
413             if ($tx->is_success)
414             {
415             local $XML::LibXML::skipXMLDeclaration = 1;
416             my $parser = XML::LibXML->new();
417             my $xml_string = $parser->parse_string($tx->decoded_content);
418             return $xml_string;
419             }
420             else
421             {
422             $self->set_lwp_error($tx);
423             }
424             }
425             }
426              
427             =head2 get_json_response
428              
429             handle http get for json output
430             required argument ('url')
431            
432             =cut
433              
434             sub get_json_response
435             {
436             my $self = shift;
437            
438             my $url = shift;
439            
440             croak "url required"
441             unless $url;
442            
443             my $ua = LWP::UserAgent->new();
444             my $req = HTTP::Request->new(GET => $url);
445             $req->header('content-type' => 'application/json');
446             $req->header('Accept' => 'application/json');
447            
448             my $tx = $ua->request($req);
449             return $tx;
450             }
451              
452             =head2 get_xml_response
453              
454             handle http get for xml output
455             required argument ('url')
456            
457             =cut
458              
459             sub get_xml_response
460             {
461             my $self = shift;
462            
463             my $url = shift;
464            
465             croak "url required"
466             unless $url;
467            
468             my $ua = LWP::UserAgent->new();
469             my $tx = $ua->get($url);
470            
471             return $tx;
472             }
473              
474             =head2 list_xml
475              
476             return xml output
477             =cut
478              
479             sub list_xml
480             {
481             my $self = shift;
482            
483             if ($self->api_output eq 'xml')
484             {
485             return $self->xml_output
486             if $self->xml_output;
487             }
488             else
489             {
490             die "api output setting is not xml\n";
491             }
492             }
493              
494             =head2 list_json
495              
496             return json output
497             =cut
498              
499             sub list_json
500             {
501             my $self = shift;
502            
503             if ($self->api_output eq 'json')
504             {
505             return $self->json_output
506             if $self->json_output;
507             }
508             else
509             {
510             die "api output setting is not json\n";
511             }
512             }
513              
514             =head2 trim
515              
516             trim function to remove whitespace from the start and end of the string
517             =cut
518              
519             sub trim()
520             {
521             my ($self, $string) = @_;
522             $string =~ s/^\s+|\s+$//g;
523             return $string;
524             }
525              
526             =head2 ltrim
527              
528             Left trim function to remove leading whitespace
529             =cut
530              
531             sub ltrim()
532             {
533             my ($self, $string) = @_;
534             $string =~ s/^\s+//;
535             return $string;
536             }
537              
538             =head2 rtrim
539              
540             Right trim function to remove leading whitespace
541             =cut
542              
543             sub rtrim()
544             {
545             my ($self, $string) = @_;
546             $string =~ s/\s+$//;
547             return $string;
548             }
549              
550             =head1 AUTHOR
551              
552             "Heince Kurniawan", C<< <"heince at cpan.org"> >>
553              
554             =head1 BUGS
555              
556             Please report any bugs or feature requests to C, or through
557             the web interface at L. I will be notified, and then you'll
558             automatically be notified of progress on your bug as I make changes.
559              
560              
561              
562              
563             =head1 SUPPORT
564              
565             You can find documentation for this module with the perldoc command.
566              
567             perldoc Ovirt
568              
569              
570             You can also look for information at:
571              
572             =over 4
573              
574             =item * RT: CPAN's request tracker (report bugs here)
575              
576             L
577              
578             =item * AnnoCPAN: Annotated CPAN documentation
579              
580             L
581              
582             =item * CPAN Ratings
583              
584             L
585              
586             =item * Search CPAN
587              
588             L
589              
590             =back
591              
592              
593             =head1 ACKNOWLEDGEMENTS
594              
595              
596             =head1 LICENSE AND COPYRIGHT
597              
598             Copyright 2015 "Heince Kurniawan".
599              
600             This program is free software; you can redistribute it and/or modify it
601             under the terms of the the Artistic License (2.0). You may obtain a
602             copy of the full license at:
603              
604             L
605              
606             Any use, modification, and distribution of the Standard or Modified
607             Versions is governed by this Artistic License. By using, modifying or
608             distributing the Package, you accept this license. Do not use, modify,
609             or distribute the Package, if you do not accept this license.
610              
611             If your Modified Version has been derived from a Modified Version made
612             by someone other than you, you are nevertheless required to ensure that
613             your Modified Version complies with the requirements of this license.
614              
615             This license does not grant you the right to use any trademark, service
616             mark, tradename, or logo of the Copyright Holder.
617              
618             This license includes the non-exclusive, worldwide, free-of-charge
619             patent license to make, have made, use, offer to sell, sell, import and
620             otherwise transfer the Package with respect to any patent claims
621             licensable by the Copyright Holder that are necessarily infringed by the
622             Package. If you institute patent litigation (including a cross-claim or
623             counterclaim) against any party alleging that the Package constitutes
624             direct or contributory patent infringement, then this Artistic License
625             to you shall terminate on the date that such litigation is filed.
626              
627             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
628             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
629             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
630             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
631             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
632             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
633             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
634             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
635              
636              
637             =cut
638              
639             1; # End of Ovirt