File Coverage

blib/lib/PITA/Image.pm
Criterion Covered Total %
statement 46 48 95.8
branch n/a
condition n/a
subroutine 16 16 100.0
pod n/a
total 62 64 96.8


line stmt bran cond sub pod time code
1             package PITA::Image;
2              
3             =pod
4              
5             =head1 NAME
6              
7             PITA::Image - PITA Guest Manager for inside system images
8              
9             =head1 SYNOPSIS
10              
11             A typical startup script
12              
13             #!/usr/bin/perl
14            
15             use strict;
16             use IPC::Run3;
17             use PITA::Image;
18            
19             # Wrap the main actions in an eval to catch errors
20             eval {
21             # Configure the image manager
22             my $manager = PITA::Image->new(
23             injector => '/mnt/hbd1',
24             workarea => '/tmp',
25             );
26             $manager->add_platform(
27             scheme => 'perl5',
28             path => '', # Default system Perl
29             );
30             $manager->add_platform(
31             scheme => 'perl5',
32             path => '/opt/perl5-6-1/bin/perl'
33             );
34            
35             # Run the tasks
36             $manager->run;
37            
38             # Report the results
39             $manager->report;
40             };
41            
42             # Shut down the computer on completion or failure
43             run3( [ 'shutdown', '-h', '0' ], \undef );
44            
45             exit(0);
46              
47             And a typical configuration image.conf
48              
49             class=PITA::Image
50             version=0.10
51             support=http://10.0.2.2/
52            
53             [ task ]
54             task=Test
55             scheme=perl5.make
56             path=/usr/bin/perl
57             request=request-512311.conf
58              
59             =head1 DESCRIPTION
60              
61             While most of the PITA system exists outside the guest images and
62             tries to have as little interaction with them as possible, there is one
63             part that needs to be run from inside it.
64              
65             The C class lives inside the image and has the
66             responsibility of accepting the injector directory at startup, executing
67             the requested tasks, and then shutting down the (virtual) computer.
68              
69             =head1 Setting up a Testing Image
70              
71             Each image that will be set up will require a bit of customization,
72             as the entire point of this type of testing is that every environment
73             is different.
74              
75             However, by keeping most of the functionality in the
76             C and L classes, all you should need
77             to do is to arrange for a relatively simple Perl script to be launched,
78             that feeds some initial configuration to to a new
79             C object.
80              
81             And it should do the rest.
82              
83             =head1 METHODS
84              
85             =cut
86              
87 3     3   219397 use 5.006;
  3         11  
  3         123  
88 3     3   18 use strict;
  3         7  
  3         97  
89 3     3   19 use Carp ();
  3         8  
  3         58  
90 3     3   3318 use URI 1.57 ();
  3         17605  
  3         104  
91 3     3   3473 use Process 0.29 ();
  3         775  
  3         187  
92 3     3   1384 use File::Temp 0.22 ();
  3         24737  
  3         79  
93 3     3   23 use File::Spec 0.80 ();
  3         51  
  3         59  
94 3     3   19 use File::Spec::Unix ();
  3         6  
  3         57  
95 3     3   3181 use File::Which 0.05 ();
  3         3463  
  3         80  
96 3     3   853 use File::Remove 1.51 ();
  3         2165  
  3         67  
97 3     3   3166 use Config::Tiny 2.00 ();
  3         3547  
  3         94  
98 3     3   1003 use Params::Util 1.00 ();
  3         2924  
  3         69  
99 3     3   3887 use HTTP::Tiny 0.014 ();
  3         160235  
  3         116  
100 3     3   1992 use PITA::Image::Platform ();
  3         9  
  3         55  
101 3     3   1644 use PITA::Image::Task ();
  3         7  
  3         51  
102 3     3   1551 use PITA::Image::Discover ();
  0            
  0            
103             use PITA::Image::Test ();
104              
105             use vars qw{$VERSION @ISA $NOSERVER};
106             BEGIN {
107             $VERSION = '0.60';
108             @ISA = 'Process';
109             }
110              
111              
112              
113              
114              
115             #####################################################################
116             # Constructor and Accessors
117              
118             =pod
119              
120             =head2 new
121              
122             my $manager = PITA::Image->new(
123             injector => '/mnt/hdb1',
124             workarea => '/tmp',
125             );
126              
127             The C creates a new image manager. It takes two named parameters.
128              
129             =over 4
130              
131             =item injector
132              
133             The required C param is a platform-specific path to the
134             root of the already-mounted F partition (or the equivalent
135             on your operating system). The image configuration is expected to
136             exist at F within this directory.
137              
138             =item workarea
139              
140             The optional C param provides a directory writable by the
141             current user that can be used to hold any files and do any processing
142             in during the running of the image tasks.
143              
144             If you do not provide a value, C will be used
145             to find a default usable directory.
146              
147             =back
148              
149             Returns a new C object, or dies on error.
150              
151             =cut
152              
153             sub new {
154             my $class = shift;
155             my $self = bless { @_ }, $class;
156              
157             # Create some lists
158             $self->{platforms} = [];
159             $self->{tasks} = [];
160              
161             # Normalize boolean params
162             $self->{cleanup} = !! $self->{cleanup};
163              
164             # Check some params
165             unless ( $self->injector ) {
166             Carp::croak("Image 'injector' was not provided");
167             }
168             unless ( -d $self->injector ) {
169             Carp::croak("Image 'injector' does not exist");
170             }
171             unless ( -r $self->injector ) {
172             Carp::croak("Image 'injector' cannot be read, insufficient permissions");
173             }
174              
175             # Find a temporary directory to use for the testing
176             unless ( $self->workarea ) {
177             $self->{workarea} = File::Temp::tempdir();
178             }
179             unless ( $self->workarea ) {
180             Carp::croak("Image 'workarea' not provided and automatic detection failed");
181             }
182             unless ( -d $self->workarea ) {
183             Carp::croak("Image 'workarea' directory does not exist");
184             }
185             unless ( -r $self->workarea and -w _ ) {
186             Carp::croak("Image 'workarea' insufficient permissions");
187             }
188              
189             # Find the main config file
190             unless ( $self->image_conf ) {
191             $self->{image_conf} = File::Spec->catfile(
192             $self->injector, 'image.conf',
193             );
194             }
195             unless ( $self->image_conf ) {
196             Carp::croak("Did not get an image.conf location");
197             }
198             unless ( -f $self->image_conf ) {
199             Carp::croak("Failed to find image.conf in the injector");
200             }
201             unless ( -r $self->image_conf ) {
202             Carp::croak("No permissions to read scheme.conf");
203             }
204              
205             $self;
206             }
207              
208             sub cleanup {
209             $_[0]->{cleanup};
210             }
211              
212             sub injector {
213             $_[0]->{injector};
214             }
215              
216             sub workarea {
217             $_[0]->{workarea};
218             }
219              
220             sub image_conf {
221             $_[0]->{image_conf};
222             }
223              
224             sub config {
225             $_[0]->{config};
226             }
227              
228             sub perl5lib {
229             $_[0]->{perl5lib};
230             }
231              
232             sub server_uri {
233             $_[0]->{server_uri};
234             }
235              
236              
237              
238              
239              
240             #####################################################################
241             # Configuration Methods
242              
243             sub add_platform {
244             my $self = shift;
245             my $platform = PITA::Image::Platform->new( @_ );
246             push @{$self->{platforms}}, $platform;
247             1;
248             }
249              
250             sub add_task {
251             my $self = shift;
252             my $task = Params::Util::_INSTANCE($_[0], 'PITA::Image::Task')
253             or die("Passed bad param to add_task");
254             push @{$self->{tasks}}, $task;
255             1;
256             }
257              
258             sub platforms {
259             @{$_[0]->{platforms}};
260             }
261              
262             sub tasks {
263             @{$_[0]->{tasks}};
264             }
265              
266              
267              
268              
269              
270             #####################################################################
271             # Process Methods
272              
273             sub prepare {
274             my $self = shift;
275             my $class = ref($self);
276              
277             # Load the main config file
278             unless ( $self->config ) {
279             $self->{config} = Config::Tiny->read( $self->image_conf );
280             }
281             unless ( Params::Util::_INSTANCE($self->config, 'Config::Tiny') ) {
282             Carp::croak("Failed to load scheme.conf config file");
283             }
284              
285             # Verify that we can use this config file
286             my $config = $self->config->{_};
287             unless ( $config->{class} and $config->{class} eq $class ) {
288             Carp::croak("Config file is incompatible with PITA::Image");
289             }
290             unless ( $config->{version} and $config->{version} eq $VERSION ) {
291             Carp::croak("Config file is incompatible with this version of PITA::Image");
292             }
293              
294             # If provided, apply the optional lib path so some libraries
295             # can be upgraded in a pince without upgrading all the images
296             if ( $config->{perl5lib} ) {
297             $self->{perl5lib} = File::Spec->catdir(
298             $self->injector, split( /\//, $config->{perl5lib} ),
299             );
300             unless ( -d $self->perl5lib ) {
301             Carp::croak("Injector lib directory does not exist");
302             }
303             unless ( -r $self->perl5lib ) {
304             Carp::croak("Injector lib directory has no read permissions");
305             }
306             require lib;
307             lib->import( $self->perl5lib );
308             }
309              
310             # Check the support server
311             unless ( $self->server_uri ) {
312             $self->{server_uri} = URI->new($config->{server_uri});
313             }
314             unless ( $self->server_uri ) {
315             Carp::croak("Missing 'server_uri' param in image.conf");
316             }
317             unless ( Params::Util::_INSTANCE($self->server_uri, 'URI::http') ) {
318             Carp::croak("The 'server_uri' is not a HTTP(S) URI");
319             }
320             unless ( $NOSERVER ) {
321             my $response = HTTP::Tiny->new( timeout => 5 )->get( $self->server_uri );
322             unless ( $response and $response->{success} ) {
323             Carp::croak("Failed to contact SupportServer at $config->{server_uri}");
324             }
325             }
326              
327             # We expect a task at [ task ]
328             unless ( $self->config->{task} ) {
329             Carp::croak("Missing [task] section in image.conf");
330             }
331             unless ( $self->config->{task}->{task} ) {
332             Carp::croak("Missing task.task value in image.conf");
333             }
334              
335             # The ping task is a nullop
336             my $taskname = $self->config->{task}->{task};
337             if ( $taskname eq 'Ping' ) {
338             # Do nothing
339              
340             } elsif ( $taskname eq 'Discover' ) {
341             # Add a discovery task
342             $self->add_task(
343             PITA::Image::Discover->new(
344             %{$self->config->{task}},
345             platforms => [ $self->platforms ],
346             ),
347             );
348              
349             } elsif ( $taskname eq 'Test' ) {
350             # Add the testing task
351             $self->add_task(
352             PITA::Image::Test->new(
353             %{$self->config->{task}},
354             injector => $self->injector,
355             workarea => $self->workarea,
356             ),
357             );
358              
359             } else {
360             Carp::croak("Unknown task.task value in image.conf");
361             }
362              
363             $self;
364             }
365              
366             sub run {
367             my $self = shift;
368              
369             # Auto-prepare
370             $self->prepare unless $self->config;
371              
372             # Test each scheme
373             foreach my $task ( $self->tasks ) {
374             $task->run;
375             }
376              
377             1;
378             }
379              
380              
381              
382              
383              
384             #####################################################################
385             # Task Methods
386              
387             sub report {
388             my $self = shift;
389              
390             # Test each scheme
391             foreach my $task ( $self->tasks ) {
392             $self->report_task( $task );
393             }
394              
395             1;
396             }
397              
398             sub report_task {
399             my $self = shift;
400             my $task = shift;
401             my $request = $self->report_task_request($task);
402             unless ( ref($request) eq 'ARRAY' ) {
403             die "Did not generate proper report request";
404             }
405             unless ( $NOSERVER ) {
406             my $response = HTTP::Tiny->new( timeout => 5 )->request(@$request);
407             unless ( $response and $response->{success} ) {
408             die "Failed to send result report to server";
409             }
410             }
411              
412             1;
413             }
414              
415             sub report_task_request {
416             my $self = shift;
417             my $task = shift;
418             unless ( $task->result ) {
419             Carp::croak("No Result Report created to PUT");
420             }
421              
422             # Serialize the data for sending
423             my $xml = '';
424             $task->result->write( \$xml );
425             unless ( length($xml) ) {
426             Carp::croak("Failed to serialize report");
427             }
428              
429             # Send the file
430             return [
431             'PUT' => $self->report_task_uri($task),
432             {
433             headers => {
434             content_type => 'application/xml',
435             content_length => length($xml),
436             },
437             content => $xml,
438             },
439             ];
440             }
441              
442             # The location to put to
443             sub report_task_uri {
444             my $self = shift;
445             my $task = shift;
446             my $uri = $self->server_uri;
447             my $job = $task->job_id;
448             my $path = File::Spec::Unix->catfile( $uri->path || '/', $job );
449             $uri->path( $path );
450             $uri;
451             }
452              
453              
454              
455              
456              
457             #####################################################################
458             # Support Methods
459              
460             sub DESTROY {
461             # Delete our tasks and platforms in reverse order
462             ### Mostly paranoia, some actual problems if we do not
463             ### do it as strictly correct as this
464             if ( defined $_[0]->{tasks} ) {
465             foreach my $i ( reverse 0 .. $#{$_[0]->{tasks}} ) {
466             undef $_[0]->{tasks}->[$i];
467             }
468             delete $_[0]->{tasks};
469             }
470             if ( defined $_[0]->{platforms} ) {
471             foreach my $i ( reverse 0 .. $#{$_[0]->{platforms}} ) {
472             undef $_[0]->{platforms}->[$i];
473             }
474             delete $_[0]->{platforms};
475             }
476              
477             # Now remove the workarea directory
478             if ( $_[0]->{cleanup} and $_[0]->{workarea} and -d $_[0]->{workarea} ) {
479             File::Remove::remove( \1, $_[0]->{workarea} );
480             }
481             }
482              
483             1;
484              
485             =pod
486              
487             =head1 SUPPORT
488              
489             Bugs should be reported via the CPAN bug tracker at
490              
491             L
492              
493             For other issues, contact the author.
494              
495             =head1 AUTHOR
496              
497             Adam Kennedy Eadamk@cpan.orgE, L
498              
499             =head1 SEE ALSO
500              
501             The Perl Image Testing Architecture (L)
502              
503             L, L, L
504              
505             =head1 COPYRIGHT
506              
507             Copyright 2005 - 2011 Adam Kennedy.
508              
509             This program is free software; you can redistribute
510             it and/or modify it under the same terms as Perl itself.
511              
512             The full text of the license can be found in the
513             LICENSE file included with this module.
514              
515             =cut