From 13b089ffd1e2ed6fee8e31fcbc02f348ca8485ce Mon Sep 17 00:00:00 2001 From: vallard Date: Sat, 3 Apr 2010 00:07:49 +0000 Subject: [PATCH] adds the imgexport/imgimport command to extract/pull in images to xCAT. This initial release just has export, hope to get import done by next week git-svn-id: https://svn.code.sf.net/p/xcat/code/xcat-core/trunk@5659 8638fb3e-16cb-4fca-ae20-7b5d299a9bcd --- xCAT-server/lib/xcat/plugins/imgport.pm | 415 ++++++++++++++++++++++++ 1 file changed, 415 insertions(+) create mode 100644 xCAT-server/lib/xcat/plugins/imgport.pm diff --git a/xCAT-server/lib/xcat/plugins/imgport.pm b/xCAT-server/lib/xcat/plugins/imgport.pm new file mode 100644 index 000000000..73d73b33b --- /dev/null +++ b/xCAT-server/lib/xcat/plugins/imgport.pm @@ -0,0 +1,415 @@ +# Sumavi Inc (C) 2010 +##################################################### +# imgport will export and import xCAT stateless, statelite, and diskful templates. +# This will make it so that you can easily share your images with others. +# All your images are belong to us! +package xCAT_plugin::imgport; +use strict; +use warnings; +use xCAT::Table; +use xCAT::Schema; +use Data::Dumper; +use XML::Simple; +use xCAT::NodeRange qw/noderange abbreviate_noderange/; +use xCAT::Utils; +use POSIX qw/strftime/; +use Getopt::Long; +use File::Temp; +use File::Copy; +use File::Path; +use Cwd; +my $requestcommand; + +1; + +#some quick aliases to table/value +my %shortnames = ( + groups => [qw(nodelist groups)], + tags => [qw(nodelist groups)], + mgt => [qw(nodehm mgt)], + #switch => [qw(switch switch)], + ); + +##################################################### +# Return list of commands handled by this plugin +##################################################### +sub handled_commands +{ + return { + imgexport => "imgport", + imgimport => "imgport", + }; +} + +##################################################### +# Process the command +##################################################### +sub process_request +{ + #use Getopt::Long; + Getopt::Long::Configure("bundling"); + #Getopt::Long::Configure("pass_through"); + Getopt::Long::Configure("no_pass_through"); + + my $request = shift; + my $callback = shift; + $requestcommand = shift; + my $command = $request->{command}->[0]; + my $args = $request->{arg}; + + if ($command eq "imgexport"){ + return xexport($request, $callback); + }elsif ($command eq "imgimport"){ + return ximport($request, $callback); + }else{ + print "Error: $command not found in export\n"; + $callback->({error=>["Error: $command not found in this module."],errorcode=>[1]}); + #return (1, "$command not found in sumavinode"); + } +} + + +# return sthe mount point to the requesting node. +sub xexport { + my $request = shift; + my $callback = shift; + my %rsp; # response + my $help; + my @extra; + + my $xusage = sub { + my $ec = shift; + push@{ $rsp{data} }, "imgexport: Creates a tarball of an existing xCAT image"; + push@{ $rsp{data} }, "Usage: imgexport [directory] [[--extra ] ... ]"; + if($ec){ $rsp{errorcode} = $ec; } + $callback->(\%rsp); + }; + unless(defined($request->{arg})){ $xusage->(1); return; } + @ARGV = @{ $request->{arg}}; + if($#ARGV eq -1){ + $xusage->(1); + return; + } + + GetOptions( + 'h|?|help' => \$help, + 'extra=s' => \@extra + ); + + if($help){ + $xusage->(0); + return; + } + + # ok, we're done with all that. Now lets actually start doing some work. + my $img_name = shift @ARGV; + my $dest = shift @ARGV; + + $callback->( {data => ["Exporting $img_name..."]}); + # check if all files are in place + my $attrs = get_image_info($img_name, $callback, @extra); + #print Dumper($attrs); + + unless($attrs){ + return 1; + } + + # make manifest and tar it up. + make_bundle($img_name, $dest, $attrs, $callback); + +} + +# verify the image and return the values +sub get_image_info { + my $imagename = shift; + my $callback = shift; + my @extra = @_; + my $errors = 0; + + my $ostab = new xCAT::Table('osimage', -create=>1); + unless($ostab){ + $callback->( + {error => ["Unable to open table 'osimage' What's going on here dude?"],errorcode=>1} + ); + return 0; + } + + (my $attrs) = $ostab->getAttribs({imagename => $imagename}, 'profile', 'provmethod', 'osvers', 'osarch' ); + if (!$attrs) { + $callback->({error=>["Cannot find image \'$imagename\' from the osimage table."],errorcode=>[1]}); + return 0; + } + + unless($attrs->{provmethod}){ + $callback->({error=>["The 'provmethod' field is not set for \'$imagename\' in the osimage table."],errorcode=>[1]}); + $errors++; + } + + unless($attrs->{profile}){ + $callback->({error=>["The 'profile' field is not set for \'$imagename\' in the osimage table."],errorcode=>[1]}); + $errors++; + } + + unless($attrs->{osvers}){ + $callback->({error=>["The 'osvers' field is not set for \'$imagename\' in the osimage table."],errorcode=>[1]}); + $errors++; + } + + unless($attrs->{osarch}){ + $callback->({error=>["The 'osarch' field is not set for \'$imagename\' in the osimage table."],errorcode=>[1]}); + $errors++; + } + + unless($attrs->{provmethod} =~ /install|netboot|statelite/){ + $callback->({error=>["Exporting images with 'provemethod' " . $attrs->{provmethod} . " is not supported. Hint: install, netboot, or statelite"],errorcode=>[1]}); + $errors++; + } + + if($errors){ + return 0; + } + + $attrs = get_files($imagename, $callback, $attrs); + if($#extra > -1){ + my $ex = get_extra($callback, @extra); + if($ex){ + $attrs->{extra} = $ex; + } + } + + # if we get nothing back, then we couldn't find the files. How sad, return nuthin' + return $attrs; + +} + + +# returns a hash of files +# extra { +# file => dir +# file => dir +# } + +sub get_extra { + my $callback = shift; + my @extra = @_; + my $extra; + + # make sure that the extra is formatted correctly: + foreach my $e (@extra){ + unless($e =~ /:/){ + $callback->({error=>["Extra argument $e is not formatted correctly and will be ignored. Hint: "],errorcode=>[1]}); + next; + } + my ($file , $to_dir) = split(/:/, $e); + unless( -r $file){ + $callback->({error=>["Can not find Extra file $file. Argument will be ignored"],errorcode=>[1]}); + next; + } + #print "$file => $to_dir"; + push @{ $extra}, { 'src' => $file, 'dest' => $to_dir }; + } + return $extra; +} + + + +# well we check to make sure the files exist and then we return them. +sub get_files{ + my $imagename = shift; + my $errors = 0; + my $callback = shift; + my $attrs = shift; # we'll hopefully get a reference to it and modify this variable. + my @arr; # array of directory search paths + my $template = ''; + + # todo is XCATROOT not going to be /opt/xcat/ in normal situations? We'll always + # assume it is for now + my $xcatroot = "/opt/xcat"; + + # get the install root + my $installroot = xCAT::Utils->getInstallDir(); + unless($installroot){ + $installroot = '/install'; + } + + my $provmethod = $attrs->{provmethod}; + + # here's the case for the install. All we need at first is the + # template. That should do it. + if($provmethod =~ /install/){ + # we need to get the template for this one! + @arr = ("$installroot/custom/install", "$xcatroot/share/xcat/install"); + my $template = look_for_file('tmpl', $attrs, @arr); + unless($template){ + $callback->({error=>["Couldn't find install template for $imagename"],errorcode=>[1]}); + $errors++; + }else{ + $callback->( {data => ["$template"]}); + $attrs->{template} = $template; + } + $attrs->{media} = "required"; + } + + + # for stateless I need to save the + # ramdisk + # the kernel + # the rootimg.gz + if($provmethod =~ /netboot/){ + @arr = ("$installroot/netboot"); + + # look for ramdisk + my $ramdisk = look_for_file('initrd.gz', $attrs, @arr); + unless($ramdisk){ + $callback->({error=>["Couldn't find ramdisk (initrd.gz) for $imagename"],errorcode=>[1]}); + $errors++; + }else{ + $callback->( {data => ["$ramdisk"]}); + $attrs->{ramdisk} = $ramdisk; + } + + # look for kernel + my $kernel = look_for_file('kernel', $attrs, @arr); + unless($kernel){ + $callback->({error=>["Couldn't find kernel (kernel) for $imagename"],errorcode=>[1]}); + $errors++; + }else{ + $callback->( {data => ["$kernel"]}); + $attrs->{kernel} = $kernel; + } + + # look for rootimg.gz + my $rootimg = look_for_file('rootimg.gz', $attrs, @arr); + unless($rootimg){ + $callback->({error=>["Couldn't find rootimg (rootimg.gz) for $imagename"],errorcode=>[1]}); + $errors++; + }else{ + $callback->( {data => ["$rootimg"]}); + $attrs->{rootimg} = $rootimg; + } + } + + + + + if($errors){ + $attrs = 0; + } + return $attrs; +} + + +# argument: +# type of file: This is usually the suffix of the file, or the file name. +# attributes: These are the paramaters you got from the osimage table in a hash. +# @dirs: Some search paths where we'll start looking for them. +# then we just return a string of the full path to where the file is. +# mostly because we just ooze awesomeness. +sub look_for_file { + my $file = shift; + my $attrs = shift; + my @dirs = @_; + my $r_file = ''; + + my $profile = $attrs->{profile}; + my $arch = $attrs->{osarch}; + my $distname = $attrs->{osvers}; + my $dd = $distname; # dd is distro directory, or disco dave, whichever you prefer. + + + # go through the directories and look for the file. We hopefully will find it... + foreach my $d (@dirs){ + # widdle down rhel5.4, rhel5., rhel5, rhel, rhe, rh, r, + until(-r "$d/$dd" or not $dd){ + chop($dd); + } + if($distname && $file eq 'tmpl'){ + # now look for the file name: foo.rhel5.x86_64.tmpl + (-r "$d/$dd/$profile.$distname.$arch.tmpl") && (return "$d/$dd/$profile.$distname.$arch.tmpl"); + + # now look for the file name: foo.rhel5.tmpl + (-r "$d/$dd/$profile.$distname.tmpl") && (return "$d/$dd/$profile.$distname.tmpl"); + + # now look for the file name: foo.x86_64.tmpl + (-r "$d/$dd/$profile.$arch.tmpl") && (return "$d/$dd/$profile.$arch.tmpl"); + + # finally, look for the file name: foo.tmpl + (-r "$d/$dd/$profile.tmpl") && (return "$d/$dd/$profile.tmpl"); + }else{ + # this may find the ramdisk: /install/netboot/ + (-r "$d/$dd/$arch/$profile/$file") && (return "$d/$dd/$arch/$profile/$file"); + } + } + + # I got nothing man. Can't find it. Sorry 'bout that. + # returning nothing: + return ''; +} + + +# here's where we make the tarball +sub make_bundle { + my $imagename = shift; + my $dest = shift; + my $attribs = shift; + my $callback = shift; + + # tar ball is made in local working directory. Sometimes doing this in /tmp + # is bad. In the case of my development machine, the / filesystem was nearly full. + # so doing it in cwd is easy and predictable. + my $dir = getcwd; + my $ttpath = mkdtemp("$dir/export.$$.XXXXXX"); + my $tpath = "$ttpath/$imagename"; + mkdir("$tpath"); + chmod 0755,$tpath; + + # make manifest.xml file. So easy! This is why we like XML. I didn't like + # the idea at first though. + my $xml = new XML::Simple(RootName =>'xcatimage'); + open(FILE,">$tpath/manifest.xml") or die "Could not open $tpath/manifest.xml"; + print FILE $xml->XMLout($attribs, noattr => 1, xmldecl => ''); + #print $xml->XMLout($attribs, noattr => 1, xmldecl => ''); + close(FILE); + + + # these are the only files we copy in. (unless you have extras) + for my $a ("kernel", "template", "ramdisk", "rootimg"){ + if($attribs->{$a}){ + copy($attribs->{$a}, $tpath); + } + } + + + # extra files get copied in the extra directory. + if($attribs->{extra}){ + mkdir("$tpath/extra"); + chmod 0755,"$tpath/extra"; + foreach(@{ $attribs->{extra} }){ + copy($_->{src}, "$tpath/extra"); + } + } + + # now get right below all this stuff and tar it up. + chdir($ttpath); + unless($dest){ + $dest = "$dir/$imagename.tgz"; + } + + # if no absolute path specified put it in the cwd + unless($dest =~ /^\//){ + $dest = "$dir/$dest"; + } + + $callback->( {data => ["Compressing $imagename bundle. Please be patient."]}); + my $rc = system("tar czvf $dest . "); + if($rc) { + $callback->({error=>["Failed to compress archive! (Maybe there was no space left?)"],errorcode=>[1]}); + return; + } + chdir($dir); + $rc = system("rm -rf $ttpath"); + if ($rc) { + $callback->({error=>["Failed to clean up temp space $ttpath"],errorcode=>[1]}); + return; + } +}