#!/usr/bin/perl -wT # # Copyright (c) 2006, 2007 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 Date::Parse; use Errno qw(EDQUOT); use Data::Dumper; # # Simple graphing functions for link data. # # Exit codes are important; they tell the web page what has happened so # it can say something useful to the user. Fatal errors are mostly done # with die(), but expected errors use this routine. At some point we will # use the DB to communicate the actual error. # # $status < 0 - Fatal error. Something went wrong we did not expect. # $status = 0 - Everything okay. # $status > 0 - Expected error. User not allowed for some reason. # sub usage() { print(STDERR "Usage: template_linkgraph [-q] [-s vnode] [-t vnode] ". "-i -r \n". "switches and arguments:\n". "-q - be less chatty\n". "-r - Experiment run to work on\n". "-i - Experiment index to work on\n". " - GUID and version\n"); exit(-1); } my $optlist = "qi:dr:s:t:"; my %options = (); my $quiet = 0; my $debug = 0; my $eid; my $exptidx; my $runidx; my $guid; my $version; my $graphtype; my $srclan; my $dstlan; my $srcvnode; my $dstvnode; # # Configure variables # my $TB = "@prefix@"; my $EVENTSYS = @EVENTSYS@; my $TBOPS = "@TBOPSEMAIL@"; my $TBLOGS = "@TBLOGSEMAIL@"; my $TBDOCBASE = "@TBDOCBASE@"; my $TBBASE = "@TBBASE@"; my $CONTROL = "@USERNODE@"; my $checkquota = "$TB/sbin/checkquota"; my $dbcontrol = "$TB/sbin/opsdb_control"; my $tempanalyze = "$TB/bin/template_analyze"; # Locals my $template; my $instance; my $archive_tag; # Protos sub ParseArgs(); sub fatal($$); sub cleanup(); # # Testbed Support libraries # use lib "@prefix@/lib"; use libdb; use libtestbed; use libtblog; use Template; use Experiment; use User; # In libdb my $projroot = PROJROOT(); # # Turn off line buffering on output # $| = 1; # # Set umask for start/swap. We want other members in the project to be # able to swap/end experiments, so the log and intermediate files need # to be 664 since some are opened for append. # umask(0002); # # Untaint the path # # un-taint path $ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin'; delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; # # Verify user and get his DB uid and other info for later. # my $this_user = User->ThisUser(); if (! defined($this_user)) { tbdie("You ($UID) do not exist!"); } my $user_uid = $this_user->uid(); # Now parse arguments. ParseArgs(); # Temporary libArchive::setdebug($debug); # # Grab template info and do access check. # $template = Template->Lookup($guid, $version); if (!defined($template)) { tbdie("Experiment template $guid/$version does not exist!"); } if (! $template->AccessCheck($this_user, TB_EXPT_READINFO)) { tberror("You do not have permission to access template $guid/$version"); exit(1); } my $pid = $template->pid(); my $gid = $template->gid(); $instance = Template::Instance->LookupByExptidx($exptidx); if (!defined($instance)) { fatal(-1, "Could not get instance record for experiment $exptidx!"); } if ($instance->ArchiveTag(\$archive_tag) < 0) { fatal(-1, "Could not get current archive tag for instance $exptidx!"); } if (system("$checkquota $user_uid") != 0) { tberror("You are over your disk quota on $CONTROL; ". "please login there and cleanup!"); exit(1); } # Get the the runs. my %runlist; $instance->RunList(\%runlist) == 0 or fatal(-1, "Could not get runlist for $instance"); # # It is an error to analyze an instance with no runs; it sorta implies # the the initial run is still active. # if (! keys(%runlist)) { tberror("There are no experiment runs in instance $instance!"); exit(1); } my $dbname; my $optargs = ""; if ($instance->Instantiated()) { my $experiment = Experiment->Lookup($instance->pid(), $instance->eid()); fatal(-1, "Could not get experiment object from $instance!") if (!defined($experiment)); fatal(-1, "No experiment database in use!") if (! $experiment->dpdb() || !$experiment->dpdbname()); $dbname = $experiment->dpdbname(); $eid = $instance->eid(); } else { # # We need to use a database from the archive. We do not want to check # this out multiple times, so look to see if a temp DB already exists. # # Note that we just want the last run since that will include the # accumulated data. # my $lastrun = $instance->LastRun(); fatal(-1, "Could not get last run for instance $instance!") if (! defined($lastrun)); my $idx = $lastrun->idx(); $dbname = "${guid},${version}," . $instance->idx() . ",$idx"; my $query_result = DBQueryFatal("select * from datapository_databases ". "where dbname='$dbname'"); if (! $query_result->numrows) { system("$tempanalyze -i $exptidx -r $idx $guid/$version") == 0 or fatal(-1, "Could not check out database for run $idx!"); } # # Since there is no current instance experiment, use the eid from the # template for the lookups below. This is not quite correct though, since # an instance might actually have a different topology or number of nodes, # or be different in some other manner. Needs more thought. # $eid = $template->eid(); } # # For a specific run, figure out the start and end times of the run # and pass those along as options to the graphing code. # if (defined($runidx)) { my $rowref = $runlist{$runidx}; my $start = $rowref->{"start_time"}; my $stop = $rowref->{"stop_time"}; if (defined($stop)) { $optargs = "-r " . str2time($start) . ":" . str2time($stop); } else { $optargs = "-r " . str2time($start) . ":0"; } } # Specific source link/node if (defined($srcvnode)) { my $query_result = DBQueryFatal("select ip from virt_lans ". "where vname='$srclan' and vnode='$srcvnode' and ". " pid='$pid' and eid='$eid'"); if ($query_result->numrows) { my ($ip) = $query_result->fetchrow_array(); $optargs .= " -s $ip"; } } # Specific destination link/node if (defined($dstvnode)) { my $query_result = DBQueryFatal("select ip from virt_lans ". "where vname='$dstlan' and vnode='$dstvnode' and ". " pid='$pid' and eid='$eid'"); if ($query_result->numrows) { my ($ip) = $query_result->fetchrow_array(); $optargs .= " -t $ip"; } } #SENDMAIL($TBOPS, "foo", "$dbname $graphtype $optargs"); system("$dbcontrol graphdb $dbname $graphtype $optargs") == 0 or fatal(-1, "Failed to generate requested graph!"); exit(0); # # Parse command arguments. Once we return from getopts, all that are # left are the required arguments. # sub ParseArgs() { if (! getopts($optlist, \%options)) { usage(); } if (@ARGV != 2) { usage(); } # # Pick up guid/version first and untaint. # my $tmp = shift(@ARGV); if ($tmp =~ /^([\w]*)\/([\d]*)$/) { $guid = $1; $version = $2; } else { tbdie("Bad data in argument: $tmp"); } # # Pick up graph type. # $tmp = shift(@ARGV); if ($tmp =~ /^([\w]*)$/) { $graphtype = $1; } else { tbdie("Bad data in argument: $tmp"); } if (defined($options{"i"})) { $exptidx = $options{"i"}; if (! TBcheck_dbslot($exptidx, "default", "int", TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) { tbdie("Improper experiment index!"); } # real check. if ($exptidx =~ /^([\w]*)$/) { $exptidx = $1; } else { tbdie("Bad data in argument: $exptidx"); } } else { tbdie("You must supply the -i option!"); } if (defined($options{"r"})) { $runidx = $options{"r"}; if (! TBcheck_dbslot($runidx, "default", "int", TBDB_CHECKDBSLOT_WARN|TBDB_CHECKDBSLOT_ERROR)) { tbdie("Improper run index!"); } # real check. if ($runidx =~ /^([\w]*)$/) { $runidx = $1; } else { tbdie("Bad data in argument: $runidx"); } } # These are really linkname/vnode ... so we can find exact IP addr. if (defined($options{"s"})) { my $tmp = $options{"s"}; if ($tmp =~ /^([-\w]+)\/([-\w]+)$/) { $srclan = $1; $srcvnode = $2; } else { tbdie("Bad srcvnode name: $srcvnode!"); } } if (defined($options{"t"})) { my $tmp = $options{"t"}; if ($tmp =~ /^([-\w]+)\/([-\w]+)$/) { $dstlan = $1; $dstvnode = $2; } else { tbdie("Bad dstvnode name: $dstvnode!"); } } if (defined($options{"q"})) { $quiet = 1; } if (defined($options{"d"})) { $debug = 2; } } # # Cleanup the mess. # sub cleanup() { } sub fatal($$) { my ($errorstat, $msg) = @_; tberror $msg; tbinfo "Cleaning up and exiting with status $errorstat ..."; exit($errorstat); }