Commit ff9061d4 authored by Leigh B. Stoller's avatar Leigh B. Stoller

Big set of changes intended to solve a couple of problems with long

term archiving of firstclass objects like users, projects, and of
course templates.

* Projects, Users, and Groups are now uniquely identified inside the
  DB by a index value that will not be reused. If necessary, this
  could easily be a globally unique identifier, but without federation
  there is no reason to do that yet.

* Currently, pid, gid, and uid still need to be locally unique until
  all of the changes are in place (which is going to take a fairly
  long time since the entire system operates in terms of those, except
  for the few places that I had to change to get the ball rolling).

* We currently archive deleted users to the deleted_users table (their
  user_stats are kept forever since they are indexed by the new index
  column). Eventually do the same with projects (not sure about
  groups) but since we rarely if ever delete a project, there is no
  rush on this one.

* At the same time, I have started a large reorg of the code, to move
  all of the user, group, project code into modules, both in php and
  perl, turning them into first class "objects" (as far as that goes
  in php and perl). Eventually, the number of query statements
  scattered around the code will be manageable, or so I hope.

* Another related part of this reorg is to make it easier to move the
  new user/project/group code in the perl backend so that it can be
  made available via the xmlrpc interface (without duplication of the
  code).
parent 52411ea0
......@@ -20,7 +20,7 @@ SBIN_SCRIPTS = avail inuse showgraph if2port backup webcontrol node_status \
dumperrorlog
LIBEXEC_SCRIPTS = webnodelog webnfree webnewwanode webidlemail xmlconvert
LIB_SCRIPTS = libdb.pm Node.pm libdb.py libadminctrl.pm Experiment.pm \
NodeType.pm Interface.pm
NodeType.pm Interface.pm User.pm Group.pm Project.pm
# Stuff installed on plastic.
USERSBINS = genelists.proxy dumperrorlog.proxy
......
......@@ -17,7 +17,6 @@ use vars qw(@ISA @EXPORT);
use lib '@prefix@/lib';
use libdb;
use libtestbed;
use libtblog;
use English;
use Data::Dumper;
use File::Basename;
......@@ -27,3 +26,429 @@ use overload ('""' => 'Stringify');
my $TB = "@prefix@";
my $BOSSNODE = "@BOSSNODE@";
my $CONTROL = "@USERNODE@";
# Cache of instances to avoid regenerating them.
my %groups = ();
my $debug = 0;
# Little helper and debug function.
sub mysystem($)
{
my ($command) = @_;
print STDERR "Running '$command'\n"
if ($debug);
return system($command);
}
#
# Lookup by idx.
#
sub Lookup($$)
{
my ($class, $gid_idx) = @_;
# Look in cache first
return $groups{"$gid_idx"}
if (exists($groups{"$gid_idx"}));
my $query_result =
DBQueryWarn("select * from groups where gid_idx=$gid_idx");
return undef
if (!$query_result || !$query_result->numrows);
my $self = {};
$self->{'GROUP'} = $query_result->fetchrow_hashref();
bless($self, $class);
# Add to cache.
$groups{"$gid_idx"} = $self;
return $self;
}
# accessors
sub field($$) { return ((! ref($_[0])) ? -1 : $_[0]->{'GROUP'}->{$_[1]}); }
sub pid($) { return field($_[0], "pid"); }
sub gid($) { return field($_[0], "gid"); }
sub pid_idx($) { return field($_[0], "pid_idx"); }
sub gid_idx($) { return field($_[0], "gid_idx"); }
sub leader($) { return field($_[0], "leader"); }
sub created($) { return field($_[0], "created"); }
sub description($) { return field($_[0], "description"); }
sub unix_gid($) { return field($_[0], "unix_gid"); }
sub unix_name($) { return field($_[0], "unix_name"); }
sub expt_count($) { return field($_[0], "expt_count"); }
sub expt_last($) { return field($_[0], "expt_last"); }
sub wikiname($) { return field($_[0], "wikiname"); }
sub mailman_password($) { return field($_[0], "mailman_password"); }
#
# Lookup given pid/gid. For backwards compat.
#
sub LookupByPidGid($$$)
{
my ($class, $pid, $gid) = @_;
my $query_result =
DBQueryWarn("select gid_idx from groups ".
"where pid='$pid' and gid='$gid'");
return undef
if (! $query_result || !$query_result->numrows);
my ($gid_idx) = $query_result->fetchrow_array();
return Group->Lookup($gid_idx);
}
#
# Refresh a class instance by reloading from the DB.
#
sub Refresh($)
{
my ($self) = @_;
return -1
if (! ref($self));
my $gid_idx = $self->gid_idx();
my $query_result =
DBQueryWarn("select * from groups where gid_idx=$gid_idx");
return -1
if (!$query_result || !$query_result->numrows);
$self->{'GROUP'} = $query_result->fetchrow_hashref();
return 0;
}
#
# Stringify for output.
#
sub Stringify($)
{
my ($self) = @_;
my $pid = $self->pid();
my $gid = $self->gid();
my $gid_idx = $self->gid_idx();
my $pid_idx = $self->pid_idx();
return "[Group: $pid/$gid, IDX: $pid_idx/$gid_idx]";
}
#
# Perform some updates ...
#
sub Update($$)
{
my ($self, $argref) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $gid_idx = $self->gid_idx();
my $query = "update groups set ".
join(",", map("$_='" . $argref->{$_} . "'", keys(%{$argref})));
$query .= " where gid_idx='$gid_idx'";
return -1
if (! DBQueryWarn($query));
return Refresh($self);
}
#
# Add a user to the group
#
sub AddMemberShip($$;$)
{
my ($self, $user, $trust) = @_;
# Must be a real reference.
return -1
if (! (ref($self) && ref($user)));
return Group::MemberShip->NewMemberShip($self, $user, $trust);
}
#
# Remove a user from a group
#
sub DeleteMemberShip($$)
{
my ($self, $user) = @_;
# Must be a real reference.
return -1
if (! (ref($self) && ref($user)));
return Group::MemberShip->DeleteMemberShip($self, $user);
}
#
# Lookup user membership in this group
#
sub LookupUser($$)
{
my ($self, $user) = @_;
return Group::MemberShip->LookupUser($self, $user);
}
############################################################################
package Group::MemberShip;
use libdb;
use libtestbed;
use English;
use overload ('""' => 'Stringify');
# Constants for membership.
my $TRUSTSTRING_NONE = "none";
my $TRUSTSTRING_USER = "user";
my $TRUSTSTRING_LOCALROOT = "local_root";
my $TRUSTSTRING_GROUPROOT = "group_root";
my $TRUSTSTRING_PROJROOT = "project_root";
my @alltrustvals = ($TRUSTSTRING_NONE, $TRUSTSTRING_USER,
$TRUSTSTRING_LOCALROOT, $TRUSTSTRING_GROUPROOT,
$TRUSTSTRING_PROJROOT);
# Cache of instances to avoid regenerating them.
my %membership = ();
#
# Lookup user membership in a group. Group and User are references. Hmm ...
#
sub LookupUser($$$)
{
my ($class, $group, $user) = @_;
# Must be a real reference.
return -1
if (! (ref($group) && ref($user)));
my $pid_idx = $group->pid_idx();
my $gid_idx = $group->gid_idx();
my $uid_idx = $user->uid_idx();
# Look in cache first
return $membership{"$uid_idx:$gid_idx"}
if (exists($membership{"$uid_idx:$gid_idx"}));
my $query_result =
DBQueryWarn("select * from group_membership ".
"where uid_idx=$uid_idx and gid_idx=$gid_idx");
return undef
if (!$query_result || !$query_result->numrows);
my $self = {};
$self->{'MEMBERSHIP'} = $query_result->fetchrow_hashref();
$self->{'GROUP'} = $group;
$self->{'USER'} = $user;
bless($self, $class);
# Add to cache.
$membership{"$uid_idx:$gid_idx"} = $self;
return $self;
}
# accessors
sub field($$) { return ((! ref($_[0])) ? -1 : $_[0]->{'MEMBERSHIP'}->{$_[1]});}
sub uid($) { return field($_[0], "uid"); }
sub pid($) { return field($_[0], "pid"); }
sub gid($) { return field($_[0], "gid"); }
sub uid_idx($) { return field($_[0], "uid_idx"); }
sub pid_idx($) { return field($_[0], "pid_idx"); }
sub gid_idx($) { return field($_[0], "gid_idx"); }
sub trust($) { return field($_[0], "trust"); }
sub date_applied($) { return field($_[0], "date_applied"); }
sub date_approved($) { return field($_[0], "date_approved"); }
sub group($) { return $_[0]->{'group'}; }
sub user($ ) { return $_[0]->{'user'}; }
#
# Refresh a class instance by reloading from the DB.
#
sub Refresh($)
{
my ($self) = @_;
return -1
if (! ref($self));
my $uid_idx = $self->uid_idx();
my $gid_idx = $self->gid_idx();
my $query_result =
DBQueryWarn("select * from group_membership ".
"where uid_idx=$uid_idx and gid_idx=$gid_idx");
return -1
if (!$query_result || !$query_result->numrows);
$self->{'MEMBERSHIP'} = $query_result->fetchrow_hashref();
return 0;
}
#
# Stringify for output.
#
sub Stringify($)
{
my ($self) = @_;
my $uid = $self->uid();
my $pid = $self->pid();
my $gid = $self->gid();
my $uid_idx = $self->uid_idx();
my $pid_idx = $self->pid_idx();
my $gid_idx = $self->gid_idx();
my $trust = $self->trust();
return "[MemberShip: $uid/$trust/$pid/$gid]";
}
#
# Perform some updates ...
#
sub Update($$)
{
my ($self, $argref) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $uid_idx = $self->uid_idx();
my $gid_idx = $self->gid_idx();
my $query = "update group_membership set ".
join(",", map("$_='" . $argref->{$_} . "'", keys(%{$argref})));
$query .= " where gid_idx='$gid_idx' and uid_idx='$uid_idx'";
return -1
if (! DBQueryWarn($query));
return Refresh($self);
}
#
# Create new membership in a group. This is a "class" method.
#
sub NewMemberShip($$$;$)
{
my ($class, $group, $user, $trust) = @_;
my $clause = "";
# Must be a real reference.
return -1
if (! (ref($group) && ref($user)));
my $uid = $user->uid();
my $pid = $group->pid();
my $gid = $group->gid();
my $uid_idx = $user->uid_idx();
my $pid_idx = $group->pid_idx();
my $gid_idx = $group->gid_idx();
$trust = $TRUSTSTRING_NONE
if (!defined($trust));
# Sanity check.
if (! grep {$_ eq $trust} @alltrustvals) {
print STDERR "*** NewMemberShip: Not a valid trust: $trust\n";
return -1;
}
# If current trust is none, then requesting membership.
$clause = ", date_approved=now() "
if ($trust ne $TRUSTSTRING_NONE);
DBQueryWarn("insert into group_membership set ".
" uid='$uid', uid_idx=$uid_idx, ".
" pid='$pid', pid_idx=$pid_idx, ".
" gid='$gid', gid_idx=$gid_idx, ".
" trust='$trust', ".
" date_applied=now() $clause ")
or return -1;
return 0;
}
#
# Delete membership from a group. This is a "class" method.
#
sub DeleteMemberShip($$$)
{
my ($class, $group, $user) = @_;
my $clause = "";
# Must be a real reference.
return -1
if (! (ref($group) && ref($user)));
my $uid = $user->uid();
my $pid = $group->pid();
my $gid = $group->gid();
my $uid_idx = $user->uid_idx();
my $pid_idx = $group->pid_idx();
my $gid_idx = $group->gid_idx();
# Remove from cache.
delete($membership{"$uid_idx:$gid_idx"})
if (exists($membership{"$uid_idx:$gid_idx"}));
DBQueryWarn("delete from group_membership ".
"where gid_idx='$gid_idx' and uid_idx='$uid_idx'")
or return -1;
return 0;
}
#
# Modify a membership trust value.
#
sub ModifyTrust($$)
{
my ($self, $trust) = @_;
my $clause = "";
# Must be a real reference.
return -1
if (! ref($self));
# Sanity check.
if (! grep {$_ eq $trust} @alltrustvals) {
print STDERR "*** ModifyTrust: Not a valid trust: $trust\n";
return -1;
}
my $uid_idx = $self->uid_idx();
my $gid_idx = $self->gid_idx();
# If current trust is none, then also update date_approved.
$clause = ", date_approved=now() "
if ($self->trust() eq $TRUSTSTRING_NONE);
DBQueryWarn("update group_membership set trust='$trust' $clause ".
"where gid_idx='$gid_idx' and uid_idx='$uid_idx'")
or return -1;
return Refresh($self);
}
# _Always_ make sure that this 1 is at the end of the file...
1;
......@@ -17,7 +17,7 @@ use vars qw(@ISA @EXPORT);
use lib '@prefix@/lib';
use libdb;
use libtestbed;
use libtblog;
use Group;
use English;
use Data::Dumper;
use File::Basename;
......@@ -27,3 +27,197 @@ use overload ('""' => 'Stringify');
my $TB = "@prefix@";
my $BOSSNODE = "@BOSSNODE@";
my $CONTROL = "@USERNODE@";
# Cache of instances to avoid regenerating them.
my %projects = ();
my $debug = 0;
# Little helper and debug function.
sub mysystem($)
{
my ($command) = @_;
print STDERR "Running '$command'\n"
if ($debug);
return system($command);
}
#
# Lookup by idx.
#
sub Lookup($$)
{
my ($class, $pid_idx) = @_;
# Look in cache first
return $projects{"$pid_idx"}
if (exists($projects{"$pid_idx"}));
my $query_result =
DBQueryWarn("select * from projects where pid_idx=$pid_idx");
return undef
if (!$query_result || !$query_result->numrows);
my $self = {};
$self->{'PROJECT'} = $query_result->fetchrow_hashref();
bless($self, $class);
# Add to cache.
$projects{"$pid_idx"} = $self;
return $self;
}
# accessors
sub field($$) { return ((! ref($_[0])) ? -1 : $_[0]->{'PROJECT'}->{$_[1]}); }
sub pid($) { return field($_[0], "pid"); }
sub gid($) { return field($_[0], "gid"); }
sub pid_idx($) { return field($_[0], "pid_idx"); }
sub gid_idx($) { return field($_[0], "gid_idx"); }
sub leader($) { return field($_[0], "leader"); }
sub created($) { return field($_[0], "created"); }
sub description($) { return field($_[0], "description"); }
sub unix_gid($) { return field($_[0], "unix_gid"); }
sub unix_name($) { return field($_[0], "unix_name"); }
sub expt_count($) { return field($_[0], "expt_count"); }
sub expt_last($) { return field($_[0], "expt_last"); }
sub wikiname($) { return field($_[0], "wikiname"); }
sub mailman_password($) { return field($_[0], "mailman_password"); }
#
# Lookup given pid For backwards compat.
#
sub LookupByPid($$)
{
my ($class, $pid) = @_;
my $query_result =
DBQueryWarn("select pid_idx from projects where pid='$pid'");
return undef
if (! $query_result || !$query_result->numrows);
my ($pid_idx) = $query_result->fetchrow_array();
return Project->Lookup($pid_idx);
}
#
# Refresh a class instance by reloading from the DB.
#
sub Refresh($)
{
my ($self) = @_;
return -1
if (! ref($self));
my $pid_idx = $self->pid_idx();
my $query_result =
DBQueryWarn("select * from projects where pid_idx=$pid_idx");
return -1
if (!$query_result || !$query_result->numrows);
$self->{'PROJECT'} = $query_result->fetchrow_hashref();
return 0;
}
#
# Stringify for output.
#
sub Stringify($)
{
my ($self) = @_;
my $pid = $self->pid();
my $pid_idx = $self->pid_idx();
return "[Project: $pid, IDX: $pid_idx]";
}
#
# Perform some updates ...
#
sub Update($$)
{
my ($self, $argref) = @_;
# Must be a real reference.
return -1
if (! ref($self));
my $pid_idx = $self->pid_idx();
my $query = "update projects set ".
join(",", map("$_='" . $argref->{$_} . "'", keys(%{$argref})));
$query .= " where pid_idx='$pid_idx'";
return -1
if (! DBQueryWarn($query));
return Refresh($self);
}
#
# Delete a user from the project (and all subgroups of course). No checks
# are made; that should be done higher up. Optionally return list of groups
# from which the user was deleted.
#
sub DeleteUser($$;$)
{
my ($self, $user, $plist) = @_;
my @grouplist = ();
# Must be a real reference.
return -1
if (! (ref($self) && ref($user)));
my $uid_idx = $user->uid_idx();
my $pid_idx = $self->pid_idx();
my $gid_idx = $pid_idx;
# Need a list of all groups for the user in this project.
# Should probably use a "class" method in the Groups module.
my $query_result =
DBQueryWarn("select gid_idx from group_membership ".
"where pid_idx=$pid_idx and uid_idx=$uid_idx");
return -1
if (! $query_result);