/usr/local/CPAN/PITA/PITA/Guest/Driver/Image.pm
package PITA::Guest::Driver::Image;
# Provides a base class for PITA Guests that are system images.
# For example, Qemu, VMWare, etc
use 5.008;
use strict;
use Carp ();
use File::Path ();
use File::Temp ();
use File::Copy ();
use File::Remove ();
use File::Basename ();
use Storable ();
use Params::Util ();
use Config::Tiny ();
use Class::Inspector ();
use PITA::Guest::Driver ();
use PITA::Guest::Server::Process ();
our $VERSION = '0.50';
our @ISA = 'PITA::Guest::Driver';
#####################################################################
# Constructor and Accessors
sub new {
my $class = shift;
my $self = $class->SUPER::new(@_);
# Check we got an image.
unless ( $self->image ) {
# Pull the filename from the XML file, mapping it relative
# to the original filename and saving as an absolute path
if ( $self->{absimage} ) {
$self->{image} = delete $self->{absimage};
} else {
$self->{image} = ($self->guest->files)[0]->filename;
}
}
unless ( $self->image ) {
Carp::croak("Did not provide the location of the image_file");
}
unless ( -f $self->image and -r _ ) {
Carp::croak($self->image . ": image does not exist, or cannot be read");
}
# How much memory to use
$self->{memory} = 256 unless $self->memory;
unless ( Params::Util::_POSINT($self->memory) ) {
Carp::croak("Invalid memory amount (in meg) '" . $self->memory . "'");
}
# Snapshot should be a binary value, defaulting to true.
# This might not be the most ACCURATE, but by always defaulting
# to snapshot mode we prevent accidental harm to the image.
$self->{snapshot} = 1 unless defined $self->snapshot;
# Unless we have a support server directory, create a new one
unless ( $self->support_server_dir ) {
$self->{support_server_dir} = File::Temp::tempdir();
}
# Create the support server result files to expect
$self->{support_server_pinged} = 0;
$self->{support_server_mirrored} = [ ];
$self->{support_server_results} = [ ];
$self;
}
sub image {
$_[0]->{image};
}
sub memory {
defined $_[0]->{memory}
? $_[0]->{memory}
: $_[0]->guest->config->{memory};
}
sub snapshot {
defined $_[0]->{snapshot}
? $_[0]->{snapshot}
: $_[0]->guest->config->{memory};
}
sub support_server {
$_[0]->{support_server};
}
sub support_server_addr {
$_[0]->{support_server_addr};
}
sub support_server_port {
$_[0]->{support_server_port};
}
sub support_server_dir {
$_[0]->{support_server_dir};
}
sub support_server_pinged {
$_[0]->{support_server_pinged};
}
sub support_server_mirrored {
$_[0]->{support_server_mirrored};
}
sub support_server_results {
$_[0]->{support_server_results};
}
# Provide a default implementation.
# Many subclasses will need to override this though.
sub support_server_uri {
my $self = shift;
URI->new( "http://"
. $self->support_server_addr . ':'
. $self->support_server_port . '/'
);
}
sub perl5lib_dir {
File::Spec->catdir( $_[0]->injector_dir, 'perl5lib' );
}
sub perl5lib_classes { qw{
PITA::Scheme
PITA::Scheme::Perl
PITA::Scheme::Perl5
PITA::Scheme::Perl5::Make
PITA::Scheme::Perl5::Build
} }
#####################################################################
# PITA::Guest::Driver Methods
sub ping {
$_[0]->clean_injector;
$_[0]->ping_prepare;
$_[0]->ping_execute;
$_[0]->ping_cleanup;
return 1;
}
sub ping_prepare {
my $self = shift;
# Generate the image.conf
$self->prepare_task('ping');
# Create the support server
$self->{support_server} = $self->support_server_new;
return 1;
}
sub ping_execute {
my $self = shift;
# By default, launch the support server
$self->support_server->prepare
and
$self->support_server->run
or
Carp::croak("Failed to execute support server");
return 1;
}
sub ping_cleanup {
my $self = shift;
# Capture results from the support server
$self->support_server->finish;
$self->{support_server_pinged} = $self->support_server->pinged;
$self->{support_server_mirrored} = $self->support_server->mirrored;
$self->{support_server_results} = $self->support_server->uploaded;
# Delete the support server
delete $self->{support_server};
return 1;
}
sub discover {
my $self = shift;
$self->clean_injector;
$self->discover_prepare;
$self->discover_execute;
$self->discover_cleanup;
return 1;
}
sub discover_prepare {
my $self = shift;
# Copy in the perl5lib modules
$self->prepare_perl5lib;
# Generate the image.conf
$self->prepare_task('discover');
# Create the support server
$self->{support_server} = $self->support_server_new;
return 1;
}
sub discover_execute {
my $self = shift;
# By default, launch the support server
$self->support_server->prepare
and
$self->support_server->run
or
Carp::croak("Failed to execute support server");
return 1;
}
sub discover_cleanup {
my $self = shift;
# Capture results from the support server
$self->support_server->finish;
$self->{support_server_pinged} = $self->support_server->pinged;
$self->{support_server_mirrored} = $self->support_server->mirrored;
$self->{support_server_results} = $self->support_server->uploaded;
# require Devel::Dumpvar;
# print STDERR Devel::Dumpvar->dump($self->support_server) . "\n";
# Get the report file contents
my $string = $self->support_server->upload('/1');
unless ( Params::Util::_SCALAR($string) ) {
Carp::croak("Discovery report was not uploaded to the support server");
}
# Parse into a report
my $report = PITA::XML::Guest->read($string);
unless ( $report->platforms ) {
Carp::croak("Discovery report did not contain any platforms");
}
# Add the detected platforms to the configured guest
foreach my $platform ( $report->platforms ) {
$self->guest->add_platform( $platform );
}
# Cleanup the support server
delete $self->{support_server};
return 1;
}
sub test {
my $self = shift;
$self->clean_injector;
$self->test_prepare(@_);
$self->test_execute(@_);
my $report = $self->test_cleanup(@_);
return $report;
}
sub test_prepare {
my $self = shift;
# Copy in the perl5lib modules
$self->prepare_perl5lib(@_);
# Generate the scheme.conf into the injector
$self->prepare_task(@_);
# Create the support server
$self->{support_server} = $self->support_server_new;
return 1;
}
sub test_execute {
my $self = shift;
# By default, launch the support server
$self->support_server->prepare
and
$self->support_server->run
or
Carp::croak("Failed to execute support server");
return 1;
}
sub test_cleanup {
my $self = shift;
my $request = shift;
# Capture results from the support server
$self->support_server->finish;
$self->{support_server_pinged} = $self->support_server->pinged;
$self->{support_server_mirrored} = $self->support_server->mirrored;
$self->{support_server_results} = $self->support_server->uploaded;
# Get the report
my $string = $self->support_server->upload('/' . $request->id);
unless ( $string ) {
Carp::croak("Failed to get report " . $request->id . " from support server");
}
# Parse into a report
my $report = PITA::XML::Report->read($string);
unless ( $report ) {
Carp::croak("Discovery report did not contain any platforms");
}
# Cleanup the support server
delete $self->{support_server};
# Return the report
return $report;
}
#####################################################################
# PITA::Guest:Driver::Image Methods
# The command used to execute the guest
sub execute_cmd {
my $class = ref $_[0] || $_[0];
die "The guest driver class $class does not implement execute_cmd";
}
sub prepare_task {
my $self = shift;
my $task = shift;
# Create the image.conf config file
my $image_conf = Config::Tiny->new;
$image_conf->{_} = {
class => 'PITA::Image',
version => '0.43',
server_uri => $self->support_server_uri,
};
if ( -d $self->perl5lib_dir ) {
$image_conf->{_}->{perl5lib} = 'perl5lib';
}
# Add the tasks
if ( Params::Util::_STRING($task) and $task eq 'ping' ) {
$image_conf->{task} = {
task => 'Ping',
job_id => 1,
};
} elsif ( Params::Util::_STRING($task) and $task eq 'discover' ) {
# Discovery always uses the job_id 1 (for now)
$image_conf->{task} = {
task => 'Discover',
job_id => 1,
};
# Tell the support server to expect the report
$self->{support_server_results} = '/1';
} elsif ( $self->_REQUEST($task) ) {
# Copy the request, because we need to alter it
my $request = Storable::dclone( $task );
# Which testing context will we run in
### Don't check for error, we WANT to be undef if not a platform
my $platform = Params::Util::_INSTANCE(shift, 'PITA::XML::Platform');
# Set the tarball filename to be relative to current
my $filename = File::Basename::basename( $request->file->filename );
my $tarball_from = $request->file->filename;
my $tarball_to = File::Spec->catfile(
$self->injector_dir, $filename,
);
$request->file->{filename} = $filename;
# Copy the tarball into the injector
unless ( File::Copy::copy( $tarball_from, $tarball_to ) ) {
Carp::croak("Failed to copy in test package: $!");
}
# Save the request file to the injector
my $request_file = 'request-' . $request->id . '.pita';
my $request_path = File::Spec->catfile( $self->injector_dir, $request_file );
$request->write( $request_path );
# Save the details of the above to the task section
$image_conf->{task} = {
task => 'Test',
job_id => $request->id,
scheme => $request->scheme,
path => $platform ? $platform->path : '', # '' is default
config => $request_file,
};
# Tell the support server to expect the report
$self->{support_server_results} = [ "/" . $request->id ];
} else {
Carp::croak("Unexpected or invalid task param to prepare_task");
}
# Save the image.conf file
my $image_file = File::Spec->catfile( $self->injector_dir, 'image.conf' );
unless ( $image_conf->write( $image_file ) ) {
Carp::croak("Failed to write config to $image_file");
}
return 1;
}
# Copy in the perl5lib modules
sub prepare_perl5lib {
my $self = shift;
my $perl5lib = $self->perl5lib_dir;
unless ( -d $perl5lib ) {
mkdir( $perl5lib ) or Carp::croak("Failed to create perl5lib dir");
}
# Locate and copy in various classes
foreach my $c ( $self->perl5lib_classes ) {
my $from = Class::Inspector->loaded_filename($c)
|| Class::Inspector->resolved_filename($c)
or die "$c is not available to copy to perl5lib";
my $to = File::Spec->catfile(
$self->perl5lib_dir,
Class::Inspector->filename( $c ),
);
File::Path::mkpath( File::Basename::dirname( $to ) ); # Croaks on error
File::Copy::copy( $from, $to )
or die "Failed to copy $from to $to";
}
return 1;
}
sub clean_injector {
my $self = shift;
# Scan for stuff in the injector
my $injector = $self->injector_dir;
opendir( INJECTOR, $injector ) or die "opendir: $!";
my @files = readdir( INJECTOR );
closedir( INJECTOR );
# Delete it all
foreach my $f ( File::Spec->no_upwards(@files) ) {
my $path = File::Spec->catfile( $injector, $f );
File::Remove::remove( \1, $path ) and next;
die "Failed to remove $f from injector directory";
}
return 1;
}
#####################################################################
# Support Methods
sub DESTROY {
$_[0]->SUPER::DESTROY();
if ( $_[0]->{support_server_dir} and -d $_[0]->{support_server_dir} ) {
File::Remove::remove( \1, $_[0]->{support_server_dir} );
delete $_[0]->{support_server_dir};
}
}
1;