clone_image.in 14.4 KB
Newer Older
1
2
#!/usr/bin/perl -w
#
3
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
23
24
25
26
27
28
29
#
use English;
use strict;
use Getopt::Std;
use Data::Dumper;
use File::Temp qw(tempfile);
use CGI;
30
use File::Basename;
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

#
# Clone an image (descriptor) from a node and then snapshot
# that node into the descriptor. Creates the descriptor if
# if it does not exist. The idea is to use all of the info
# from the current image descriptor that is loaded on the node
# to quickly create a new descriptor by inheriting all of the
# attributes of the original.
#
# We also want to support taking a snapshot of a previously
# created clone. To make everything work properly, require
# that the imagename exist in the experiment project, which
# ensures that we are operating on a clone, not an image in
# some other project or a system image.
#
sub usage()
{
48
    print("Usage: clone_image [-dwe] [-n | -s] <imagename> <node_id>\n".
49
50
51
	  "Options:\n".
	  "       -d     Turn on debug mode\n".
	  "       -e     Create a whole disk image\n".
52
	  "       -g 0,1 Override base image global setting\n".
53
	  "       -s     Create descriptor but do not snapshot\n".
54
	  "       -n     Impotent mode\n".
55
	  "       -F     Create a full image even if deltas are on\n".
56
	  "       -w     Wait for image to be created\n");
57
58
    exit(-1);
}
59
my $optlist     = "densg:wF";
60
61
62
63
my $debug       = 0;
my $wholedisk   = 0;
my $impotent    = 0;
my $nosnapshot  = 0;
64
my $isvirtnode  = 0;
65
my $waitmode    = 0;
66
my $nodelta     = 0; # To pass to create_image.
67
my $global;
68
69
70
71
72
73

#
# Configure variables
#
my $TB           = "@prefix@";
my $PROJROOT     = "@PROJROOT_DIR@";
74
my $GROUPROOT    = "@GROUPSROOT_DIR@";
75
76
my $CREATEIMAGE  = "$TB/bin/create_image";
my $NEWIMAGEEZ   = "$TB/bin/newimageid_ez";
77
my $DOPROVENANCE = @IMAGEPROVENANCE@;
78
my $doprovenance = 0;
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

#
# Untaint the path
#
$ENV{'PATH'} = "$TB/bin:$TB/sbin:/bin:/usr/bin:/usr/bin:/usr/sbin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};

#
# Turn off line buffering on output
#
$| = 1;

#
# Load the Testbed support stuff.
#
use lib "@prefix@/lib";
use EmulabConstants;
use emutil;
use User;
use Project;
use Image;
use OSinfo;
use Node;
102
use EmulabFeatures;
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

# Protos
sub fatal($);

#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
    usage();
}
if (defined($options{"d"})) {
    $debug = 1;
}
if (defined($options{"e"})) {
    $wholedisk = 1;
}
if (defined($options{"n"})) {
    $impotent = 1;
}
if (defined($options{"s"})) {
    $nosnapshot = 1;
}
127
128
129
if (defined($options{"w"})) {
    $waitmode = 1;
}
130
131
132
if (defined($options{"F"})) {
    $nodelta = 1;
}
133
134
135
if (defined($options{"g"})) {
    $global = $options{"g"};
}
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
usage()
    if (@ARGV != 2);

my $imagename = shift(@ARGV);
my $node_id   = shift(@ARGV);

#
# Map invoking user to object. 
#
my $this_user = User->ThisUser();
if (! defined($this_user)) {
    fatal("You ($UID) do not exist!");
}

#
# The node must of course be allocated and the user must have
# permission to clone it. 
#
my $node = Node->Lookup($node_id);
if (!defined($node)) {
    fatal("No such node");
}
158
159
$isvirtnode = $node->isvirtnode();

160
161
162
163
164
165
166
167
168
if (!$node->AccessCheck($this_user, TB_NODEACCESS_LOADIMAGE())) {
    fatal("Not enough permission");
}
my $experiment = $node->Reservation();
if (!defined($experiment)) {
    fatal("Node is not reserved");
}
my $pid     = $experiment->pid();
my $group   = $experiment->GetGroup();
169
my $gid     = $group->gid();
170
171
172
173
174
175
my $project = $experiment->GetProject();
if (! (defined($project) && defined($group))) {
    fatal("Could not get project/group for $experiment");
}
my $image = Image->Lookup($project->pid(), $imagename);

176
177
178
179
180
181
182
183
184
185
186
187
#
# Need to look up the base image; the image that is currently running
# on the node and being cloned.
#
my ($base_osinfo, $base_image) = $node->RunningOsImage();
# No support for cloning MFSs, so there will always be a base image.
if (! (defined($base_osinfo) && defined($base_image))) {
    fatal("Could not determine osid/imageid for $node_id");
}
print "$node_id is running $base_osinfo,$base_image\n"
    if ($debug);

188
189
190
191
192
# Temporary override for all geni projects until we can export deltas.
$doprovenance = 0
    if ($project->IsNonLocal());

# But allow feature override.
193
194
195
$doprovenance = 
    EmulabFeatures->FeatureEnabled("ImageProvenance", $this_user, $project);

196
197
198
199
200
#
# The simple case is that the descriptor already exists. So it is just
# a simple snapshot to the image file. 
#
if (defined($image)) {
201
202
    my $needdelete = 0;
    
203
204
205
206
207
208
    #
    # Only EZ images via this interface.
    #
    if (!$image->ezid()) {
	fatal("Cannot clone a non-ez image");
    }
209

210
211
212
213
214
215
216
217
218
219
    #
    # The access check above determines if the caller has permission
    # to overwrite the image file. 
    # Not that this matters, cause create_image is going to make the
    # same checks.
    #
    if ($impotent) {
	print "Not doing anything in impotent mode\n";
	exit(0);
    }
220
221
222
223
224
225
226

    #
    # Before we do anything destructive, we lock the image.
    #
    if ($image->Lock()) {
	fatal("Image is locked, please try again later!\n");
    }
227
    if ($DOPROVENANCE && $doprovenance) {
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
        #
	# This will include unreleased images (in image_versions, but
	# not the one pointed to by the images table). 
	#
	$image = $image->LookupMostRecent();
	if (!defined($image)) {
	    $image->Unlock();
	    fatal("Cannot lookup osinfo for $image");
	}
	
	#
	# We create a new version of the image descriptor for the new
	# snapshot. We mark it as not ready so that others know it is
	# in transition. When we later call createimage, it will make
	# sure the ready bit is clear before trying to use it.
	#
	my $needclone = 1;
	
246
	#
247
248
249
250
	# Does the most recent version in the table not have its ready bit set?
	# If so it means something went wrong with a previous image creation.
	# We can reuse it, but reset the provenance just in case the node got
	# reloaded.
251
252
	#
	if (!$image->ready()) {
253
	    my $osinfo = OSinfo->Lookup($image->imageid(), $image->version());
254
255
256
257
258
259
	    if (!defined($osinfo)) {
		$image->Unlock();
		fatal("Cannot lookup osinfo for $image");
	    }
	    $image->SetProvenance($base_image);
	    $osinfo->SetProvenance($base_osinfo);
260
261
262
263
	    $needclone = 0;
	    print "Reusing image version " . $image->version() . ", ".
		"since it was never marked ready.\n";
	    
264
	}
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
	#
	# If the new image is going to based on the exact same base,
	# lets not create another new version, but overwrite the current
	# one. Save lots of space this way. We miss saving intermediate
	# versions, but this is a typical approach to getting an image
	# ready for use; change, snapshot, test, change, snapshot ... 
	#
	if ($needclone && !$image->released() && 
	    ($image->parent_imageid() == $base_image->imageid() &&
	     $image->parent_version() == $base_image->version())) {
	    # For create_image to be happy. 
	    $image->ClearReady(0);
	    $needclone = 0;
	    print "Reusing image version " . $image->version() . ", ".
		"since the base is the same and it was not released.\n";
	}

	if ($needclone) {
283
284
	    my $clone_error;
	    my $clone = $image->NewVersion($this_user,
285
					   $base_image, \$clone_error);
286
287
288
289
290
291
292
293
294
295
296
297
	    if (!defined($clone)) {
		$image->Unlock();
		fatal("Could not clone image descriptor" .
		      (defined($clone_error) ? ": $clone_error" : "") . "\n");
	    }
	    $image = $clone;
	    $needdelete = 1;

	    #
	    # Watch for a system image that is saved elsewhere; see equiv code
	    # in create_image. We change the path to point over to the /proj
	    # directory so that we do not burn up space on boss until it is
298
	    # officially "released". We *can* use this version of the image
299
300
	    # by explicitly using its version number, before it is released. 
	    #
301
	    if ($image->path() =~ /^$TB/) {
302
		my $path = $PROJROOT . "/" . $image->pid() . "/images/" .
Leigh B Stoller's avatar
Leigh B Stoller committed
303
		    basename($image->path());
304

305
		if ($image->Update({"path" => $path})) {
306
		    $image->DeleteVersion();
307
		    fatal("Could not update path!");
308
309
		}
	    }
310
311
312
313
	}
    }
    $image->Unlock();
    
314
    if ($nosnapshot) {
315
316
	print "Not taking a snapshot, as directed\n"
	    if ($debug);
317
318
	exit(0);
    }
319
320
    my $opts = "-p $pid ";
    $opts   .= "-w " if ($waitmode);
321
    $opts   .= "-F " if ($nodelta);
322
323
324
325
326
327
328

    #
    # Mike says do not pass versioned imagenames to create_image when
    # provenance is turned off. 
    #
    $imagename  = $image->imagename();
    $imagename .= ":" . $image->version() if ($DOPROVENANCE && $doprovenance);
329
    
330
    my $output = emutil::ExecQuiet("$CREATEIMAGE $opts $imagename $node_id");
331
    if ($?) {
332
333
	$image->DeleteVersion()
	    if ($needdelete);
334
335
336
337
338
339
	print STDERR $output;
	fatal("Failed to create image");
    }
    print "Image is being created. This can take 15-30 minutes.\n";
    exit(0);
}
340
DoNew:
341
342

#
343
# Only EZ images via this interface.
344
#
345
346
347
348
if (!$base_image->ezid()) {
    fatal("Cannot clone a non-ez image");
}

349
350
351
352
353
#
# Not allowed to derive an image from one that has not been released.
# Maybe relax this in the future, but this is a good simplification for
# now.
#
354
if ($DOPROVENANCE && $doprovenance && !$base_image->released()) {
355
356
357
358
    fatal("Not allowed to derive a new image from unreleased ".
	  "base $base_image");
}

359
360
361
362
363
364
#
# To avoid confusion, we do not allow users to shadow system images
# in their own project. 
#
if (Image->LookupByName($imagename) && !$this_user->IsAdmin()) {
    fatal("Not allowed to shadow snapshot a system image");
365
366
}

367
368
369
370
371
# Subgroups change the path
my $path = ($experiment->pid() eq $experiment->gid() ?
	    "$PROJROOT/$pid/images/${imagename}.ndz" :
	    "$GROUPROOT/$pid/$gid/images/${imagename}.ndz");

372
373
374
375
376
377
378
379
380
381
382
383
#
# Create the image descriptor. We use the backend script to do the
# heavy lifting, but we have to cons up an XML file based on the image
# descriptor that is being cloned.
#
# These are the fields we have to come up with, plus a number
# of mtype_* entries.
#
my %xmlfields =
    ("imagename"	=> $imagename,
     "pid"		=> $project->pid(),
     "gid"		=> $experiment->gid(),
384
     "description"	=> $base_osinfo->description(),
385
386
     "OS"		=> $base_osinfo->OS(),
     "version"		=> $base_osinfo->version(),
387
     "path"		=> $path,
388
     "op_mode",		=> $base_osinfo->op_mode(),
389
     "wholedisk",	=> $wholedisk,
390
);
Leigh B Stoller's avatar
Leigh B Stoller committed
391
392
$xmlfields{"reboot_waittime"} = $base_osinfo->reboot_waittime()
    if (defined($base_osinfo->reboot_waittime()));
393
394
395
$xmlfields{"osfeatures"} = $base_osinfo->osfeatures()
    if (defined($base_osinfo->osfeatures()) &&
	$base_osinfo->osfeatures() ne "");
396
397
$xmlfields{"global"} = 1
    if (defined($global) && $global);
Leigh B Stoller's avatar
Leigh B Stoller committed
398
    
399
400
if (defined($base_image)) {
    $xmlfields{"mbr_version"}     = $base_image->mbr_version();
401
    $xmlfields{"loadpart"}        = $base_image->loadpart();
402
    $xmlfields{"noexport"}        = $base_image->noexport();
403
404
405
406
407
408
409
410
411

    # Short form uses wholedisk instead. Should fix this. 
    if ($base_image->loadpart() == 0 && $base_image->loadlength() == 4) {
	$xmlfields{"loadpart"}    = 1;
	$xmlfields{"wholedisk"}   = 1;
    }
    elsif ($wholedisk) {
	$xmlfields{"loadpart"}    = 1;
    }
412
413
414
415
416
417
418
419
420
421
422
}
elsif ($isvirtnode) {
    $xmlfields{"reboot_waittime"} = 240;
    $xmlfields{"loadpart"}        = 1;
    $xmlfields{"mtype_pcvm"}      = 1;
    $xmlfields{"wholedisk"}       = 1;
}
else {
    fatal("No base image for $node_id");
}
# This needs more thought.
423
if (($isvirtnode || $base_osinfo) && $base_osinfo->def_parentosid()) {
424
425
426
427
    my $parentosinfo = OSinfo->Lookup($base_osinfo->def_parentosid());
    if (!defined($parentosinfo)) {
	fatal("Could not lookup object for parent osid of $base_osinfo");
    }
428
    $xmlfields{"def_parentosid"} =
429
	$parentosinfo->pid() . "," . $parentosinfo->osname();
430
431

    # And this is just plain bogus. 
432
    #$xmlfields{"mbr_version"} = 99;
433
}
434
435
436
437

#
# Grab the existing type list and generate new mtype_* variables.
#
438
439
440
441
442
443
444
if (defined($base_image)) {
    my @typelist = $base_image->TypeList($base_osinfo);
    if (! @typelist) {
	fatal("$base_image does not run on any types");
    }
    foreach my $type (@typelist) {
	my $type_id = $type->type();
445
    
446
447
	$xmlfields{"mtype_${type_id}"} = 1;
    }
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
}

#
# Create the XML file to pass to newimageid_ez.
#
my ($fh, $filename) = tempfile(UNLINK => 1);
fatal("Could not create temporary file")
    if (!defined($fh));

print $fh "<image>\n";
foreach my $key (keys(%xmlfields)) {
    my $value = $xmlfields{$key};

    print $fh "<attribute name=\"$key\">";
    print $fh "<value>" . CGI::escapeHTML($value) . "</value>";
    print $fh "</attribute>\n";
}
print $fh "</image>\n";
close($fh);

468
469
470
471
472
if ($debug) {
    system("/bin/cat $filename");
}

my $output = emutil::ExecQuiet("$NEWIMAGEEZ -s -v $filename");
473
474
if ($?) {
    print STDERR $output;
475
476
    my $foo = `cat $filename`;
    print STDERR $foo;
477
478
479
480
481
482
483
    fatal("Failed to verify image descriptor from $filename");
}
if ($impotent) {
    print "Not doing anything in impotent mode\n";
    system("cat $filename");
    exit(0);
}
484
$output = emutil::ExecQuiet("$NEWIMAGEEZ -s $filename");
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
if ($?) {
    print STDERR $output;
    my $foo = `cat $filename`;
    print STDERR $foo;
    fatal("Failed to create image descriptor");
}

$image = Image->Lookup($project->pid(), $imagename);
if (!defined($image)) {
    fatal("Cannot lookup newly created image for $imagename");
}
my $osinfo = OSinfo->Lookup($image->imageid());
if (!defined($osinfo)) {
    fatal("Cannot lookup newly created osinfo for $image");
}
500
if ($DOPROVENANCE && $doprovenance) {
501
502
503
    $image->SetProvenance($base_image);
    $osinfo->SetProvenance($base_osinfo);
}
504
505
506
507
508
if ($debug) {
    print "Created $osinfo\n";
    print "Created $image\n";
}
if ($nosnapshot) {
509
510
    print "Not taking a snapshot, as directed\n"
	if ($debug);
511
512
    exit(0);
}
513
514
my $opts = "-p $pid ";
$opts   .= "-w " if ($waitmode);
515
$opts   .= "-F " if ($nodelta);
516
$output  = emutil::ExecQuiet("$CREATEIMAGE $opts $imagename $node_id");
517
518
519
520
521
522
523
524
525
526
527
528
529
530
if ($?) {
    print STDERR $output;
    fatal("Failed to create image");
}
print "Image is being created. This can take 15-30 minutes.\n";
exit(0);

sub fatal($)
{
    my ($mesg) = @_;

    die("*** $0:\n".
	"    $mesg\n");
}