From ac711ea5106252583bd281f651b97d42daecbbf8 Mon Sep 17 00:00:00 2001 From: Mike Hibler Date: Thu, 11 Aug 2011 21:35:20 -0600 Subject: [PATCH] Initial support for loading Windows7 .wim images via WinPE/ImageX. 1. Support for "one-shot" PXE booting ala the one-shot osid. Switches to pxelinux to boot WinPE and then switch back after done. Painful now because we have to HUP dhcpd everytime we change the PXE path, but we may be able to fix this in the future by going all-pxelinux-all-the-time. 2. Added pxe_select, analogous to os_select, for changing the pxe_boot_path including the one time path. 3. Added the WIMRELOAD state machine to shepherd a node through the process. Still has some rough edges and may need refining. --- configure | 2 +- configure.in | 2 +- db/EmulabConstants.pm.in | 5 +- db/Node.pm.in | 28 +++++ db/dhcpd_makeconf.in | 13 +- db/elabinelab_bossinit.in | 3 + db/nfree.in | 7 +- event/stated/stated.in | 23 ++++ sql/database-fill.sql | 37 ++++++ tbsetup/GNUmakefile.in | 1 + tbsetup/libosload_new.pm.in | 51 +++++++- tbsetup/pxe_select.in | 233 ++++++++++++++++++++++++++++++++++++ tmcd/tmcd.c | 19 ++- 13 files changed, 407 insertions(+), 17 deletions(-) create mode 100644 tbsetup/pxe_select.in diff --git a/configure b/configure index 8c34886fd..8cfa09066 100755 --- a/configure +++ b/configure @@ -7206,7 +7206,7 @@ outfiles="$outfiles Makeconf GNUmakefile \ tbsetup/console_reset tbsetup/bwconfig tbsetup/power_rpc27.pm \ tbsetup/power_mail.pm tbsetup/power_whol.pm \ tbsetup/os_load tbsetup/os_setup tbsetup/os_select tbsetup/power \ - tbsetup/node_reboot tbsetup/nscheck \ + tbsetup/node_reboot tbsetup/nscheck tbsetup/pxe_select \ tbsetup/resetvlans tbsetup/rmuser tbsetup/rmproj \ tbsetup/sched_reload tbsetup/sched_reserve tbsetup/reload_daemon \ tbsetup/batchexp tbsetup/batch_daemon tbsetup/repos_daemon \ diff --git a/configure.in b/configure.in index 2c833eea0..a0d5b771c 100755 --- a/configure.in +++ b/configure.in @@ -927,7 +927,7 @@ outfiles="$outfiles Makeconf GNUmakefile \ tbsetup/console_reset tbsetup/bwconfig tbsetup/power_rpc27.pm \ tbsetup/power_mail.pm tbsetup/power_whol.pm \ tbsetup/os_load tbsetup/os_setup tbsetup/os_select tbsetup/power \ - tbsetup/node_reboot tbsetup/nscheck \ + tbsetup/node_reboot tbsetup/nscheck tbsetup/pxe_select \ tbsetup/resetvlans tbsetup/rmuser tbsetup/rmproj \ tbsetup/sched_reload tbsetup/sched_reserve tbsetup/reload_daemon \ tbsetup/batchexp tbsetup/batch_daemon tbsetup/repos_daemon \ diff --git a/db/EmulabConstants.pm.in b/db/EmulabConstants.pm.in index 6a13343f5..92d0222b5 100644 --- a/db/EmulabConstants.pm.in +++ b/db/EmulabConstants.pm.in @@ -139,8 +139,8 @@ use vars qw(@ISA @EXPORT); TBDB_EXPT_WORKDIR TB_OSID_MBKERNEL TB_OSID_FREEBSD_MFS TB_OSID_FRISBEE_MFS - TBDB_TBCONTROL_RESET TBDB_TBCONTROL_RELOADDONE - TBDB_TBCONTROL_RELOADDONE_V2 + TBDB_TBCONTROL_PXERESET TBDB_TBCONTROL_RESET + TBDB_TBCONTROL_RELOADDONE TBDB_TBCONTROL_RELOADDONE_V2 TBDB_TBCONTROL_TIMEOUT TBDB_NO_STATE_TIMEOUT TBDB_TBCONTROL_PXEBOOT TBDB_TBCONTROL_BOOTING TBDB_TBCONTROL_CHECKGENISUP @@ -454,6 +454,7 @@ sub TBDB_ALLOCSTATE_RES_REBOOT() { "RES_REBOOT"; } sub TBDB_ALLOCSTATE_RES_TEARDOWN() { "RES_TEARDOWN"; } sub TBDB_ALLOCSTATE_UNKNOWN() { "UNKNOWN"; }; +sub TBDB_TBCONTROL_PXERESET { "PXERESET"; } sub TBDB_TBCONTROL_RESET { "RESET"; } sub TBDB_TBCONTROL_RELOADDONE { "RELOADDONE"; } sub TBDB_TBCONTROL_RELOADDONE_V2{ "RELOADDONEV2"; } diff --git a/db/Node.pm.in b/db/Node.pm.in index b65d921db..f7e7fc14c 100644 --- a/db/Node.pm.in +++ b/db/Node.pm.in @@ -2628,6 +2628,34 @@ sub OSSelect($$$$) return Refresh($self); } +# +# +# +sub PXESelect($$$$$) +{ + my ($self, $path, $field, $debug, $changedp) = @_; + my $nodeid = $self->node_id(); + my $didit = 0; + + print STDERR "Setting $field for $nodeid to '$path'.\n" + if ($debug && $path); + + my $cur = ($field eq "pxe_boot_path") ? + $self->pxe_boot_path() : $self->next_pxe_boot_path(); + $cur = "" if (!$cur); + if ($cur ne $path) { + DBQueryWarn("update nodes set ${field}=". + ($path ? "'$path'" : "NULL") . + " where node_id='$nodeid'") + or return -1; + $didit = 1; + } + $$changedp = $didit + if (defined($changedp)); + + return Refresh($self); +} + sub ResetNextOpMode($$) { my ($self,$debug) = @_; diff --git a/db/dhcpd_makeconf.in b/db/dhcpd_makeconf.in index a1be9c0b5..84dcc293e 100755 --- a/db/dhcpd_makeconf.in +++ b/db/dhcpd_makeconf.in @@ -213,7 +213,8 @@ while () { my $nodetype = $2; $query_result = - DBQueryWarn("select n.node_id,n.pxe_boot_path,n.type, ". + DBQueryWarn("select n.node_id,n.type, ". + " n.pxe_boot_path,n.next_pxe_boot_path,". " i.IP,i.MAC,r.pid,r.eid,r.vname, ". " r.inner_elab_role,r.inner_elab_boot, ". " r.plab_role,r.plab_boot ". @@ -308,13 +309,17 @@ while () { # # Handle alternate boot program filename if it exists. - # Use mutable nodes.pxe_boot_path if it is defined. + # Use mutable nodes.next_pxe_boot_path or + # nodes.pxe_boot_path if one is defined. # Otherwise use the node_types.pxe_boot_path if it is # defined. Otherwise don't set anything (use the global # default). # - $filename = SetupPXEBoot($node_id, $mac, 1, - $row{"pxe_boot_path"}); + my $pxeboot = $row{"next_pxe_boot_path"}; + if (!$pxeboot) { + $pxeboot = $row{"pxe_boot_path"}; + } + $filename = SetupPXEBoot($node_id, $mac, 1, $pxeboot); if (!$filename) { # Get the type info for this type. my $nodetype = NodeType->Lookup($row{"type"}); diff --git a/db/elabinelab_bossinit.in b/db/elabinelab_bossinit.in index 487f3306c..0a6da5e7b 100755 --- a/db/elabinelab_bossinit.in +++ b/db/elabinelab_bossinit.in @@ -275,8 +275,11 @@ if (1) { } # fixup any nodes table entries with non-standard pxe_boot_path's + # and clear any one-shot values DBQueryFatal("update nodes set pxe_boot_path=NULL ". " where pxe_boot_path is not NULL"); + DBQueryFatal("update nodes set next_pxe_boot_path=NULL ". + " where next_pxe_boot_path is not NULL"); # # Now symlink all the alternate boots to pxeboot.emu diff --git a/db/nfree.in b/db/nfree.in index 550744796..9d987fae8 100755 --- a/db/nfree.in +++ b/db/nfree.in @@ -1,7 +1,7 @@ #!/usr/bin/perl -w # # EMULAB-COPYRIGHT -# Copyright (c) 2000-2010 University of Utah and the Flux Group. +# Copyright (c) 2000-2011 University of Utah and the Flux Group. # All rights reserved. # use strict; @@ -351,8 +351,9 @@ if ($moveToOldReserved) { # NOTE: this does not happen very often. elabinelab, plabinelab? # foreach my $node (@freed_nodes) { - if ($node->pxe_boot_path() && $node->pxe_boot_path() ne "") { - $node->Update({"pxe_boot_path" => "NULL"}) == 0 or + if ($node->pxe_boot_path() || $node->next_pxe_boot_path()) { + $node->Update({"pxe_boot_path" => "NULL", + "next_pxe_boot_path" => "NULL"}) == 0 or die("*** $0:\n". " Could not update pxe_boot_path for $node\n"); $mustmakeconf = 1; diff --git a/event/stated/stated.in b/event/stated/stated.in index 8b5bae333..493796f89 100755 --- a/event/stated/stated.in +++ b/event/stated/stated.in @@ -26,6 +26,7 @@ my $TBOPS = "@TBSTATEDEMAIL@"; my $REALTBOPS = "@TBOPSEMAIL@"; my $TBDBNAME = "@TBDBNAME@"; my $REALTBDBNAME = "tbdb"; # So we know if we're using the "real" db +my $pxeselect = "$TB/bin/pxe_select"; my $osselect = "$TB/bin/os_select"; my $nodereboot = "$TB/bin/node_reboot"; my $rebootlog = "$TB/log/nodereboot.log"; @@ -124,6 +125,7 @@ if ($opt{d}) { # Grab some constants into variables my $TBANYMODE = TBDB_NODEOPMODE_ANY; +my $TBPXERESET = TBDB_TBCONTROL_PXERESET; my $TBRESET = TBDB_TBCONTROL_RESET; my $TBTIMEOUT = TBDB_TBCONTROL_TIMEOUT; my $PXEBOOT = TBDB_TBCONTROL_PXEBOOT; @@ -891,6 +893,12 @@ sub stateTransition($$) { CheckPortRegistration($node); next; }; + /^$TBPXERESET$/ && do { + # We successfully booted, so reset one-shot PXEboot if any + # Check if we really need to do a reset + handleCtrlEvent($node,$trig); + next; + }; /^$TBRESET$/ && do { # We successfully booted, so clear some flags $nodes{$node}{noretry} = 0; @@ -1050,6 +1058,21 @@ sub handleCtrlEvent($$) { info("CtrlEvent: $node, $event\n"); foreach ($event) { + /^$TBPXERESET$/ && do { + # + # Clear next_pxe_boot_path with pxe_select. + # + # Note that this will recreate the DHCPD config file and HUP it. + # It will also ssh over to any subbosses and do the same. + # Thus there is lost of potential to get hung or take a long time. + # + $cmd = "$pxeselect -d -c -1 $node"; + system($cmd) and + notify("$node/$event: Could not clear next_pxe_boot_path!\n"); + + info("Performed $TBPXERESET for $node\n"); + next; + }; /^$TBRESET$/ && do { # # Clear next_boot_path with os_select. diff --git a/sql/database-fill.sql b/sql/database-fill.sql index 4bff2cfa9..10b9600c6 100644 --- a/sql/database-fill.sql +++ b/sql/database-fill.sql @@ -329,6 +329,24 @@ REPLACE INTO mode_transitions VALUES ('SECUREBOOT','TPMSIGNOFF','NORMALv2','SHUT REPLACE INTO mode_transitions VALUES ('SECUREBOOT','TPMSIGNOFF','PXEFBSD','SHUTDOWN',''); REPLACE INTO mode_transitions VALUES ('SECUREBOOT','TPMSIGNOFF','PXEKERNEL','BOOTING','SecureBootDone'); REPLACE INTO mode_transitions VALUES ('NORMALv2','SHUTDOWN','SECURELOAD','SHUTDOWN','SecureLoadStart'); +# +REPLACE INTO mode_transitions VALUES ('PXEFBSD','SHUTDOWN','WIMRELOAD','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('MINIMAL','SHUTDOWN','WIMRELOAD','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('NETBOOT','SHUTDOWN','WIMRELOAD','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('NORMAL','SHUTDOWN','WIMRELOAD','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('NORMALv1','SHUTDOWN','WIMRELOAD','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('NORMALv2','SHUTDOWN','WIMRELOAD','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('WIMRELOAD','SHUTDOWN','PXEFBSD','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('WIMRELOAD','SHUTDOWN','MINIMAL','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('WIMRELOAD','SHUTDOWN','NETBOOT','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('WIMRELOAD','SHUTDOWN','NORMAL','REBOOTING',''); +REPLACE INTO mode_transitions VALUES ('WIMRELOAD','SHUTDOWN','NORMALv1','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('WIMRELOAD','SHUTDOWN','NORMALv2','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('WIMRELOAD','RELOADDONE','MINIMAL','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('WIMRELOAD','RELOADDONE','NETBOOT','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('WIMRELOAD','RELOADDONE','NORMAL','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('WIMRELOAD','RELOADDONE','NORMALv1','SHUTDOWN',''); +REPLACE INTO mode_transitions VALUES ('WIMRELOAD','RELOADDONE','NORMALv2','SHUTDOWN',''); -- -- Dumping data for table `priorities` @@ -407,6 +425,11 @@ REPLACE INTO state_timeouts VALUES ('SECURELOAD','RELOADING',3600,'STATE:SECVIOL REPLACE INTO state_timeouts VALUES ('SECURELOAD','RELOADSETUP',60,'STATE:SECVIOLATION'); REPLACE INTO state_timeouts VALUES ('SECURELOAD','SHUTDOWN',300,'STATE:SECVIOLATION'); REPLACE INTO state_timeouts VALUES ('SECURELOAD','TPMSIGNOFF',300,'STATE:SECVIOLATION'); +# +REPLACE INTO state_timeouts VALUES ('WIMRELOAD','SHUTDOWN',240,'REBOOT'); +REPLACE INTO state_timeouts VALUES ('WIMRELOAD','RELOADSETUP',60,'NOTIFY'); +REPLACE INTO state_timeouts VALUES ('WIMRELOAD','RELOADING',1800,'NOTIFY'); +REPLACE INTO state_timeouts VALUES ('WIMRELOAD','RELOADDONE',60,'NOTIFY'); -- -- Dumping data for table `state_transitions` @@ -610,6 +633,7 @@ REPLACE INTO state_transitions VALUES ('RELOAD-PUSH','RELOADDONE','SHUTDOWN','Re REPLACE INTO state_transitions VALUES ('RELOAD-PUSH','SHUTDOWN','RELOADSETUP','ReloadSetup'); REPLACE INTO state_transitions VALUES ('SECUREBOOT','BOOTING','SECVIOLATION','QuoteFailed'); REPLACE INTO state_transitions VALUES ('SECUREBOOT','BOOTING','TPMSIGNOFF','QuoteOK'); +REPLACE INTO state_transitions VALUES ('SECUREBOOT','BOOTING','PXEBOOTING','re-BootInfo'); REPLACE INTO state_transitions VALUES ('SECUREBOOT','GPXEBOOTING','PXEBOOTING','DHCP'); REPLACE INTO state_transitions VALUES ('SECUREBOOT','PXEBOOTING','BOOTING','BootInfo'); REPLACE INTO state_transitions VALUES ('SECURELOAD','BOOTING','PXEBOOTING','re-BootInfo'); @@ -633,6 +657,14 @@ REPLACE INTO state_transitions VALUES ('NORMALv2','*','POWEROFF','Power Off'); REPLACE INTO state_transitions VALUES ('NORMAL','POWEROFF','SHUTDOWN','Power On'); REPLACE INTO state_transitions VALUES ('NORMALv1','POWEROFF','SHUTDOWN','Power On'); REPLACE INTO state_transitions VALUES ('NORMALv2','POWEROFF','SHUTDOWN','Power On'); +# +REPLACE INTO state_transitions VALUES ('WIMRELOAD','SHUTDOWN','RELOADSETUP','BootOK'); +REPLACE INTO state_transitions VALUES ('WIMRELOAD','RELOADSETUP','RELOADING','ReloadStart'); +REPLACE INTO state_transitions VALUES ('WIMRELOAD','RELOADING','RELOADDONE','ReloadDone'); +REPLACE INTO state_transitions VALUES ('WIMRELOAD','SHUTDOWN','SHUTDOWN','Retry'); +REPLACE INTO state_transitions VALUES ('WIMRELOAD','SHUTDOWN','PXEBOOTING','WrongPXEboot'); +REPLACE INTO state_transitions VALUES ('WIMRELOAD','RELOADSETUP','SHUTDOWN','Error'); +REPLACE INTO state_transitions VALUES ('WIMRELOAD','RELOADING','SHUTDOWN','Error'); -- -- Dumping data for table `state_triggers` @@ -662,6 +694,11 @@ REPLACE INTO state_triggers VALUES ('*','SECUREBOOT','TPMSIGNOFF','PXEBOOT, BOOT REPLACE INTO state_triggers VALUES ('*','SECURELOAD','BOOTING','BOOTING'); REPLACE INTO state_triggers VALUES ('*','SECURELOAD','PXEBOOTING',''); REPLACE INTO state_triggers VALUES ('*','SECURELOAD','RELOADDONE','RESET, RELOADDONE'); +# +REPLACE INTO state_triggers VALUES ('*','WIMRELOAD','RELOADDONE','PXERESET, RESET, RELOADDONE'); +REPLACE INTO state_triggers VALUES ('*','WIMRELOAD','PXEBOOTING','REBOOT'); +REPLACE INTO state_triggers VALUES ('*','WIMRELOAD','BOOTING','REBOOT'); +REPLACE INTO state_triggers VALUES ('*','WIMRELOAD','ISUP','REBOOT'); -- -- Dumping data for table `table_regex` diff --git a/tbsetup/GNUmakefile.in b/tbsetup/GNUmakefile.in index 567740c89..be202dc48 100644 --- a/tbsetup/GNUmakefile.in +++ b/tbsetup/GNUmakefile.in @@ -26,6 +26,7 @@ BIN_STUFF = power snmpit snmpit_new tbend tbprerun tbreport \ node_reboot nscheck node_update savelogs node_control \ portstats checkports eventsys_control os_select tbrestart \ tbswap nseswap tarfiles_setup node_history tbrsync \ + pxe_select \ archive_control template_create \ template_swapin template_swapout template_graph \ template_exprun template_delete template_metadata \ diff --git a/tbsetup/libosload_new.pm.in b/tbsetup/libosload_new.pm.in index ce50b6b76..ea64f63d3 100644 --- a/tbsetup/libosload_new.pm.in +++ b/tbsetup/libosload_new.pm.in @@ -44,6 +44,11 @@ my $OUTERBOSS = "@OUTERBOSS_NODENAME@"; my $TBUISP = "$TB/bin/tbuisp"; my $FRISBEE = "$TB/sbin/frisbee"; +# XXX windows load +my $MAKECONF = "$TB/sbin/dhcpd_makeconf"; +my $IMAGEX_OSNAME = "IMAGEX-MFS"; +my $IMAGEX_PXEPATH = "/tftpboot/pxelinux/winpe"; + # # Max number of retries (per node) before its deemed fatal. This allows # for the occasional pxeboot failure. @@ -215,7 +220,7 @@ sub nodeflag($$$;$) return $retval; } -# Get/Set the osid->osinfo mapping. Nice to keep this to avoid lookups. +# Get/Set the imageid->images mapping. Nice to keep this to avoid lookups. sub imageinfo($$;$) { my ($self,$imageid,$imageinfo) = @_; @@ -2188,6 +2193,7 @@ sub SetupReload($$) { my ($self,$nodeobject) = @_; my $node_id = $nodeobject->node_id(); + my $osinfo; $self->dprint(0,"SetupReload($node_id): setting up reload"); @@ -2208,9 +2214,24 @@ sub SetupReload($$) return -1 if (!$query_result); + # XXX windows load + my $needswinpe = 0; + my $idx = 1; foreach my $imageid (@imageids) { my $prepare0 = $idx == 1 && $prepare ? 1 : 0; + + # XXX windows load + my $rowref = $self->imageinfo($imageid); + if (!defined($rowref)) { + tberror "$self: Mike make a bad assumption!"; + return -1; + } + my $format = $rowref->{'format'}; + if (defined($format) && $format eq "wim") { + $needswinpe++; + } + my $query_result = DBQueryWarn("insert into current_reloads ". "(node_id, idx, image_id, mustwipe, prepare) values ". @@ -2220,12 +2241,38 @@ sub SetupReload($$) ++$idx; } - my $osinfo = OSinfo->Lookup($osid); + # XXX windows load + if ($needswinpe) { + if (@imageids > 1) { + tberror("$self: Cannot load more than one image with WinPE/ImageX"); + return -1; + } + # Look up the magic osid for the imageX loader + $osinfo = OSinfo->Lookup(TB_OPSPID, $IMAGEX_OSNAME); + } + else { + $osinfo = OSinfo->Lookup($osid); + } + if ($nodeobject->OSSelect($osinfo,"next_boot_osid",$self->debug())) { tberror "$self: os_select $osid failed!"; return -1; } + # XXX windows load + # whether windows or not, we always make sure this is set correctly + my $changed = 0; + my $path = $needswinpe ? $IMAGEX_PXEPATH : ""; + if ($nodeobject->PXESelect($path, "next_pxe_boot_path", + $self->debug(), \$changed)) { + tberror "$self: pxe_select $path failed!"; + return -1; + } + if ($changed && system("$MAKECONF -i -r")) { + tberror "$self: restart DHCPD failed!"; + return -1; + } + return 0; } diff --git a/tbsetup/pxe_select.in b/tbsetup/pxe_select.in new file mode 100644 index 000000000..fa6a88bc6 --- /dev/null +++ b/tbsetup/pxe_select.in @@ -0,0 +1,233 @@ +#!/usr/bin/perl -wT + +# +# EMULAB-COPYRIGHT +# Copyright (c) 2000-2011 University of Utah and the Flux Group. +# All rights reserved. +# + +# pxe_select sets the PXE boot program that should be loaded on a node, +# either one time or permanently. + +sub usage() { + print <<"EOF"; +Usage: pxe_select -P [-c] [-1] [] [ ...] + -h Display this help message + -d Debug mode + -c Clear the PXE boot path for nodes. Do not provide a path. + Either clears the one-time field (for -1) or resets to the default PXE boot. + -1 Apply change to one-time PXE boot field + -N Do not restart dhcpd on a change + path Path to PXE boot program (must be in /tftpboot) + node Node identifiers (ie pcXX) +EOF + exit(-1); +} +my $optlist = "hdc1lN"; + +# un-taint path +$ENV{'PATH'} = '/bin:/usr/bin:/usr/local/bin'; +delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'}; + +$| = 1; #Turn off line buffering on output + +# Configure variables +my $TB = "@prefix@"; +my $TBOPS = "@TBSTATEDEMAIL@"; +my $TBLOG = "@TBLOGFACIL@"; + +# Testbed Support libraries +use lib "@prefix@/lib"; +use libdb; +use libtestbed; +use OSinfo; +use Node; +use English; +use Getopt::Std; +use Sys::Syslog; + +# Constants +my $makeconf = "$TB/sbin/dhcpd_makeconf"; +my $MBKERNEL = TB_OSID_MBKERNEL; +my %osidmap = # Map some magic OSIDs to op_modes + ( $MBKERNEL => "MINIMAL"); + +# Functions + +sub set_nextmode($;$); +sub set_boot_osid($); +sub node_opmode($); +sub debug($;$); +sub notify($); +sub info($); +sub fatal($); +sub warning($); + +# Global vars +my $debug = 0; # debug/verbose +my $oneshot = 0; # apply change to next_boot_osid. +my $clear = 0; # Clear the selected boot (def,temp,next). +my $list = 0; # Just list the current settings. +my $nochange = 0; # Do not restart dhcpd on a change +my @nodes = (); +my $pxebootpath = ""; + +# Set up syslog +openlog("pxeselect", "pid", $TBLOG); + +# +# Parse command arguments. Once we return from getopts, all that should be +# left are the required arguments. +# +%options = (); +if (! getopts($optlist, \%options)) { usage(); } +if (defined($options{"h"})) { usage(); } +if (defined($options{"d"})) { $debug=1; } +if (defined($options{"1"})) { $oneshot=1; } +if (defined($options{"c"})) { $clear=1; } +if (defined($options{"l"})) { $list=1; } +if (defined($options{"N"})) { $nochange=1; } + +if (! ($clear || $list)) { + usage() + if (@ARGV < 2); + $pxebootpath = shift(); + + # Untaint args. + if ($pxebootpath !~ /\.\./ && + $pxebootpath =~ /^(\/tftpboot\/[-\w\+\.\/]+)$/) { + $pxebootpath = $1; + } + else { + fatal("Bad path: '$pxebootpath'"); + } +} +else { + usage() + if (@ARGV < 1); +} + +# Untaint the nodes. +foreach my $node ( @ARGV ) { + if ($node =~ /^([-\@\w]+)$/) { + $node = $1; + } + else { + fatal("Bad node name: $node"); + } + push(@nodes, $node); +} + +# +# Figure out who called us. Only root, people with admin status +# in the DB, or members of the right project can do this. +# +if ($UID && !TBAdmin($UID)) { + if ($list && !TBNodeAccessCheck($UID, TB_NODEACCESS_READINFO, @nodes)) { + fatal("pxe_select: You do not have permission to access ". + "one or more of the nodes.\n"); + } + if (!$list && !TBNodeAccessCheck($UID, TB_NODEACCESS_MODIFYINFO, @nodes)) { + fatal("pxe_select: You do not have permission to modify ". + "one or more of the nodes.\n"); + } +} + +my $changed = 0; +foreach my $node (@nodes) { + my $nodeobject = Node->Lookup($node); + + # In list mode, show all the IDs + if ($list) { + my $npb = $nodeobject->next_pxe_boot_path(); + my $pb = $nodeobject->pxe_boot_path(); + if (!$pb) { + $nodeobject->NodeTypeAttribute("pxe_boot_path", \$pb); + if ($pb) { + $pb = "$pb (NODE TYPE DEFAULT)"; + } else { + $pb = "/tftpboot/pxeboot.emu (SYSTEM DEFAULT)"; + } + } + print "$node: "; + if ($npb) { + print "next_pxeboot=$npb"; + print ", "; + } + if ($pb) { + print "pxeboot=$pb"; + } + print "\n"; + next; + } + + # The field to change in the DB. + my $field = "pxe_boot_path"; + $field = "next_pxe_boot_path" + if ($oneshot); + my $did; + if ($nodeobject->PXESelect($pxebootpath, $field, $debug, \$did) != 0) { + fatal("PXESelect(): " . ($pxebootpath ? "$pxebootpath " : "") ."failed on $node"); + } + $changed += $did; +} + +# +# Need to remake the dhcpd.conf file if we changed a +# pxeboot path. +# +if (!$nochange && $changed) { + system("$makeconf -i -r") == 0 || + notify("$makeconf failed!\n"); +} + +exit(0); + +sub debug($;$) +{ + my $msg = shift; + my $notice = shift || 0; + my $prio="info"; + + if ($notice) { $prio = "notice"; } + + syslog($prio, $msg); + if ($debug) { print $msg; } +} + +sub notify($) +{ + my $msg = shift; + + if (!$debug) { + SENDMAIL($TBOPS, "pxe_select error", $msg); + } + debug($msg, 1); +} + +sub info($) +{ + my $msg = shift; + + debug($msg); +} + +sub fatal($) +{ + my $msg = shift; + + notify("FATAL: $msg\n"); + exit(1); +} + +sub warning($) +{ + my $msg = shift; + + info("WARNING: $msg\n"); +} + +# This is called when we exit with exit() or die() +END { + closelog(); +} diff --git a/tmcd/tmcd.c b/tmcd/tmcd.c index 31f0da476..c747141d3 100644 --- a/tmcd/tmcd.c +++ b/tmcd/tmcd.c @@ -9312,14 +9312,15 @@ COMMAND_PROTOTYPE(dodhcpdconf) mysql_free_result(res); res = mydb_query("select n.node_id,n.pxe_boot_path,i.IP,i.mac,n.type,r.eid,r.pid," - "r.inner_elab_role,r.inner_elab_boot,r.plab_role,r.plab_boot " + "r.inner_elab_role,r.inner_elab_boot,r.plab_role,r.plab_boot," + "n.next_pxe_boot_path " "from nodes as n " "left join subbosses as s on n.node_id = s.node_id " "left join interfaces as i on n.node_id = i.node_id " "left join reserved as r on n.node_id = r.node_id " "where s.subboss_id = '%s' and " - "s.service='dhcp' and i.role='ctrl' order by n.priority", 11, - reqp->nodeid); + "s.service='dhcp' and i.role='ctrl' " + "order by n.priority", 12, reqp->nodeid); if (!res) { error("dodhcpconf: %s: " @@ -9475,7 +9476,17 @@ COMMAND_PROTOTYPE(dodhcpdconf) remain -= rc; } - if (row[1] != NULL) { + if (row[11] && row[11][0]) { + rc = snprintf(b, remain, " FILENAME=\"%s\"", row[11]); + + if (rc < 0) { + error("dodhcpdconf: error creating output\n"); + return 1; + } + + b += rc; + remain -= rc; + } else if (row[1] && row[1][0]) { rc = snprintf(b, remain, " FILENAME=\"%s\"", row[1]); if (rc < 0) { -- GitLab