Commit 1d430992 authored by Leigh Stoller's avatar Leigh Stoller

New module, called Emulab Features. The basic usage (see tbswap) is:

use EmulabFeatures;

if (EmulabFeatures->FeatureEnabled("NewMapper", $user, $group, $experiment)) {
   # Do something
}
else {
   # Do something else.
}

where $user, $group, and $experiment is the current Emulab user, group, and
experiment the script is operating as. Any of them can be undef. Note that
features can easily be globally enabled or disabled (bypassing user/group
check). See below.

There are two scripts to deal with features. The easy one is the script to
grant (or revoke) feature usage to a particular user or group or experiment:

boss> wap grantfeature -u stoller NewMapper
boss> wap grantfeature -p geni NewMapper
boss> wap grantfeature -e geni,myexp NewMapper

Add -r to revoke the feature.

The other script is for managing features. To create a new feature:

boss> wap emulabfeature create NewFeature 'A pithy description'

which adds the feature to the emulab_features DB table. Use "delete"
to remove a feature from the DB.

You can globally enable and disable features for all users/groups (the
user/group checks are bypassed). Global disable overrides global
enable. There are actually two different flags. Lots of rope, I mean
flexibility.

boss> wap emulabfeature enable NewFeature 1
boss> wap emulabfeature enable NewFeature 0

boss> wap emulabfeature disable NewFeature 1
boss> wap emulabfeature disable NewFeature 0

To display a list of all features and associated settings:

boss> wap emulabfeature list

To show the details (including the users and groups) of a specific
feature:

boss> wap emulabfeature show NewFeature

Oh, if a test is made in the code for a feature, and that feature is
not in the emulab_features table (as might be the case on other
Emulab's), the feature is "disabled".
parent 60a40a3c
......@@ -163,7 +163,7 @@ use vars qw(@ISA @EXPORT);
);
use English;
use vars qw($TB $TBOPS $TBOPSPID $EXPTLOGNAME $PROJROOT);
use vars qw($TB $TBOPS $TBOPSPID $EXPTLOGNAME $PROJROOT $MAINSITE);
# Configure variables
$TB = "@prefix@";
......@@ -171,6 +171,7 @@ $TBOPS = "@TBOPSEMAIL@";
$TBOPSPID = "emulab-ops";
$EXPTLOGNAME = "activity.log";
$PROJROOT = "@PROJROOT_DIR@";
$MAINSITE = @TBMAINSITE@;
1;
@SELFLOADER_DATA@
......
This diff is collapsed.
......@@ -30,7 +30,7 @@ LIB_SCRIPTS = libdb.pm Node.pm libdb.py libadminctrl.pm Experiment.pm \
NodeType.pm Interface.pm User.pm Group.pm Project.pm \
Image.pm OSinfo.pm Archive.pm Logfile.pm Lan.pm emdbi.pm \
emdb.pm emutil.pm Firewall.pm VirtExperiment.pm libGeni.pm \
libEmulab.pm EmulabConstants.pm TraceUse.pm
libEmulab.pm EmulabConstants.pm TraceUse.pm EmulabFeatures.pm
# Stuff installed on plastic.
USERSBINS = genelists.proxy dumperrorlog.proxy backup
......
......@@ -471,6 +471,20 @@ CREATE TABLE `elabinelab_vlans` (
UNIQUE KEY `pideid` (`pid`,`eid`,`inner_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `emulab_features`
--
DROP TABLE IF EXISTS `emulab_features`;
CREATE TABLE `emulab_features` (
`feature` varchar(64) NOT NULL default '',
`description` mediumtext,
`added` datetime NOT NULL,
`enabled` tinyint(1) NOT NULL default '0',
`disabled` tinyint(1) NOT NULL default '0',
PRIMARY KEY (`feature`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `emulab_indicies`
--
......@@ -642,6 +656,20 @@ CREATE TABLE `eventlist` (
KEY `vnode` (`vnode`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `experiment_features`
--
DROP TABLE IF EXISTS `experiment_features`;
CREATE TABLE `experiment_features` (
`feature` varchar(64) NOT NULL default '',
`added` datetime NOT NULL,
`exptidx` int(11) NOT NULL default '0',
`pid` varchar(12) NOT NULL default '',
`eid` varchar(32) NOT NULL default '',
PRIMARY KEY (`feature`,`exptidx`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `experiment_input_data`
--
......@@ -1316,6 +1344,21 @@ CREATE TABLE `global_vtypes` (
PRIMARY KEY (`vtype`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `group_features`
--
DROP TABLE IF EXISTS `group_features`;
CREATE TABLE `group_features` (
`feature` varchar(64) NOT NULL default '',
`added` datetime NOT NULL,
`pid_idx` mediumint(8) unsigned NOT NULL default '0',
`gid_idx` mediumint(8) unsigned NOT NULL default '0',
`pid` varchar(12) NOT NULL default '',
`gid` varchar(12) NOT NULL default '',
PRIMARY KEY (`feature`,`gid_idx`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `group_membership`
--
......@@ -3480,6 +3523,19 @@ CREATE TABLE `unixgroup_membership` (
-- Table structure for table `user_policies`
--
DROP TABLE IF EXISTS `user_features`;
CREATE TABLE `user_features` (
`feature` varchar(64) NOT NULL default '',
`uid_idx` mediumint(8) unsigned NOT NULL default '0',
`added` datetime NOT NULL,
`uid` varchar(8) NOT NULL default '',
PRIMARY KEY (`feature`,`uid_idx`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
--
-- Table structure for table `user_policies`
--
DROP TABLE IF EXISTS `user_policies`;
CREATE TABLE `user_policies` (
`uid` varchar(8) NOT NULL default '',
......
#
# Add Emulab Features.
#
use strict;
use libdb;
use EmulabFeatures;
use EmulabConstants;
use Project;
sub DoUpdate($$$)
{
my ($dbhandle, $dbname, $version) = @_;
DBQueryFatal("CREATE TABLE `emulab_features` ( ".
" `feature` varchar(64) NOT NULL default '', ".
" `description` mediumtext, ".
" `added` datetime NOT NULL, ".
" `enabled` tinyint(1) NOT NULL default '0', ".
" `disabled` tinyint(1) NOT NULL default '0', ".
" PRIMARY KEY (`feature`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1")
if (! DBTableExists("emulab_features"));
DBQueryFatal("CREATE TABLE `group_features` ( ".
" `feature` varchar(64) NOT NULL default '', ".
" `added` datetime NOT NULL, ".
" `pid_idx` mediumint(8) unsigned NOT NULL default '0', ".
" `gid_idx` mediumint(8) unsigned NOT NULL default '0', ".
" `pid` varchar(12) NOT NULL default '', ".
" `gid` varchar(12) NOT NULL default '', ".
" PRIMARY KEY (`feature`,`gid_idx`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1")
if (! DBTableExists("group_features"));
DBQueryFatal("CREATE TABLE `user_features` ( ".
" `feature` varchar(64) NOT NULL default '', ".
" `added` datetime NOT NULL, ".
" `uid_idx` mediumint(8) unsigned NOT NULL default '0', ".
" `uid` varchar(8) NOT NULL default '', ".
"PRIMARY KEY (`feature`,`uid_idx`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1")
if (! DBTableExists("user_features"));
DBQueryFatal("CREATE TABLE `experiment_features` ( ".
" `feature` varchar(64) NOT NULL default '', ".
" `added` datetime NOT NULL, ".
" `exptidx` int(11) NOT NULL default '0', ".
" `pid` varchar(12) NOT NULL default '', ".
" `eid` varchar(32) NOT NULL default '', ".
"PRIMARY KEY (`feature`,`exptidx`) ".
") ENGINE=MyISAM DEFAULT CHARSET=latin1")
if (! DBTableExists("experiment_features"));
my $emulabops = Project->Lookup("emulab-ops");
my $testbed = Project->Lookup("testbed");
#
# These are the features in production code at this time.
#
my $feature = EmulabFeatures->Lookup("SyncVlans");
if (!defined($feature)) {
$feature = EmulabFeatures->Create("SyncVlans",
"Use SyncVlansFromTables() instead of ".
"DoVlansFromTables() in snmpit, which reduces ".
"churning on the switches.");
}
return -1
if (!defined($feature));
$feature->Enable($emulabops) == 0
or return -1 if (defined($emulabops));
$feature = EmulabFeatures->Lookup("NewMapper");
if (!defined($feature)) {
$feature = EmulabFeatures->Create("NewMapper",
"Use mapper instead of assign_wrapper. ".
"The mapper replaces assign_wrapper.");
}
return -1
if (!defined($feature));
$feature->Enable($emulabops) == 0
or return -1 if (defined($emulabops));
$feature->Enable($testbed) == 0
or return -1 if (defined($testbed));
$feature = EmulabFeatures->Lookup("NewOsSetup");
if (!defined($feature)) {
$feature = EmulabFeatures->Create("NewOsSetup",
"Use os_setup_new instead of os_setup. ".
"os_setup_new will eventually replace os_setup.");
}
return -1
if (!defined($feature));
$feature->Enable($emulabops) == 0
or return -1 if (defined($emulabops));
$feature->Enable($testbed) == 0
or return -1 if (defined($testbed));
#
# Utah only ...
#
if ($EmulabConstants::MAINSITE) {
foreach my $projname ("tbres", "geni", "utahstud", "ClemsonGENI",
"ResiliNets", "gec8tutorial") {
my $project = Project->Lookup($projname);
next
if (!defined($project));
foreach my $fname ("NewMapper", "NewOsSetup", "SyncVlans") {
$feature = EmulabFeatures->Lookup($fname);
return -1
if (!defined($feature));
$feature->Enable($project) == 0
or return -1;
}
}
}
return 0;
}
1;
......@@ -85,6 +85,7 @@ use libArchive;
use User;
use Template;
use Experiment;
use EmulabFeatures;
my $tbdir = "$TB/bin/";
my $tbdata = "tbdata";
......@@ -532,6 +533,12 @@ if (!$template_mode) {
$experiment->ClearLogFile();
}
#
# Kill any features
#
EmulabFeatures->DeleteAll($experiment) == 0 or
fatal("Could not delete all features for $experiment");
#
# Cleanup DB state and remove directory. Purge flag is optional and generally
# used by admins for cleaning up bad DB state caused by script errors.
......
......@@ -77,6 +77,7 @@ use libdb;
use libtestbed;
use User;
use Group;
use EmulabFeatures;
# Defined in libtestbed;
my $PROJROOT = PROJROOT();
......@@ -254,6 +255,12 @@ if (system("grep -q '^${unix_gid}:' /etc/group")) {
}
}
#
# Kill any features
#
EmulabFeatures->DeleteAll($group) == 0 or
fatal("Could not delete all features for $group");
# Last step, so we can repeat above actions on failures.
$group->Delete() == 0 or
fatal("Could not delete group $group");
......
......@@ -81,6 +81,7 @@ use libtestbed;
use User;
use Project;
use Experiment;
use EmulabFeatures;
my $HOMEDIR = USERROOT();
......@@ -299,6 +300,12 @@ if (! $nuke) {
$EUID = 0;
}
#
# Kill any features
#
EmulabFeatures->DeleteAll($target_user) == 0 or
fatal("Could not delete all features for $target_user");
#
# Rename the users home dir if its there.
#
......
......@@ -60,6 +60,7 @@ use libtestbed;
use libadminctrl;
use libadminmfs;
use libtblog;
use EmulabFeatures;
use Experiment;
use User;
use Lan;
......@@ -104,7 +105,6 @@ my $noswapout = 0;
my $genimode = 0;
my $errors = 0;
my $updatehosed = 0;
my $state;
my $canceled;
my $os_setup_pid;
my $nextState;
......@@ -193,29 +193,18 @@ my $experiment = Experiment->Lookup($pid, $eid);
if (!defined($experiment)) {
tbdie("Could not lookup experiment object!")
}
my $special = ($pid eq "testbed" || $pid eq "tbres" || $pid eq "geni" ||
$pid eq "emulab-ops" || $pid eq "utahstud" ||
$pid eq "ClemsonGENI" ||
$pid eq "ResiliNets" || $pid eq "gec8tutorial");
my $newsetup = $special;
my $syncvlans = ($pid eq "emulab-ops" || ($MAINSITE && $special));
#
# Print starting message.
#
my $exptidx;
TBExptIDX($pid, $eid, \$exptidx);
my $exptidx = $experiment->idx();
my $state = $experiment->state();
my $group = $experiment->GetGroup();
print "Beginning swap-$swapop for $pid/$eid ($exptidx). " .
TBTimeStampWithDate() . "\n";
TBDebugTimeStamp("tbswap $swapop started");
#
# Get experiment state; verify that experiment exists.
#
if (! ($state = ExpState($pid, $eid))) {
tbdie "No such experiment $pid/$eid";
}
# Sanity check the current state.
if (!$force) {
if ($swapop eq "in") {
......@@ -247,6 +236,14 @@ if ($elabinelab && $plabinelab) {
exit(1);
}
#
# See if we use the new version of SyncVlans.
#
my $syncvlans =
(EmulabFeatures->FeatureEnabled("SyncVlans",
$this_user, $group, $experiment)
|| $pid eq "emulab-ops");
#
# See if the experiment is firewalled
#
......@@ -905,7 +902,9 @@ sub doSwapin($) {
#
my $exitcode;
my $cmd = "$wrapper";
if ($special || $this_user->stud() || $experiment->virtnode_count()) {
if (EmulabFeatures->FeatureEnabled("NewMapper",
$this_user, $group, $experiment) ||
$this_user->stud() || $experiment->virtnode_count()) {
$cmd = "$mapper";
}
$cmd .= " -f"
......@@ -1281,8 +1280,11 @@ sub doSwapin($) {
print "Resetting OS and rebooting.\n";
TBDebugTimeStamp("launching os_setup");
if (!($os_setup_pid = fork())) {
my $oscmd = ($newsetup ? "os_setup_new" : "os_setup");
my $oscmd = "os_setup";
if (EmulabFeatures->FeatureEnabled("NewOsSetup",
$this_user, $group, $experiment)) {
$oscmd = "os_setup_new";
}
exec("$oscmd $pid $eid") or return 1;
} elsif ($os_setup_pid == -1) {
tberror "Fork failed.";
......
......@@ -26,7 +26,7 @@ SBIN_SCRIPTS = vlandiff vlansync withadminprivs export_tables cvsupd.pl \
wanodecheckin wanodecreate spewimage \
anonsendmail epmodeset fixexpinfo node_traffic \
dumpdescriptor subboss_tftpboot_sync testbed-control \
archive-expinfo
archive-expinfo grantfeature emulabfeature
WEB_SBIN_SCRIPTS= webnewnode webdeletenode webspewconlog webarchive_list \
webwanodecheckin webspewimage
......
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2003-2010 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
use Getopt::Std;
#
# Create/Delete and globally enable/disable emulab features.
#
sub usage()
{
print STDERR "Usage: emulabfeature <action> <feature>\n";
print STDERR " -h This message\n";
print STDERR " create Create a feature. Provide pithy description.\n";
print STDERR " delete Delete existing feature.\n";
print STDERR " enable Globally enable feature. Provide onoff flag.\n";
print STDERR " disable Globally disable feature. Provide onoff flag.\n";
print STDERR " show Show details of feature.\n";
print STDERR " list List of all features.\n";
exit(-1);
}
my $optlist = "h";
my $debug = 0;
# Protos
sub fatal($);
#
# Please do not run as root. Hard to track what has happened.
#
if ($UID == 0) {
die("*** $0:\n".
" Please do not run this as root!\n");
}
#
# Configure variables
#
my $TB = "@prefix@";
#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use emdb;
use EmulabFeatures;
use libtestbed;
use User;
#
# Turn off line buffering on output
#
$| = 1;
#
# Untaint the path
#
$ENV{'PATH'} = "/bin:/sbin:/usr/bin:";
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{h})) {
usage();
}
usage()
if (@ARGV < 1 || @ARGV > 3);
my $action = $ARGV[0];
#
# Verify user, must be admin.
#
my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
if (!$this_user->IsAdmin()) {
fatal("You are not a testbed administrator!");
}
sub ShowFeature($)
{
my ($feature) = @_;
my $featurename = $feature->feature();
my $enabled = $feature->enabled();
my $disabled = $feature->disabled();
my $description = $feature->description();
my $created = $feature->added();
my @flags = ();
push(@flags, "Globally enabled")
if ($enabled);
push(@flags, "Globally disabled")
if ($disabled);
my $flags = join(",", @flags);
print "$featurename: $flags\n";
print " Created: $created\n";
print " $description\n";
}
if ($action eq "list") {
my @list = EmulabFeatures->List();
foreach my $feature (@list) {
ShowFeature($feature);
}
exit(0);
}
#
# Everything else needs a featurename.
#
usage()
if (@ARGV < 2);
my $featurename = $ARGV[1];
my $feature = EmulabFeatures->Lookup($featurename);
if ($action eq "create") {
usage()
if (@ARGV != 3);
fatal("Feature $featurename already exists!")
if (defined($feature));
my $description = $ARGV[2];
$feature = EmulabFeatures->Create($featurename, $description);
fatal("Could not create new feature $featurename!")
if (!defined($feature));
print "Feature $featurename has been created.\n";
}
elsif ($action eq "delete") {
fatal("Feature $featurename does not exist!")
if (!defined($feature));
$feature->Delete() == 0
or fatal("Could not delete feature $featurename!");
}
elsif ($action eq "enable" || $action eq "disable") {
fatal("Feature $featurename does not exist!")
if (!defined($feature));
my $result;
#
# By default, we are setting the global enable/disable flag.
# Override on command line.
#
my $onoff = (@ARGV == 3 ? ($ARGV[2] ? 1 : 0) : 1);
if ($action eq "enable") {
$result = $feature->SetGlobalEnable($onoff);
}
elsif ($action eq "disable") {
$result = $feature->SetGlobalDisable($onoff);
}
fatal("Could not $action feature $featurename!")
if ($result != 0);
}
elsif ($action eq "show") {
fatal("Feature $featurename does not exist!")
if (!defined($feature));
my @users = ();
my @groups = ();
my @experiments = ();
$feature->ListEnabled(\@users, \@groups, \@experiments);
ShowFeature($feature);
if (@users) {
print "Users: ";
foreach my $user (@users) {
print $user->uid;
print " ";
}
print "\n";
}
if (@groups) {
print "Groups: ";
foreach my $group (@groups) {
print $group->pid() . "/" . $group->gid();
print " ";
}
print "\n";
}
if (@experiments) {
print "Experiments: ";
foreach my $experiment (@experiments) {
print $experiment->pid() . "/" . $experiment->eid();
print " ";
}
print "\n";
}
}
else {
fatal("Unknown action $action!");
}
exit(0);
sub fatal($)
{
my ($mesg) = $_[0];
die("*** $0:\n".
" $mesg\n");
}
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2003-2010 University of Utah and the Flux Group.
# All rights reserved.
#
use strict;
use English;
use Getopt::Std;
#
# Grant and revoke permission to use specific emulab features.
#
sub usage()
{
print STDERR "Usage: grantfeature [-r] ";
print STDERR "[-p <pid> | -u <user> | -e <exp> ] <feature>\n";
print STDERR " -h This message\n";
print STDERR " -r Revoke access instead of grant\n";
exit(-1);
}
my $optlist = "hp:dnru:e:";
my $impotent = 0;
my $debug = 0;
my $revoke = 0;
my $pid;
my $uid;
my $eid;
my $target;
# Protos
sub fatal($);
#
# Please do not run as root. Hard to track what has happened.
#
if ($UID == 0) {
die("*** $0:\n".
" Please do not run this as root!\n");
}
#
# Configure variables
#
my $TB = "@prefix@";
#
# Testbed Support libraries
#
use lib "@prefix@/lib";
use emdb;
use EmulabFeatures;
use libtestbed;
use Experiment;
use Project;
use Group;
use User;
#
# Turn off line buffering on output
#
$| = 1;
#
# Untaint the path
#
$ENV{'PATH'} = "/bin:/sbin:/usr/bin:";
#
# Parse command arguments. Once we return from getopts, all that should be
# left are the required arguments.
#
my %options = ();
if (! getopts($optlist, \%options)) {
usage();
}
if (defined($options{h})) {
usage();
}
if (defined($options{n})) {
$impotent = 1;
}
if (defined($options{r})) {
$revoke = 1;
}
if (defined($options{d})) {
$debug = 1;
}
if (defined($options{p})) {
$pid = $options{p};
}
if (defined($options{u})) {
$uid = $options{u};
}
if (defined($options{e})) {
$eid = $options{e};
}
usage()
if (@ARGV != 1);
usage()
if (! (defined($pid) || defined($uid) || defined($eid)));
my $featurename = $ARGV[0];
#
# Verify user, must be admin.
#
my $this_user = User->ThisUser();
if (! defined($this_user)) {
fatal("You ($UID) do not exist!");
}
if (!$this_user->IsAdmin()) {
fatal("You are not a testbed administrator!");
}
if (defined($eid)) {
$target = Experiment->Lookup($eid);
if (!defined($target)) {
fatal("No such experiment $eid\n");
}
}
elsif (defined($pid)) {
$target = Project->Lookup($pid);
if (!defined($target)) {
$target = Group->Lookup($pid);
}
if (!defined($target)) {
fatal("No such project or group $pid\n");
}
}
elsif (defined($uid)) {
$target = User->Lookup($uid);
if (!defined($target)) {
fatal("No such user $uid\n");
}
}
my $feature = EmulabFeatures->Lookup($featurename);
if (!defined($feature)) {