# 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"); } } # extract the bundle, then add it to the osimage table. Basically the ying of the yang of the xexport # function. sub ximport { my $request = shift; my $callback = shift; my %rsp; # response my $help; my $xusage = sub { my $ec = shift; push@{ $rsp{data} }, "imgimport: Takes in an xCAT image bundle and defines it to xCAT so you can use it"; push@{ $rsp{data} }, "Usage: "; push@{ $rsp{data} }, "\timgimport [-h|--help] - displays this help message"; push@{ $rsp{data} }, "\timgimport - imports image. Image should be an xCAT bundle"; 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, ); if($help){ $xusage->(0); return; } # first extract the bundle extract_bundle($request, $callback); } # function to export your image. The image should already be in production, work well, and have # no bugs. Lots of places will have problems because the image may not be in osimage table # or they may have hardcoded things, or have post install scripts. 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; } } $attrs->{imagename} = $imagename; # 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/imgexport.$$.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; } } sub extract_bundle { my $request = shift; my $callback = shift; @ARGV = @{ $request->{arg} }; my $xml; my $data; my $datas; my $bundle = shift @ARGV; # extract the image in temp path in cwd my $dir = getcwd; my $tpath = mkdtemp("$dir/imgimport.$$.XXXXXX"); $callback->({data=>["Unbundling image..."],errorcode=>[1]}); my $rc = system("tar zxf $bundle -C $tpath"); if($rc){ $callback->({error => ["Failed to extract bundle $bundle"],errorcode=>[1]}); } # get all the files in the tpath. These should be all the image names. my @files = < $tpath/* >; # go through each image directory. Find the XML and put it into the array. If there are any # errors then the whole thing is over and we error and leave. foreach my $imgdir (@files){ print "$imgdir \n"; unless(-r "$imgdir/manifest.xml"){ $callback->({error=>["Failed to find manifest.xml file in image bundle"],errorcode=>[1]}); return; } $xml = new XML::Simple; # get the data! # put it in an eval string so that it $data = eval { $xml->XMLin("$imgdir/manifest.xml") }; if($@){ $callback->({error=>$@,errorcode=>[1]}); return; } print Dumper($data); #push @{$datas}, $data; # now we need to import the files... unless(verify_manifest($data, $callback)){ next; } #print "manifest looks good, lets import!\n"; set_config($data, $callback); # now place files in appropriate directories. make_files($data, $callback); } } sub set_config { my $data = shift; my $callback = shift; my $ostab = xCAT::Table->new('osimage',-create => 1,-autocommit => 0); my %keyhash; my $osimage = $data->{imagename}; $callback->({data=>["Adding $osimage"],errorcode=>[1]}); # now we make a quick hash of what we want to put into this $keyhash{provmethod} = $data->{provmethod}; $keyhash{profile} = $data->{profile}; $keyhash{osvers} = $data->{osvers}; $keyhash{osarch} = $data->{osarch}; $ostab->setAttribs({imagename => $osimage }, \%keyhash ); $ostab->commit; } sub verify_manifest { my $data = shift; my $callback = shift; my $errors = 0; # first make sure that the stuff is defined! unless($data->{imagename}){ $callback->({error=>["The 'imagename' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{provmethod}){ $callback->({error=>["The 'provmethod' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{profile}){ $callback->({error=>["The 'profile' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{osvers}){ $callback->({error=>["The 'osvers' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{osarch}){ $callback->({error=>["The 'osarch' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{provmethod} =~ /install|netboot|statelite/){ $callback->({error=>["Importing images with 'provemethod' " . $data->{provmethod} . " is not supported. Hint: install, netboot, or statelite"],errorcode=>[1]}); $errors++; } # if the install method is used, then we need to have certain files in place. if($data->{provmethod} =~ /install/){ # we need to get the template for this one! unless($data->{template}){ $callback->({error=>["The 'osarch' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } #$attrs->{media} = "required"; (need to do something to verify media! }elsif($data->{provmethod} =~ /netboot|statelite/){ unless($data->{ramdisk}){ $callback->({error=>["The 'ramdisk' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{kernel}){ $callback->({error=>["The 'kernel' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } unless($data->{rootimg}){ $callback->({error=>["The 'rootimg' field is not defined in manifest.xml."],errorcode=>[1]}); $errors++; } } if($errors){ # we had problems, error and exit. return 0; } # returning 1 means everything went good! return 1; } sub make_files { my $data = shift; my $callback = shift; my $os = $data->{osvers}; my $arch = $data->{osarch}; my $profile = $data->{profile}; if($data->{provmethod} =~ /install/){ my $template = $data->{template}; print "mkdir -p /install/custom/$os/$arch/$profile\n"; print "cp $template /install/netboot/$os/$arch/$profile\n"; }elsif($data->{provmethod} =~/netboot|statelite/){ print "mkdir -p /install/netboot/$os/$arch/$profile\n"; print "cp kernel /install/netboot/$os/$arch/$profile\n"; print "cp initrd.gz /install/netboot/$os/$arch/$profile\n"; print "cp rootimg.gz /install/netboot/$os/$arch/$profile\n"; } if($data->{extra}){ # have to copy extras print "copying extras...\n"; } }