File Coverage

blib/lib/PITA/Guest/Driver/Image.pm
Criterion Covered Total %
statement 37 39 94.8
branch n/a
condition n/a
subroutine 13 13 100.0
pod n/a
total 50 52 96.1


line stmt bran cond sub pod time code
1             package PITA::Guest::Driver::Image;
2              
3             # Provides a base class for PITA Guests that are system images.
4             # For example, Qemu, VMWare, etc
5              
6 1     1   2734 use 5.008;
  1         4  
  1         55  
7 1     1   9 use strict;
  1         2  
  1         46  
8 1     1   7 use Carp ();
  1         2  
  1         20  
9 1     1   8 use File::Path ();
  1         2  
  1         20  
10 1     1   6 use File::Temp ();
  1         3  
  1         16  
11 1     1   6 use File::Copy ();
  1         2  
  1         29  
12 1     1   7 use File::Remove ();
  1         2  
  1         35  
13 1     1   8 use File::Basename ();
  1         3  
  1         213  
14 1     1   38 use Storable ();
  1         3  
  1         21  
15 1     1   5 use Params::Util ();
  1         2  
  1         25  
16 1     1   3462 use Config::Tiny ();
  1         1864  
  1         38  
17 1     1   4439 use Class::Inspector ();
  1         4836  
  1         35  
18 1     1   94 use PITA::Guest::Driver ();
  0            
  0            
19             use PITA::Guest::Server::Process ();
20              
21             our $VERSION = '0.60';
22             our @ISA = 'PITA::Guest::Driver';
23              
24              
25              
26              
27              
28             #####################################################################
29             # Constructor and Accessors
30              
31             sub new {
32             my $class = shift;
33             my $self = $class->SUPER::new(@_);
34              
35             # Check we got an image.
36             unless ( $self->image ) {
37             # Pull the filename from the XML file, mapping it relative
38             # to the original filename and saving as an absolute path
39             if ( $self->{absimage} ) {
40             $self->{image} = delete $self->{absimage};
41             } else {
42             $self->{image} = ($self->guest->files)[0]->filename;
43             }
44             }
45             unless ( $self->image ) {
46             Carp::croak("Did not provide the location of the image_file");
47             }
48             unless ( -f $self->image and -r _ ) {
49             Carp::croak($self->image . ": image does not exist, or cannot be read");
50             }
51              
52             # How much memory to use
53             $self->{memory} = 256 unless $self->memory;
54             unless ( Params::Util::_POSINT($self->memory) ) {
55             Carp::croak("Invalid memory amount (in meg) '" . $self->memory . "'");
56             }
57              
58             # Snapshot should be a binary value, defaulting to true.
59             # This might not be the most ACCURATE, but by always defaulting
60             # to snapshot mode we prevent accidental harm to the image.
61             $self->{snapshot} = 1 unless defined $self->snapshot;
62              
63             # Unless we have a support server directory, create a new one
64             unless ( $self->support_server_dir ) {
65             $self->{support_server_dir} = File::Temp::tempdir();
66             }
67              
68             # Create the support server result files to expect
69             $self->{support_server_pinged} = 0;
70             $self->{support_server_mirrored} = [ ];
71             $self->{support_server_results} = [ ];
72              
73             $self;
74             }
75              
76             sub image {
77             $_[0]->{image};
78             }
79              
80             sub memory {
81             defined $_[0]->{memory}
82             ? $_[0]->{memory}
83             : $_[0]->guest->config->{memory};
84             }
85              
86             sub snapshot {
87             defined $_[0]->{snapshot}
88             ? $_[0]->{snapshot}
89             : $_[0]->guest->config->{memory};
90             }
91              
92             sub support_server {
93             $_[0]->{support_server};
94             }
95              
96             sub support_server_addr {
97             $_[0]->{support_server_addr};
98             }
99              
100             sub support_server_port {
101             $_[0]->{support_server_port};
102             }
103              
104             sub support_server_dir {
105             $_[0]->{support_server_dir};
106             }
107              
108             sub support_server_pinged {
109             $_[0]->{support_server_pinged};
110             }
111              
112             sub support_server_mirrored {
113             $_[0]->{support_server_mirrored};
114             }
115              
116             sub support_server_results {
117             $_[0]->{support_server_results};
118             }
119              
120             # Provide a default implementation.
121             # Many subclasses will need to override this though.
122             sub support_server_uri {
123             my $self = shift;
124             URI->new( "http://"
125             . $self->support_server_addr . ':'
126             . $self->support_server_port . '/'
127             );
128             }
129              
130             sub perl5lib_dir {
131             File::Spec->catdir( $_[0]->injector_dir, 'perl5lib' );
132             }
133              
134             sub perl5lib_classes { qw{
135             PITA::Scheme
136             PITA::Scheme::Perl
137             PITA::Scheme::Perl5
138             PITA::Scheme::Perl5::Make
139             PITA::Scheme::Perl5::Build
140             } }
141              
142              
143              
144              
145              
146             #####################################################################
147             # PITA::Guest::Driver Methods
148              
149             sub ping {
150             $_[0]->clean_injector;
151             $_[0]->ping_prepare;
152             $_[0]->ping_execute;
153             $_[0]->ping_cleanup;
154             return 1;
155             }
156              
157             sub ping_prepare {
158             my $self = shift;
159              
160             # Generate the image.conf
161             $self->prepare_task('ping');
162              
163             # Create the support server
164             $self->{support_server} = $self->support_server_new;
165              
166             return 1;
167             }
168              
169             sub ping_execute {
170             my $self = shift;
171              
172             # By default, launch the support server
173             $self->support_server->prepare
174             and
175             $self->support_server->run
176             or
177             Carp::croak("Failed to execute support server");
178              
179             return 1;
180             }
181              
182             sub ping_cleanup {
183             my $self = shift;
184              
185             # Capture results from the support server
186             $self->support_server->finish;
187             $self->{support_server_pinged} = $self->support_server->pinged;
188             $self->{support_server_mirrored} = $self->support_server->mirrored;
189             $self->{support_server_results} = $self->support_server->uploaded;
190              
191             # Delete the support server
192             delete $self->{support_server};
193              
194             return 1;
195             }
196              
197             sub discover {
198             my $self = shift;
199             $self->clean_injector;
200             $self->discover_prepare;
201             $self->discover_execute;
202             $self->discover_cleanup;
203             return 1;
204             }
205              
206             sub discover_prepare {
207             my $self = shift;
208              
209             # Copy in the perl5lib modules
210             $self->prepare_perl5lib;
211              
212             # Generate the image.conf
213             $self->prepare_task('discover');
214              
215             # Create the support server
216             $self->{support_server} = $self->support_server_new;
217              
218             return 1;
219             }
220              
221             sub discover_execute {
222             my $self = shift;
223              
224             # By default, launch the support server
225             $self->support_server->prepare
226             and
227             $self->support_server->run
228             or
229             Carp::croak("Failed to execute support server");
230              
231             return 1;
232             }
233              
234             sub discover_cleanup {
235             my $self = shift;
236              
237             # Capture results from the support server
238             $self->support_server->finish;
239             $self->{support_server_pinged} = $self->support_server->pinged;
240             $self->{support_server_mirrored} = $self->support_server->mirrored;
241             $self->{support_server_results} = $self->support_server->uploaded;
242              
243             # Get the report file contents
244             my $string = $self->support_server->upload('/1');
245             unless ( Params::Util::_SCALAR($string) ) {
246             die "Discovery report was not uploaded to the support server";
247             }
248              
249             # Parse into a report
250             my $report = PITA::XML::Guest->read($string);
251             unless ( $report->platforms ) {
252             die "Discovery report did not contain any platforms";
253             }
254              
255             # Add the detected platforms to the configured guest
256             foreach my $platform ( $report->platforms ) {
257             $self->guest->add_platform( $platform );
258             }
259              
260             # Cleanup the support server
261             delete $self->{support_server};
262              
263             return 1;
264             }
265              
266             sub test {
267             my $self = shift;
268             $self->clean_injector;
269             $self->test_prepare(@_);
270             $self->test_execute(@_);
271             my $report = $self->test_cleanup(@_);
272             return $report;
273             }
274              
275             sub test_prepare {
276             my $self = shift;
277              
278             # Copy in the perl5lib modules
279             $self->prepare_perl5lib(@_);
280              
281             # Generate the scheme.conf into the injector
282             $self->prepare_task(@_);
283              
284             # Create the support server
285             $self->{support_server} = $self->support_server_new;
286              
287             return 1;
288             }
289              
290             sub test_execute {
291             my $self = shift;
292              
293             # By default, launch the support server
294             $self->support_server->prepare
295             and
296             $self->support_server->run
297             or
298             Carp::croak("Failed to execute support server");
299              
300             return 1;
301             }
302              
303             sub test_cleanup {
304             my $self = shift;
305             my $request = shift;
306              
307             # Capture results from the support server
308             $self->support_server->finish;
309             $self->{support_server_pinged} = $self->support_server->pinged;
310             $self->{support_server_mirrored} = $self->support_server->mirrored;
311             $self->{support_server_results} = $self->support_server->uploaded;
312              
313             # Get the report
314             my $string = $self->support_server->upload('/' . $request->id);
315             unless ( $string ) {
316             Carp::croak("Failed to get report " . $request->id . " from support server");
317             }
318              
319             # Parse into a report
320             my $report = PITA::XML::Report->read($string);
321             unless ( $report ) {
322             Carp::croak("Discovery report did not contain any platforms");
323             }
324              
325             # Cleanup the support server
326             delete $self->{support_server};
327              
328             # Return the report
329             return $report;
330             }
331              
332              
333              
334              
335              
336             #####################################################################
337             # PITA::Guest:Driver::Image Methods
338              
339             # The command used to execute the guest
340             sub execute_cmd {
341             my $class = ref $_[0] || $_[0];
342             die "The guest driver class $class does not implement execute_cmd";
343             }
344              
345             sub prepare_task {
346             my $self = shift;
347             my $task = shift;
348              
349             # Create the image.conf config file
350             my $image_conf = Config::Tiny->new;
351             $image_conf->{_} = {
352             class => 'PITA::Image',
353             version => '0.60',
354             server_uri => $self->support_server_uri,
355             };
356             if ( -d $self->perl5lib_dir ) {
357             $image_conf->{_}->{perl5lib} = 'perl5lib';
358             }
359              
360             # Add the tasks
361             if ( Params::Util::_STRING($task) and $task eq 'ping' ) {
362             $image_conf->{task} = {
363             task => 'Ping',
364             job_id => 1,
365             };
366              
367             } elsif ( Params::Util::_STRING($task) and $task eq 'discover' ) {
368             # Discovery always uses the job_id 1 (for now)
369             $image_conf->{task} = {
370             task => 'Discover',
371             job_id => 1,
372             };
373              
374             # Tell the support server to expect the report
375             $self->{support_server_results} = '/1';
376              
377             } elsif ( $self->_REQUEST($task) ) {
378             # Copy the request, because we need to alter it
379             my $request = Storable::dclone( $task );
380              
381             # Which testing context will we run in
382             ### Don't check for error, we WANT to be undef if not a platform
383             my $platform = Params::Util::_INSTANCE(shift, 'PITA::XML::Platform');
384              
385             # Set the tarball filename to be relative to current
386             my $filename = File::Basename::basename( $request->file->filename );
387             my $tarball_from = $request->file->filename;
388             my $tarball_to = File::Spec->catfile(
389             $self->injector_dir, $filename,
390             );
391             $request->file->{filename} = $filename;
392              
393             # Copy the tarball into the injector
394             unless ( File::Copy::copy( $tarball_from, $tarball_to ) ) {
395             Carp::croak("Failed to copy in test package: $!");
396             }
397              
398             # Save the request file to the injector
399             my $request_file = 'request-' . $request->id . '.pita';
400             my $request_path = File::Spec->catfile( $self->injector_dir, $request_file );
401             $request->write( $request_path );
402              
403             # Save the details of the above to the task section
404             $image_conf->{task} = {
405             task => 'Test',
406             job_id => $request->id,
407             scheme => $request->scheme,
408             path => $platform ? $platform->path : '', # '' is default
409             config => $request_file,
410             };
411              
412             # Tell the support server to expect the report
413             $self->{support_server_results} = [ "/" . $request->id ];
414              
415             } else {
416             Carp::croak("Unexpected or invalid task param to prepare_task");
417             }
418              
419             # Save the image.conf file
420             my $image_file = File::Spec->catfile( $self->injector_dir, 'image.conf' );
421             unless ( $image_conf->write( $image_file ) ) {
422             Carp::croak("Failed to write config to $image_file");
423             }
424              
425             return 1;
426             }
427              
428             # Copy in the perl5lib modules
429             sub prepare_perl5lib {
430             my $self = shift;
431             my $perl5lib = $self->perl5lib_dir;
432             unless ( -d $perl5lib ) {
433             mkdir( $perl5lib ) or Carp::croak("Failed to create perl5lib dir");
434             }
435              
436             # Locate and copy in various classes
437             foreach my $c ( $self->perl5lib_classes ) {
438             my $from = Class::Inspector->loaded_filename($c)
439             || Class::Inspector->resolved_filename($c)
440             or die "$c is not available to copy to perl5lib";
441             my $to = File::Spec->catfile(
442             $self->perl5lib_dir,
443             Class::Inspector->filename( $c ),
444             );
445             File::Path::mkpath( File::Basename::dirname( $to ) ); # Croaks on error
446             File::Copy::copy( $from, $to )
447             or die "Failed to copy $from to $to";
448             }
449              
450             return 1;
451             }
452              
453             sub clean_injector {
454             my $self = shift;
455              
456             # Scan for stuff in the injector
457             my $injector = $self->injector_dir;
458             # unless ( -d $injector ) {
459             # File::Path::mkpath( $injector ) or die "mkpath($injector): $!";
460             # }
461             opendir( INJECTOR, $injector ) or die "opendir($injector): $!";
462             my @files = readdir( INJECTOR );
463             closedir( INJECTOR );
464              
465             # Delete it all
466             foreach my $f ( File::Spec->no_upwards(@files) ) {
467             my $path = File::Spec->catfile( $injector, $f );
468             File::Remove::remove( \1, $path ) and next;
469             die "Failed to remove $f from injector directory";
470             }
471              
472             return 1;
473             }
474              
475              
476              
477              
478              
479             #####################################################################
480             # Support Methods
481              
482             sub DESTROY {
483             $_[0]->SUPER::DESTROY();
484             if ( $_[0]->{support_server_dir} and -d $_[0]->{support_server_dir} ) {
485             File::Remove::remove( \1, $_[0]->{support_server_dir} );
486             delete $_[0]->{support_server_dir};
487             }
488             }
489              
490             1;