Commit dd4c67d0 authored by David Johnson's avatar David Johnson

Let experimenters customize prepare, and interface and hosts file setup.

The prepare script now supports pre and post hooks.  It runs all hooks
in rc order, from the DYNRUNDIR/prepare.pre.d and BINDIR/prepare.pre.d
dirs (rc order in this case is the BSD order, or my version of it ---
any file prefixed with a number is run in numeric order; other files are
run sorted alphabetically following numeric files).  Post hooks are in
prepare.post.d, and are run at the end of prepare.

(DYNRUNDIR is always /var/run/emulab .  STATICRUNDIR is usually
/etc/emulab/run but could be /etc/testbed/run, depending on the
clientside installation.)

We now allow users to override our default interface configuration --
and if they do, and tell us about it by writing a file in either
$DYNRUNDIR or $STATICRUNDIR named interface-done-$mac , we will not
attempt to configure it, and will assume they have done it!  If they are
nice to us and write
  $iface $ipaddr $mac
into the file, we will parse that and put it into the @ifacemap and
%mac2iface structures in doboot().  We do *not* attempt to provide them
the ifconfig info in env vars or anything; they have to grok our
ifconfig file format, in all its potential glory.

We read the hosts.head file(s) from /etc, DYNRUNDIR, and STATICRUNDIR,
and prepend them to our Emulab hosts content.  Then, we append the
content of the hosts.tail file(s) from /etc, DYNRUNDIR, and STATICDIR
--- and that file becomes the new /etc/hosts file.

getmanifest() has become getrcmanifest() to avoid confusion with the
GENI manifest.  Also, it now supports local manifests embedded in the
filesystem from $DYNRUNDIR and $STATICRUNDIR (priority is manifest from
exp, then DYNRUNDIR, then STATICRUNDIR).  All manifests read and
applied.  Local manifests may also reference local files instead of blob
ids, of course.  It is important to support local manifests so that
experimenters can hook our services by default in the disk image.
parent 73e5be91
......@@ -178,11 +178,11 @@ sub doaction($@)
my $optarg = (defined($vnodeid) ? "-j $vnodeid" : "");
#
# Grab our manifest so we can enable/disable/replace this script and
# Grab our rc manifest so we can enable/disable/replace this script and
# run (or not) its hooks.
#
my %manifest = ();
getmanifest(\%manifest);
getrcmanifest(\%manifest);
foreach my $script (@scripts) {
my $bargs = '';
......
#!/usr/bin/perl -w
#
# Copyright (c) 2004-2010 University of Utah and the Flux Group.
# Copyright (c) 2004-2010, 2016 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -115,6 +115,51 @@ SWITCH: for ($action) {
}
exit(0);
#
# We allow users to override interface configuration -- and if they do, and
# tell us about it by writing a file in either $DYNRUNDIR or $STATICRUNDIR
# named interface-done-$mac , we will not attempt to configure it, and will
# assume they have done it! If they are nice to us and write
# $iface $ipaddr $mac
# into the file, we will parse that and put it into the @ifacemap and %mac2iface
# structures in doboot().
#
sub isifacedone($$)
{
my ($ifconfig,$lineref) = @_;
my @macs = ();
if (exists($ifconfig->{MAC})) {
push @macs, lc($ifconfig->{MAC});
push @macs, uc($ifconfig->{MAC});
}
if (exists($ifconfig->{VMAC})) {
push @macs, lc($ifconfig->{VMAC});
push @macs, uc($ifconfig->{VMAC});
}
if (@macs == 0) {
return 0;
}
my @dirs = ("$DYNRUNDIR","STATICRUNDIR");
foreach my $dir (@dirs) {
foreach my $mac (@macs) {
if ( -f "$dir/interface-done-$mac" ) {
my $FH;
if (!open($FH,"$dir/interface-done-$mac")) {
next;
}
$$lineref = <$FH>;
chomp($$lineref);
close($FH);
return 1;
}
}
}
return 0;
}
#
# Boot Action. We create a script to enable/disable interfaces, and then
# execute it. We use a shell script so that it is easy to see exactly what
......@@ -140,6 +185,24 @@ sub doboot()
my %oscookie = ();
foreach my $ifconfig (@ifacelist) {
#
# Check to see if something else has already configured this
# interface.
#
my $doneline = "";
if (isifacedone($ifconfig,\$doneline)) {
if ($doneline) {
my ($done_iface,$done_inet,$done_mac) = split(/ /,$doneline);
# Trivially parsable map for users, which associate an IP
# with a local interface.
push(@ifacemap, "$doneline");
if ($done_iface && $done_mac) {
$mac2iface{$done_mac} = $done_iface;
}
}
next;
}
if (! $ifconfig->{ISVIRT}) {
my $inet = $ifconfig->{IPADDR};
my $type = $ifconfig->{TYPE};
......
......@@ -42,8 +42,9 @@ use Exporter;
getlinkdelayconfig getloadinfo getbootwhat getnodeattributes
copyfilefromnfs getnodeuuid getarpinfo
getstorageconfig getstoragediskinfo getimagesize
getmanifest fetchmanifestblobs runbootscript runhooks
getrcmanifest fetchrcmanifestblobs runbootscript runhooks
build_fake_macs getenvvars getpnetnodeattrs
sortedlistallfilesindir sortedreadallfilesindir
TBDebugTimeStamp TBDebugTimeStampWithDate
TBDebugTimeStampsOn TBDebugTimeStampsOff
......@@ -803,40 +804,114 @@ sub donodeuuid()
return 0;
}
sub rcordersort($$) {
my ($a,$b) = @_;
my $ca = substr($a,0,1);
my $cb = substr($b,0,1);
my $na = ($ca ge '0' && $ca le '9');
my $nb = ($cb ge '0' && $cb le '9');
if ($na && $nb) {
return int($a) <=> int($b);
}
elsif ($na && !$nb) {
return -1;
}
elsif (!$na && $nb) {
return 1;
}
else {
return $a cmp $b;
}
}
sub sortedlistallfilesindir($$;$) {
my ($dir,$rptr,$qualify) = @_;
my $DIRH;
my $rc = opendir($DIRH,$dir);
if (!$rc) {
return $rc;
}
my @files = grep { /^[^\.\#].*[^\~]$/ && -f "$dir/$_" } readdir($DIRH);
closedir($DIRH);
my @sfiles = sort rcordersort @files;
if (defined($qualify) && $qualify != 0) {
my @tfiles = ();
for my $file (@sfiles) {
push(@tfiles,"$dir/$file");
}
@sfiles = @tfiles;
}
@$rptr = @sfiles;
return 0;
}
sub sortedreadallfilesindir($$) {
my ($dir,$rptr) = @_;
my @sfiles = ();
my $rc = sortedlistallfilesindir($dir,\@sfiles,1);
return $rc if ($rc);
for my $file (@sfiles) {
my $FH;
if (!open($FH,"$file")) {
next;
}
my @lines = <$FH>;
close($FH);
push(@$rptr,@lines);
}
return 0;
}
#
# Get the boot script manifest -- whether scripts are enabled, or hooked, and
# how and when they or their hooks run!
#
sub getmanifest($;$)
sub getrcmanifest($;$)
{
my ($rptr,$nofetch) = @_;
my @tmccresults;
my @tmccresults = ();
my %manifest = ();
my $retval = 0;
print "Checking manifest...\n";
if (tmcc(TMCCCMD_MANIFEST, undef, \@tmccresults) < 0) {
warn("*** WARNING: Could not get manifest from server!\n");
%$rptr = ();
return -1;
$retval = -1;
}
# Always allow local manifests to be run, so add them into our results.
sortedreadallfilesindir("$DYNRUNDIR/rcmanifest.d",\@tmccresults);
sortedreadallfilesindir("$STATICRUNDIR/rcmanifest.d",\@tmccresults);
if (@tmccresults == 0) {
%$rptr = ();
return 0;
return $retval;
}
my $servicepat = q(SERVICE NAME=([\w\.\-]+) ENV=(\w+) WHENCE=(\w+));
$servicepat .= q( ENABLED=(0|1) HOOKS_ENABLED=(0|1));
$servicepat .= q( FATAL=(0|1) BLOBID=([\w\-]*));
$servicepat .= q( FATAL=(0|1) (BLOBID)=([\w\-]*));
my $servicepatfile = q(SERVICE NAME=([\w\.\-]+) ENV=(\w+) WHENCE=(\w+));
$servicepatfile .= q( ENABLED=(0|1) HOOKS_ENABLED=(0|1));
$servicepatfile .= q( FATAL=(0|1) (FILE)=([^ ]*));
my $hookpat = q(HOOK SERVICE=([\w\.\-]+) ENV=(\w+) WHENCE=(\w+));
$hookpat .= q( OP=(\w+) POINT=(\w+));
$hookpat .= q( FATAL=(0|1) BLOBID=([\w\-]+));
$hookpat .= q( FATAL=(0|1) (BLOBID)=([\w\-]+));
$hookpat .= q( ARGV="([^"]*)");
my $hookpatfile = q(HOOK SERVICE=([\w\.\-]+) ENV=(\w+) WHENCE=(\w+));
$hookpatfile .= q( OP=(\w+) POINT=(\w+));
$hookpatfile .= q( FATAL=(0|1) (FILE)=([^ ]+));
$hookpatfile .= q( ARGV="([^"]*)");
my @loadinforesults = ();
if (tmcc(TMCCCMD_LOADINFO, undef, \@loadinforesults) < 0) {
warn("*** WARNING: getmanifest could not get loadinfo from server,\n".
warn("*** WARNING: getrcmanifest could not get loadinfo from server,\n".
" unsure if node is in MFS and reloading, continuing!\n");
}
......@@ -872,12 +947,16 @@ sub getmanifest($;$)
my $line = $tmccresults[$i];
my %service;
if ($line =~ /^$servicepat/) {
if ($line =~ /^$servicepat/ || $line =~ /^$servicepatfile/) {
my %service = ( 'ENABLED' => $4,
'HOOKS_ENABLED' => $5,
'BLOBID' => $7,
"$7" => $8,
'WHENCE' => $3,
'FATAL' => $6 );
if (exists($service{'FILE'})) {
$service{'BLOBPATH'} = $service{'FILE'};
}
#
# Filter the service part of the manifest so that only the
# settings that apply here are passed to scripts.
......@@ -905,7 +984,7 @@ sub getmanifest($;$)
next;
}
}
elsif ($line =~ /^$hookpat/) {
elsif ($line =~ /^$hookpat/ || $line =~ /^$hookpatfile/) {
#
# Filter the service part of the manifest so that only the
# settings that apply here are passed to scripts.
......@@ -916,11 +995,14 @@ sub getmanifest($;$)
$manifest{$1}{$hookstr} = [];
}
my $hook = { 'BLOBID' => $7,
my $hook = { "$7" => $8,
'OP' => $4,
'WHENCE' => $3,
'FATAL' => $6,
'ARGV' => $8 };
'ARGV' => $9 };
if (exists($hook->{'FILE'})) {
$hook->{'BLOBPATH'} = $hook->{'FILE'};
}
$manifest{$1}{$hookstr}->[@{$manifest{$1}{$hookstr}}] = $hook;
}
......@@ -935,19 +1017,19 @@ sub getmanifest($;$)
}
}
my $retval = 0;
$retval = 0;
if (!defined($nofetch) || $nofetch != 1) {
print "Downloading any manifest blobs...\n";
%$rptr = %manifest;
$retval = fetchmanifestblobs($rptr,undef,'manifest');
$retval = fetchrcmanifestblobs($rptr,undef,'manifest');
}
%$rptr = %manifest;
return $retval;
}
sub fetchmanifestblobs($;$$)
sub fetchrcmanifestblobs($;$$)
{
my ($manifest,$savedir,$basename) = @_;
if (!defined($savedir)) {
......@@ -968,7 +1050,7 @@ sub fetchmanifestblobs($;$$)
$retval = libtmcc::blob::getblob($manifest->{$script}{'BLOBID'},
$bpath);
if ($retval == -1) {
print STDERR "ERROR(fetchmanifestblobs): could not fetch " .
print STDERR "ERROR(fetchrcmanifestblobs): could not fetch " .
$manifest->{$script}{'BLOBID'} . "!\n";
++$failed;
}
......@@ -982,13 +1064,14 @@ sub fetchmanifestblobs($;$$)
my @hooktypes = ('_PREHOOKS','_POSTHOOKS');
foreach my $hooktype (@hooktypes) {
next
if (!exists($manifest->{$script}{$hooktype}));
if (!exists($manifest->{$script}{$hooktype})
|| !exists($manifest->{$script}{'BLOBID'}));
foreach my $hook (@{$manifest->{$script}{$hooktype}}) {
my $bpath = $blobpath . "." . $hook->{'BLOBID'};
$retval = libtmcc::blob::getblob($hook->{'BLOBID'},$bpath);
if ($retval == -1) {
print STDERR "ERROR(fetchmanifestblobs): could not fetch " .
print STDERR "ERROR(fetchrcmanifestblobs): could not fetch " .
$hook->{'BLOBID'} . "!\n";
++$failed;
}
......@@ -1023,6 +1106,13 @@ sub runhooks($$$$)
for (my $i = 0; $i < @{$manifest->{$script}{$hookstr}}; ++$i) {
my $hook = $manifest->{$script}{$hookstr}->[$i];
if (!exists($hook->{'BLOBID'}) && exists($hook->{'BLOBPATH'})) {
# This is a local manifest hook; turn the path into an ID.
$hook->{'BLOBID'} = $hook->{'BLOBPATH'};
$hook->{'BLOBID'} =~ tr/\//_/;
}
my $blobid = $hook->{'BLOBID'};
my $argv = $hook->{'ARGV'};
my $hookrunfile = "$VARDIR/db/$script.${which}hook.$blobid.run";
......@@ -1140,7 +1230,7 @@ sub runbootscript($$$$;@)
else {
$argv .= " $what";
}
if ($havemanifest && $manifest->{$script}{'BLOBID'} ne '') {
if ($havemanifest && $manifest->{$script}{'BLOBPATH'} ne '') {
my $blobpath = $manifest->{$script}{'BLOBPATH'};
print " Running $blobpath (instead of $path/$script)\n";
system("$blobpath $argv");
......@@ -1594,6 +1684,22 @@ sub genhostsfile($@)
return 1;
}
#
# Read any hosts.head files. We prefer /etc/hosts.head ; then
# $DYNRUNDIR/hosts.head ; then $STATICRUNDIR/hosts.head . However,
# we'll take from all three places, so all three had better be
# correct!
#
my @hdirs = ("/etc",$DYNRUNDIR,$STATICRUNDIR);
foreach my $dir (@hdirs) {
next if (! -f "$dir/hosts.head");
if (!open(my $FH,"$dir/hosts.head") == 0) {
my @lines = <$FH>;
close($FH);
print HOSTS @lines;
}
}
my $localaliases = "loghost";
#
......@@ -1632,6 +1738,22 @@ sub genhostsfile($@)
warn("Ignoring bad hosts line: $str");
}
}
#
# Read any hosts.tail files. We prefer /etc/hosts.tail ; then
# $DYNRUNDIR/hosts.tail ; then $STATICRUNDIR/hosts.tail . However,
# we'll take from all three places, so all three had better be
# correct!
#
foreach my $dir (@hdirs) {
next if (! -f "$dir/hosts.tail");
if (!open(my $FH,"$dir/hosts.tail") == 0) {
my @lines = <$FH>;
close($FH);
print HOSTS @lines;
}
}
close(HOSTS);
system("mv -f $HTEMP $pathname");
if ($?) {
......
#!/usr/bin/perl -wT
#
# Copyright (c) 2000-2003 University of Utah and the Flux Group.
# Copyright (c) 2000-2003, 2016 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -24,7 +24,9 @@
package emulabpaths;
use Exporter;
@ISA = qw(Exporter);
@EXPORT = qw( $BINDIR $ETCDIR $VARDIR $BOOTDIR $DBDIR $LOGDIR $LOCKDIR $BLOBDIR);
@EXPORT = qw( $BINDIR $ETCDIR $VARDIR $BOOTDIR $DBDIR $LOGDIR $LOCKDIR $BLOBDIR
$DYNRUNDIR $STATICRUNDIR
);
#
# This path stuff will go away when the world is consistent. Until then
......@@ -44,9 +46,11 @@ if (-d "/usr/local/etc/emulab") {
unshift(@INC, "/usr/local/etc/emulab");
if (-d "/etc/emulab") {
$ETCDIR = "/etc/emulab";
$STATICRUNDIR = "/etc/emulab/run";
}
else {
$ETCDIR = "/usr/local/etc/emulab";
$STATICRUNDIR = "/usr/local/etc/emulab/run";
}
$VARDIR = "/var/emulab";
$BOOTDIR = "/var/emulab/boot";
......@@ -61,6 +65,7 @@ elsif (-d "/etc/testbed") {
$VARDIR = "/etc/testbed";
$BOOTDIR = "/etc/testbed";
$DBDIR = "/etc/testbed";
$STATICRUNDIR = "/etc/testbed/run";
}
elsif (-d "/etc/rc.d/testbed") {
unshift(@INC, "/etc/rc.d/testbed");
......@@ -69,6 +74,7 @@ elsif (-d "/etc/rc.d/testbed") {
$VARDIR = "/etc/rc.d/testbed";
$BOOTDIR = "/etc/rc.d/testbed";
$DBDIR = "/etc/rc.d/testbed";
$STATICRUNDIR = "/etc/rc.d/testbed/run";
}
else {
print "$0: Cannot find proper emulab paths!\n";
......@@ -76,6 +82,7 @@ else {
}
$BLOBDIR = $BOOTDIR;
$DYNRUNDIR = "/var/run/emulab";
#
# Untaint path
......
#!/bin/sh
#
# Copyright (c) 2000-2004 University of Utah and the Flux Group.
# Copyright (c) 2000-2004, 2016 University of Utah and the Flux Group.
#
# {{{EMULAB-LICENSE
#
......@@ -35,8 +35,10 @@ if [ -d /usr/local/etc/emulab ]; then
BINDIR=/usr/local/etc/emulab
if [ -e /etc/emulab/client.pem ]; then
ETCDIR=/etc/emulab
STATICRUNDIR=/etc/emulab/run
else
ETCDIR=/usr/local/etc/emulab
STATICRUNDIR=/usr/local/etc/emulab/run
fi
VARDIR=/var/emulab
BOOTDIR=/var/emulab/boot
......@@ -51,17 +53,21 @@ elif [ -d /etc/testbed ]; then
LOGDIR=/tmp
LOCKDIR=/tmp
DBDIR=/etc/testbed
STATICRUNDIR=/etc/testbed/run
elif [ -d /etc/rc.d/testbed ]; then
ETCDIR=/etc/rc.d/testbed
BINDIR=/etc/rc.d/testbed
VARDIR=/etc/rc.d/testbed
BOOTDIR=/etc/rc.d/testbed
DBDIR=/etc/rc.d/testbed
STATICRUNDIR=/etc/rc.d/testbed/run
else
echo "$0: Cannot find proper emulab paths!"
exit 1
fi
DYNRUNDIR=/var/run/emulab
export ETCDIR
export BINDIR
export VARDIR
......@@ -69,5 +75,7 @@ export BOOTDIR
export LOGDIR
export DBDIR
export LOCKDIR
export STATICRUNDIR
export DYNRUNDIR
PATH=$BINDIR:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:\
/usr/site/bin:/usr/site/sbin
......@@ -170,7 +170,7 @@ if ($action eq "boot" || $action eq "reconfig") {
# run (or not) its hooks.
#
my %manifest;
getmanifest(\%manifest);
getrcmanifest(\%manifest);
# Execute the action.
SWITCH: for ($action) {
......
......@@ -199,6 +199,8 @@ simple-install: common-install \
dir-install:
$(INSTALL) -m 755 -o root -g wheel -d $(ETCDIR)
$(INSTALL) -m 755 -o root -g wheel -d $(BINDIR)
$(INSTALL) -m 755 -o root -g wheel -d $(BINDIR)/prepare.pre.d
$(INSTALL) -m 755 -o root -g wheel -d $(BINDIR)/prepare.post.d
$(INSTALL) -m 755 -o root -g wheel -d $(RCDIR)
$(INSTALL) -m 755 -o root -g wheel -d $(VARDIR)
$(INSTALL) -m 755 -o root -g wheel -d $(VARDIR)/db
......
......@@ -87,6 +87,21 @@ if (getopts("N", \%options)) {
}
}
#
# Really first, look for user prepare prehooks and run them.
#
my @PHOOKSDIRS = ("$DYNRUNDIR/prepare.pre.d","$BINDIR/prepare.pre.d");
for my $pdir (@PHOOKSDIRS) {
my @sfiles = ();
my $rc = sortedlistallfilesindir($pdir,\@sfiles,1);
if (defined($rc) && $rc == 0) {
for my $file (@sfiles) {
print "Running prepare hook $file ...\n";
system("$file");
}
}
}
#
# First clean up the node as it would be if free.
#
......@@ -336,6 +351,21 @@ if (-e $PUBSUBEXPR) {
warn("*** could not remove $PUBSUBEXPR\n");
}
#
# Really finally, look for user prepare posthooks and run them.
#
@PHOOKSDIRS = ("$DYNRUNDIR/prepare.post.d","$BINDIR/prepare.post.d");
for my $pdir (@PHOOKSDIRS) {
my @sfiles = ();
my $rc = sortedlistallfilesindir($pdir,\@sfiles,1);
if (defined($rc) && $rc == 0) {
for my $file (@sfiles) {
print "Running prepare hook $file ...\n";
system("$file");
}
}
}
# Leave this print statement here; create_image depends on it.
print "prepare ran successfully!\n";
exit 0;
......
......@@ -161,6 +161,8 @@ dir-install:
$(INSTALL) -m 755 -o root -g $(DIRGROUP) -d $(RCDIR)/rc6.d
$(INSTALL) -m 755 -o root -g $(DIRGROUP) -d $(ETCDIR)
$(INSTALL) -m 755 -o root -g $(DIRGROUP) -d $(BINDIR)
$(INSTALL) -m 755 -o root -g $(DIRGROUP) -d $(BINDIR)/prepare.pre.d
$(INSTALL) -m 755 -o root -g $(DIRGROUP) -d $(BINDIR)/prepare.post.d
$(INSTALL) -m 755 -o root -g $(DIRGROUP) -d $(RCDIR)
$(INSTALL) -m 755 -o root -g $(DIRGROUP) -d $(VARDIR)
$(INSTALL) -m 755 -o root -g $(DIRGROUP) -d $(VARDIR)/db
......
......@@ -111,6 +111,21 @@ if (-e "$RUNDIR/updatemasterpasswdfiles") {
}
my $isvm = ((-e "$ETCDIR/genvmtype") ? 1 : 0);
#
# Really first, look for user prepare prehooks and run them.
#
my @PHOOKSDIRS = ("$DYNRUNDIR/prepare.pre.d","$BINDIR/prepare.pre.d");
for my $pdir (@PHOOKSDIRS) {
my @sfiles = ();
my $rc = sortedlistallfilesindir($pdir,\@sfiles,1);
if (defined($rc) && $rc == 0) {
for my $file (@sfiles) {
print "Running prepare hook $file ...\n";
system("$file");
}
}
}
#
# First clean up the node as it would be if free.
#
......@@ -469,6 +484,21 @@ if (-e $PUBSUBEXPR) {
warn("*** could not remove $PUBSUBEXPR\n");
}
#
# Really finally, look for user prepare posthooks and run them.
#
@PHOOKSDIRS = ("$DYNRUNDIR/prepare.post.d","$BINDIR/prepare.post.d");
for my $pdir (@PHOOKSDIRS) {
my @sfiles = ();
my $rc = sortedlistallfilesindir($pdir,\@sfiles,1);
if (defined($rc) && $rc == 0) {
for my $file (@sfiles) {
print "Running prepare hook $file ...\n";
system("$file");
}
}
}
# Leave this print statement here; create_image depends on it.
print "prepare ran successfully!\n";
exit 0;
......
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