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

Allow for more flexible setup of pxe_boot_path.

If nodes.pxe_boot_path is set to '/tftpboot/pxelinux/<something>', then
dhcpd_makeconf will set the (pxeboot) filename to /tftpboot/pxelinux.0
and symlink the node's config file (/tftpboot/pxelinux.cfg/<mac>) to
/tftpboot/pxelinux.cfg/<something>.

In other words, we can customize pxelinux to some small degree, using one
of some small number of pre-existing configurations. We were using pxelinux
before for plab-in-elab and we will also need it for loading WinPE for
configuring Windows7 images. For the latter we will set the pxe_boot_path
to /tftpboot/pxelinux/winpe.

Anyway, ideally we would allow the user to specify a pxelinux config file
through the NS file, but need to think about the implications of that some
more. Small steps...
parent 6d993d40
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2006 University of Utah and the Flux Group.
# Copyright (c) 2000-2011 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
......@@ -33,6 +33,7 @@ my $restart = 0;
# Configure variables
#
my $TBOPS = "@TBOPSEMAIL@";
my $ELABINELAB = @ELABINELAB@;
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/sbin:/usr/local/bin';
......@@ -46,6 +47,14 @@ use libdb;
use libtestbed;
use NodeType;
#
# PXE boot constants
#
my $PXEBOOTDIR = "/tftpboot";
my $PXELINUXPREFIX = "$PXEBOOTDIR/pxelinux";
my $PXELINUXBOOT = "pxelinux.0";
my $PXELINUXCONFIG = "pxelinux.cfg";
my $CRTLTAG = TBDB_IFACEROLE_CONTROL();
my $DHCPD_CONF = "/usr/local/etc/dhcpd.conf";
my $DHCPD_TEMPLATE = "/usr/local/etc/dhcpd.conf.template";
......@@ -60,6 +69,8 @@ my $OUT = *STDOUT;
my %subboss_dhcp_servers;
sub RestartSubbossDhcp($$);
sub SetupPXEBoot($$$$);
sub ClearPXEBoot($$);
#
# Parse command arguments. Once we return from getopts, all that should
......@@ -302,38 +313,31 @@ while (<IF>) {
# defined. Otherwise don't set anything (use the global
# default).
#
if (defined($row{"pxe_boot_path"})) {
my $fn = $row{"pxe_boot_path"};
# make sure it is pretty constrained
if ($fn =~ /^\/tftpboot\// && $fn !~ /\.\./) {
$filename = "${spaces}\tfilename \"$fn\";\n";
}
}
$filename = SetupPXEBoot($node_id, $mac, 1,
$row{"pxe_boot_path"});
if (!$filename) {
# Get the type info for this type.
my $nodetype = NodeType->Lookup($row{"type"});
# Get the type specific version of pxe_boot_path
my $nt_pxe_boot_path;
$nodetype->pxe_boot_path(\$nt_pxe_boot_path) == 0
or fatal("Could not get pxe_boot_path for ".
"$node_id");
if (defined($nt_pxe_boot_path)) {
my $fn = $nt_pxe_boot_path;
# make sure it is pretty constrained
if ($fn =~ /^\/tftpboot\// && $fn !~ /\.\./) {
$filename = "${spaces}\tfilename \"$fn\";\n";
}
}
$filename = SetupPXEBoot($node_id, $mac, 0,
$nt_pxe_boot_path);
}
if ($filename) {
$filename = "${spaces}\tfilename \"$filename\";\n";
}
# Need to make MAC look right..
$mac =~ s/(..)\B/$1:/g;
print $OUT "${spaces}host $ip {\n";
print $OUT $filename;
print $OUT $filename
if (defined($filename));
print $OUT $next_server;
print $OUT $bootinfo_server;
print $OUT $dns;
......@@ -387,6 +391,125 @@ if ($install) {
}
exit(0);
#
# Special handling of pxe_boot_path argument.
# Any specified pxeboot path must start with PXEBOOTDIR (/tftpboot).
# If path starts with $PXELINUXPREFIX (/tftpboot/pxelinux/), then
# the remainder of the path identifies the pxelinux configuration file
# from $PXELINUXCONFIG (/tftpboot/pxelinux.cfg) to use. This config file
# for the node (MAC address) will be symlinked to this config file.
#
# Returns the string to be used for the "filename" option in dhcpd.conf.
#
sub SetupPXEBoot($$$$)
{
my ($node, $mac, $clear, $str) = @_;
# get rid of any old pxelinux magic
if ($clear) {
ClearPXEBoot($node, $mac);
}
return undef
if (!defined($str));
# don't allow if outside TFTP directory
if ($str !~ /^$PXEBOOTDIR\// || $str =~ /\.\./) {
print STDERR "$node: ignoring bogus pxe_boot_path '$str'\n";
return undef;
}
# if it starts with the magic pxelinux prefix, setup a config file
if ($str =~ /^$PXELINUXPREFIX\/(.*)$/) {
my $cfile = $1;
my $cpath = "$PXEBOOTDIR/$PXELINUXCONFIG";
if (! -r "$cpath/$cfile") {
print STDERR "$node: ignoring invalid pxelinux config '$cfile'\n";
return undef;
}
#
# XXX ugh. In an inner elab with an inner control network,
# pxelinux will use the MAC address of the real control net interface
# to construct its config file name. So we need to look that up.
#
if ($ELABINELAB) {
my $qr = DBQueryWarn("select MAC from interfaces where ".
" node_id='$node' and role='outer_ctrl'");
if ($qr && $qr->numrows) {
my %row = $qr->fetchhash();
if (defined($row{"MAC"})) {
$mac = $row{"MAC"};
}
}
}
if ($mac =~ /^(..)(..)(..)(..)(..)(..)$/) {
$macname = "01-$1-$2-$3-$4-$5-$6";
} else {
print STDERR "$node: ignoring unparsable MAC address '$mac'\n";
return undef;
}
if ($install) {
if (-e "$cpath/$macname") {
if (!unlink("$cpath/$macname")) {
print STDERR "$node: could not remove old pxelinux config\n";
return undef;
}
}
if (!symlink($cfile, "$cpath/$macname")) {
print STDERR "$node: could not symlink to pxelinux config '$cfile'\n";
return undef;
}
} else {
print STDERR "WARNING: not changing pxelinux config for $node\n";
}
# return the pxelinux binary
$str = "$PXEBOOTDIR/$PXELINUXBOOT";
}
return $str;
}
#
# Make sure there is no pxelinux config for the given node
#
sub ClearPXEBoot($$)
{
my ($node, $mac) = @_;
my $cpath = "$PXEBOOTDIR/$PXELINUXCONFIG";
#
# XXX ugh. In an inner elab with an inner control network,
# pxelinux will use the MAC address of the real control net interface
# to construct its config file name. So we need to look that up.
#
if ($ELABINELAB) {
my $qr = DBQueryWarn("select MAC from interfaces where ".
" node_id='$node' and role='outer_ctrl'");
if ($qr && $qr->numrows) {
my %row = $qr->fetchhash();
if (defined($row{"MAC"})) {
$mac = $row{"MAC"};
}
}
}
if ($mac =~ /^(..)(..)(..)(..)(..)(..)$/) {
$macname = "01-$1-$2-$3-$4-$5-$6";
if ($install) {
if (-e "$cpath/$macname" && !unlink("$cpath/$macname")) {
print STDERR "$node: could not remove old pxelinux config\n";
}
} else {
print STDERR "WARNING: not changing pxelinux config for $node\n";
}
}
}
#
# Die.
#
......@@ -413,4 +536,3 @@ sub RestartSubbossDhcp($$)
print STDERR "Failed to restart dhcpd on $subboss\n";
}
}
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2006 University of Utah and the Flux Group.
# Copyright (c) 2000-2011 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
......@@ -21,7 +21,7 @@ sub usage {
exit(1);
}
my $optlist = "h:r";
my $install = 0;
my $install = 1;
my $vnames = 0;
my $restart = 0;
......@@ -39,10 +39,21 @@ $| = 1;
use lib "@prefix@/lib";
use libtestbed;
use libsetup 'gendhcpdconf';
use liblocsetup 'os_gendhcpdconf';
use libtmcc;
#
# PXE boot constants
# XXX copied from dhcpd_makeconf.in
#
my $PXEBOOTDIR = "/tftpboot";
my $PXELINUXPREFIX = "$PXEBOOTDIR/pxelinux";
my $PXELINUXBOOT = "pxelinux.0";
my $PXELINUXCONFIG = "pxelinux.cfg";
sub SetupPXEBoot($$$$);
sub ClearPXEBoot($$);
sub gendhcpdconf($$);
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
......@@ -69,7 +80,16 @@ if ((my $locked = TBScriptLock("dhcpd.conf", 1)) != TBSCRIPTLOCK_OKAY()) {
#
# Generate the dhcp configuration
#
liblocsetup::os_gendhcpdconf();
my $dhcpddir;
if (-r "/usr/local/etc/dhcpd.conf") {
$dhcpddir = "/usr/local/etc";
} elsif (-r "/etc/dhcpd.conf") {
$dhcpddir = "/etc";
} else {
fatal("Cannot find DHCPD config!?");
}
gendhcpdconf("$dhcpddir/dhcpd.conf",
"$dhcpddir/dhcpd.conf.subboss.template");
if ($restart) {
$dpid = `cat /var/run/dhcpd.pid`;
......@@ -89,6 +109,138 @@ if ($restart) {
TBScriptUnlock();
exit(0);
#
# Generate ISC dhcpd configuration file
#
sub gendhcpdconf($$)
{
my ($outfile, $template) = @_;
my $tmpfile = "/tmp/gendhcpdconf.$$";
my @tmccresults;
my @nodes;
# don't cache this stuff, can't get stale dhcpd info!
my %opthash = ( 'nocache' => 1 );
if (tmcc(TMCCCMD_DHCPDCONF, undef, \@tmccresults, %opthash) == 0
&& scalar(@tmccresults)) {
#} else {
# fatal("No dhcpd configuration data returned by tmcd\n");
}
for (@tmccresults) {
my $node = {};
for my $pair (split /\s+/, $_) {
$pair =~ /([^=]+)=(.*)/;
$$node{$1} = $2;
}
push @nodes, $node;
}
if (!open(OF, ">$tmpfile")) {
warn("Could not open $tmpfile\n");
return 1;
}
if (!open(IF,"<$template")) {
warn("Unable to open $template for reading");
return 1;
}
while (<IF>) {
if (/^(\s*)\%\%nodes/) {
my $spaces = $1;
for my $row (@nodes) {
my $ip = $$row{"IP"};
my $mac = $$row{"MAC"};
my $next_server = $$row{"TFTP"};
my $bootinfo_server = $$row{"BOOTINFO"};
my $hostname = $$row{"HOSTNAME"};
my $filename = $$row{"FILENAME"};
my $singlenet = $$row{"SINGLENET"};
my $inner_elab_boot = $$row{"INNER_ELAB_BOOT"};
my $plab_boot = $$row{"PLAB_BOOT"};
my $booting;
my $dns;
my $node_id = $hostname;
if (defined $hostname) {
$hostname =
"${spaces}\toption host-name \"$hostname\";\n";
}
#
# Handle alternate boot program filename if it
# exists. Otherwise don't set anything (use the
# global default).
#
if (defined $filename) {
$filename =~ s/^"(.*)"$/$1/;
$filename = SetupPXEBoot($node_id, $mac, 1,
$filename);
if (defined $filename) {
$filename =
"${spaces}\tfilename \"$filename\";\n";
}
} else {
ClearPXEBoot($node_id, $mac);
}
if (defined $next_server) {
$next_server = "${spaces}\tnext-server " .
$next_server . ";\n";
}
if (defined $bootinfo_server) {
$bootinfo_server = "${spaces}\toption " .
"PXE.emulab-bootinfo " . $bootinfo_server . ";\n";
}
if ($inner_elab_boot) {
if ($singlenet) {
$booting = "${spaces}\tignore booting;\n";
} else {
$dns = "${spaces}\toption ".
"domain-name-servers 1.1.1.1;\n";
}
}
# Need to make MAC look right..
$mac =~ s/(..)\B/$1:/g;
print OF "${spaces}host $ip {\n";
print OF $filename if $filename;
print OF $next_server if $next_server;
print OF $bootinfo_server if $bootinfo_server;
print OF $dns if $dns;
print OF $booting if $booting;
print OF "${spaces}\thardware ethernet $mac;\n";
print OF $hostname;
print OF "${spaces}\tfixed-address $ip;\n";
print OF "${spaces}}\n\n";
}
} elsif (/(.*\s*)\%\%subboss_ip(.*)/) {
my $ip = `cat /var/emulab/boot/myip`;
chomp $ip;
print OF "$1" . $ip . "$2\n";
} else {
# It's a regular line
print OF $_;
}
}
close(IF);
close(OF);
if (-e $outfile) {
system("cp -fp $outfile ${outfile}.old") == 0 or
fatal("Could not backup copy of ${outfile}");
}
system("mv -f $tmpfile $outfile") == 0 or
fatal("Could not install new ${outfile}");
return 0;
}
#
# Die.
#
......@@ -102,3 +254,93 @@ sub fatal {
" $msg\n");
}
#
# XXX The following are copied from dhcpd_makeconf.in
#
#
# Special handling of pxe_boot_path argument.
# Any specified pxeboot path must start with PXEBOOTDIR (/tftpboot).
# If path starts with $PXELINUXPREFIX (/tftpboot/pxelinux/), then
# the remainder of the path identifies the pxelinux configuration file
# from $PXELINUXCONFIG (/tftpboot/pxelinux.cfg) to use. This config file
# for the node (MAC address) will be symlinked to this config file.
#
# Returns the string to be used for the "filename" option in dhcpd.conf.
#
sub SetupPXEBoot($$$$)
{
my ($node, $mac, $clear, $str) = @_;
# get rid of any old pxelinux magic
if ($clear) {
ClearPXEBoot($node, $mac);
}
return undef
if (!defined($str));
# don't allow if outside TFTP directory
if ($str !~ /^$PXEBOOTDIR\// || $str =~ /\.\./) {
print STDERR "$node: ignoring bogus pxe_boot_path '$str'\n";
return undef;
}
# if it starts with the magic pxelinux prefix, setup a config file
if ($str =~ /^$PXELINUXPREFIX\/(.*)$/) {
my $cfile = $1;
my $cpath = "$PXEBOOTDIR/$PXELINUXCONFIG";
if (! -r "$cpath/$cfile") {
print STDERR "$node: ignoring invalid pxelinux config '$cfile'\n";
return undef;
}
if ($mac =~ /^(..)(..)(..)(..)(..)(..)$/) {
$macname = "01-$1-$2-$3-$4-$5-$6";
} else {
print STDERR "$node: ignoring unparsable MAC address '$mac'\n";
return undef;
}
if ($install) {
if (-e "$cpath/$macname") {
if (!unlink("$cpath/$macname")) {
print STDERR "$node: could not remove old pxelinux config\n";
return undef;
}
}
if (!symlink($cfile, "$cpath/$macname")) {
print STDERR "$node: could not symlink to pxelinux config '$cfile'\n";
return undef;
}
} else {
print STDERR "WARNING: not changing pxelinux config for $node\n";
}
# return the pxelinux binary
$str = "$PXEBOOTDIR/$PXELINUXBOOT";
}
return $str;
}
#
# Make sure there is no pxelinux config for the given node
#
sub ClearPXEBoot($$)
{
my ($node, $mac) = @_;
my $cpath = "$PXEBOOTDIR/$PXELINUXCONFIG";
if ($mac =~ /^(..)(..)(..)(..)(..)(..)$/) {
$macname = "01-$1-$2-$3-$4-$5-$6";
if ($install) {
if (-e "$cpath/$macname" && !unlink("$cpath/$macname")) {
print STDERR "$node: could not remove old pxelinux config\n";
}
} else {
print STDERR "WARNING: not changing pxelinux config for $node\n";
}
}
}
......@@ -22,7 +22,7 @@ use Exporter;
ixpsetup libsetup_refresh gettopomap getfwconfig gettiptunnelconfig
gettraceconfig genhostsfile getmotelogconfig calcroutes fakejailsetup
getlocalevserver genvnodesetup getgenvnodeconfig stashgenvnodeconfig
getlinkdelayconfig getloadinfo getbootwhat gendhcpdconf
getlinkdelayconfig getloadinfo getbootwhat
forcecopy
getmanifest fetchmanifestblobs runbootscript runhooks
......@@ -1522,143 +1522,6 @@ sub genhostsfile($@)
return 0;
}
#
# Generate ISC dhcpd configuration file
#
sub gendhcpdconf($$)
{
my ($outfile, $template) = @_;
my $tmpfile = "/tmp/gendhcpdconf.$$";
my @tmccresults;
my @nodes;
# don't cache this stuff, can't get stale dhcpd info!
my %opthash = ( 'nocache' => 1 );
return 0 if (!SUBBOSS());
if (tmcc(TMCCCMD_DHCPDCONF, undef, \@tmccresults, %opthash) == 0
&& scalar(@tmccresults)) {
#} else {
# fatal("No dhcpd configuration data returned by tmcd\n");
}
for (@tmccresults) {
my $node = {};
for my $pair (split /\s+/, $_) {
$pair =~ /([^=]+)=(.*)/;
$$node{$1} = $2;
}
push @nodes, $node;
}
if (!open(OF, ">$tmpfile")) {
warn("Could not open $tmpfile\n");
return 1;
}
if (!open(IF,"<$template")) {
warn("Unable to open $template for reading");
return 1;
}
while (<IF>) {
if (/^(\s*)\%\%nodes/) {
my $spaces = $1;
for my $row (@nodes) {
my $ip = $$row{"IP"};
my $mac = $$row{"MAC"};
my $node_id;
my $next_server = $$row{"TFTP"};
my $bootinfo_server = $$row{"BOOTINFO"};
my $hostname = $$row{"HOSTNAME"};
my $filename = $$row{"FILENAME"};
my $singlenet = $$row{"SINGLENET"};
my $inner_elab_boot = $$row{"INNER_ELAB_BOOT"};
my $plab_boot = $$row{"PLAB_BOOT"};
my $booting;
my $dns;
if (defined $hostname) {
$hostname =
"${spaces}\toption host-name \"$hostname\";\n";
}
if (defined $filename) {
$filename =~ s/^"(.*)"$/$1/;
$filename =
"${spaces}\tfilename \"$filename\";\n";
}
if (defined $next_server) {
$next_server = "${spaces}\tnext-server " .
$next_server . ";\n";
}
if (defined $bootinfo_server) {
$bootinfo_server = "${spaces}\toption " .
"PXE.emulab-bootinfo " . $bootinfo_server . ";\n";
}
if ($inner_elab_boot) {
if ($singlenet) {
$booting = "${spaces}\tignore booting;\n";
} else {
$dns = "${spaces}\toption ".
"domain-name-servers 1.1.1.1;\n";
}
}
#
# Handle alternate boot program filename if it exists.
# Use mutable nodes.pxe_boot_path if it is defined.
# Otherwise use the node_types.pxe_boot_path if it is
# defined. Otherwise don't set anything (use the global
# default).
#
if (defined $filename) {
# make sure it is pretty constrained
if ($filename =~ /^\/tftpboot\// && $fn !~ /\.\./) {
$filename = "${spaces}\tfilename \"$filename\";\n";
}
}
# Need to make MAC look right..
$mac =~ s/(..)\B/$1:/g;
print OF "${spaces}host $ip {\n";
print OF $filename if $filename;
print OF $next_server if $next_server;
print OF $bootinfo_server if $bootinfo_server;
print OF $dns if $dns;
print OF $booting if $booting;
print OF "${spaces}\thardware ethernet $mac;\n";
print OF $hostname;
print OF "${spaces}\tfixed-address $ip;\n";
print OF "${spaces}}\n\n";
}
} elsif (/(.*\s*)\%\%subboss_ip(.*)/) {
my $ip = os_get_ctrlnet_ip();
print OF "$1" . $ip . "$2\n";
} else {
# It's a regular line
print OF $_;
}
}
close(IF);
close(OF);
if (-e $outfile) {
system("cp -fp $outfile ${outfile}.old") == 0 or
fatal("Could not backup copy of ${outfile}");
}
system("mv -f $tmpfile $outfile") == 0 or
fatal("Could not install new ${outfile}");
return 0;
}
#
# Convert from MAC to iface name (eth0/fxp0/etc) using little helper program.
#
......
#!/usr/bin/perl -wT
#
# 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.
#
......@@ -21,7 +21,7 @@ use Exporter;
os_routing_add_manual os_routing_del_manual os_homedirdel
os_groupdel os_getnfsmounts os_islocaldir os_mountextrafs
os_fwconfig_line os_fwrouteconfig_line os_config_gre os_nfsmount
os_find_freedisk os_gendhcpdconf os_get_ctrlnet_ip
os_find_freedisk os_get_ctrlnet_ip
);
sub VERSION() { return 1.0; }
......@@ -503,15 +503,6 @@ sub os_setup()
}
}
#
# Generate ISC dhcpd config file for subbosses
#
sub os_gendhcpdconf()
{
return libsetup::gendhcpdconf("/usr/local/etc/dhcpd.conf",
"/usr/local/etc/dhcpd.conf.subboss.template");
}
#
# OS dependent, routing-related commands
#
......
#!/usr/bin/perl -wT
#
# 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.
#
......@@ -22,7 +22,7 @@ use Exporter;
os_groupdel os_getnfsmounts os_islocaldir os_mountextrafs
os_fwconfig_line os_fwrouteconfig_line os_config_gre
os_get_disks os_get_disk_size os_get_partition_info os_nfsmount
os_gendhcpdconf os_get_ctrlnet_ip
os_get_ctrlnet_ip
);
sub VERSION() { return 1.0; }
......@@ -1076,15 +1076,6 @@ sub os_setup()
return 0;
}
#
# Generate ISC dhcpd config file for subbosses
#
sub os_gendhcpdconf()
{
return gendhcpdconf("/etc/dhcpd.conf",
"/etc/dhcpd.conf.subboss.template");
}
#
# OS dependent, routing-related commands
#
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment