Commit b4231fbf authored by Leigh Stoller's avatar Leigh Stoller

Add support for moving deleted users to a deleted users table. This

would be no big deal, except that we want to retain user_stats for
deleted users, and rather then a deleted_user_stats table, I want to
retain stats for deleted users in the user_stats table, since that
is a more natural place for them.

The main problem is that we use the login (uid) as the cross table
reference slot all over the DB, which is fundamentally incorrect, if
we want to be able reuse uids and still know what historical data
refers to.

So, I have taken a few baby steps towards weaning us off the uid, and
towards permanently unique key for users, using the unix_uid integer
for now, but probably something slightly different later.

The user_stats is now indexed on this new key (called uid_idx in the
users_stats table) instead of the plain uid.

The unix_uid slot in the users table is no longer an auto_increment
field, but instead uses the emulab_indicies table for the next
available index.
parent d29f8858
......@@ -28,7 +28,7 @@ use vars qw(@ISA @EXPORT);
PROJMEMBERTRUST_ROOT PROJMEMBERTRUST_GROUPROOT
PROJMEMBERTRUST_PROJROOT
PROJROOT GROUPROOT USERROOT TBOPSPID
PROJROOT GROUPROOT USERROOT TBOPSPID EXPTLOGNAME
PLABMOND_PID PLABMOND_EID PLABHOLDING_PID PLABHOLDING_EID
TBTrustConvert TBMinTrust TBGrpTrust TBProjTrust MapNumericUID
......@@ -243,6 +243,7 @@ my $SCRIPTNAME = "Unknown";
my $PROJROOT = "/proj";
my $GROUPROOT = "/groups";
my $USERROOT = "/users";
my $EXPTLOGNAME = "activity.log";
if ($EVENTSYS) {
require event;
......@@ -350,6 +351,7 @@ sub PROJROOT() { $PROJROOT; }
sub GROUPROOT() { $GROUPROOT; }
sub USERROOT() { $USERROOT; }
sub TBOPSPID() { $TBOPSPID; }
sub EXPTLOGNAME() { $EXPTLOGNAME; }
sub NODEBOOTSTATUS_OKAY() { "okay" ; }
sub NODEBOOTSTATUS_FAILED() { "failed"; }
......@@ -5249,9 +5251,9 @@ sub TBcheck_dbslot($$$;$)
#
# WARNING: this will unlock all locked tables, be careful where you call it!
#
sub TBGetUniqueIndex($)
sub TBGetUniqueIndex($;$)
{
my ($name) = @_;
my ($name, $initval) = @_;
#
# Lock the table to avoid conflicts
......@@ -5262,8 +5264,9 @@ sub TBGetUniqueIndex($)
DBQueryFatal("select idx from emulab_indicies ".
"where name='$name'");
my ($curidx) = $query_result->fetchrow_array();
$curidx = 1
if (!defined($curidx));
if (!defined($curidx)) {
$curidx = (defined($initval) ? $initval : 1);
}
my $nextidx = $curidx + 1;
DBQueryFatal("replace into emulab_indicies (name, idx) ".
......
......@@ -202,6 +202,33 @@ CREATE TABLE delays (
-- Table structure for table `delta_inst`
--
CREATE TABLE deleted_users (
uid varchar(8) NOT NULL default '',
uid_idx smallint(5) unsigned NOT NULL default '0',
usr_created datetime default NULL,
usr_deleted datetime default NULL,
usr_name tinytext,
usr_title tinytext,
usr_affil tinytext,
usr_email tinytext,
usr_URL tinytext,
usr_addr tinytext,
usr_addr2 tinytext,
usr_city tinytext,
usr_state tinytext,
usr_zip tinytext,
usr_country tinytext,
usr_phone tinytext,
webonly tinyint(1) default '0',
wikionly tinyint(1) default '0',
notes text,
PRIMARY KEY (uid_idx)
) TYPE=MyISAM;
--
-- Table structure for table `delta_inst`
--
CREATE TABLE delta_inst (
node_id varchar(32) NOT NULL default '',
partition tinyint(4) NOT NULL default '0',
......@@ -2022,6 +2049,7 @@ CREATE TABLE user_sslcerts (
CREATE TABLE user_stats (
uid varchar(8) NOT NULL default '',
uid_idx smallint(5) unsigned NOT NULL default '0',
weblogin_count int(11) unsigned default '0',
weblogin_last datetime default NULL,
exptstart_count int(11) unsigned default '0',
......@@ -2040,7 +2068,7 @@ CREATE TABLE user_stats (
allexpt_vnode_duration int(11) unsigned default '0',
allexpt_pnodes int(11) unsigned default '0',
allexpt_pnode_duration int(11) unsigned default '0',
PRIMARY KEY (uid)
PRIMARY KEY (uid_idx)
) TYPE=MyISAM;
--
......@@ -2067,7 +2095,7 @@ CREATE TABLE users (
usr_shell tinytext,
usr_pswd tinytext NOT NULL,
usr_w_pswd tinytext,
unix_uid smallint(5) unsigned NOT NULL auto_increment,
unix_uid smallint(5) unsigned NOT NULL default '0',
status enum('newuser','unapproved','unverified','active','frozen','other') NOT NULL default 'newuser',
admin tinyint(4) default '0',
dbedit tinyint(4) default '0',
......
......@@ -2975,3 +2975,49 @@ last_net_act,last_cpu_act,last_ext_act);
PRIMARY KEY (fn(255)),
KEY idx (idx)
) TYPE=MyISAM;
4.24: Add support for maintaining records of deleted users.
Change unix_uid in the users table to use an index from
emulab_indicies table (instead of an auto increment).
replace into emulab_indicies (name, idx) \
select 'next_uid',max(unix_uid)+1 from users;
alter table users change unix_uid unix_uid smallint(5) \
unsigned NOT NULL default '0';
alter table user_stats add uid_idx smallint(5) unsigned NOT NULL \
default '0' after uid;
Run this script:
sql/mod-userstats.pl
alter table user_stats add unique uid_idx (uid_idx);
alter table user_stats drop primary key;
alter table user_stats add primary key (uid_idx);
alter table user_stats drop index uid_idx;
CREATE TABLE deleted_users (
uid varchar(8) NOT NULL default '',
uid_idx smallint(5) unsigned NOT NULL default '0',
usr_created datetime default NULL,
usr_deleted datetime default NULL,
usr_name tinytext,
usr_title tinytext,
usr_affil tinytext,
usr_email tinytext,
usr_URL tinytext,
usr_addr tinytext,
usr_addr2 tinytext,
usr_city tinytext,
usr_state tinytext,
usr_zip tinytext,
usr_country tinytext,
usr_phone tinytext,
webonly tinyint(1) default '0',
wikionly tinyint(1) default '0',
notes text,
PRIMARY KEY (uid_idx)
) TYPE=MyISAM;
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2005 University of Utah and the Flux Group.
# All rights reserved.
#
use English;
use lib "/usr/testbed/lib";
use libdb;
use libtestbed;
#
# Untaint the path
#
$ENV{'PATH'} = '/bin:/usr/bin:/usr/sbin';
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV'};
$query_result =
DBQueryFatal("select uid,unix_uid from users");
while (($uid,$unix_uid) = $query_result->fetchrow_array()) {
DBQueryFatal("update user_stats set uid_idx='$unix_uid' ".
"where uid='$uid'");
}
......@@ -172,16 +172,17 @@ else {
}
}
# Get current status.
$query_result =
DBQueryFatal("select status from users where uid='$user'");
my ($status) = $query_result->fetchrow_array();
#
# If nuke mode is also specified, then the account is being nuked from
# web page because of a project join denial. Check to make sure user
# is not an active user (must be newuser or unapproved).
#
if ($nuke) {
$query_result =
DBQueryFatal("select status from users where uid='$user'");
my ($status) = $query_result->fetchrow_array();
if ($status ne USERSTATUS_NEWUSER &&
$status ne USERSTATUS_UNAPPROVED) {
die("*** $0:\n".
......@@ -265,7 +266,6 @@ DBQueryFatal("delete from nodeuidlastlogin where uid='$user'");
DBQueryFatal("delete from uidnodelastlogin where uid='$user'");
DBQueryFatal("delete from unixgroup_membership where uid='$user'");
DBQueryFatal("delete from userslastlogin where uid='$user'");
DBQueryFatal("delete from user_stats where uid='$user'");
#
# Remove user from both local and control node. No need to do this in
......@@ -305,6 +305,20 @@ if (-d "$HOMEDIR/$user") {
}
}
# Note that we no longer delete from the user_stats table. We maintain
# deleted user records.
DBQueryFatal("replace into deleted_users ".
" (uid, usr_deleted, uid_idx, usr_created, usr_name, ".
" usr_title, usr_affil, usr_email, usr_URL, ".
" usr_addr, usr_addr2, usr_city, usr_state, usr_zip, ".
" usr_country, usr_phone, webonly, wikionly, notes) ".
" select '$user', now(), unix_uid, usr_created, usr_name, ".
" usr_title, usr_affil, usr_email, usr_URL, ".
" usr_addr, usr_addr2, usr_city, usr_state, usr_zip, ".
" usr_country, usr_phone, webonly, wikionly, notes ".
" from users where uid='$user'")
if ($status eq USERSTATUS_ACTIVE || $status eq USERSTATUS_FROZEN);
#
# Then the users table. This is the very last step, since if there were
# any failures above, we can still rerun the script without it barfing.
......
......@@ -121,6 +121,9 @@ if (system "/usr/sbin/pw useradd $protouser -u $uid -g $agid -G $Ggid -h - " .
die "Unable to add user to the password file!\n";
}
# Initialize the index value;
DBQueryFatal("replace into emulab_indicies set name='next_uid',idx=$uid");
print "Creating user in database...\n";
DBQueryFatal("insert into users set uid='$protouser', usr_created=now(), " .
"usr_name='$protouser_name', usr_pswd='$encpass', unix_uid=$uid, ".
......
......@@ -2061,6 +2061,37 @@ function TBUniqueEmail($uid, $email)
return 1;
}
#
# Return a unique index from emulab_indicies for the indicated name.
# Updates the index to be, well, unique.
# Eats flaming death on error.
#
function TBGetUniqueIndex($name)
{
#
# Lock the table to avoid conflicts
#
DBQueryFatal("lock tables emulab_indicies write");
$query_result =
DBQueryFatal("select idx from emulab_indicies ".
"where name='$name'");
$row = mysql_fetch_array($query_result);
$curidx = $row["idx"];
if (!isset($curidx)) {
$curidx = 1;
}
$nextidx = $curidx + 1;
DBQueryFatal("replace into emulab_indicies (name, idx) ".
"values ('$name', $nextidx)");
DBQueryFatal("unlock tables");
return $curidx;
}
#
# DB Interface.
#
......
......@@ -824,6 +824,9 @@ if (! $returning) {
# Initial mailman_password.
$mailman_password = substr(GENHASH(), 0, 10);
# Unique Unix UID.
$unix_uid = TBGetUniqueIndex('next_uid');
DBQueryFatal("INSERT INTO users ".
"(uid,usr_created,usr_expires,usr_name,usr_email,usr_addr,".
" usr_addr2,usr_city,usr_state,usr_zip,usr_country, ".
......@@ -835,11 +838,12 @@ if (! $returning) {
"'$usr_addr', '$usr_addr2', '$usr_city', '$usr_state', '$usr_zip', ".
"'$usr_country', ".
"'$usr_URL', '$usr_phone', 'tcsh', '$usr_title', '$usr_affil', ".
"'$encoding', NULL, 'newuser', ".
"'$encoding', $unix_uid, 'newuser', ".
"date_add(now(), interval 1 year), now(), $forwikionly, '$wikiname', ".
"'$mailman_password')");
DBQueryFatal("INSERT INTO user_stats (uid) VALUES ('$joining_uid')");
DBQueryFatal("INSERT INTO user_stats (uid, uid_idx) ".
"VALUES ('$joining_uid', $unix_uid)");
$key = TBGenVerificationKey($joining_uid);
......
......@@ -1041,6 +1041,9 @@ if (! $returning) {
# Initial mailman_password.
$mailman_password = substr(GENHASH(), 0, 10);
# Unique Unix UID.
$unix_uid = TBGetUniqueIndex('next_uid');
DBQueryFatal("INSERT INTO users ".
"(uid,usr_created,usr_expires,usr_name,usr_email,usr_addr,".
" usr_addr2,usr_city,usr_state,usr_zip,usr_country, ".
......@@ -1051,11 +1054,12 @@ if (! $returning) {
"'$usr_addr', '$usr_addr2', '$usr_city', '$usr_state', '$usr_zip', ".
"'$usr_country', ".
"'$usr_URL', '$usr_title', '$usr_affil', ".
"'$usr_phone', 'tcsh', '$encoding', NULL, 'newuser', ".
"'$usr_phone', 'tcsh', '$encoding', $unix_uid, 'newuser', ".
"date_add(now(), interval 1 year), now(), '$wikiname', ".
"'$mailman_password')");
DBQueryFatal("INSERT INTO user_stats (uid) VALUES ('$proj_head_uid')");
DBQueryFatal("INSERT INTO user_stats (uid, uid_idx) ".
"VALUES ('$proj_head_uid', $unix_uid)");
if (! $FirstInitState) {
$key = TBGenVerificationKey($proj_head_uid);
......
......@@ -2889,7 +2889,9 @@ function SHOWWIDEAREANODE($node_id, $embedded = 0) {
function SHOWUSERSTATS($uid) {
$query_result =
DBQueryFatal("SELECT * from user_stats where uid='$uid'");
DBQueryFatal("select s.* from users as u ".
"left join user_stats as s on s.uid_idx=u.unix_uid ".
"where u.uid='$uid'");
if (! mysql_num_rows($query_result)) {
return;
......
......@@ -126,7 +126,7 @@ function showsummary ($showby, $sortby) {
$which = "uid";
$table = "user_stats";
$title = "User Summary Stats (Epoch)";
$link = "showuser.php3?target_uid=";
$link = "showuser.php3?target_idx=";
break;
default:
USERERROR("Invalid showby argument: $showby!", 1);
......@@ -159,15 +159,30 @@ function showsummary ($showby, $sortby) {
USERERROR("Invalid sortby argument: $sortby!", 1);
}
$query_result =
DBQueryFatal("select $which, allexpt_pnodes, ".
"allexpt_pnode_duration / (24 * 3600) as pnode_days, ".
"allexpt_duration / (24 * 3600) as expt_days, ".
"exptswapin_count+exptstart_count as expt_swapins, ".
"exptpreload_count+exptstart_count as expt_new ".
"from $table ".
"$wclause ".
"order by $order");
if ($showby == "users") {
$query_result =
DBQueryFatal("select s.uid, allexpt_pnodes, ".
"allexpt_pnode_duration / (24 * 3600) as pnode_days,".
"allexpt_duration / (24 * 3600) as expt_days, ".
"exptswapin_count+exptstart_count as expt_swapins, ".
"exptpreload_count+exptstart_count as expt_new, ".
"u.usr_name ".
"from user_stats as s ".
"left join users as u on u.unix_uid=s.uid_idx ".
"$wclause ".
"order by $order");
}
else {
$query_result =
DBQueryFatal("select $which, allexpt_pnodes, ".
"allexpt_pnode_duration / (24 * 3600) as pnode_days,".
"allexpt_duration / (24 * 3600) as expt_days, ".
"exptswapin_count+exptstart_count as expt_swapins, ".
"exptpreload_count+exptstart_count as expt_new ".
"from $table ".
"$wclause ".
"order by $order");
}
if (mysql_num_rows($query_result) == 0) {
USERERROR("No summary stats of interest!", 1);
......@@ -251,9 +266,22 @@ function showsummary ($showby, $sortby) {
$swapins = $row["expt_swapins"];
$new = $row["expt_new"];
echo "<tr>
<td><A href='$link${heading}'>$heading</A></td>
<td>$pnodes</td>
echo "<tr>";
if ($showby == "users") {
# A current or a deleted user?
$usr_name = $row["usr_name"];
if (isset($usr_name)) {
echo "<td><A href='$link${heading}'>$heading</A></td>";
}
else {
echo "<td>$heading</td>";
}
}
else {
echo "<td><A href='$link${heading}'>$heading</A></td>";
}
echo " <td>$pnodes</td>
<td>$phours</td>
<td>$ehours</td>
<td>$swapins</td>
......
<?php
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2004 University of Utah and the Flux Group.
# Copyright (c) 2000-2005 University of Utah and the Flux Group.
# All rights reserved.
#
include("defs.php3");
......@@ -131,7 +131,7 @@ $query_result =
"FROM users as u ".
"$clause ".
"left join userslastlogin as ull on u.uid=ull.uid ".
"left join user_stats as ll on u.uid=ll.uid ".
"left join user_stats as ll on u.unix_uid=ll.uid_idx ".
"$where ".
"order by $order");
......
......@@ -598,7 +598,7 @@ function DOLOGIN($token, $password, $adminmode = 0) {
$user_result =
DBQueryFatal("select uid,usr_pswd,admin,weblogin_frozen,".
" weblogin_failcount,weblogin_failstamp, ".
" usr_email,usr_name ".
" usr_email,usr_name,unix_uid ".
"from users where ".
(TBvalid_email($token) ?
"usr_email='$token'" :
......@@ -617,6 +617,7 @@ function DOLOGIN($token, $password, $adminmode = 0) {
$failstamp = $row['weblogin_failstamp'];
$usr_email = $row['usr_email'];
$usr_name = $row['usr_name'];
$uid_idx = $row['unix_uid'];
# Check for frozen accounts. We do not update the IP record when
# an account is frozen.
......@@ -680,7 +681,7 @@ function DOLOGIN($token, $password, $adminmode = 0) {
DBQueryFatal("update user_stats set ".
" weblogin_count=weblogin_count+1, ".
" weblogin_last=now() ".
"where uid='$uid'");
"where uid_idx='$uid_idx'");
#
# Issue the cookie requests so that subsequent pages come back
......@@ -893,7 +894,9 @@ function LASTWEBLOGIN($uid) {
global $TBDBNAME;
$query_result =
DBQueryFatal("SELECT weblogin_last from user_stats where uid='$uid'");
DBQueryFatal("select weblogin_last from users as u ".
"left join user_stats as s on s.uid_idx=u.unix_uid ".
"where u.uid='$uid'");
if (mysql_num_rows($query_result)) {
$lastrow = mysql_fetch_array($query_result);
......
<?php
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2003 University of Utah and the Flux Group.
# Copyright (c) 2000-2003, 2005 University of Utah and the Flux Group.
# All rights reserved.
#
include("defs.php3");
......@@ -859,6 +859,9 @@ if (! $returning) {
ADDPUBKEY($usr_uid, "webaddpubkey $addpubkeyargs");
}
# Unique Unix UID.
$unix_uid = TBGetUniqueIndex('next_uid');
DBQueryFatal("INSERT INTO users ".
"(uid,usr_created,usr_expires,usr_name,usr_email,usr_addr,".
" usr_addr2,usr_city,usr_state,usr_zip,usr_country, ".
......@@ -869,10 +872,11 @@ if (! $returning) {
"'$usr_addr', '$usr_addr2', '$usr_city', '$usr_state', '$usr_zip', ".
"'$usr_country', ".
"'$usr_URL', '$usr_title', '$usr_affil', ".
"'$usr_phone', '$encoding', NULL, 'newuser', ".
"'$usr_phone', '$encoding', $unix_uid, 'newuser', ".
"date_add(now(), interval 1 year), now(), 1)");
DBQueryFatal("INSERT INTO user_stats (uid) VALUES ('$usr_uid')");
DBQueryFatal("INSERT INTO user_stats (uid, uid_idx) ".
"VALUES ('$usr_uid', $unix_uid)");
$key = TBGenVerificationKey($usr_uid);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment