xcat-core/xCAT-server/lib/xcat/plugins/imgport.pm
linggao 1ff3a1b106 finally fixed defect 3357
git-svn-id: https://svn.code.sf.net/p/xcat/code/xcat-core/trunk@15078 8638fb3e-16cb-4fca-ae20-7b5d299a9bcd
2013-02-06 19:26:29 +00:00

1428 lines
42 KiB
Perl

# Sumavi Inc (C) 2010
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
#####################################################
# 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;
BEGIN
{
$::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";
use strict;
use warnings;
#use xCAT::Table;
#use xCAT::Schema;
#use xCAT::NodeRange qw/noderange abbreviate_noderange/;
#use xCAT::Utils;
use xCAT::TableUtils;
use Data::Dumper;
use XML::Simple;
use POSIX qw/strftime/;
use Getopt::Long;
use File::Temp;
use File::Copy;
use File::Path qw/mkpath/;
use File::Basename;
use xCAT::NodeRange;
use Cwd;
my $requestcommand;
$::VERBOSE = 0;
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 $nodes;
my $new_profile;
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]";
push@{ $rsp{data} }, "\timgimport <bundle_file_name> [-p|--postscripts <nodelist>] [-f|--profile <new_profile>] [-v]";
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,
'v|verbose' => \$::VERBOSE,
'p|postscripts=s' => \$nodes,
'f|profile=s' => \$new_profile,
);
if($help){
$xusage->(0);
return;
}
# first extract the bundle
extract_bundle($request, $callback,$nodes,$new_profile);
}
# 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 $node;
my $xusage = sub {
my $ec = shift;
push@{ $rsp{data} }, "imgexport: Creates a tarball (bundle) of an existing xCAT image";
push@{ $rsp{data} }, "Usage: ";
push@{ $rsp{data} }, "\timgexport [-h|--help]";
push@{ $rsp{data} }, "\timgexport <image_name> [destination] [[-e|--extra <file:dir> ] ... ] [-p|--postscripts <node_name>] [-v]";
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,
'p|postscripts=s' => \$node,
'e|extra=s' => \@extra,
'v|verbose' => \$::VERBOSE
);
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;
my $cwd = $request->{cwd}; #getcwd;
$cwd = $cwd->[0];
$callback->( {data => ["Exporting $img_name to $cwd..."]});
# check if all files are in place
my $attrs = get_image_info($img_name, $callback, $node, @extra);
#print Dumper($attrs);
unless($attrs){
return 1;
}
# make manifest and tar it up.
make_bundle($img_name, $dest, $attrs, $callback,$cwd);
}
# verify the image and return the values
sub get_image_info {
my $imagename = shift;
my $callback = shift;
my $node = shift;
my @extra = @_;
my $errors = 0;
my $ostab = new xCAT::Table('osimage', -create=>1);
unless($ostab){
$callback->(
{error => ["Unable to open table 'osimage'."],errorcode=>1}
);
return 0;
}
(my $attrs) = $ostab->getAttribs({imagename => $imagename}, 'profile', 'imagetype', 'provmethod', 'osname', 'osvers', 'osdistro', 'osarch', 'synclists');
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++;
}
$attrs->{imagename} = $imagename;
if($errors){
return 0;
}
my $linuximagetab = new xCAT::Table('linuximage', -create=>1);
unless($linuximagetab){
$callback->(
{error => ["Unable to open table 'linuximage'"],errorcode=>1}
);
return 0;
}
#from linuximage table
(my $attrs1) = $linuximagetab->getAttribs({imagename => $imagename}, 'template', 'pkglist', 'pkgdir', 'otherpkglist', 'otherpkgdir', 'exlist', 'postinstall', 'rootimgdir', 'nodebootif', 'otherifce', 'netdrivers', 'kernelver', 'permission');
if (!$attrs1) {
$callback->({error=>["Cannot find image \'$imagename\' from the linuximage table."],errorcode=>[1]});
return 0;
}
#merge attrs with attrs1
foreach (keys %$attrs1) {
$attrs->{$_} = $attrs1->{$_};
}
$attrs = get_files($imagename, $callback, $attrs);
if($#extra > -1){
my $ex = get_extra($callback, @extra);
if($ex){
$attrs->{extra} = $ex;
}
}
#get postscripts
if ($node) {
$attrs = get_postscripts($node, $callback, $attrs)
}
# if we get nothing back, then we couldn't find the files. How sad, return nuthin'
return $attrs;
}
sub get_postscripts {
my $node = shift;
my $errors = 0;
my $callback = shift;
my $attrs = shift;
my @nodes = noderange($node);
if (@nodes > 0) { $node = $nodes[0]; }
else {
$callback->(
{error => ["Unable to get postscripts, $node is not a valide node."],errorcode=>1}
);
return 0;
}
my $postscripts;
my $postbootscripts;
my $ptab = new xCAT::Table('postscripts', -create=>1);
unless($ptab){
$callback->(
{error => ["Unable to open table 'postscripts'."],errorcode=>1}
);
return 0;
}
my $ent = $ptab->getNodeAttribs($node, ['postscripts', 'postbootscripts']);
if ($ent)
{
if ($ent->{postscripts}) { $postscripts = $ent->{postscripts}; }
if ($ent->{postbootscripts}) { $postbootscripts = $ent->{postbootscripts}; }
}
(my $attrs1) = $ptab->getAttribs({node => "xcatdefaults"}, 'postscripts', 'postbootscripts');
if ($attrs1) {
if ($attrs1->{postscripts}) {
if ($postscripts) {
$postscripts = $attrs1->{postscripts} . ",$postscripts";
} else {
$postscripts = $attrs1->{postscripts};
}
}
if ($attrs1->{postbootscripts}) {
if ($postbootscripts) {
$postbootscripts = $attrs1->{postbootscripts} . ",$postbootscripts";
} else {
$postbootscripts = $attrs1->{postbootscripts};
}
}
}
if ($postscripts) {
$attrs->{postscripts} = $postscripts;
}
if ($postbootscripts) {
$attrs->{postbootscripts} = $postbootscripts;
}
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){
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";
if (! $to_dir) {
if (-d $file) {
$to_dir=$file;
} else {
$to_dir=dirname($file);
}
}
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::TableUtils->getInstallDir();
unless($installroot){
$installroot = '/install';
}
my $provmethod = $attrs->{provmethod};
my $osvers = $attrs->{osvers};
# here's the case for the install. All we need at first is the
# template. That should do it.
if($provmethod =~ /install/){
@arr = ("$installroot/custom/install", "$xcatroot/share/xcat/install");
#get .tmpl file
if (! $attrs->{template}) {
my $template = look_for_file('tmpl', $callback, $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($osvers !~ /esx/){
# don't do anything because these files don't exist for ESX stateless.
if($provmethod =~ /netboot/){
@arr = ("$installroot/custom/netboot", "$xcatroot/share/xcat/netboot");
#get .pkglist file
if (! $attrs->{pkglist}) {
# we need to get the .pkglist for this one!
my $temp = look_for_file('pkglist', $callback, $attrs, @arr);
unless($temp){
$callback->({error=>["Couldn't find pkglist file for $imagename"],errorcode=>[1]});
$errors++;
}else{
$attrs->{pkglist} = $temp;
}
}
@arr = ("$installroot/netboot");
# look for ramdisk
my $ramdisk = look_for_file('initrd-stateless.gz', $callback, $attrs, @arr);
unless($ramdisk){
$callback->({error=>["Couldn't find ramdisk (initrd-stateless.gz) for $imagename"],errorcode=>[1]});
$errors++;
}else{
$attrs->{ramdisk} = $ramdisk;
}
# look for kernel
my $kernel = look_for_file('kernel', $callback, $attrs, @arr);
unless($kernel){
$callback->({error=>["Couldn't find kernel (kernel) for $imagename"],errorcode=>[1]});
$errors++;
}else{
$attrs->{kernel} = $kernel;
}
# look for rootimg.gz
my $rootimg = look_for_file('rootimg.gz', $callback, $attrs, @arr);
unless($rootimg){
$callback->({error=>["Couldn't find rootimg (rootimg.gz) for $imagename"],errorcode=>[1]});
$errors++;
}else{
$attrs->{rootimg} = $rootimg;
}
} elsif ($provmethod =~ /statelite/) {
@arr = ("$installroot/custom/netboot", "$xcatroot/share/xcat/netboot");
#get .pkglist file
if (! $attrs->{pkglist}) {
# we need to get the .pkglist for this one!
my $temp = look_for_file('pkglist', $callback, $attrs, @arr);
unless($temp){
$callback->({error=>["Couldn't find pkglist file for $imagename"],errorcode=>[1]});
$errors++;
}else{
$attrs->{pkglist} = $temp;
}
}
@arr = ("$installroot/netboot");
# look for kernel
my $kernel = look_for_file('kernel', $callback, $attrs, @arr);
unless($kernel){
$callback->({error=>["Couldn't find kernel (kernel) for $imagename"],errorcode=>[1]});
$errors++;
}else{
$attrs->{kernel} = $kernel;
}
# look for ramdisk
my $ramdisk = look_for_file('initrd-statelite.gz', $callback, $attrs, @arr);
unless($ramdisk){
$callback->({error=>["Couldn't find ramdisk (initrd-statelite.gz) for $imagename"],errorcode=>[1]});
$errors++;
}else{
$attrs->{ramdisk} = $ramdisk;
}
}
}
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 $callback = shift;
my $attrs = shift;
my @dirs = @_;
my $r_file = '';
my $profile = $attrs->{profile};
my $arch = $attrs->{osarch};
my $distname = $attrs->{osvers};
# 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,
my $dd = $distname; # dd is distro directory, or disco dave, whichever you prefer.
if($dd =~ /win/){ $dd = 'windows' };
until(-r "$d/$dd" or not $dd){
$callback->({data=>["not in $d/$dd..."]}) if $::VERBOSE;
chop($dd);
}
if($distname && (($file eq 'tmpl') || ($file eq 'pkglist'))){
$callback->({data=>["looking in $d/$dd..."]}) if $::VERBOSE;
# now look for the file name: foo.rhel5.x86_64.tmpl
(-r "$d/$dd/$profile.$distname.$arch.$file") && (return "$d/$dd/$profile.$distname.$arch.$file");
# now look for the file name: foo.rhel5.tmpl
(-r "$d/$dd/$profile.$distname.$file") && (return "$d/$dd/$profile.$distname.$file");
# now look for the file name: foo.x86_64.tmpl
(-r "$d/$dd/$profile.$arch.$file") && (return "$d/$dd/$profile.$arch.$file");
# finally, look for the file name: foo.tmpl
(-r "$d/$dd/$profile.$file") && (return "$d/$dd/$profile.$file");
}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 = shift;
#my $dir = getcwd;
# get rid of spaces and put in underlines.
$imagename =~ s/\s+/_/g;
# we may find that cwd doesn't work, so we use the request cwd.
my $ttpath = mkdtemp("$dir/imgexport.$$.XXXXXX");
$callback->({data=>["Creating $ttpath..."]}) if $::VERBOSE;
my $tpath = "$ttpath/$imagename";
mkdir("$tpath");
chmod 0755,$tpath;
#for statelite
if ($attribs->{provmethod} eq 'statelite') {
#copy the rootimgdir over
my $rootimgdir=$attribs->{rootimgdir};
if ($rootimgdir) {
$callback->({data=>["Packing root image. It will take a while"]});
system("cd $rootimgdir; find rootimg |cpio -H newc -o | gzip -c - > $tpath/rootimgtree.gz");
$attribs->{'rootimgtree'} = "$rootimgdir/rootimgtree.gz";
} else {
$callback->({error=>["Couldn't locate the root image directory. "],errorcode=>[1]});
return 0;
}
#get litefile table setting for the image
my $lftab= xCAT::Table->new("litefile" ,-create=>1);
if (!$lftab) {
$callback->({error=>["Could not open the litefile table."],errorcode=>[1]});
return 0;
}
$callback->({data=>["Getting litefile settings"]});
my @imageInfo;
my @imagegroupsattr = ('groups');
# Check if this image contains osimage.groups attribute.
# if so, means user wants to use specific directories to this image.
my $osimagetab = xCAT::Table->new("osimage",-create=>1);
my $imagegroups = $osimagetab->getAttribs({imagename => $imagename}, @imagegroupsattr);
if ($imagegroups and $imagegroups->{groups}) {
# get the directories with no names
push @imageInfo, $lftab->getAttribs({image => ''}, ('file','options'));
# get for the image groups specific directories
push @imageInfo, $lftab->getAttribs({image => $imagegroups->{groups}}, ('file','options'));
# get for the image specific directories
push @imageInfo, $lftab->getAttribs({image => $imagename}, ('file','options'));
} else {
# get the directories with no names
push @imageInfo, $lftab->getAttribs({image => ''}, ('file','options'));
# get the ALL directories
push @imageInfo, $lftab->getAttribs({image => 'ALL'}, ('file','options'));
# get for the image specific directories
push @imageInfo, $lftab->getAttribs({image => $imagename}, ('file','options'));
}
open(FILE,">$tpath/litefile.csv") or die "Could not open $tpath/litefile.csv";
foreach(@imageInfo){
my $file=$_->{file};
if(!$file){ next; }
my $o = $_->{options};
if(!$o){
$o = "tmpfs";
}
print FILE "\"$imagename\",\"$file\",\"$o\",,\n";
}
close(FILE);
$attribs->{'litefile'} = "$rootimgdir/litefile.csv";
}
# 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 => '<?xml version="1.0"?>');
#print $xml->XMLout($attribs, noattr => 1, xmldecl => '<?xml version="1.0">');
close(FILE);
# these are the only files we copy in. (unless you have extras)
for my $a ("kernel", "template", "ramdisk", "rootimg", "pkglist", "synclists", "otherpkglist", "postinstall", "exlist"){
my $filenames=$attribs->{$a};
if($filenames) {
my @file_array=split(',', $filenames);
foreach my $fn (@file_array) {
$callback->({data => ["$fn"]});
if (-r $fn) {
system("cp $fn $tpath");
} else {
$callback->({error=>["Couldn't find file $fn for $imagename. Skip."],errorcode=>[1]});
}
}
}
}
# extra files get copied in the extra directory.
if($attribs->{extra}){
mkdir("$tpath/extra");
chmod 0755,"$tpath/extra";
foreach(@{ $attribs->{extra} }){
my $fromf = $_->{src};
print " $fromf\n";
if(-d $fromf ){
print "fromf is a directory";
mkpath("$tpath/extra/$fromf");
`cp -a $fromf/* $tpath/extra/$fromf/`;
}else{
`cp $fromf $tpath/extra`;
}
}
}
# now get right below all this stuff and tar it up.
chdir($ttpath);
$callback->( {data => ["Inside $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;
if($::VERBOSE){
$callback->({data => ["tar czvf $dest . "]});
$rc = system("tar czvf $dest . ");
}else{
$rc = system("tar czf $dest . ");
}
$callback->( {data => ["Done!"]});
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;
#print Dumper($request);
my $callback = shift;
my $nodes=shift;
my $new_profile=shift;
@ARGV = @{ $request->{arg} };
my $xml;
my $data;
my $datas;
my $error = 0;
my $bundle = shift @ARGV;
# extract the image in temp path in cwd
my $dir = $request->{cwd}; #getcwd;
$dir = $dir->[0];
#print Dumper($dir);
unless(-r $bundle){
$bundle = "$dir/$bundle";
}
unless(-r $bundle){
$callback->({error => ["Can not find $bundle"],errorcode=>[1]});
return;
}
my $tpath = mkdtemp("$dir/imgimport.$$.XXXXXX");
$callback->({data=>["Unbundling image..."]});
my $rc;
if($::VERBOSE){
$callback->({data=>["tar zxvf $bundle -C $tpath"]});
$rc = system("tar zxvf $bundle -C $tpath");
$rc = system("tar zxvf $bundle -C $tpath");
}else{
$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){
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=>["invalid manifest.xml file inside the bundle. Please verify the XML"],errorcode=>[1]});
#my $foo = $@;
#$foo =~ s/\n//;
#$callback->({error=>[$foo],errorcode=>[1]});
#foreach($@){
# last;
#}
return;
}
#print Dumper($data);
#push @{$datas}, $data;
# now we need to import the files...
unless(verify_manifest($data, $callback)){
$error++;
next;
}
# check media first
unless(check_media($data, $callback)){
$error++;
next;
}
#change profile name if needed
if ($new_profile) {
$data=change_profile($data, $callback, $new_profile, $imgdir);
}
#import manifest.xml into xCAT database
unless(set_config($data, $callback)){
$error++;
next;
}
# now place files in appropriate directories.
unless(make_files($data, $imgdir, $callback)){
$error++;
next;
}
# put postscripts in the postsctipts table
if ($nodes) {
unless(set_postscripts($data, $callback, $nodes)){
$error++;
next;
}
}
my $osimage = $data->{imagename};
$callback->({data=>["Successfully imported the image."]});
}
# remove temp file only if there were no problems.
unless($error){
$rc = system("rm -rf $tpath");
if ($rc) {
$callback->({error=>["Failed to clean up temp space $tpath"],errorcode=>[1]});
return;
}
}
}
sub change_profile {
my $data = shift;
my $callback = shift;
my $new_profile=shift;
my $srcdir=shift;
my $old_profile= $data->{profile};
if ($old_profile eq $new_profile) {
return $data; #do nothing if old profile is the same as the new one.
}
$data->{profile}=$new_profile;
my $installdir = xCAT::TableUtils->getInstallDir();
unless($installdir){
$installdir = '/install';
}
if ($data->{rootimgdir}) {
$data->{rootimgdir}="$installdir/netboot/" . $data->{osvers} . "/" . $data->{osarch} . "/$new_profile";
for my $a ("kernel", "ramdisk", "rootimg", "rootimgtree", "litefile") {
if ($data->{$a}) {
my $fn=basename($data->{$a});
$data->{$a}=$data->{rootimgdir} . "/$fn";
}
}
}
my $prov="netboot";
if ($data->{provmethod} eq "install") { $prov = "install"; }
my $platform;
my $os=$data->{osvers};
if ($os) {
if ($os =~ /rh.*/) { $platform = "rh"; }
elsif ($os =~ /centos.*/) { $platform = "centos"; }
elsif ($os =~ /fedora.*/) { $platform = "fedora"; }
elsif ($os =~ /sles.*/) { $platform = "sles"; }
elsif ($os =~ /SL.*/) { $platform = "SL"; }
elsif ($os =~ /win/) {$platform = "windows"; }
elsif ($os =~ /ubuntu*/) {$platform = "ubuntu"; }
}
for my $a ("template", "pkglist", "synclists", "otherpkglist", "postinstall", "exlist") {
my $filenames=$data->{$a};
if($filenames) {
my @file_array=split(',', $filenames);
my @new_file_array=();
foreach my $old (@file_array) {
my $oldfn=basename($old);
my $olddir=dirname($old);
my $newfn;
my $newdir;
#if source file is from /opt/xcat/share...,
#then copy it to /install/custom... directory.
#Otherwise, copy the file in the same directory
if ($olddir =~ /$::XCATROOT\/share\/xcat/) {
$newdir="$installdir/custom/$prov/$platform";
} else {
$newdir=$olddir;
}
#if the file name contains the old profile name,
#replace it with the new profile name.
#Otherwise prefix the old file name with the new profile name.
if ($oldfn =~ /$old_profile/) {
$newfn=$oldfn;
$newfn =~ s/$old_profile/$new_profile/;
} else {
$newfn="$new_profile.$oldfn";
}
move("$srcdir/$oldfn", "$srcdir/$newfn");
push (@new_file_array, "$newdir/$newfn");
}
$data->{$a}= join(',', @new_file_array);
}
}
#change the image name
$data->{imagename}=$data->{osvers} . "-" . $data->{osarch} . "-" . $data->{provmethod} . "-$new_profile";
return $data;
}
# return 1 for true 0 for false.
# need to make sure media is copied before importing image.
sub check_media {
my $data = shift;
my $callback = shift;
my $rc = 0;
unless( $data->{'media'}) {
$rc = 1;
}elsif($data->{media} eq 'required'){
my $os = $data->{osvers};
my $arch = $data->{osarch};
my $installroot = xCAT::TableUtils->getInstallDir();
unless($installroot){
$installroot = '/install';
}
unless(-d "$installroot/$os/$arch"){
$callback->({error=>["This image requires that you first copy media for $os-$arch"],errorcode=>[1]});
}else{
$rc = 1;
}
}
return $rc;
}
sub set_postscripts {
my $data = shift;
my $callback = shift;
my $nodes=shift;
$callback->({data=>["Adding postscripts."]});
my @good_nodes=noderange($nodes);
if (@good_nodes > 0) {
my @missed = nodesmissed();
if (@missed > 0) {
$callback->(
{warning => ["The following nodes will be skipped because they are not in the nodelist table.\n " . join(',', @missed)],errorcode=>1}
);
}
} else {
$callback->(
{error => ["The nodes $nodes are not defined in xCAT DB."],errorcode=>1}
);
return 0;
}
my $ptab = xCAT::Table->new('postscripts',-create => 1,-autocommit => 0);
unless($ptab){
$callback->(
{error => ["Unable to open table 'postscripts'"],errorcode=>1}
);
return 0;
}
# get xcatdefaults settings
my @a1=();
my @a2=();
(my $attrs1) = $ptab->getAttribs({node => "xcatdefaults"}, 'postscripts', 'postbootscripts');
if ($attrs1) {
if ($attrs1->{postscripts}) {
@a1=split(',', $attrs1->{postscripts});
}
if ($attrs1->{postbootscripts}) {
@a2=split(',', $attrs1->{postbootscripts});
}
}
#remove the script if it is already in xcatdefaults
my @a3=();
my @a4=();
my $postscripts = $data->{postscripts};
my $postbootscripts = $data->{postbootscripts};
if ($postscripts) { @a3 = split(',', $postscripts); }
if ($postbootscripts) { @a4 = split(',', $postbootscripts); }
my @a30;
my @a40;
if (@a1>0 && @a3>0) {
foreach my $tmp1 (@a3) {
if (! grep /^$tmp1$/, @a1) {
push(@a30, $tmp1);
}
}
$postscripts=join(',', @a30);
}
if (@a2>0 && @a4>0) {
foreach my $tmp2 (@a4) {
if (! grep /^$tmp2$/, @a2) {
push(@a40, $tmp2);
}
}
$postbootscripts=join(',', @a40);
}
#now save to the db
my %keyhash;
if ($postscripts || $postbootscripts) {
$keyhash{postscripts} = $postscripts;
$keyhash{postbootscripts} = $postbootscripts;
$ptab->setNodesAttribs(\@good_nodes, \%keyhash );
$ptab->commit;
}
return 1;
}
sub set_config {
my $data = shift;
my $callback = shift;
my $ostab = xCAT::Table->new('osimage',-create => 1,-autocommit => 0);
my $linuxtab = xCAT::Table->new('linuximage',-create => 1,-autocommit => 0);
my %keyhash;
my $osimage = $data->{imagename};
unless($ostab){
$callback->(
{error => ["Unable to open table 'osimage'"],errorcode=>1}
);
return 0;
}
unless($linuxtab){
$callback->(
{error => ["Unable to open table 'linuximage'"],errorcode=>1}
);
return 0;
}
$callback->({data=>["Adding $osimage"]}) if $::VERBOSE;
# 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};
if ($data->{imagetype}) {
$keyhash{imagetype} = $data->{imagetype};
};
if ($data->{osname}) {
$keyhash{osname} = $data->{osname};
};
if ($data->{osdistro}) {
$keyhash{osdistro} = $data->{osdistro};
};
if ($data->{synclists}) {
$keyhash{synclists} = $data->{synclists};
};
$ostab->setAttribs({imagename => $osimage }, \%keyhash );
$ostab->commit;
%keyhash=();
if ($data->{template}) {
$keyhash{template} = $data->{template};
};
if ($data->{pkglist}) {
$keyhash{pkglist} = $data->{pkglist};
};
if ($data->{pkgdir}) {
$keyhash{pkgdir} = $data->{pkgdir};
};
if ($data->{otherpkglist}) {
$keyhash{otherpkglist} = $data->{otherpkglist};
};
if ($data->{otherpkgdir}) {
$keyhash{otherpkgdir} = $data->{otherpkgdir};
};
if ($data->{exlist}) {
$keyhash{exlist} = $data->{exlist};
};
if ($data->{postinstall}) {
$keyhash{postinstall} = $data->{postinstall};
};
if ($data->{rootimgdir}) {
$keyhash{rootimgdir} = $data->{rootimgdir};
};
if ($data->{netdrivers}) {
$keyhash{netdrivers} = $data->{netdrivers};
};
if ($data->{kernelver}) {
$keyhash{kernelver} = $data->{kernelver};
};
if ($data->{permission}) {
$keyhash{permission} = $data->{permission};
};
if ($data->{nodebootif}) {
$keyhash{nodebootif} = $data->{nodebootif};
};
if ($data->{otherifce}) {
$keyhash{otherifce} = $data->{otherifce};
};
$linuxtab->setAttribs({imagename => $osimage }, \%keyhash );
$linuxtab->commit;
return 1;
}
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->{osvers} =~ /esx/){
$callback->({info => ['this is an esx image']});
# do nothing for ESX
1;
}elsif($data->{provmethod} =~ /netboot/){
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++;
}
}elsif($data->{provmethod} =~ /statelite/){
unless($data->{kernel}){
$callback->({error=>["The 'kernel' field is not defined in manifest.xml."],errorcode=>[1]});
$errors++;
}
unless($data->{ramdisk}){
$callback->({error=>["The 'ramdisk' field is not defined in manifest.xml."],errorcode=>[1]});
$errors++;
}
unless($data->{'rootimgtree'}){
$callback->({error=>["The 'rootimgtree' 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 $imgdir = shift;
my $callback = shift;
my $os = $data->{osvers};
my $arch = $data->{osarch};
my $profile = $data->{profile};
my $installroot = xCAT::TableUtils->getInstallDir();
unless($installroot){
$installroot = '/install';
}
# you'll get a hash like this for install:
#$VAR1 = {
# 'provmethod' => 'install',
# 'profile' => 'all',
# 'template' => '/opt/xcat/share/xcat/install/centos/all.tmpl',
# 'pkglist' => '/opt/xcat/share/xcat/install/centos/all.pkglist',
# 'otherpkglist' => '/opt/xcat/share/xcat/install/centos/all.othetpkgs.pkglist',
# 'synclists' => '/opt/xcat/share/xcat/install/centos/all.othetpkgs.synclist',
# 'imagename' => 'Default_Stateful',
# 'osarch' => 'x86_64',
# 'media' => 'required',
# 'osvers' => 'centos5.4'
# };
# data will look something like this for netboot:
#$VAR1 = {
# 'provmethod' => 'netboot',
# 'profile' => 'compute',
# 'ramdisk' => '/install/netboot/centos5.4/x86_64/compute/initrd-stateless.gz',
# 'kernel' => '/install/netboot/centos5.4/x86_64/compute/kernel',
# 'imagename' => 'Default_Stateless_1265981465',
# 'osarch' => 'x86_64',
# 'pkglist' => '/opt/xcat/share/xcat/install/centos/compute.pkglist',
# 'otherpkglist' => '/opt/xcat/share/xcat/install/centos/compute.othetpkgs.pkglist',
# 'synclists' => '/opt/xcat/share/xcat/install/centos/compute.othetpkgs.synclist',
# 'exlist' => '/opt/xcat/share/xcat/install/centos/compute.exlist',
# 'postinstall' => '/opt/xcat/share/xcat/install/centos/compute.postinstall',
# 'extra' => [
# {
# 'dest' => '/install/custom/netboot/centos',
# 'src' => '/opt/xcat/share/xcat/netboot/centos/compute.centos5.4.pkglist'
# },
# {
# 'dest' => '/install/custom/netboot/centos',
# 'src' => '/opt/xcat/share/xcat/netboot/centos/compute.exlist'
# }
# ],
# 'osvers' => 'centos5.4',
# 'rootimg' => '/install/netboot/centos5.4/x86_64/compute/rootimg.gz'
# };
# data will look something like this for statelite:
#$VAR1 = {
# 'provmethod' => 'statelite',
# 'profile' => 'compute',
# 'ramdisk' => '/install/netboot/centos5.4/x86_64/compute/initrd-statelite.gz',
# 'kernel' => '/install/netboot/centos5.4/x86_64/compute/kernel',
# 'imagename' => 'Default_Stateless_1265981465',
# 'osarch' => 'x86_64',
# 'pkglist' => '/opt/xcat/share/xcat/install/centos/compute.pkglist',
# 'otherpkglist' => '/opt/xcat/share/xcat/install/centos/compute.othetpkgs.pkglist',
# 'synclists' => '/opt/xcat/share/xcat/install/centos/compute.othetpkgs.synclist',
# 'exlist' => '/opt/xcat/share/xcat/install/centos/compute.exlist',
# 'postinstall' => '/opt/xcat/share/xcat/install/centos/compute.postinstall',
# 'extra' => [
# {
# 'dest' => '/install/custom/netboot/centos',
# 'src' => '/opt/xcat/share/xcat/netboot/centos/compute.centos5.4.pkglist'
# },
# {
# 'dest' => '/install/custom/netboot/centos',
# 'src' => '/opt/xcat/share/xcat/netboot/centos/compute.exlist'
# }
# ],
# 'osvers' => 'centos5.4',
# 'rootimgtree' => '/install/netboot/centos5.4/x86_64/compute/rootimg/rootimgtree.gz'
# };
for my $a ("kernel", "template", "ramdisk", "rootimg", "rootimgtree", "litefile", "pkglist", "synclists", "otherpkglist", "postinstall", "exlist") {
my $filenames=$data->{$a};
if($filenames) {
my @file_array=split(',', $filenames);
foreach my $fn (@file_array) {
$callback->({data => ["$fn"]});
my $basename=basename($fn);
my $dirname=dirname($fn);
if (! -r $dirname) {
mkpath("$dirname", { verbose => 1, mode => 0755 });
}
if (-r $fn) {
$callback->( {data => [" Moving old $fn to $fn.ORIG."]});
move("$fn", "$fn.ORIG");
}
move("$imgdir/$basename",$fn);
}
}
}
#unpack the rootimgtree.gz for statelite
my $fn=$data->{'rootimgtree'};
if($fn) {
if (-r $fn) {
my $basename=basename($fn);
my $dirname=dirname($fn);
#print "dirname=$dirname, basename=$basename\n";
$callback->({data => ["Extracting rootimgtree.gz. It will take a while."]});
system("mkdir -p $dirname; cd $dirname; zcat $basename |cpio -idum; rm $basename");
}
}
if($data->{extra}){
# have to copy extras
print "copying extras\n" if $::VERBOSE;
#if its just a hash then there is only one entry.
if (ref($data->{extra}) eq 'HASH'){
my $ex = $data->{extra};
#my $f = basename($ex->{src});
my $ff = $ex->{src};
my $dest = $ex->{dest};
unless(moveExtra($callback, $ff, $dest, $imgdir)){
return 0;
}
# if its an array go through each item.
}else{
foreach(@{ $data->{extra} }) {
#my $f = basename($_->{src});
my $ff = $_->{src};
my $dest = $_->{dest};
unless(moveExtra($callback, $ff, $dest, $imgdir)){
return 0;
}
}
}
}
#litefile table for statelite
if ($data->{provmethod} eq 'statelite') {
$callback->( {data => ["Updating the litefile table."]});
my $fn=$data->{litefile};
if (!$fn) {
$callback->({error=>["Could not find liefile.csv."],errorcode=>[1]});
return 1;
} elsif (! -r $fn) {
$callback->({error=>["Could not find $fn."],errorcode=>[1]});
return 1;
}
my $lftab= xCAT::Table->new("litefile" ,-create=>1);
if (!$lftab) {
$callback->({error=>["Could not open the litefile table."],errorcode=>[1]});
return 0;
}
open(FILE,"$fn") or die "Could not open $fn.";
foreach my $line (<FILE>) {
chomp($line);
print "$line\n";
my @tmp=split('"', $line);
my %keyhash;
my %updates;
$keyhash{image}=$data->{imagename};
$keyhash{file}=$tmp[3];
$updates{options}=$tmp[5];
$lftab->setAttribs(\%keyhash, \%updates );
}
close(FILE);
$lftab->commit;
$callback->( {data => ["The litetree and statelite talbes are untouched. You can update them if needed."]});
}
# return 1 meant everything was successful!
return 1;
}
sub moveExtra {
my $callback = shift;
my $ff = shift;
my $dest = shift;
my $imgdir = shift;
my $f = basename($ff);
if(-d "$imgdir/extra/$ff"){
#print "This is a directory\n";
# this extra file is a directory, so we are moving the directory over.
$callback->( {data => ["$dest"]});
unless(-d $dest){
unless(mkpath($dest)){
$callback->( {error=>["Failed to create $dest"], errorcode => 1});
return 0;
}
}
# this could cause some problems. This is one of the reasons we may not want to
# allow copying of directories.
`cp -a -f $imgdir/extra/$ff/* $dest`;
if($?){
$callback->( {error=>["Failed to cp -a $imgdir/extra/$ff/* to $dest"], errorcode => 1});
return 0;
}
}else{
#print "This is a file\n";
# this extra file is a file and we can just copy to the destination.
$callback->( {data => ["$dest/$f"]}) ;
if(-r "$dest/$f"){
$callback->( {data => [" Moving old $dest/$f to $dest/$f.ORIG."]});
move("$dest/$f", "$dest/$f.ORIG");
}
`cp $imgdir/extra/$f $dest`;
if ($?) {
$callback->( {error=>["Failed to copy $imgdir/extra/$f to $dest"], errorcode => 1});
return 0;
}
}
return 1;
}