Commit 3d95a752 authored by Ryan Jackson's avatar Ryan Jackson

Various subboss-related bits

- Add support for new tmcd dhcpdconf command to watchdog
- Fix dhcpd.conf template so that ddns-update-style is set to none
- Pull some utility functions from boss's libtestbed.pm into the client
  libtestbed.pm for use by subboss_dhcpd_makeconf and daemon_wrapper.
- Add stuff to simplify getting control interface IP address.
parent 3ba405f2
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2006 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
use Getopt::Std;
use Fcntl ':flock';
#
# dhcpd_makeconf - helper script to create dhcpd.conf files from the database.
# The template file should look like an ordinary dhcpd.conf file, but have
# the string %%nodetype=<type> where you want entries for a set of nodes
# filled out. See the template file in the dhcp directory for an example.
#
sub usage {
print "Usage: $0 [-h] [-r]\n";
print "-h Show this message\n";
print "-r Restart DHCPD after config file regeneration\n";
exit(1);
}
my $optlist = "h:r";
my $install = 0;
my $vnames = 0;
my $restart = 0;
#
# Configure variables
#
my $TBOPS = "@TBOPSEMAIL@";
# un-taint path
$ENV{'PATH'} = '/bin:/usr/bin:/usr/sbin:/usr/local/bin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
# Turn off line buffering on output
$| = 1;
use lib "@prefix@/lib";
use libtestbed;
use libsetup;
use libtmcc;
# Parse command arguments. Once we return from getopts, all that should
# left are the required arguments.
#
%options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{"r"})) {
$restart = 1;
}
if (defined($options{"h"})) {
usage();
}
#
# If we are going to actually install this file, must serialize to
# avoid a trashed config file.
#
if ((my $locked = TBScriptLock("dhcpd.conf", 1)) != TBSCRIPTLOCK_OKAY()) {
exit(0) if ($locked == TBSCRIPTLOCK_IGNORE);
fatal("Could not get the lock after a long time!\n");
}
#
# Generate the dhcp configuration
#
liblocsetup::os_gendhcpdconf();
if ($restart) {
$dpid = `cat /var/run/dhcpd.pid`;
chomp($dpid);
# untaint
if ($dpid =~ /^([\d]+)$/) {
$dpid = $1;
}
else {
fatal("Bad pid for DHCPD: $dpid");
}
if (kill('TERM', $dpid) == 0) {
fatal("Could not kill(TERM) process $dpid (dhcpd): $!");
}
}
TBScriptUnlock();
exit(0);
#
# Die.
#
sub fatal {
my $msg = $_[0];
TBScriptUnlock()
if ($install);
die("*** $0:\n".
" $msg\n");
}
#
# dhcpd.conf.template - From the emulab software distribution
#
# This file is used with dhcpd_makeconf to generate an actual dhpd.conf file
# from the database.
#
# Make sure to change all subnets (don't forget the mask!), the DHCP and DNS
# servers' IP addresses, the router's IP, and see the comment about nodetypes
# below.
#
server-identifier %%subboss_ip;
server-name "%%subboss_ip";
# Config these too?
option root-path "/tftpboot";
filename "/tftpboot/pxeboot.emu";
#default-lease-time 36000; # 10 hours
#max-lease-time 36000; # 10 hours
ddns-update-style none; # req'd vers 3 directive
# Define the PXE option space
# Code 1: Multicast IP address of bootfile
# Code 2: UDP port that client should monitor for MTFTP responses
# Code 3: UDP port that MTFTP servers are using to listen for MTFTP requests
# Code 4: Number of secondes a client must listen for activity before trying
# to start a new MTFTP transfer
# Code 5: Number of secondes a client must listen before trying to restart
# a MTFTP transfer
# Code 128: IP address of Emulab bootinfo server
option space PXE;
option PXE.mtftp-ip code 1 = ip-address;
option PXE.mtftp-cport code 2 = unsigned integer 16;
option PXE.mtftp-sport code 3 = unsigned integer 16;
option PXE.mtftp-tmout code 4 = unsigned integer 8;
option PXE.mtftp-delay code 5 = unsigned integer 8;
option PXE.discovery-control code 6 = unsigned integer 8;
option PXE.discovery-mcast-addr code 7 = ip-address;
option PXE.emulab-bootinfo code 128 = ip-address;
#
# For PXE booting nodes we set the magic bits to tell the client that we are
# PXE-saavy so they don't wait around for 12 seconds or so waiting for a
# better response.
#
# If, for some reason, this doesn't work for you, just comment out the
# entire PXE class block. It will work without it, it will just take longer.
#
class "PXE" {
match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
option vendor-class-identifier "PXEClient";
vendor-option-space PXE;
# bit 3 = If set, and a boot file name is present in the initial
# DHCP or ProxyDHCP offer packet, download the boot file (do not
# prompt/menu/discover).
option PXE.discovery-control 8;
option PXE.emulab-bootinfo @BOSSNODE_IP@;
}
shared-network emulab {
subnet @CONTROL_NETWORK@ netmask @CONTROL_NETMASK@ {
option subnet-mask @CONTROL_NETMASK@;
option routers @CONTROL_ROUTER_IP@;
option domain-name-servers @BOSSNODE_IP@;
option domain-name "@OURDOMAIN@";
# testbed PCs
group {
%%nodes
}
}
}
This diff is collapsed.
......@@ -12,10 +12,11 @@
package libtestbed;
use Exporter;
@ISA = "Exporter";
@EXPORT = qw( TB_BOSSNODE TB_EVENTSERVER
TBScriptLock TBScriptUnlock
@EXPORT = qw( SENDMAIL TB_BOSSNODE TB_EVENTSERVER
TBScriptLock TBScriptUnlock
TBSCRIPTLOCK_OKAY TBSCRIPTLOCK_TIMEDOUT
TBSCRIPTLOCK_IGNORE TBSCRIPTLOCK_FAILED TBSCRIPTLOCK_GLOBALWAIT
TBTimeStamp TBTimeStampWithDate
);
# Must come after package declaration!
......@@ -41,6 +42,91 @@ BEGIN
# Need this.
use libtmcc;
sub SENDMAILWith($$$$;$$@);
sub SENDMAIL($$$;$$@)
{
my($To, $Subject, $Message, $From, $Headers, @Files) = @_;
SENDMAILWith("/usr/sbin/sendmail -t", $To, $Subject, $Message, $From, $Headers, @Files);
}
sub SENDMAILWith($$$$;$$@)
{
my($Command, $To, $Subject, $Message, $From, $Headers, @Files) = @_;
my $tag = uc($MAILTAG);
#
# Untaint the path locally. Note that using a "local" fails on older perl!
#
my $SAVE_PATH = $ENV{'PATH'};
$ENV{'PATH'} = "/bin:/usr/bin";
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
if (! open(MAIL, "| $Command")) {
print STDERR "SENDMAIL: Could not start sendmail: $!\n";
goto bad;
}
#
# Sendmail will figure this out if not given.
#
if (defined($From) && $From) {
print MAIL "From: $From\n";
}
if (defined($Headers) && length($Headers) > 0) {
print MAIL "$Headers\n";
}
print MAIL "X-NetBed: $SCRIPTNAME\n";
if (defined($To)) {
print MAIL "To: $To\n";
}
print MAIL "Subject: $tag: $Subject\n";
print MAIL "\n";
print MAIL "$Message\n";
print MAIL "\n";
if (@Files) {
foreach my $file ( @Files ) {
if (defined($file) && open(IN, "$file")) {
print MAIL "\n--------- $file --------\n";
while (<IN>) {
print MAIL "$_";
}
close(IN);
}
}
}
print MAIL "\n";
if (! close(MAIL)) {
print STDERR "SENDMAIL: Could not finish sendmail: $!\n";
goto bad;
}
$ENV{'PATH'} = $SAVE_PATH;
return 1;
bad:
$ENV{'PATH'} = $SAVE_PATH;
return 0;
}
#
# Return a timestamp. We don't care about day/date/year. Just the time mam.
#
# TBTimeStamp()
#
sub TBTimeStamp()
{
my ($seconds, $microseconds) = gettimeofday();
return POSIX::strftime("%H:%M:%S", localtime($seconds)) . ":$microseconds";
}
sub TBTimeStampWithDate()
{
return POSIX::strftime("%m/%d/20%y %H:%M:%S", localtime());
}
#
# Return name of the bossnode.
#
......@@ -93,7 +179,7 @@ sub TBSCRIPTLOCK_IGNORE() { 2; }
sub TBSCRIPTLOCK_FAILED() { -1; }
sub TBSCRIPTLOCK_GLOBALWAIT() { 1; }
#
#
# There are two kinds of serialization.
#
# * Usual Kind: Each party just waits for a chance to go.
......@@ -158,17 +244,17 @@ sub TBScriptLock($;$$$)
#
# If we don't get it the first time, we wait for:
# 1) The lock to become free, in which case we do our thing
# 2) The time on the lock to change, in which case we wait for that
# 2) The time on the lock to change, in which case we wait for that
# process to finish, and then we are done since there is no
# reason to duplicate what the just finished process did.
#
if (flock(LOCK, LOCK_EX|LOCK_NB) == 0) {
my $oldlocktime = (stat(LOCK))[9];
my $gotlock = 0;
while (1) {
print "Another $token in progress. Waiting a moment ...\n";
if (flock(LOCK, LOCK_EX|LOCK_NB) != 0) {
# OK, got the lock
$gotlock = 1;
......@@ -179,7 +265,7 @@ sub TBScriptLock($;$$$)
$oldlocktime = $locktime;
last;
}
$waittime--;
if ($waittime <= 0) {
print STDERR "Could not get the lock after a long time!\n";
......@@ -209,7 +295,7 @@ sub TBScriptLock($;$$$)
"a long time!\n";
return TBSCRIPTLOCK_TIMEDOUT();
}
sleep(1);
sleep(1);
}
}
}
......@@ -218,7 +304,7 @@ sub TBScriptLock($;$$$)
#
my $now = time;
utime $now, $now, $lockname;
if (defined($lockhandle_ref)) {
$$lockhandle_ref = *LOCK;
}
......
......@@ -9,8 +9,8 @@
# TMCC library. Provides an interface to the TMCD via a config file
# or directly to it via TCP
#
# TODO: Proxy path in a jail.
#
# TODO: Proxy path in a jail.
#
package libtmcc;
use Exporter;
@ISA = "Exporter";
......@@ -34,7 +34,7 @@ use Exporter;
TMCCCMD_PLABEVENTKEYS TMCCCMD_PORTREGISTER
TMCCCMD_MOTELOG TMCCCMD_BOOTWHAT TMCCCMD_ROOTPSWD
TMCCCMD_LTMAP TMCCCMD_LTPMAP TMCCCMD_TOPOMAP TMCCCMD_LOADINFO
TMCCCMD_TPMBLOB TMCCCMD_TPMPUB
TMCCCMD_TPMBLOB TMCCCMD_TPMPUB TMCCCMD_DHCPDCONF
);
# Must come after package declaration!
......@@ -66,7 +66,7 @@ my $beproxy = 0;
#
# Configuration. The importer of this library should set these values
# accordingly.
# accordingly.
#
%config =
( "debug" => 0,
......@@ -191,6 +191,7 @@ my %commandset =
"tpmblob" => {TAG => "tpmblob"},
"tpmpubkey" => {TAG => "tpmpubkey"},
"loadinfo" => {TAG => "loadinfo"},
"dhcpdconf" => {TAG => "dhcpdconf"},
);
#
......@@ -257,10 +258,11 @@ sub TMCCCMD_LTPMAP() { $commandset{"ltpmap"}->{TAG}; }
sub TMCCCMD_TPMBLOB() { $commandset{"tpmblob"}->{TAG}; }
sub TMCCCMD_TPMPUB() { $commandset{"tpmpubkey"}->{TAG}; }
sub TMCCCMD_LOADINFO() { $commandset{"loadinfo"}->{TAG}; }
sub TMCCCMD_DHCPDCONF() { $commandset{"dhcpdconf"}->{TAG}; }
#
# Caller uses this routine to set configuration of this library
#
#
sub configtmcc($$)
{
my ($opt, $val) = @_;
......@@ -350,7 +352,7 @@ sub optionstring($%)
# If a "results" argument (pass by reference) has been provided, then
# take the results of tmcc, and store a list of strings into it.
# If an options hash is passed, use that to extend the global config options.
#
#
sub runtmcc ($;$$%)
{
my ($cmd, $args, $results, %optconfig) = @_;
......@@ -434,7 +436,7 @@ sub tmcc ($;$$%)
}
#
# See if this is a cmd we can get from the local config stash.
# See if this is a cmd we can get from the local config stash.
#
if (!$nocache && (!defined($args) || $args eq "")) {
foreach my $key (keys(%commandset)) {
......@@ -453,7 +455,7 @@ sub tmcc ($;$$%)
#
print STDERR "Fetching locally from $filename\n"
if ($debug);
while (<TD>) {
next
if ($_ =~ /^\*\*\* $tag$/);
......@@ -475,8 +477,8 @@ sub tmcc ($;$$%)
#
if (!$config{"dounix"} && !$noproxy && -e $PROXYDEF) {
#
# Suck out the path and untaint.
#
# Suck out the path and untaint.
#
open(PP, "$BOOTDIR/proxypath");
my $ppath = <PP>;
close(PP);
......@@ -565,7 +567,7 @@ sub tmccbossinfo()
sub tmccclrconfig()
{
my $dir = CacheDir();
if (-d $dir) {
system("rm -rf $dir");
}
......@@ -583,12 +585,12 @@ sub tmccgetconfig()
my $noproxy = $config{"noproxy"};
#
# Check for proxypath file, but do not override config option.
# Check for proxypath file, but do not override config option.
#
if (!$config{"dounix"} && !$noproxy && -e $PROXYDEF) {
#
# Suck out the path and untaint.
#
# Suck out the path and untaint.
#
open(PP, "$BOOTDIR/proxypath");
my $ppath = <PP>;
close(PP);
......@@ -606,7 +608,7 @@ sub tmccgetconfig()
warn("*** WARNING: Could not mkdir $cdir: $!\n");
return -1;
}
# XXX Can't "use libsetup" in libtmcc to reference the WINDOWS() function.
my $arg = (-e "$ETCDIR/iscygwin") ? "windows" : undef;
if (runtmcc("fullconfig", $arg, \@tmccresults) < 0 ||
......@@ -619,7 +621,7 @@ sub tmccgetconfig()
while ($str = shift(@tmccresults)) {
if ($str =~ /^\*\*\* ([-\w]*)$/) {
my $param = $1;
if (open(TD, "> $cdir/$param")) {
#
# Set the permission on the file first if necessary
......@@ -637,7 +639,7 @@ sub tmccgetconfig()
}
while (@tmccresults) {
$str = shift(@tmccresults);
last
if ($str =~ /^\*\*\* $param$/);
print TD $str;
......
......@@ -31,8 +31,8 @@ BEGIN { require "/etc/emulab/paths.pm"; import emulabpaths; }
#
# Load the OS independent support library. It will load the OS dependent
# library and initialize itself.
#
# library and initialize itself.
#
use libsetup;
use libtmcc;
......@@ -83,7 +83,7 @@ my %faketimes;
#
my %iv = (
check => 0,
isalive => ((REMOTE() == 1) ? (PLAB() ? 600 : 60) : ((JAILED()
isalive => ((REMOTE() == 1) ? (PLAB() ? 600 : 60) : ((JAILED()
|| GENVNODE()) ? 600 : 180)),
drift => (60 * 60 * 12),
cvsup => (60 * 60 * 12),
......@@ -91,6 +91,7 @@ my %iv = (
hkeys => 0,
batt => (STARGATE() ? 60 : 0),
rootpswd=> 0,
dhcpdconf=> 0,
);
my %funcs = (
......@@ -102,6 +103,7 @@ my %funcs = (
hkeys => \&sendhkeys,
batt => \&sendbatt,
rootpswd=> \&setrootpswd,
dhcpdconf=> \&gendhcpdconf,
);
my %immediate = (
......@@ -113,6 +115,7 @@ my %immediate = (
hkeys => 1,
batt => 1,
rootpswd=> 1,
dhcpdconf=> 1,
);
#
......@@ -128,6 +131,7 @@ sub sendbatt($);
sub setrootpswd($);
sub logmsg($;$);
sub saysomething($);
sub gendhcpdconf();
#
# Parse command arguments. Once we return from getopts, all that should be
......@@ -166,7 +170,7 @@ if (@ARGV) {
#
# Must be root.
#
#
if ($UID != 0) {
die("*** $0:\n".
" Must be root to run this script!\n");
......@@ -187,7 +191,7 @@ if ($action eq "stop") {
#
# Put this into the background and log its output. We *must* do this cause
# we do not want to halt the boot if the testbed is down!
#
#
if (!$debug && TBBackGround($logname)) {
#
# Parent exits normally
......@@ -221,7 +225,7 @@ $SIG{TERM} = \&handler;
$SIG{INT} = \&handler;
#
# If jailed, get our jailname.
# If jailed, get our jailname.
#
if (JAILED() || GENVNODE() || PLAB()) {
my $vnodeid = libsetup_getvnodeid();
......@@ -246,7 +250,7 @@ if (JAILED() || GENVNODE() || PLAB()) {
#
# For sending back ntpdrift.
#
#
if (-e "/etc/ntp.drift") {
$driftfile = "/etc/ntp.drift";
} elsif (-e "/etc/ntp/drift") {
......@@ -255,6 +259,15 @@ if (-e "/etc/ntp.drift") {
$driftfile = "/var/lib/ntp/drift";
}
#
# Location of dhcpd.conf
#
if (-e "/etc/dhcpd.conf") {
$dhcpdconf = "/etc/dhcpd.conf";
} elsif (-e "/usr/local/etc/dhcpd.conf") {
$dhcpdconf = "/usr/local/etc/dhcpd.conf";
}
#
# Initial drift value, we only update if it changes
#
......@@ -297,7 +310,7 @@ qinsert($curtime + $speakupiv, \&saysomething)
#
# Loop, sleeping and then processing events
#
#
my $lasttime = 0;
while (1) {
my ($nexttime, $event);
......@@ -327,7 +340,7 @@ exit(0);
sub sendisalive($)
{
my ($curtime) = @_;
if ($fakeit) {
my $delta = $curtime - $faketimes{isalive};
$faketimes{isalive} = $curtime;
......@@ -405,6 +418,12 @@ sub setintervals($)
$iv{batt} = 25;
$iv{rootpswd} = 10;
if (SUBBOSS()) {
$iv{dhcpdconf} = 5;
} else {
$iv{dhcpdconf} = 0;
}
my $delta = $curtime - $faketimes{check};
$faketimes{check} = $curtime;
......@@ -467,7 +486,7 @@ sub setintervals($)
foreach my $token (@tokens) {
my $key;
my $val;
if ($token =~ /^(.*)=(-?\d+)$/) {
$key = $1;
$val = $2;
......@@ -477,7 +496,7 @@ sub setintervals($)
next;
}
print "$key => $val\n";
SWITCH: for ($key) {
/^INTERVAL$/ && do {
$iv{check} = $val
......@@ -514,6 +533,11 @@ sub setintervals($)
if ($val >= 0);
last SWITCH;
};
/^DHCPDCONF$/ && do {
$iv{dhcpdconf} = $val
if ($val >= 0);
last SWITCH;
};
};
}
......@@ -527,6 +551,15 @@ sub setintervals($)
$iv{hkeys} = 0;
$iv{batt} = 0;
$iv{rootpswd} = 0;
$iv{dhcpdconf} = 0;
}
#
# Only sub-bosses run dhcpd
#
if (!SUBBOSS()) {
$iv{dhcpdconf} = 0;
$immediate{dhcpdconf} = 0;
}
foreach my $key (keys %iv) {
......@@ -586,12 +619,12 @@ sub setintervals($)
}
}
}
if ($report) {
logmsg("setintervals: check=$iv{check}, isalive=$iv{isalive}, ".
"drift=$iv{drift}, cvsup=$iv{cvsup}, rusage=$iv{rusage}, ".
"hostkeys=$iv{hkeys}, battery=$iv{batt}, ".
"rootpswd=$iv{rootpswd}\n");
"rootpswd=$iv{rootpswd} dhcpdconf=$iv{dhcpdconf}\n");
}
#
......@@ -607,7 +640,7 @@ sub setintervals($)
sub ntpdrift($)
{
my ($curtime) = @_;
if ($fakeit) {
my $delta = $curtime - $faketimes{drift};
$faketimes{drift} = $curtime;
......@@ -620,7 +653,7 @@ sub ntpdrift($)
my $drift = `cat $driftfile`;
chomp($drift);
if ($drift ne $lastdrift && $drift =~ /^([-\d\.]*)$/) {
logmsg("ntpdrift: updating NTP drift from $lastdrift to $drift\n");
......@@ -640,7 +673,7 @@ sub ntpdrift($)
sub runcvsup($)
{
my ($curtime) = @_;
if ($fakeit) {
my $delta = $curtime - $faketimes{cvsup};
$faketimes{cvsup} = $curtime;
......@@ -668,7 +701,7 @@ sub runcvsup($)
sub sendrusage($)
{
my ($curtime) = @_;
if ($fakeit) {
my $delta = $curtime - $faketimes{rusage};
$faketimes{rusage} = $curtime;
......@@ -716,7 +749,7 @@ sub sendrusage($)
else {
logmsg("rusage: no way to get the averages\n");
goto resched;
}
}
}
logmsg("rusage: sending: $rusagestr\n", 1);
......@@ -769,7 +802,7 @@ resched:
sub sendhkeys($)
{
my ($curtime) = @_;
if ($fakeit) {
my $delta = $curtime - $faketimes{hkeys};
$faketimes{hkeys} = $curtime;
......@@ -849,7 +882,7 @@ sub sendhkeys($)
sub sendbatt($)
{
my ($curtime) = @_;
if ($fakeit) {
my $delta = $curtime - $faketimes{batt};
$faketimes{batt} = $curtime;
......@@ -929,7 +962,7 @@ resched:
sub setrootpswd($)
{
my ($curtime) = @_;
if ($fakeit) {
my $delta = $curtime - $faketimes{rootpswd};
$faketimes{rootpswd} = $curtime;
......@@ -959,6 +992,34 @@ resched:
if ($iv{rootpswd});
}
sub gendhcpdconf($)
{
my ($curtime) = @_;
if ($fakeit) {
my $delta = $curtime - $faketimes{dhcpdconf};
$faketimes{dhcpdconf} = $curtime;
logmsg("gendhcpdconf at +$delta\n");
qinsert($curtime + $iv{dhcpdconf}, \&gendhcpdconf) if ($iv{dhcpdconf});
return;
}
#
# Generate the dhcp configuration
#
system("$BINDIR/subboss_dhcpd_makeconf -r") == 0
or die("Could not generate dhcpd.conf");
resched:
#
# Set up for another interval.
# Since the tmcc call and update can take awhile, we update curtime
#
$curtime = time();
qinsert($curtime + $iv{dhcpdconf}, \&gendhcpdconf)
if ($iv{dhcpdconf});
}
sub saysomething($)
{
my ($curtime) = @_;
......
......@@ -13,7 +13,7 @@ use Exporter;
@ISA = "Exporter";
@EXPORT =
qw ( $CP $EGREP $NFSMOUNT $UMOUNT $TMPASSWD $SFSSD $SFSCD $RPMCMD $HOSTSFILE