dumpdescriptor.in 13.7 KB
Newer Older
1 2
#!/usr/bin/perl -w
#
3
# Copyright (c) 2010-2019 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 30 31 32 33 34
#
use English;
use strict;
use Getopt::Std;
use CGI;
use Data::Dumper;

#
# Dump an EZ descriptor out.
#
sub usage()
{
35
    print("Usage: dumpdescriptor ".
36
	  "[-d] [-e] [-v clientvers] [-i <imageid> [-t]] | [-o <osid>]\n");
37 38
    exit(-1);
}
39 40 41 42 43
my $optlist    = "di:o:tev:";
my $debug      = 0;
my $dotypes    = 0;
my $export     = 0;
my $clientvers = 0;
44
my $argumentid;
45 46 47 48 49 50 51 52 53

#
# Configure variables
#
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";
my $TBAUDIT	= "@TBAUDITEMAIL@";
my $TBGROUP_DIR	= "@GROUPSROOT_DIR@";
my $TBPROJ_DIR	= "@PROJROOT_DIR@";
54
my $TBBASE	= "@TBBASE@";
55 56 57
my $PGENISUPPORT= @PROTOGENI_SUPPORT@;
my $OURDOMAIN   = "@OURDOMAIN@";
my $WITHPROVENANCE= @IMAGEPROVENANCE@;
58
my $MAINSITE    = @TBMAINSITE@;
59

60 61 62 63 64
#
# When fetching the metadata, we now tell the server what client
# version of the software we are so it gives something we can handle.
# Be sure to update this if you change the version in image_import
#
65
my $METADATA_SERVERVERSION = 2;
66

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
#
# 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 libdb;
use libtestbed;
use User;
use Project;
use Image;
use OSinfo;
88
use NodeType;
89 90
use EmulabFeatures;
$EmulabFeatures::verbose = 0;
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107

# Protos
sub fatal($);
sub DumpImage($);
sub DumpOS($);

#
# 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;
}
108 109 110
if (defined($options{"t"})) {
    $dotypes = 1;
}
111 112 113
if (defined($options{"e"})) {
    $export = 1;
}
114 115 116 117
if (defined($options{"v"})) {
    $clientvers = $options{"v"};
}

118 119 120 121 122
if (@ARGV) {
    usage();
}

if (defined($options{"i"})) {
123 124
    $argumentid = $options{"i"};
    my $image = Image->Lookup($argumentid);
125
    if (!defined($image)) {
126
	fatal("No such image: $argumentid");
127 128 129 130
    }
    DumpImage($image);
}
elsif (defined($options{"o"})) {
131 132
    $argumentid = $options{"o"};
    my $osinfo = OSinfo->Lookup($argumentid);
133
    if (!defined($osinfo)) {
134
	fatal("No such osid: $argumentid");
135 136 137 138 139 140 141 142 143 144 145 146 147 148
    }
    DumpOS($osinfo);
}
else {
    fatal("Must supply an image or os ID");
}

sub DumpImage($)
{
    my ($image) = @_;
    
    # Array of string values to print. 
    my %xmlfields = ();

149 150 151
    # For version info.
    my @imagelist = ();

152
    $xmlfields{"imagename"}    = $image->imagename();
153
    $xmlfields{"format"}       = $image->format();
154 155 156 157
    if (!$export) {
	$xmlfields{"pid"}      = $image->pid();
	$xmlfields{"gid"}      = $image->gid();
    }
158
    $xmlfields{"description"}  = $image->description();
159 160 161
    if (!$image->isdataset()) {
	$xmlfields{"loadpart"} = $image->loadpart();
    }
162 163
    $xmlfields{"global"}       = $image->global();
    $xmlfields{"shared"}       = $image->shared();
164 165 166 167 168 169
    if (defined($image->path()) && $image->path() ne "") {
	#
	# Old clients cannot handle directory based paths, so give them
	# a filename instead.
	#
	my $path = $image->path();
Leigh Stoller's avatar
Leigh Stoller committed
170
	if ($clientvers == 0 && $image->IsDirPath()) {
171 172 173 174 175
	    $path .= $image->imagename() . ".ndz" .
		($image->version() ? ":" . $image->version() : "");
	}
	$xmlfields{"path"} = $path;
    }
176 177
    $xmlfields{"hash"}         = $image->hash()
    	if ($export && defined($image->hash()) && $image->hash() ne "");
178
    $xmlfields{"mbr_version"}  = $image->mbr_version();
179
    if ($export && $clientvers > 0) {
180
	$xmlfields{"isdataset"}    = $image->isdataset();
181 182 183 184 185
	if ($clientvers > 1 && $image->isdataset()) {
	    $xmlfields{"lba_low"}  = $image->lba_low();
	    $xmlfields{"lba_high"} = $image->lba_high();
	    $xmlfields{"lba_size"} = $image->lba_size();
	}
186
    }
187

188
    if ($export) {
189
	my $url;
190
	my $imageid    = $image->imageid();
191
	my $uuid       = $image->uuid();
192
	my $image_uuid = $image->image_uuid();
193 194 195 196 197 198 199 200
	my $access_key = $image->access_key();
	if (!defined($access_key) || $access_key eq "") {
	    $access_key = TBGenSecretKey();
	    if ($image->Update({'access_key' => $access_key})) {
		fatal("Could not initialize access key");
	    }
	}
	#
201 202 203 204 205 206 207
	# Generate a url that allows the image to be downloaded. This is a
	# little confusing cause of the ways the caller can request an
	# image.  If using the image uuid (which means, whatever the
	# current version is right now), then return a URL that is not
	# version specific. Otherwise, return URL to the actual image
	# version. The messiness is how to determine that we want the
	# version specific or not, from the script arguments.
208
	#
209 210 211 212 213 214 215 216
	if ($argumentid =~ /:/) {
	    $url = "$TBBASE/spewimage.php".
		"?imageid=$uuid&access_key=$access_key";
	}
	else {
	    $url = "$TBBASE/spewimage.php".
		"?imageid=$image_uuid&access_key=$access_key";
	}
217
	$xmlfields{"imagefile_url"} = $url;
218 219 220 221 222 223 224 225 226 227 228 229 230

	if ($clientvers > 2) {
	    #
	    # If this is a nonlocal image, send along where it came from.
	    # This allows the caller to trace back to the origin.
	    #
	    if (!$image->IsLocal()) {
		$xmlfields{'metadata_url'} = $image->metadata_url();
	    }
	    # Tell the caller if we have delta/full images.
	    $xmlfields{'havedelta'} = $image->HaveDeltaImage();
	    $xmlfields{'havefull'}  = $image->HaveFullImage();

231 232 233 234
	    # Tell the caller some extras for debugging image tracking (IMS).
	    $xmlfields{'origin_uuid'} = $image_uuid;
	    $xmlfields{'origin_name'} = $image->versname();
	    
235 236 237 238 239 240 241
	    if ($PGENISUPPORT) {
		# And the URN is required for image tracking.
		require GeniHRN;
		
		$xmlfields{'origin_urn'}  =
		    GeniHRN::Generate($OURDOMAIN, "authority", "cm");
	    }
242 243 244 245
	    # Send the architecture if it is set here.
	    if ($image->architecture()) {
		$xmlfields{'architecture'} = $image->architecture();
	    }
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
	    else {
		#
		# Temporary patch for importing images into the Cloudlab
		# Utah, which is the only multi-architecture cluster. If
		# we do not give it an architecture, then it will assign
		# all types to the image, and that definitely wrong. So
		# lets just assume that if the cluster does not have any
		# m400 node types, its an x86 image. And if the importing
		# cluster is not using architectures, this will be ignored
		# anyway.
		#
		if ($MAINSITE || !defined(NodeType->Lookup("m400"))) {
		    $xmlfields{'architecture'} = "x86_64";
		}
	    }
261
	    
262 263 264 265
	    #
	    # Send along the history so that the caller can get all
	    # the versions, which is important for delta based images.
	    #
266
	    if ($clientvers > 4) {
267 268 269 270 271 272 273 274 275 276 277 278 279
		#
		# Send the non-version metadata url so that the other side
		# can have it for when an experiment wants whatever the
		# latest version is. This allows different users to ask for
		# different versions or just latest version, and have them
		# come from the same image, instead of making new independent
		# shadow images.
		#
		$xmlfields{'image_metadata_url'} = $image->LocalURL();
		$xmlfields{'metadata_url'}       = $image->LocalVersionURL();
		
		$xmlfields{'image_version'}  = $image->version();
		$xmlfields{"deltahash"}      = $image->deltahash()
280
		    if (defined($image->deltahash()) &&
281 282
			$image->deltahash() ne "");

283
		if ($clientvers > 5) {
284 285 286 287 288 289 290 291
		    $xmlfields{'size'} = $image->size()
			if ($image->size());
		    $xmlfields{'deltasize'} = $image->deltasize()
			if ($image->deltasize());
		    $xmlfields{'notes'} = $image->notes()
			if (defined($image->notes()) and $image->notes() ne '');
		}

292 293 294 295 296 297 298 299 300 301 302 303 304
		#
		# We need to send the parent info too, for the delta chain
		# to be constructed. 
		#
		if (defined($image->parent_imageid())) {
		    $xmlfields{'parent_imageid'} = $image->parent_imageid();
		    $xmlfields{'parent_version'} = $image->parent_version();
		}
		
		if ($image->version()) {
		    my @tmp;
		    
		    # This returns a reverse ordered list.
305
		    if ($image->AllVersions(\@tmp, 1)) {
306 307 308 309 310 311 312 313 314 315 316 317 318
			fatal("Could not get image version list");
		    }
		    #
		    # But just the history prior to this image.
		    #		
		    foreach my $im (@tmp) {
			next
			    if ($im->version() >= $image->version());
			
			$imagelist[$im->version()] = $im;
		    }
		}
	    }
319
	}
320 321
    }

322 323 324 325 326 327 328 329 330 331 332 333
    sub MapOS($) {
	my ($osid) = @_;
	return "none"
	    if (!defined($osid));
	
	my $osinfo = OSinfo->Lookup($osid);
	if (!defined($osinfo)) {
	    fatal("Could not find osid $osid");
	}
	return $osinfo->pid() . "," . $osinfo->osname();
    }

334
    if (! ($image->ezid() || $image->isdataset())) {
335 336 337 338 339 340 341
	$xmlfields{"loadlength"}   = $image->loadlength();
	$xmlfields{"part1_osid"}   = MapOS($image->part1_osid());
	$xmlfields{"part2_osid"}   = MapOS($image->part2_osid());
	$xmlfields{"part3_osid"}   = MapOS($image->part3_osid());
	$xmlfields{"part4_osid"}   = MapOS($image->part4_osid());
	$xmlfields{"default_osid"} = MapOS($image->default_osid());
    }
342
    elsif (!$image->isdataset()) {
343 344 345 346 347
	my $osinfo = OSinfo->Lookup($image->imageid());
	if (!defined($osinfo)) {
	    fatal("Could not find osid for $image");
	}
	$xmlfields{"OS"}              = $osinfo->OS();
348 349
	$xmlfields{"version"}         = $osinfo->version()
	    if (defined($osinfo->version()) && $osinfo->version() ne "");
350
	$xmlfields{"op_mode"}         = $osinfo->op_mode();
351 352
	$xmlfields{"osfeatures"}      = $osinfo->osfeatures()
	    if (defined($osinfo->osfeatures()) && $osinfo->osfeatures() ne "");
353 354
	$xmlfields{"reboot_waittime"} = $osinfo->reboot_waittime()
	    if (defined($osinfo->reboot_waittime()));
355 356 357 358 359
	# whole disk images are described differently in EZ format
	if ($image->loadpart() == 0 && $image->loadlength() == 4) {
	    $xmlfields{"wholedisk"} = 1;
	    # find the partition which has an osid defined
	    for (my $i = 1; $i <= 4; $i++) {
360 361
		my $func = "part${i}_osid";
		my $foo  = $image->$func();
362 363 364 365 366 367
		if (defined($foo)) {
		    $xmlfields{"loadpart"} = $i;
		    last;
		}
	    }
	}
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
	if (defined($osinfo->nextosid()) && $osinfo->nextosid()) {
	    my $nextosinfo = OSinfo->Lookup($osinfo->nextosid());
	    if (!defined($nextosinfo)) {
		fatal("Could not look up nextosid for $osinfo");
	    }
	    $xmlfields{"nextosid"} =
		$nextosinfo->pid() . "," . $nextosinfo->osname();
	}
	if (defined($osinfo->def_parentosid()) && $osinfo->def_parentosid()) {
	    my $nextosinfo = OSinfo->Lookup($osinfo->def_parentosid());
	    if (!defined($nextosinfo)) {
		fatal("Could not look up def_parentosid for $osinfo");
	    }
	    $xmlfields{"def_parentosid"} =
		$nextosinfo->pid() . "," . $nextosinfo->osname();
	}
384 385 386 387 388 389
	# Send along the taint states.
	if ($clientvers > 3) {
	    if ($osinfo->IsTainted()) {
		$xmlfields{"taint_states"} = $osinfo->taint_states();
	    }
	}
390
    }
391 392 393 394 395 396 397 398
    if ($dotypes) {
	my @typelist = $image->TypeList();
	foreach my $nodetype (@typelist) {
	    my $type = $nodetype->type();
	    
	    $xmlfields{"mtype_$type"} = "1";
	}
    }
399 400 401 402 403 404 405 406 407 408

    #
    # Old sites cannot handle a version element.
    #
    if ($clientvers > 0) {
	print "<image metadata_version=\"${METADATA_SERVERVERSION}\">\n";
    }
    else {
	print "<image>\n";
    }
409
    foreach my $key (sort keys(%xmlfields)) {
410 411
	my $val = $xmlfields{$key};

412 413 414
	print " <attribute name=\"$key\">";
	print "<value>" . CGI::escapeHTML($val) . "</value>";
	print "</attribute>\n";
415
    }
416 417 418 419 420 421 422 423 424 425
    if (@imagelist) {
	foreach my $im (@imagelist) {
	    my $version = $im->version();
	    my $url     = $im->LocalVersionURL();

	    print " <version_history name='$version'>";
	    print CGI::escapeHTML($url);
	    print "</version_history>\n";
	}
    }
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441
    print "</image>\n";

    return 0;
}

sub DumpOS($)
{
    my ($osinfo) = @_;
    
    # Array of string values to print. 
    my %xmlfields = ();

    $xmlfields{"description"}     = $osinfo->description();
    $xmlfields{"osname"}          = $osinfo->osname();
    $xmlfields{"pid"}             = $osinfo->pid();
    $xmlfields{"OS"}              = $osinfo->OS();
442 443
    $xmlfields{"version"}         = $osinfo->version()
    	if (defined($osinfo->version()) && $osinfo->version() ne "");
444
    $xmlfields{"path"}            = $osinfo->path()
445
	if (defined($osinfo->path()) && $osinfo->path() ne "");
446 447 448
    $xmlfields{"magic"}           = $osinfo->magic()
	if (defined($osinfo->magic()));
    $xmlfields{"op_mode"}         = $osinfo->op_mode();
449 450
    $xmlfields{"features"}        = $osinfo->osfeatures()
	if (defined($osinfo->osfeatures()) && $osinfo->osfeatures() ne "");
451
    $xmlfields{"shared"}          = $osinfo->shared();
452
    $xmlfields{"mfs"}             = $osinfo->mfs() if ($osinfo->mfs());
453 454 455 456 457 458 459 460 461 462 463 464 465 466
    $xmlfields{"mustclean"}       = $osinfo->mustclean();
    $xmlfields{"reboot_waittime"} = $osinfo->reboot_waittime()
	if (defined($osinfo->reboot_waittime()));

    if (defined($osinfo->nextosid()) && $osinfo->nextosid()) {
	my $nextosinfo = OSinfo->Lookup($osinfo->nextosid());
	if (!defined($nextosinfo)) {
	    fatal("Could not look up nextosid for $osinfo");
	}
	$xmlfields{"nextosid"} =
	    $nextosinfo->pid() . "," . $nextosinfo->osname();
    }

    print "<osid>\n";
467
    foreach my $key (sort keys(%xmlfields)) {
468 469
	my $val = $xmlfields{$key};

470 471 472
	print " <attribute name=\"$key\">";
	print "<value>" . CGI::escapeHTML($val) . "</value>";
	print "</attribute>\n";
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
    }
    print "</osid>\n";

    return 0;
}
exit(0);

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

    print STDERR "*** $0:\n".
	         "    $mesg\n";
    exit(-1);
}