#!/usr/bin/perl -w # # Copyright (c) 2010-2017 University of Utah and the Flux Group. # # {{{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 . # # }}} # use English; use strict; use Getopt::Std; use CGI; use Data::Dumper; # # Dump an EZ descriptor out. # sub usage() { print("Usage: dumpdescriptor ". "[-d] [-e] [-v clientvers] [-i [-t]] | [-o ]\n"); exit(-1); } my $optlist = "di:o:tev:"; my $debug = 0; my $dotypes = 0; my $export = 0; my $clientvers = 0; my $argumentid; # # Configure variables # my $TB = "@prefix@"; my $TBOPS = "@TBOPSEMAIL@"; my $TBAUDIT = "@TBAUDITEMAIL@"; my $TBGROUP_DIR = "@GROUPSROOT_DIR@"; my $TBPROJ_DIR = "@PROJROOT_DIR@"; my $TBBASE = "@TBBASE@"; my $PGENISUPPORT= @PROTOGENI_SUPPORT@; my $OURDOMAIN = "@OURDOMAIN@"; my $WITHPROVENANCE= @IMAGEPROVENANCE@; my $MAINSITE = @TBMAINSITE@; # # 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 # my $METADATA_SERVERVERSION = 2; # # 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; use NodeType; use EmulabFeatures; $EmulabFeatures::verbose = 0; # 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; } if (defined($options{"t"})) { $dotypes = 1; } if (defined($options{"e"})) { $export = 1; } if (defined($options{"v"})) { $clientvers = $options{"v"}; } if (@ARGV) { usage(); } if (defined($options{"i"})) { $argumentid = $options{"i"}; my $image = Image->Lookup($argumentid); if (!defined($image)) { fatal("No such image: $argumentid"); } DumpImage($image); } elsif (defined($options{"o"})) { $argumentid = $options{"o"}; my $osinfo = OSinfo->Lookup($argumentid); if (!defined($osinfo)) { fatal("No such osid: $argumentid"); } DumpOS($osinfo); } else { fatal("Must supply an image or os ID"); } sub DumpImage($) { my ($image) = @_; # Array of string values to print. my %xmlfields = (); # For version info. my @imagelist = (); $xmlfields{"imagename"} = $image->imagename(); $xmlfields{"format"} = $image->format(); if (!$export) { $xmlfields{"pid"} = $image->pid(); $xmlfields{"gid"} = $image->gid(); } $xmlfields{"description"} = $image->description(); if (!$image->isdataset()) { $xmlfields{"loadpart"} = $image->loadpart(); } $xmlfields{"global"} = $image->global(); $xmlfields{"shared"} = $image->shared(); if (defined($image->path()) && $image->path() ne "") { # # Old clients cannot handle directory based paths, so give them # a filename instead. # my $path = $image->path(); if ($clientvers == 0 && $image->IsDirPath()) { $path .= $image->imagename() . ".ndz" . ($image->version() ? ":" . $image->version() : ""); } $xmlfields{"path"} = $path; } $xmlfields{"hash"} = $image->hash() if ($export && defined($image->hash()) && $image->hash() ne ""); $xmlfields{"mbr_version"} = $image->mbr_version(); if ($export && $clientvers > 0) { $xmlfields{"isdataset"} = $image->isdataset(); if ($clientvers > 1 && $image->isdataset()) { $xmlfields{"lba_low"} = $image->lba_low(); $xmlfields{"lba_high"} = $image->lba_high(); $xmlfields{"lba_size"} = $image->lba_size(); } } if ($export) { my $url; my $imageid = $image->imageid(); my $uuid = $image->uuid(); my $image_uuid = $image->image_uuid(); 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"); } } # # 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. # if ($argumentid =~ /:/) { $url = "$TBBASE/spewimage.php". "?imageid=$uuid&access_key=$access_key"; } else { $url = "$TBBASE/spewimage.php". "?imageid=$image_uuid&access_key=$access_key"; } $xmlfields{"imagefile_url"} = $url; 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(); # Tell the caller some extras for debugging image tracking (IMS). $xmlfields{'origin_uuid'} = $image_uuid; $xmlfields{'origin_name'} = $image->versname(); if ($PGENISUPPORT) { # And the URN is required for image tracking. require GeniHRN; $xmlfields{'origin_urn'} = GeniHRN::Generate($OURDOMAIN, "authority", "cm"); } # Send the architecture if it is set here. if ($image->architecture()) { $xmlfields{'architecture'} = $image->architecture(); } 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"; } } # # Send along the history so that the caller can get all # the versions, which is important for delta based images. # if ($clientvers > 3) { # # 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() if (defined($image->deltahash()) && $image->deltahash() ne ""); if ($clientvers > 4) { $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 ''); } # # 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. if ($image->AllVersions(\@tmp, 1)) { 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; } } } } } 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(); } if (! ($image->ezid() || $image->isdataset())) { $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()); } elsif (!$image->isdataset()) { my $osinfo = OSinfo->Lookup($image->imageid()); if (!defined($osinfo)) { fatal("Could not find osid for $image"); } $xmlfields{"OS"} = $osinfo->OS(); $xmlfields{"version"} = $osinfo->version() if (defined($osinfo->version()) && $osinfo->version() ne ""); $xmlfields{"op_mode"} = $osinfo->op_mode(); $xmlfields{"osfeatures"} = $osinfo->osfeatures() if (defined($osinfo->osfeatures()) && $osinfo->osfeatures() ne ""); $xmlfields{"reboot_waittime"} = $osinfo->reboot_waittime() if (defined($osinfo->reboot_waittime())); # 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++) { my $func = "part${i}_osid"; my $foo = $image->$func(); if (defined($foo)) { $xmlfields{"loadpart"} = $i; last; } } } 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(); } } if ($dotypes) { my @typelist = $image->TypeList(); foreach my $nodetype (@typelist) { my $type = $nodetype->type(); $xmlfields{"mtype_$type"} = "1"; } } # # Old sites cannot handle a version element. # if ($clientvers > 0) { print "\n"; } else { print "\n"; } foreach my $key (sort keys(%xmlfields)) { my $val = $xmlfields{$key}; print " "; print "" . CGI::escapeHTML($val) . ""; print "\n"; } if (@imagelist) { foreach my $im (@imagelist) { my $version = $im->version(); my $url = $im->LocalVersionURL(); print " "; print CGI::escapeHTML($url); print "\n"; } } print "\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(); $xmlfields{"version"} = $osinfo->version() if (defined($osinfo->version()) && $osinfo->version() ne ""); $xmlfields{"path"} = $osinfo->path() if (defined($osinfo->path()) && $osinfo->path() ne ""); $xmlfields{"magic"} = $osinfo->magic() if (defined($osinfo->magic())); $xmlfields{"op_mode"} = $osinfo->op_mode(); $xmlfields{"features"} = $osinfo->osfeatures() if (defined($osinfo->osfeatures()) && $osinfo->osfeatures() ne ""); $xmlfields{"shared"} = $osinfo->shared(); $xmlfields{"mfs"} = $osinfo->mfs() if ($osinfo->mfs()); $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 "\n"; foreach my $key (sort keys(%xmlfields)) { my $val = $xmlfields{$key}; print " "; print "" . CGI::escapeHTML($val) . ""; print "\n"; } print "\n"; return 0; } exit(0); sub fatal($) { my ($mesg) = @_; print STDERR "*** $0:\n". " $mesg\n"; exit(-1); }