Commit 1d430992 authored by Leigh B Stoller's avatar Leigh B 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@
......
#!/usr/bin/perl -wT
#
# EMULAB-COPYRIGHT
# Copyright (c) 2009, 2010 University of Utah and the Flux Group.
# All rights reserved.
#
package EmulabFeatures;
use strict;
use Exporter;
use vars qw(@ISA @EXPORT $debug $verbose);
@ISA = "Exporter";
@EXPORT = qw();
# Configure variables
my $TB = "@prefix@";
my $BOSSNODE = "@BOSSNODE@";
my $TBDOCBASE = "@TBDOCBASE@";
my $MAINSITE = @TBMAINSITE@;
$debug = 0;
$verbose = 1;
use emdb;
#
# Lookup a feature by its name.
#
sub Lookup($$)
{
my ($class, $token) = @_;
if (! ($token =~ /^[-\w]+$/)) {
return undef;
}
my $query_result =
DBQueryWarn("select * from emulab_features where feature='$token'");
return undef
if (! ($query_result && $query_result->numrows));
my $self = {};
$self->{"DBROW"} = $query_result->fetchrow_hashref();
bless($self, $class);
return $self;
}
sub feature($) { return $_[0]->{'DBROW'}->{'feature'}; }
sub description($) { return $_[0]->{'DBROW'}->{'description'}; }
# Global disable flag.
sub disabled($) { return $_[0]->{'DBROW'}->{'disabled'}; }
# Global enable flag.
sub enabled($) { return $_[0]->{'DBROW'}->{'enabled'}; }
sub added($) { return $_[0]->{'DBROW'}->{'added'}; }
#
# Make a new feature.
#
sub Create($$$)
{
my ($class, $featurename, $description) = @_;
my $feature = EmulabFeatures->Lookup($featurename);
return $feature
if (defined($feature));
my $safe_description = DBQuoteSpecial($description);
DBQueryWarn("replace into emulab_features set ".
" feature='$featurename', added=now(), ".
" description=$safe_description")
or return undef;
return EmulabFeatures->Lookup($featurename);
}
#
# Delete a feature.
#
sub Delete($)
{
my ($self) = @_;
my $featurename = $self->feature();
DBQueryWarn("delete from user_features where feature='$featurename'")
or return -1;
DBQueryWarn("delete from group_features where feature='$featurename'")
or return -1;
DBQueryWarn("delete from experiment_features where feature='$featurename'")
or return -1;
DBQueryWarn("delete from emulab_features where feature='$featurename'")
or return -1;
return 0;
}
#
# Set/Clear the global enable and disable flags
#
sub SetGlobalEnable($$)
{
my ($self, $value) = @_;
my $featurename = $self->feature();
$value = ($value ? 1 : 0);
DBQueryWarn("update emulab_features set enable='$value' ".
"where feature='$featurename'")
or return -1;
return 0;
}
sub SetGlobalDisable($$)
{
my ($self, $value) = @_;
my $featurename = $self->feature();
$value = ($value ? 1 : 0);
DBQueryWarn("update emulab_features set disable='$value' ".
"where feature='$featurename'")
or return -1;
return 0;
}
#
# Add a feature to a group or user.
#
sub Enable($$)
{
my ($self, $target) = @_;
my $featurename = $self->feature();
if (ref($target) eq "User") {
my $uid_idx = $target->uid_idx();
my $uid = $target->uid();
my $query_result =
DBQueryWarn("select * from user_features ".
"where feature='$featurename' and uid_idx='$uid_idx'");
return -1
if (!$query_result);
return 0
if ($query_result->numrows);
DBQueryWarn("replace into user_features set ".
" feature='$featurename', added=now(), ".
" uid='$uid', uid_idx='$uid_idx'")
or return -1;
return 0;
}
elsif (ref($target) eq "Group" || ref($target) eq "Project") {
if (ref($target) eq "Project") {
$target = $target->GetProjectGroup();
}
my $pid_idx = $target->pid_idx();
my $gid_idx = $target->gid_idx();
my $pid = $target->pid();
my $gid = $target->gid();
my $query_result =
DBQueryWarn("select * from group_features ".
"where feature='$featurename' and gid_idx='$gid_idx'");
return -1
if (!$query_result);
return 0
if ($query_result->numrows);
DBQueryWarn("replace into group_features set ".
" feature='$featurename', added=now(), ".
" gid='$gid', gid_idx='$gid_idx', ".
" pid='$pid', pid_idx='$pid_idx'")
or return -1;
return 0;
}
elsif (ref($target) eq "Experiment") {
my $exptidx = $target->idx();
my $pid = $target->pid();
my $eid = $target->eid();
my $query_result =
DBQueryWarn("select * from experiment_features ".
"where feature='$featurename' and exptidx='$exptidx'");
return -1
if (!$query_result);
return 0
if ($query_result->numrows);
DBQueryWarn("replace into experiment_features set ".
" feature='$featurename', added=now(), ".
" eid='$eid', pid='$pid', exptidx='$exptidx'")
or return -1;
return 0;
}
return -1;
}
#
# Remove a feature from a group or user.
#
sub Disable($$)
{
my ($self, $target) = @_;
my $featurename = $self->feature();
if (ref($target) eq "User") {
my $uid_idx = $target->uid_idx();
my $uid = $target->uid();
DBQueryWarn("delete from user_features ".
"where feature='$featurename' and uid_idx='$uid_idx'")
or return -1;
return 0;
}
elsif (ref($target) eq "Group" || ref($target) eq "Project") {
if (ref($target) eq "Project") {
$target = $target->GetProjectGroup();
}
my $pid_idx = $target->pid_idx();
my $gid_idx = $target->gid_idx();
my $pid = $target->pid();
my $gid = $target->gid();
DBQueryWarn("delete from group_features ".
"where feature='$featurename' and gid_idx='$gid_idx'")
or return -1;
return 0;
}
elsif (ref($target) eq "Experiment") {
my $exptidx = $target->idx();
DBQueryWarn("delete from experiment_features ".
"where feature='$featurename' and exptidx='$exptidx'")
or return -1;
return 0;
}
return -1;
}
#
# Delete all features for a target, as when that target is deleted.
#
sub DeleteAll($$)
{
my ($self, $target) = @_;
my $featurename = $self->feature();
if (ref($target) eq "User") {
my $uid_idx = $target->uid_idx();
DBQueryWarn("delete from user_features where uid_idx='$uid_idx'")
or return -1;
return 0;
}
elsif (ref($target) eq "Group") {
my $gid_idx = $target->gid_idx();
DBQueryWarn("delete from group_features where gid_idx='$gid_idx'")
or return -1;
return 0;
}
elsif (ref($target) eq "Project") {
my $pid_idx = $target->pid_idx();
DBQueryWarn("delete from group_features where pid_idx='$pid_idx'")
or return -1;
return 0;
}
elsif (ref($target) eq "Experiment") {
my $exptidx = $target->idx();
DBQueryWarn("delete from experiment_features where exptidx='$exptidx'")
or return -1;
return 0;
}
return -1;
}
sub FeatureEnabled($$$$)
{
my ($class, $featurename, $user, $group, $experiment) = @_;
print STDERR "Checking for feature $featurename.\n";
#
# See if feature is globally disabled;
#
my $feature = EmulabFeatures->Lookup($featurename);
# A non existent feature is always disabled.
# Do not warn; not all sites will have the same set.
if (!defined($feature)) {
print STDERR
"*** Checking for non-existent Emulab Feature: $featurename\n"
if ($MAINSITE || $debug);
return 0;
}
# Globally disabled.
if ($feature->disabled()) {
print STDERR " Feature is globally disabled\n"
if ($debug);
return 0;
}
# Globally enabled.
if ($feature->enabled()) {
print STDERR " Feature is globally enabled\n"
if ($debug);
return 1;
}
my $enabled = 0;
if (defined($user)) {
my $uid_idx = $user->uid_idx();
my $query_result =
DBQueryWarn("select * from user_features ".
"where feature='$featurename' and uid_idx='$uid_idx'");
return 0
if (!$query_result);
print STDERR " Feature is " .
($query_result->numrows ? "enabled" : "disabled") . " for $user\n"
if ($debug);
$enabled += $query_result->numrows;
}
if (defined($group)) {
my $pid_idx = $group->pid_idx();
my $gid_idx = $group->gid_idx();
my $query_result =
DBQueryWarn("select * from group_features ".
"where feature='$featurename' and ".
" pid_idx='$pid_idx' and gid_idx='$gid_idx'");
return 0
if (!$query_result);
print STDERR " Feature is " .
($query_result->numrows ? "enabled" : "disabled") . " for $group\n"
if ($debug);
$enabled += $query_result->numrows;
}
if (defined($experiment)) {
my $exptidx = $experiment->idx();
my $query_result =
DBQueryWarn("select * from experiment_features ".
"where feature='$featurename' and ".
" exptidx='$exptidx'");
return 0
if (!$query_result);
print STDERR " Feature is " .
($query_result->numrows ? "enabled" : "disabled") .
" for $experiment\n"
if ($debug);
$enabled += $query_result->numrows;
}
print STDERR " Feature is " . ($enabled ? "enabled\n" : "disabled\n")
if ($verbose);
return $enabled;
}
#
# List of all current features.
#
sub List($)
{
my ($class) = @_;
my @features = ();
my $query_result =
DBQueryWarn("select feature from emulab_features order by feature");
return undef
if (!defined($query_result));
while (my ($featurename) = $query_result->fetchrow_array()) {
my $feature = EmulabFeatures->Lookup($featurename);
push(@features, $feature)
if (defined($feature));
}
return @features;
}
#
# List users and groups a feature is enabled for.
#
sub ListEnabled($$$$)
{
my ($self, $pusers, $pgroups, $pexp) = @_;
my $featurename = $self->feature();
my @users = ();
my @groups = ();
my @experiments = ();
require Group;
require User;
require Experiment;
my $query_result =
DBQueryWarn("select uid_idx from user_features ".
"where feature='$featurename' ".
"order by uid");
return -1
if (!defined($query_result));
while (my ($uid_idx) = $query_result->fetchrow_array()) {
my $user = User->Lookup($uid_idx);
push(@users, $user)
if (defined($user));
}
$query_result =
DBQueryWarn("select gid_idx from group_features ".
"where feature='$featurename' ".
"order by pid,gid");
return -1
if (!defined($query_result));
while (my ($gid_idx) = $query_result->fetchrow_array()) {
my $group = Group->Lookup($gid_idx);
push(@groups, $group)
if (defined($group));
}
$query_result =
DBQueryWarn("select exptidx from experiment_features ".
"where feature='$featurename' ".
"order by pid,eid");
return -1
if (!defined($query_result));
while (my ($idx) = $query_result->fetchrow_array()) {
my $experiment = Experiment->Lookup($idx);
push(@experiments, $experiment)
if (defined($experiment));
}
@$pusers = @users;
@$pgroups = @groups;
@$pexp = @experiments;
return 0;
}
1;
......@@ -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