Commit 2c33fc02 authored by Mike Hibler's avatar Mike Hibler

All the grue necessary to autogenerate pxelinux config files for nodes.

Keeping them up to date throughout the node lifecycle is not a lot of fun...
parent 7bdb3fdb
......@@ -1560,6 +1560,22 @@ sub default_pxeboot($) {
return NodeTypeAttribute($self, "pxe_boot_path", undef);
}
sub boot_method($) {
my ($self) = @_;
my $val = undef;
if (NodeAttribute($self, "boot_method", \$val) == 0 && defined($val)) {
return $val;
}
$val = NodeTypeAttribute($self, "boot_method", undef);
# XXX need a mechanism for setting default values for attributes
$val = "pxeboot"
if (!$val);
return $val;
}
sub disksize($;$) {
my ($self,$stuff) = @_;
my $val = undef;
......@@ -1699,6 +1715,10 @@ sub ClearBootAttributes($)
"where node_id='$node_id'")
or return -1;
if ($self->boot_method() eq "pxelinux") {
TBPxelinuxConfig($self);
}
return 0;
}
......@@ -2861,6 +2881,9 @@ sub ClearReservation($)
if (DBQueryWarn("delete from reserved where node_id='$node_id'")) {
$self->FlushReserved();
}
if ($self->boot_method() eq "pxelinux") {
TBPxelinuxConfig($self);
}
return 0;
}
......@@ -3170,6 +3193,10 @@ sub OSSelect($$$$)
if ($self->ResetNextOpMode($debug) < 0);
}
if ($self->boot_method() eq "pxelinux") {
TBPxelinuxConfig($self);
}
return Refresh($self);
}
......@@ -3387,6 +3414,10 @@ sub ClearOsids($) {
$self->{"DBROW"}{"temp_boot_osid_vers"} = 0;
$self->{"DBROW"}{"next_boot_osid_vers"} = 0;
if ($self->boot_method() eq "pxelinux") {
TBPxelinuxConfig($self);
}
return 0;
}
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2005-2012 University of Utah and the Flux Group.
# Copyright (c) 2005-2014 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -331,6 +331,7 @@ sub memory($;$) {return GetAttribute($_[0], "memory", $_[1]); }
sub disksize($;$) {return GetAttribute($_[0], "disksize", $_[1]); }
sub disktype($;$) {return GetAttribute($_[0], "disktype", $_[1]); }
sub bootdisk_unit($;$) {return GetAttribute($_[0], "bootdisk_unit", $_[1]); }
sub boot_method($;$) {return GetAttribute($_[0], "boot_method", $_[1]); }
sub pxe_boot_path($;$) {return GetAttribute($_[0], "pxe_boot_path", $_[1]); }
sub processor($;$) {return GetAttribute($_[0], "processor", $_[1]); }
sub frequency($;$) {return GetAttribute($_[0], "frequency", $_[1]); }
......
......@@ -87,6 +87,7 @@ use vars qw(@ISA @EXPORT);
TBSetNodeHistory
TBRobotLabExpt
TBExptContainsNodeCT
TBPxelinuxConfig
);
use emdb;
......@@ -165,6 +166,14 @@ sub LocalUserLookupByUnixId(@)
return User->LookupByUnixId(@_);
}
# Local lookup for the control net interface
sub LocalInterfaceLookupControl($)
{
require Interface;
return Interface->LookupControl($_[0]);
}
#
# Auth stuff.
#
......@@ -448,11 +457,13 @@ sub ExpNodeVnames($$;$$)
# Find out what osid a node will boot next time it comes up,
# Usually (but not always) the currently running OS as well.
#
sub TBBootWhat($;$)
sub TBBootWhat($;$$)
{
my ($node, $debug) = @_;
my ($nodeid, $debug, $wantfield) = @_;
$debug = 0
if (!defined($debug));
$wantfield = 0
if (!defined($wantfield));
#
# WARNING!!!
......@@ -476,32 +487,143 @@ sub TBBootWhat($;$)
"left join os_info as onext on onext.osid=next_boot_osid ".
"left join os_info_versions as vnext on ".
" vnext.osid=onext.osid and vnext.vers=onext.version ".
"where node_id='$node'");
"where node_id='$nodeid'");
if (!$query_result || !$query_result->numrows) {
print("*** Warning: No bootwhat info for $node\n");
print("*** Warning: No bootwhat info for $nodeid\n");
return undef;
}
my ($def_boot_osid, $def_boot_opmode,
$temp_boot_osid, $temp_boot_opmode,
$next_boot_osid, $next_boot_opmode) = $query_result->fetchrow_array();
my $boot_osid = 0;
my $boot_opmode = 0;
my $field = "";
#
# The priority would seem pretty clear.
#
return ($next_boot_osid, $next_boot_opmode)
if (defined($next_boot_osid) && $next_boot_osid ne 0);
return ($temp_boot_osid, $temp_boot_opmode)
if (defined($temp_boot_osid) && $temp_boot_osid ne 0);
return ($def_boot_osid, $def_boot_opmode)
if (defined($def_boot_osid) && $def_boot_osid ne 0);
if (defined($next_boot_osid) && $next_boot_osid ne 0) {
$boot_osid = $next_boot_osid;
$boot_opmode = $next_boot_opmode;
$field = "next_boot_osid";
}
elsif (defined($temp_boot_osid) && $temp_boot_osid ne 0) {
$boot_osid = $temp_boot_osid;
$boot_opmode = $temp_boot_opmode;
$field = "temp_boot_osid";
}
elsif (defined($def_boot_osid) && $def_boot_osid ne 0) {
$boot_osid = $def_boot_osid;
$boot_opmode = $def_boot_opmode;
$field = "def_boot_osid";
}
#
# If all info is clear, the node will boot into PXEWAIT.
# This is not an error.
#
print("*** Warning: node '$node': All boot info was null!\n");
return (0, 0);
else {
print("*** Warning: node '$nodeid': All boot info was null!\n");
}
# XXX hack for TBpxelinuxConfig below...
if ($wantfield) {
return $field;
}
return ($boot_osid, $boot_opmode);
}
#
# Note that this is a simplified version of what goes on in bootinfo.
# We are targetting just the Moonshot nodes where the choices are:
#
# 1. boot from THE disk
# 2. boot the frisbee MFS
# 3. boot a "general" MFS
#
# Its not yet clear how PXEWAIT will be handled. The nodes might be
# powered off in that state, or maybe we go into one of the MFSes to
# await further instructions.
#
sub TBPxelinuxConfig($)
{
my ($node) = @_;
my $nodeid = $node->node_id();
my $action;
my $verbose = 1;
my $field;
if ($node->IsReserved() && ($field = TBBootWhat($nodeid, 0, 1))) {
$action = "pxefail";
my $query_result =
DBQueryWarn("SELECT v.path,v.mfs,p.partition FROM nodes AS n ".
" LEFT JOIN partitions AS p ON ".
" n.node_id=p.node_id AND ".
" n.${field}=p.osid ".
" LEFT JOIN os_info AS i ON ".
" n.${field}=i.osid ".
" LEFT JOIN os_info_versions AS v ON ".
" i.osid=v.osid AND i.version=v.vers ".
" WHERE n.node_id='$nodeid'");
if (!$query_result || !$query_result->numrows) {
print("*** Warning: invalid bootinfo for $nodeid, ".
"booting to MFS\n");
} else {
my ($path, $mfs, $part) = $query_result->fetchrow_array();
if (defined($path) && defined($mfs)) {
# XXX how we identify the frisbee MFS
if ($path =~ /frisbee/) {
$action = "mfsboot";
} else {
$action = "nfsboot";
}
} elsif (defined($part)) {
$action = "diskboot";
} else {
print("*** Warning: invalid $field info for $nodeid, ".
"booting to MFS\n");
}
}
} else {
$action = "pxewait";
}
my $cnet = LocalInterfaceLookupControl($node);
if ($cnet && $cnet->mac() &&
$cnet->mac() =~ /^(..)(..)(..)(..)(..)(..)$/) {
my $nfile = "/tftpboot/pxelinux.cfg/01-$1-$2-$3-$4-$5-$6";
# already exists, see if it is set correctly
if (-e "$nfile" && open(FD, "<$nfile")) {
while (<FD>) {
if (/^ONTIMEOUT\s+(\S+)/) {
if ($1 eq $action) {
close(FD);
return;
}
last;
}
}
close(FD);
}
# argh...gotta use a setuid script to install a new pxelinux config!
if (!system("$TB/sbin/pxelinux_makeconf -d -a $action $nodeid >>/tmp/p_m.log 2>&1")) {
print("$nodeid: set pxelinux boot to '$action'\n");
return;
}
print("*** Warning: $nodeid: could not update pxelinux config");
} else {
print("*** Warning: $nodeid: could not find MAC for cnet iface");
}
print (", who knows what it will boot!\n");
return;
}
#
......
#!/usr/bin/perl -w
#
# Copyright (c) 2000-2013 University of Utah and the Flux Group.
# Copyright (c) 2000-2014 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -103,6 +103,7 @@ my @nodes = ();
my $need_consetup = 0;
my @need_history = ();
my @need_clearbl = ();
my @need_pxeconfig= ();
TBDebugTimeStamp("nalloc started");
......@@ -202,6 +203,8 @@ foreach my $n (@node_names) {
push(@need_history, $node);
# ... and need to have their bootlogs purged.
push(@need_clearbl, $node);
# ... and may need to recreate its pxelinux conf
push(@need_pxeconfig, $node);
}
#
......@@ -284,6 +287,13 @@ if (!$error && (!$noalloc || $partial) && @nodes) {
foreach my $node (@need_clearbl) {
$node->ClearBootLog();
}
# And since the node is now allocated, we need to redo its pxelinux
# config file so it won't be stuck in pxewait
foreach my $node (@need_pxeconfig) {
TBPxelinuxConfig($node)
if ($node->boot_method() eq "pxelinux");
}
}
TBDebugTimeStamp("updated node_activity, history, and bootlogs");
......
......@@ -62,6 +62,8 @@ on commit {
set clhw = binary-to-ascii(16, 8, ":", substring(hardware, 1, 6));
if (substring (option vendor-class-identifier, 0, 9) = "PXEClient") {
set boot = "PXE";
} elsif (substring (option vendor-class-identifier, 0, 6) = "U-boot") {
set boot = "PXE";
} else {
set boot = "OS";
}
......
......@@ -1110,10 +1110,15 @@ sub opModeTransition($$;$) {
$nodes{$node}{notified} = 0;
info("$node: $mode/$oldstate => $newmode/$nextstate\n");
DBQueryFatal("UPDATE nodes SET eventstate='$nextstate', ".
"next_op_mode='', op_mode='$newmode', ".
"state_timestamp='$now', ".
"op_mode_timestamp='$now' WHERE node_id='$node'");
my $nodeobj = Node->LookupSync($node);
my %updates = ("eventstate" => $nextstate,
"next_op_mode" => '',
"op_mode" => $newmode,
"state_timestamp" => $now,
"op_mode_timestamp" => $now);
if ($nodeobj->Update(\%updates)) {
notify("Could not make op_mode transition to $newmode for $node!\n");
}
# Check if this state has a timeout, and if so, put it in the queue
setTimeout($newmode,$nextstate,$node,$now);
......
......@@ -82,11 +82,19 @@ int
bootinfo(struct in_addr ipaddr, char *node_id, struct boot_info *boot_info,
void *opaque, int no_event_send, int *event_sent)
{
boot_what_t *boot_whatp = (boot_what_t *) &boot_info->data;
int err;
#ifdef EVENTSYS
int needevent = 0, eventfailed = 0;
int doevents = 0;
/*
* We are not going to send events for nodes we don't know about
* or that are "pxelinux" nodes.
*/
if (!findnode_bootinfo_db(ipaddr, &doevents) || doevents == 0)
no_event_send = 1;
#endif
int err;
boot_what_t *boot_whatp = (boot_what_t *) &boot_info->data;
switch (boot_info->opcode) {
case BIOPCODE_BOOTWHAT_KEYED_REQUEST:
......
/*
* Copyright (c) 2000-2012 University of Utah and the Flux Group.
* Copyright (c) 2000-2014 University of Utah and the Flux Group.
*
* {{{EMULAB-LICENSE
*
......@@ -32,6 +32,7 @@ int bootinfo(struct in_addr ipaddr, char *node_id,
int query_bootinfo_db(struct in_addr ipaddr, char *node_id,
int version, struct boot_what *info,
char *key);
int findnode_bootinfo_db(struct in_addr ipaddr, int *events);
int elabinelab_hackcheck(struct sockaddr_in *target);
extern int debug;
......
......@@ -56,6 +56,42 @@ open_bootinfo_db(void)
return 0;
}
int
findnode_bootinfo_db(struct in_addr ipaddr, int *events)
{
MYSQL_RES *res;
MYSQL_ROW row;
res = mydb_query("select n.node_id,na.attrvalue,nt.attrvalue "
"from nodes as n "
"left join interfaces as i "
" on n.node_id=i.node_id "
"left join node_attributes as na on "
" n.node_id=na.node_id and na.attrkey='boot_method' "
"left join node_type_attributes as nt on "
" n.type=nt.type and nt.attrkey='boot_method' "
"where i.IP='%s' and i.role='ctrl'",
3, inet_ntoa(ipaddr));
if (!res) {
error("Query failed for host %s\n", inet_ntoa(ipaddr));
return 0;
}
if (!mysql_num_rows(res)) {
mysql_free_result(res);
return 0;
}
row = mysql_fetch_row(res);
if ((row[0] && strcmp(row[0], "pxelinux") == 0) ||
(row[1] && strcmp(row[1], "pxelinux") == 0))
*events = 0;
else
*events = 1;
mysql_free_result(res);
return 1;
}
/*
WARNING!!!
......
#!/usr/bin/perl -w
#
# Copyright (c) 2014 University of Utah and the Flux Group.
......@@ -50,13 +48,17 @@ my $debug = 0;
#
# Functions
#
sub findnode($$);
sub logit($);
sub sendevent($$);
#
# Configure variables
#
my $TB = "@prefix@";
my $TBOPS = "@TBOPSEMAIL@";
my $EVPORT = @BOSSEVENTPORT@;
my $EVSERVER = "boss";
# Locals
my $logfile = "$TB/log/reportboot.log";
......@@ -79,6 +81,12 @@ use lib "@prefix@/lib";
use libtestbed;
use libdb;
use libtblog;
use event;
use Interface;
if ($UID != 0) {
die("Must be root to run this script\n");
}
my %options = ();
if (! getopts($optlist, \%options)) {
......@@ -100,8 +108,11 @@ if ($ARGV[0] =~ /^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
print STDERR "'$ARGV[0]' is not an IP address\n";
usage();
}
if ($ARGV[1] =~ /^([0-9a-fA-F]+:[0-9a-fA-F]+:[0-9a-fA-F]+:[0-9a-fA-F]+:[0-9a-fA-F]+:[0-9a-fA-F]+)$/) {
$MAC = $1;
$MAC = lc($ARGV[1]);
if ($MAC =~ /^([0-9a-f]+):([0-9a-f]+):([0-9a-f]+):([0-9a-f]+):([0-9a-f]+):([0-9a-f]+)$/) {
# dhcpd will return an octet like "02" as "2", so we have to compensate
$MAC = sprintf("%02x%02x%02x%02x%02x%02x",
hex($1), hex($2), hex($3), hex($4), hex($5), hex($6));
} else {
print STDERR "'$ARGV[1]' is not a MAC address\n";
usage();
......@@ -127,10 +138,39 @@ if ($background) {
die("setsid failed: $!");
}
logit("Invoked by $boottype: IP=$IP MAC=$MAC");
my $nodeid = findnode($IP, $MAC);
if (!$nodeid) {
logit("Ignoring unknown node $IP");
exit(0);
}
my $event = ($boottype eq "OS") ? "BOOTING" : "PXEBOOTING";
if (sendevent($nodeid, $event)) {
logit("$nodeid: could not send $event event");
exit(1);
}
logit("$nodeid: sent $event event");
exit(0);
sub findnode($$)
{
my ($IP,$mac) = @_;
my $iface = Interface->LookupByIP($IP);
return undef
if (!$iface);
# XXX this should never happen since dhcpd looked up the IP by mac.
if ($iface->mac() ne $mac) {
logit("$IP: came in on $mac but expected " . $iface->mac());
return undef;
}
return $iface->node_id();
}
sub logit($)
{
my $message = shift;
......@@ -148,3 +188,47 @@ sub logit($)
print STDERR "$message\n" if ($debug);
}
sub sendevent($$)
{
my ($node,$event) = @_;
my $URL = "elvin://$EVSERVER:$EVPORT";
# Connect to the event system, and subscribe the the events we want
my $handle = event_register($URL, 0);
if (!$handle) {
logit("$node: unable to register with event system");
return 1;
}
my $tuple = address_tuple_alloc();
if (!$tuple) {
logit("$node: could not allocate an address tuple");
return 1;
}
%$tuple = ( objtype => "TBNODESTATE",
objname => $node,
eventtype => $event,
host => "boss");
my $notification = event_notification_alloc($handle, $tuple);
if (!$notification) {
logit("$node: could not allocate notification");
return 1;
}
if (!event_notify($handle, $notification)) {
logit("$node: could not send $event notification");
return 1;
}
event_notification_free($handle, $notification);
if (event_unregister($handle) == 0) {
logit("$node: WARNING: could not unregister with event system");
}
return 0;
}
......@@ -1321,7 +1321,8 @@ handle_request(int sock, struct sockaddr_in *client, char *rdata, int istcp)
* Redirect is allowed from the local host only.
*/
if (redirect &&
redirect_client.sin_addr.s_addr != myipaddr.s_addr) {
redirect_client.sin_addr.s_addr != myipaddr.s_addr &&
redirect_client.sin_addr.s_addr != htonl(INADDR_LOOPBACK)) {
char buf1[32], buf2[32];
strcpy(buf1, inet_ntoa(redirect_client.sin_addr));
......@@ -6078,7 +6079,7 @@ COMMAND_PROTOTYPE(dosecurestate)
return 1;
}
quotelen = strlen(quote)/2;
printf("quotelen is %d\n",quotelen);
printf("quotelen is %ld\n", (long)quotelen);
for (i = 0; i < quotelen; i++) {
if (!ishex(quote[i * 2]) || !ishex(quote[i * 2 + 1])) {
error("Error parsing quote\n");
......@@ -7865,7 +7866,7 @@ COMMAND_PROTOTYPE(dojailconfig)
if (getrandomchars(saltbuf, 8) != 0) {
snprintf(saltbuf, sizeof(saltbuf),
"%ud", time(NULL));
"%lu", (unsigned long)time(NULL));
}
sprintf(buf, "$1$%s", saltbuf);
bp = crypt(row[0], buf);
......@@ -12534,8 +12535,8 @@ COMMAND_PROTOTYPE(dogenicommands)
p += snprintf( p, buf + sizeof buf - p,
" \"%s\":%*s\"%s\"",
genicommands[ i ].tag,
maxlen + 1 - strlen( genicommands[ i ].tag ), "",
genicommands[ i ].desc );
(int)(maxlen + 1 - strlen( genicommands[ i ].tag )),
"", genicommands[ i ].desc );
}
}
......@@ -12629,7 +12630,9 @@ getImageInfo(char *path, char *nodeid, char *pid, char *imagename,
goto badimage1;
sb.st_mtime = 0;
if (_buf[0] != 0 && _buf[0] != '\n') {
sscanf(_buf, "%u", &sb.st_mtime);
long _tmp;
sscanf(_buf, "%ld", &_tmp);
sb.st_mtime = _tmp;
}
if (sb.st_mtime == 0)
goto badimage1;
......@@ -12651,7 +12654,9 @@ getImageInfo(char *path, char *nodeid, char *pid, char *imagename,
goto badimage2;
sb.st_size = 0;
if (_buf[0] != 0 && _buf[0] != '\n') {
sscanf(_buf, "%lld", &sb.st_size);
long long _tmp;
sscanf(_buf, "%lld", &_tmp);
sb.st_size = _tmp;
}
if (sb.st_size == 0)
goto badimage2;
......
......@@ -54,7 +54,7 @@ SBIN_SCRIPTS = vlandiff vlansync withadminprivs export_tables cvsupd.pl \
update_sitevars delete_image sitecheckin sitecheckin_client \
mktestbedtest fixrootcert addservers poolmonitor \
node_exclude managetaint shutdown-shared imagerelease \
runsonxen
runsonxen pxelinux_makeconf
WEB_SBIN_SCRIPTS= webnewnode webdeletenode webspewconlog webarchive_list \
webwanodecheckin webspewimage webdumpdescriptor \
......@@ -70,7 +70,9 @@ CTRLSBIN_SCRIPTS= opsdb_control.proxy daemon_wrapper ec2import.proxy \
# These scripts installed setuid, with sudo.
SETUID_BIN_SCRIPTS = create_image
SETUID_SBIN_SCRIPTS = grabwebcams checkquota spewconlog opsdb_control suchown \
anonsendmail readblob image_import delete_image
anonsendmail readblob image_import delete_image \
pxelinux_makeconf
SETUID_SUEXEC_SCRIPTS = xlogin
#
......@@ -78,7 +80,8 @@ SETUID_SUEXEC_SCRIPTS = xlogin
# configure if the .in file is changed.
#
all: $(BIN_SCRIPTS) $(SBIN_SCRIPTS) $(LIBEXEC_SCRIPTS) $(CTRLSBIN_SCRIPTS) \
$(SUBDIRS) firstuser setbuildinfo
$(SETUID_BIN_SCRIPTS) $(SETUID_SBIN_SCRIPTS) $(SUBDIRS) \
firstuser setbuildinfo
subboss: daemon_wrapper
......
#!/usr/bin/perl -w
#
# Copyright (c) 2014 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 <http://www.gnu.org/licenses/>.
#
# }}}
#
use English;
use strict;
use Getopt::Std;
#
# Import an image from an external source.
#
sub usage()
{
print STDERR "Usage: pxelinux_makeconf [-a action] node\n";
print STDERR "Options:\n";
print STDERR " -a action - Menu action to select, one of:\n";
print STDERR " diskboot, mfsboot, pxewait, pxefail, nfsboot\n";
print STDERR "Without an action it will (someday) determine the correct\n";
print STDERR " current boot action and configure it for that.\n";
exit(-1);
}
my $optlist = "a:d";
my $action;
my $debug = 0;
#
# Configure variables
#
my $TB = "@prefix@";
#
# 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;
#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
die("*** $0:\n".
" Must be setuid! Maybe its a development version?\n");
}
#
# Load the Testbed support stuff.
#
use lib "@prefix@/lib";
use libdb;
use Node;
use Interface;
# Locals;
my $cfile;
my $configdir = "/tftpboot/pxelinux.cfg";
my $template = "$configdir/boot.template";
# Protos
#
# 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{"a"})) {
if ($options{"a"} =~ /^(diskboot|mfsboot|nfsboot|pxewait|pxefail)$/) {
$action = $1;
} else {
usage();
}
}
# XXX
if (!$action) {
print STDERR "Sorry, gotta specify an action right now...\n";
exit(1);
}
# Right now we only act on a single node. No reason we cannot do more.
if (@ARGV < 1) {
usage();
}