#!/usr/bin/perl -wT # # EMULAB-COPYRIGHT # Copyright (c) 2000-2010 University of Utah and the Flux Group. # All rights reserved. # 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). # # 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. # sub usage() { print STDERR "Usage: $0 [-d] \n"; exit(1); } my $optlist = "d"; my $debug = 0; sub mysystem($); # # Configure variables # my $TB = "@prefix@"; my $TBOPS = "@TBOPSEMAIL@"; my $ELABINELAB = @ELABINELAB@; my $SAVEUID = $UID; # 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; # Defined in libdb ... my $TBOPSPID = TBOPSPID(); 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"); } # # 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"); } # # 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(); # # Untaint the arguments. # if ($pid =~ /^([-\w]+)$/) { $pid = $1; } else { die("Tainted argument $pid!\n"); } # Temporary ... See utils/firstuser ... DBQueryFatal("update group_membership set pid_idx=1,gid_idx=1 ". "where pid='$TBOPSPID' and pid=gid"); # Do not want to share the UUIDs with outer Emulab. DBQueryFatal("update users set uid_uuid=UUID()"); DBQueryFatal("update nodes set uuid=UUID()"); # # Shift to real user for these scripts. # $EUID = $UID; # # Build the project. # mysystem("$TB/sbin/mkproj -s $pid"); # # Get the list of users and admin status. Admin users get a real shell # on boss. Create the users, and note that we have to do this before the # groups are created (tbacct add does not do a setgroups). # my $users_result = DBQueryFatal("select distinct u.uid,u.uid_idx,u.admin,u.status ". " from group_membership as m ". "left join users as u on u.uid_idx=m.uid_idx "); while (my ($uid,$uid_idx,$admin,$status) = $users_result->fetchrow_array()) { next if ($uid eq "elabman"); if ($admin) { # Add admin users to group wheel for convenience. DBQueryFatal("replace into unixgroup_membership ". "values ('$uid','$uid_idx','wheel')"); } next if ($status ne USERSTATUS_ACTIVE()); mysystem("$TB/sbin/tbacct -b add $uid"); if ($admin) { # Flip back to root for pw command. $EUID = 0; mysystem("pw usermod -n $uid -s /bin/tcsh"); $EUID = $UID; } } # # Get the list of subgroups in the project and create those groups. # my $query_result = DBQueryFatal("select gid_idx from groups where pid='$pid' and pid!=gid"); while (my ($gid_idx) = $query_result->fetchrow_array()) { mysystem("$TB/sbin/mkgroup -s $gid_idx"); } # # Now do a setgroups. # $users_result->dataseek(0); while (my ($uid,$uid_idx,$admin,$status) = $users_result->fetchrow_array()) { next if ($uid eq "elabman"); next if ($status ne USERSTATUS_ACTIVE()); mysystem("$TB/sbin/setgroups $uid"); } # # 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"); # # Fixup pxeboot and MFS info. # # Right now we only load the standard BSD-based pxeboot and 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. # # 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) # and "create" them on the inner boss by symlinking them to the standard # pxeboot. SWEET! # # XXX this should go away if/when we settle on a single set of tools. # if (1) { # first find the OSIDs for the "standard" MFSes my $qr = DBQueryFatal("select osname,osid from os_info where osname in ". " ('FREEBSD-MFS','FRISBEE-MFS','NEWNODE-MFS') ". " and pid='$TBOPSPID'"); my ($amfs,$dmfs,$nmfs,$nmfspath); while (my ($name, $osid) = $qr->fetchrow_array()) { if ($name eq "FREEBSD-MFS") { $amfs = $osid; } elsif ($name eq "FRISBEE-MFS") { $dmfs = $osid; } else { $nmfs = $osid; $nmfspath = "/tftpboot/freebsd.newnode"; # XXX hardwired } } # make sure newnode MFS points to the correct place DBQueryFatal("update os_info set path='$nmfspath' where osid=$nmfs"); # 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()) { push @bogoboots, $path; } # ..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()) { push @bogoboots, $path; } # 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 DBQueryFatal("delete from node_type_attributes ". " where attrkey='pxe_boot_path' and type='$ntype'"); 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'"); } # fixup any nodes table entries with non-standard pxe_boot_path's DBQueryFatal("update nodes set pxe_boot_path=NULL ". " where pxe_boot_path is not NULL"); # # Now symlink all the alternate boots to pxeboot.emu # XXX we assume everything is at the top level of /tftpboot right now. # 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"; } } } } # # Remake the dhcpd.conf file to reflect any pxeboot change. # XXX dhcpd is not running yet so don't need this. # #mysystem("$TB/sbin/dhcpd_makeconf -ir"); } # # Run a command string. # sub mysystem($) { my ($command) = @_; if ($debug) { print "Command: '$command\'\n"; } system($command); if ($?) { die("*** $0:\n". " Command failed: $? - $command\n"); } }