elabinelab_bossinit.in 13 KB
Newer Older
1
#!/usr/bin/perl -wT
2
#
3
# Copyright (c) 2000-2017 University of Utah and the Flux Group.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# 
# {{{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/>.
# 
# }}}
23 24 25 26 27 28 29 30
#
use English;
use Getopt::Std;

#
# ElabInElab: This is run on the inner boss to construct a bunch stuff
# from the db (groups, projects, users, etc).
#
31 32 33 34 35 36
# We also use this opportunity to munge node-related bootstrap state.
# Right now we only load the standard BSD-based pxeboot and MFSes into
# an elabinelab, but many of our node types now use the Linux-based tools
# instead.  So for the moment, we tweak the DB to rewrite everything to use
# the BSD tools.
#
37 38 39 40 41 42 43
sub usage()
{
    print STDERR "Usage: $0 [-d] <pid>\n";
    exit(1);
}
my $optlist = "d";
my $debug   = 0;
44
sub mysystem($);
45 46 47 48 49 50 51

#
# Configure variables
#
my $TB		= "@prefix@";
my $TBOPS       = "@TBOPSEMAIL@";
my $ELABINELAB  = @ELABINELAB@;
52
my $SAVEUID     = $UID;
53 54 55 56 57 58 59 60 61 62 63

# 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 libdb;
use libtestbed;
64 65 66
use OSImage;
use Node;
use NodeType;
67

Leigh Stoller's avatar
Leigh Stoller committed
68 69 70
# Defined in libdb ...
my $TBOPSPID    = TBOPSPID();

71 72 73 74 75 76 77 78 79
if (!$ELABINELAB) {
    die("*** $0:\n".
	"    This script can only run on an inner Emulab!\n");
}
# Only admin types!
if (!TBAdmin($UID)) {
    die("*** $0:\n".
	"    Only TB administrators can run this script!\n");
}
80 81 82 83 84 85 86
#
# We don't want to run this script unless its the real version.
#
if ($EUID != 0) {
    die("*** $0:\n".
	"    Must be root! Maybe its a development version?\n");
}
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104

#
# 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{"d"})) {
    $debug = 1;
}

usage()
    if (scalar(@ARGV) != 1);
my $pid = shift();

#
105
# Untaint the arguments.
106
#
107 108 109 110 111 112
if ($pid =~ /^([-\w]+)$/) {
    $pid = $1;
}
else {
    die("Tainted argument $pid!\n");
}
113

Mike Hibler's avatar
Mike Hibler committed
114 115 116 117 118
#
# XXX Fixup the pid/gid indicies for emulab-ops in various tables.
# At this point they have the values of the outer Emulab so they need
# to be reset to reflect the newly minted version here.
#
119 120
my @PIDGIDTABLES = ("group_membership", "images", "image_versions");
my @PIDONLYTABLES = ("os_info", "os_info_versions");
Mike Hibler's avatar
Mike Hibler committed
121 122 123 124

my $q = DBQueryFatal("select pid_idx from projects ".
		     "where pid='$TBOPSPID'");
my ($npid) = $q->fetchrow_array();
125 126
$q = DBQueryFatal("select gid_idx from groups ".
		  "where pid='$TBOPSPID' and gid=pid");
Mike Hibler's avatar
Mike Hibler committed
127 128 129 130 131 132 133 134 135
my ($ngid) = $q->fetchrow_array();

foreach my $table (@PIDGIDTABLES) {
    DBQueryFatal("update $table set pid_idx=$npid,gid_idx=$ngid ".
		 "where pid='$TBOPSPID' and pid=gid");
}
foreach my $table (@PIDONLYTABLES) {
    DBQueryFatal("update $table set pid_idx=$npid where pid='$TBOPSPID'");
}
136

137 138 139 140
# Do not want to share the UUIDs with outer Emulab.
DBQueryFatal("update users set uid_uuid=UUID()");
DBQueryFatal("update nodes set uuid=UUID()");

141
#
142
# Shift to real user for these scripts.
143
#
144 145 146 147 148
$EUID = $UID;

#
# Build the project.
#
149
mysystem("$TB/sbin/mkproj -s $pid");
150 151 152

#
# Get the list of users and admin status. Admin users get a real shell
Mike Hibler's avatar
Mike Hibler committed
153
# on boss. Create the users, and note that we have to do this before the
154
# groups are created (tbacct add does not do a setgroups).
155
#
156
my $users_result =
157
    DBQueryFatal("select distinct u.uid,u.uid_idx,u.admin,u.status ".
158 159
		 "   from group_membership as m ".
		 "left join users as u on u.uid_idx=m.uid_idx ");
160 161 162 163

# Need to do this we want to seek around inside the results.
$users_result = $users_result->WrapForSeek();

164
while (my ($uid,$uid_idx,$admin,$status) = $users_result->fetchrow_array()) {
165 166 167
    next
	if ($uid eq "elabman");
    
168 169
    if ($admin) {
	# Add admin users to group wheel for convenience.
170
	DBQueryFatal("replace into unixgroup_membership ".
171
		     "values ('$uid','$uid_idx','wheel')");
172
    }
173 174 175 176 177
    next
	if ($status ne USERSTATUS_ACTIVE());
    
    mysystem("$TB/sbin/tbacct -b add $uid");
    
178
    if ($admin) {
179 180
	# Flip back to root for pw command.
	$EUID = 0;
181
	mysystem("pw usermod -n $uid -s /bin/tcsh");
182
	$EUID = $UID;
183 184 185
    }
}

186 187 188 189
#
# Get the list of subgroups in the project and create those groups.
#
my $query_result =
190 191
    DBQueryFatal("select gid_idx from groups where pid='$pid' and pid!=gid");
while (my ($gid_idx) = $query_result->fetchrow_array()) {
192
    mysystem("$TB/sbin/mkgroup -s $gid_idx");
193 194 195 196 197 198
}

#
# Now do a setgroups.
#
$users_result->dataseek(0);
Mike Hibler's avatar
Mike Hibler committed
199
while (my ($uid,$uid_idx,$admin,$status) = $users_result->fetchrow_array()) {
200 201
    next
	if ($uid eq "elabman");
202 203
    next
	if ($status ne USERSTATUS_ACTIVE());
204 205 206 207
    
    mysystem("$TB/sbin/setgroups $uid");
}

208 209 210 211 212 213 214
#
# Do the exports setup and the genelists all at once now that all the above
# stuff happened.
#
mysystem("$TB/sbin/genelists -a");
mysystem("$TB/sbin/exports_setup");

215 216 217 218 219 220 221 222 223 224 225 226
#
# Set some sitevars that are different for inner elabs
#
if (TBSiteVarExists("images/frisbee/maxrate_std")) {
    # XXX traditional value for elabinelab
    TBSetSiteVar("images/frisbee/maxrate_std", "54000000");
}
if (TBSiteVarExists("images/frisbee/maxrate_usr")) {
    # XXX traditional value for elabinelab
    TBSetSiteVar("images/frisbee/maxrate_usr", "54000000");
}

227 228 229
#
# Fixup pxeboot and MFS info.
#
230 231 232 233
# Right now we only load versions of the standard BSD-based pxeboot and the
# standard BSD-based MFSes into an elabinelab. But many of our node types use
# the Linux-based tools instead so, for the moment, we tweak the DB to rewrite
# everything to use the BSD tools.
234
#
Mike Hibler's avatar
Mike Hibler committed
235 236 237 238 239 240 241 242 243 244
# Note that re-writing the pxe_boot_path doesn't have any effect for elabs
# NOT using the SINGLE_CONTROLNET setting.  This is because as long as
# outer (real) boss responds first, the filename it returns is what gets
# used.  We could rewrite pxe_boot_path in the real boss DB for nodes
# that are in an elabinelab, but then we could lose custom per-node settings
# for that field.  To fix that, we could introduce a temporary field for
# holding any custom value, but I don't want to go there...
#
# Anyway, the way we work around the non-SINGLE_CONTROLNET problem is to
# find all the custom pxe_boot_path values (in nodes or node_type_attributes)
245 246
# and, if they do not exist, "create" them on the inner boss by symlinking
# them to the standard pxeboot.  SWEET!
Mike Hibler's avatar
Mike Hibler committed
247 248
#
# XXX this should go away if/when we settle on a single set of tools.
249 250 251
#
if (1) {
    # first find the OSIDs for the "standard" MFSes
Leigh Stoller's avatar
Leigh Stoller committed
252 253
    my $qr = DBQueryFatal("select osname,osid,version from os_info ".
			  " where osname in ".
254 255
			  "  ('FREEBSD-MFS','FRISBEE-MFS','NEWNODE-MFS') ".
			  " and pid='$TBOPSPID'");
Leigh Stoller's avatar
Leigh Stoller committed
256 257
    my ($amfs,$dmfs,$nmfs,$nmfspath,$nmfsvers);
    while (my ($name, $osid, $version) = $qr->fetchrow_array()) {
258 259 260 261 262 263
	if ($name eq "FREEBSD-MFS") {
	    $amfs = $osid;
	} elsif ($name eq "FRISBEE-MFS") {
	    $dmfs = $osid;
	} else {
	    $nmfs = $osid;
Leigh Stoller's avatar
Leigh Stoller committed
264
	    $nmfsvers = $version;
265 266 267 268 269
	    $nmfspath = "/tftpboot/freebsd.newnode"; # XXX hardwired
	}
    }

    # make sure newnode MFS points to the correct place
Leigh Stoller's avatar
Leigh Stoller committed
270 271
    DBQueryFatal("update os_info_versions set path='$nmfspath' ".
		 "where osid=$nmfs and vers=$nmfsvers");
272

Mike Hibler's avatar
Mike Hibler committed
273 274 275 276 277 278
    # collect up non-standard PXE boot paths, first from node_type_attributes..
    my @bogoboots = ();
    $qr = DBQueryFatal("select attrvalue from node_type_attributes ".
		       "where attrkey='pxe_boot_path' and attrvalue!='' ".
		       "group by attrvalue");
    while (my ($path) = $qr->fetchrow_array()) {
279 280 281 282
	# XXX we will install the various pxeboot.emu* files, so allow those
	if ($path !~ /^\/tftpboot\/pxeboot.emu/) {
	    push @bogoboots, $path;
	}
Mike Hibler's avatar
Mike Hibler committed
283 284 285 286 287
    }
    # ..and then from nodes
    $qr = DBQueryFatal("select pxe_boot_path from nodes ".
		       "where pxe_boot_path is not NULL and role='testnode'");
    while (my ($path) = $qr->fetchrow_array()) {
288 289 290 291
	# XXX we will install the various pxeboot.emu* files, so allow those
	if ($path !~ /^\/tftpboot\/pxeboot.emu-/) {
	    push @bogoboots, $path;
	}
Mike Hibler's avatar
Mike Hibler committed
292 293
    }

294 295 296 297
    # and find all the node types and update their attributes.
    $qr = DBQueryFatal("select type from node_types where class='pc'");
    while (my ($ntype) = $qr->fetchrow_array()) {
	# XXX assumes that BSD version is the default in dhcpd.conf.template
298
	# XXX and again we can handle the assorted versions of pxeboot.emu
299
	DBQueryFatal("delete from node_type_attributes ".
300 301
		     "  where attrkey='pxe_boot_path' and type='$ntype' ".
		     "  and not attrvalue like '/tftpboot/pxeboot.emu-%%'");
302 303 304 305 306 307
	DBQueryFatal("update node_type_attributes set attrvalue='$amfs' ".
		     "  where attrkey='adminmfs_osid' and type='$ntype'");
	DBQueryFatal("update node_type_attributes set attrvalue='$dmfs' ".
		     "  where attrkey='diskloadmfs_osid' and type='$ntype'");
    }

Mike Hibler's avatar
Mike Hibler committed
308
    # fixup any nodes table entries with non-standard pxe_boot_path's
309
    # and clear any one-shot values
Mike Hibler's avatar
Mike Hibler committed
310 311
    DBQueryFatal("update nodes set pxe_boot_path=NULL ".
		 "  where pxe_boot_path is not NULL");
312 313
    DBQueryFatal("update nodes set next_pxe_boot_path=NULL ".
		 "  where next_pxe_boot_path is not NULL");
Mike Hibler's avatar
Mike Hibler committed
314 315

    #
316 317
    # Make all versions of pxeboot.emu-* available
    # XXX only worry about the most recent versions.
Mike Hibler's avatar
Mike Hibler committed
318
    #
319
    $EUID = 0;
320 321 322 323 324 325 326
    system("cp -fp /tftpboot/pxeboot72/pxeboot.emu-* /tftpboot/")
	if (-d "/tftpboot/pxeboot72");

    #
    # Now symlink all the unknown alternate boots to pxeboot.emu
    # XXX we assume everything is at the top level of /tftpboot right now.
    #
Mike Hibler's avatar
Mike Hibler committed
327 328 329 330 331 332 333 334 335 336 337 338
    foreach my $boot (@bogoboots) {
	if ($boot =~ /^\/tftpboot\/([^\/]+)$/) {
	    $boot = $1;
	    if (! -e "/tftpboot/$boot") {
		if (system("ln -s pxeboot.emu /tftpboot/$boot")) {
		    print STDERR
			"*** could not symlink non-standard boot '$boot';",
			" some inner nodes will not boot properly!\n";
		}
	    }
	}
    }
339
    $EUID = $UID;
Mike Hibler's avatar
Mike Hibler committed
340

341 342
    #
    # Remake the dhcpd.conf file to reflect any pxeboot change.
343
    # XXX dhcpd is not running yet so don't need this.
344
    #
345
    #mysystem("$TB/sbin/dhcpd_makeconf -ir");
346 347
}

348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382
if (1) {
    #
    # Need to fix up imported images; there was no type table at the
    # time of import, so we could not use architecture or type slots
    # when creating the new image descriptors. Lets just get the list
    # of images and the list of pc types, and do them all.
    #
    my @alltypes = NodeType->AllTypes();
    
    my $query_result =
	DBQueryFatal("select imageid from images where pid='$TBOPSPID'");
    while (my ($imageid) = $query_result->fetchrow_array()) {
	my $image = OSImage->Lookup($imageid);
	if (!defined($image)) {
	    print STDERR "Could not lookup imageid $imageid\n";
	    next;
	}
	foreach my $type (@alltypes) {
	    next
		if ($type->class() ne "pc" || $type->type() eq "pc");

	    # See if we have any actual nodes of this type.
	    my @nodes = Node->LookupByType($type->type());
	    next
		if (!@nodes);

	    $image->SetRunsOnNodeType($type->type());
	}
    }
    #
    # We also have to set the default image/os for real types, since
    # the stuff in the table we imported from the outer Emulab refers
    # to image IDs that are not valid in this inner Emulab. So just
    # pick whatever UBUNTU image we happen to have.
    #
383 384
    # Ditto for delay_osid, find an available FBSD10 image.
    #
385 386 387 388 389 390 391 392 393 394 395 396 397
    $query_result =
	DBQueryFatal("select imageid from images ".
		     "where imagename like 'UBUNTU%' and pid='$TBOPSPID'");
    if (!$query_result->numrows) {
	die("*** $0:\n".
	    "    Could not find a suitable UBUNTU image");
    }
    my ($imageid) = $query_result->fetchrow_array();
    my $image = OSImage->Lookup($imageid);
    if (! defined($image)) {
	die("*** $0:\n".
	    "    Could not lookup image $imageid");
    }
398 399 400 401 402 403 404 405 406 407 408 409 410 411 412

    $query_result =
	DBQueryFatal("select imageid from images ".
		     "where imagename like 'FBSD10%' and pid='$TBOPSPID'");
    if (!$query_result->numrows) {
	die("*** $0:\n".
	    "    Could not find a suitable FBSD10 image for delay nodes");
    }
    my ($dimageid) = $query_result->fetchrow_array();
    my $dimage = OSImage->Lookup($dimageid);
    if (! defined($dimage)) {
	die("*** $0:\n".
	    "    Could not lookup image $dimageid");
    }

413 414 415 416 417 418 419 420 421 422 423
    foreach my $type (@alltypes) {
	next
	    if ($type->class() ne "pc" || $type->type() eq "pc");

	# See if we have any actual nodes of this type.
	my @nodes = Node->LookupByType($type->type());
	next
	    if (!@nodes);

	$type->SetAttribute("default_imageid", $image->imageid());
	$type->SetAttribute("default_osid", $image->imageid());
424
	$type->SetAttribute("delay_osid", $dimage->imageid());
425 426 427
    }
}

428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
#
# Run a command string.
#
sub mysystem($)
{
    my ($command) = @_;

    if ($debug) {
	print "Command: '$command\'\n";
    }

    system($command);
    if ($?) {
	die("*** $0:\n".
	    "    Command failed: $? - $command\n");
    }
}