Commit 5030b44d authored by Leigh Stoller's avatar Leigh Stoller

Begin the transition away from the ancient Mysql.pm module to the more

current and maintained DBI::mysql module. A couple of things make this
a little more work then you might think.

Mysql exports a slightly different API then DBI, both at the DB *and*
the statement level. The former required some restructuring of
emdbi.pm, partly cause we want external sites to continue using Mysql
for a while longer. So, emdbi suppports both interfaces, via the
configure variable TBUSEDBI.

I also took the opportunity to also scrap the existing fork()
detection code and redo it in an easier to understand manner.
Actually, I had no idea what the previous code was trying to do, so it
was easier to just get rid of it, rather then try to make it work for
the DBI API.

There are also API differences in the "statement" class, but
fortunately this can be hidden by wrapping the statement class with a
wrapper that adds the routines we need to avoid making silly changes
to 1000s of queries. They are all trivial little things since mostly
its a matter of naming (numrows --> rows).

I also changed the library we use on ops (db/libtbdb.pm.in) to use
DBI, but in this case I just switched it over. Seemed like overkill to
worry about supporting both APIs on ops. If it works it works, and so
far it does. 

Lastly, the following modules still use Mysql directly. They all need
to be changed, but none of these are on the critical path to swapin
and swapout, so they can change later.

db/dumperrorlog.proxy.in
db/showgraph.in
db/sitevarscheck.in
bgmon/find-asymmetric
db/pelab_opspush.proxy.in
slothd/sdisrunning.in
utils/export_tables.in
utils/setbuildinfo.in
pelab/bgmon/libpelabdb.pm
pelab/dbmonitor/libtbdb.pm
parent 73a9212c
......@@ -1543,6 +1543,7 @@ CLIENT_VARDIR="/var/emulab"
CLIENT_MANDIR="/usr/local/man"
TBSECURECOOKIES=1
TBMAINSITE=0
TBUSEDBI=0
FANCYBANNER=0
VIRTNODE_NETWORK=172.16.0.0
VIRTNODE_NETMASK=255.240.0.0
......@@ -2599,6 +2600,7 @@ fi
# overridable for other sites
if test "$TBMAINSITE" = 1; then
FANCYBANNER=1
TBUSEDBI=0
fi
#
......@@ -3061,6 +3063,7 @@ s%@TBCOOKIESUFFIX@%$TBCOOKIESUFFIX%g
s%@TBAUTHDOMAIN@%$TBAUTHDOMAIN%g
s%@TBAUTHTIMEOUT@%$TBAUTHTIMEOUT%g
s%@TBMAINSITE@%$TBMAINSITE%g
s%@TBUSEDBI@%$TBUSEDBI%g
s%@FANCYBANNER@%$FANCYBANNER%g
s%@SFSSUPPORT@%$SFSSUPPORT%g
s%@ELABINELAB@%$ELABINELAB%g
......
......@@ -221,6 +221,7 @@ AC_SUBST(NOSTACKMIB)
AC_SUBST(EXP_VIS_SUPPORT)
AC_SUBST(NOSHAREDFS)
AC_SUBST(SELFLOADER_DATA)
AC_SUBST(TBUSEDBI)
#
# Offer both versions of the email addresses that have the @ escaped
......@@ -262,6 +263,7 @@ CLIENT_VARDIR="/var/emulab"
CLIENT_MANDIR="/usr/local/man"
TBSECURECOOKIES=1
TBMAINSITE=0
TBUSEDBI=0
FANCYBANNER=0
VIRTNODE_NETWORK=172.16.0.0
VIRTNODE_NETMASK=255.240.0.0
......@@ -829,6 +831,7 @@ fi
# overridable for other sites
if test "$TBMAINSITE" = 1; then
FANCYBANNER=1
TBUSEDBI=1
fi
#
......
#
# EMULAB-COPYRIGHT
# Copyright (c) 2000-2010 University of Utah and the Flux Group.
# Copyright (c) 2000-2011 University of Utah and the Flux Group.
# All rights reserved.
#
SRCDIR = @srcdir@
......
......@@ -7,10 +7,10 @@
package emdbi;
use strict;
use File::Basename;
use Mysql;
use English;
use Carp;
use Exporter;
use Data::Dumper;
use vars qw(@ISA @EXPORT);
@ISA = "Exporter";
......@@ -18,9 +18,7 @@ use vars qw(@ISA @EXPORT);
my $TB = "@prefix@";
my $SCRIPTNAME = "Unknown";
my $TBOPS = "@TBOPSEMAIL@";
# Remember the DBname for subsequent reconnects.
my @DBNAMES = ();
my $USEDBI = @TBUSEDBI@;
# Untainted scriptname for below.
if ($PROGRAM_NAME =~ /^([-\w\.\/]+)$/) {
......@@ -30,59 +28,163 @@ else {
$SCRIPTNAME = "Tainted";
}
#############################################################################
#
# We are going to wrap the DB handle in a wrapper object so that we
# can attach the current process ID to it. This is needed so that the
# child process after a fork() 1) set's InactiveDestroy to avoid
# sending a disconnect message since it will also close the parent's
# database handle 2) reconnects since two separate processes should not
# share the same handle. (1) is handled via overridding the database
# handle DESTROY method, (2) is handled in the DBQueryN function.
#
package emdbi_wrapper::Mysql;
use vars '@ISA';
@ISA = ('Mysql');
sub Wrap($$$)
{
#
# Create a special class for keeping track of the process the
# database handle was created. This is needed so that the child
# process after a fork() 1) set's InactiveDestroy to avoid sending
# a disconnect message since it will also close the parent's
# database handle 2) reconnects since two separate processes
# shouldn't share the same handle. (1) is handled via overridding
# the database handle DESTROY method, (2) is handled in the
# DBQueryN function.
#
package TestbedDBHandle;
use vars '@ISA';
@ISA = ('Mysql');
@TestbedDBHandle::Statement::ISA = ('Mysql::Statement');
my %DB_PID; # hash based on db handle
sub obj_hash( $ ) {
# Return a hex string of the location of the object in memory.
# This is slightly better than just converting it to a scalar
# as two objects as the scalar also included the class name
# the object is "blessed" into which might change over time
sprintf("0x%x", $_[0]);
}
sub MakeA ( $ ) {
my ($obj) = @_;
return unless defined $obj;
bless ($obj);
$DB_PID{obj_hash($obj)} = $$;
}
sub db_pid () {
my ($self) = @_;
return $DB_PID{obj_hash($self)};
my ($class, $dbname, $dbuser) = @_;
require Mysql;
$Mysql::QUIET = 1;
my $dbh = Mysql->connect("localhost", $dbname, $dbuser, "none");
return undef
if (!defined($dbh));
$dbh->{'dbh'}->{'PrintError'} = 0;
my $self = {};
bless($self, $class);
$self->{'PID'} = $$;
$self->{'DBH'} = $dbh;
$self->{'DBNAME'} = $dbname;
return $self;
}
sub pid($) { return $_[0]->{'PID'}; };
sub dbh($) { return $_[0]->{'DBH'}; };
sub dbname($) { return $_[0]->{'DBNAME'}; };
sub query($$) { return $_[0]->dbh->query($_[1]); };
sub DESTROY
{
my ($self) = @_;
# XXX Seems like a problem if parent gets here first.
if ($self->pid() != $$) {
$self->dbh()->setInactiveDestroy(1);
}
sub DESTROY {
my ($self) = @_;
if ($self->db_pid() != $$) {
$self->setInactiveDestroy(1);
}
delete $DB_PID{obj_hash($self)};
$self->SUPER::DESTROY() if $self->can("SUPER::DESTROY");
$self->dbh()->SUPER::DESTROY()
if $self->dbh()->can("SUPER::DESTROY");
}
#############################################################################
#
# We are making the transition to DBI so we can stop using the ancient
# and unmaintained Mysql module.
#
package emdbi_wrapper::DBI;
use vars '@ISA';
@ISA = ('DBI::db');
sub Wrap($$$)
{
my ($class, $dbname, $dbuser) = @_;
require DBI;
my $dbh = DBI->connect("DBI:mysql:database=$dbname;host=localhost",
$dbuser, "none",
{'PrintError' => 0});
return undef
if (!defined($dbh));
my $self = {};
bless($self, $class);
$self->{'PID'} = $$;
$self->{'DBH'} = $dbh;
$self->{'DBNAME'} = $dbname;
return $self;
}
sub pid($) { return $_[0]->{'PID'}; };
sub dbh($) { return $_[0]->{'DBH'}; };
sub dbname($) { return $_[0]->{'DBNAME'}; };
#
# Wrap query for proper DBI syntax.
#
sub query($$)
{
my ($self, $query) = @_;
my $dbh = $self->dbh();;
my $sth = $dbh->prepare($query);
return undef
if (!$sth);
my $query_result = $sth->execute();
return undef
if (!$query_result);
# See below; we add a couple of extra routines.
bless($sth, "emdbi_wrapper::DBI::st");
return $sth;
}
sub DESTROY
{
my ($self) = @_;
# XXX Seems like a problem if parent gets here first.
if ($self->pid() != $$) {
$self->dbh()->{'InactiveDestroy'} = 1;
}
$self->dbh()->SUPER::DESTROY()
if $self->dbh()->can("SUPER::DESTROY");
}
#############################################################################
# Trivial wrapper for the DBI statement class to avoid a zillion silly
# changes to the rest of the code. These were defined in the Mysql
# wrapper we used to use. Pretty simple stuff, no big deal.
#
package emdbi_wrapper::DBI::st;
use vars '@ISA';
@ISA = ('DBI::st');
sub dataseek($$) { return $_[0]->func($_[1], 'dataseek'); };
sub numrows($) { return $_[0]->rows(); };
sub num_rows($) { return $_[0]->rows(); };
sub affectedrows($) { return $_[0]->rows(); };
sub insertid($) { return $_[0]->{'mysql_insertid'}; };
sub fetchrow($)
{
my ($self) = @_;
my @row = $self->fetchrow_array();
return (@row ? (wantarray ? @row : $row[0]) : ());
}
sub fetchhash($)
{
my ($self) = @_;
my $ref = $self->fetchrow_hashref();
return ($ref ? %$ref : ());
}
#############################################################################
# Back to the main package.
#
package emdbi;
#
# Set up for querying the database. Note that fork causes a reconnect
# to the DB in the child.
#
my @DB;
my @DB = ();
use vars qw($DBQUERY_MAXTRIES $DBCONN_MAXTRIES $DBErrorString
use vars qw($DBQUERY_MAXTRIES $DBCONN_MAXTRIES $DBErrorString $DBCONN_USEDBI
$DBCONN_EXITONERR $DBQUERY_RECONNECT $DBQUERY_DEBUG);
$DBQUERY_MAXTRIES = 1;
$DBQUERY_RECONNECT = 1;
$DBCONN_USEDBI = $USEDBI;
$DBCONN_MAXTRIES = 5;
$DBCONN_EXITONERR = 1;
$DBQUERY_DEBUG = 0;
......@@ -106,16 +208,17 @@ sub TBDBConnect($$)
# Do nothing if this DB handle is already connected to DB.
#
if (defined($DB[$dbnum])) {
my $dbhw = $DB[$dbnum];
return 0
if ($DBNAMES[$dbnum] eq $dbname);
if ($dbhw->dbname() eq $dbname);
print STDERR "DBnum $dbnum already connected to another DB: ".
$DBNAMES[$dbnum] . "!\n";
$dbhw->dbname() . "!\n";
return -1
if (! $DBCONN_EXITONERR);
exit(-1);
}
$DBNAMES[$dbnum] = $dbname;
#
# Construct a 'username' from the name of this script and the user who
......@@ -129,12 +232,18 @@ sub TBDBConnect($$)
while ($maxtries) {
if ($DBQUERY_DEBUG) {
print STDERR "DBConnect:$dbnum $dbname\n";
print STDERR "DBConnect:$dbnum $dbname $$\n";
}
$DB[$dbnum] = Mysql->connect("localhost", $dbname, $dbuser, "none");
TestbedDBHandle::MakeA($DB[$dbnum]);
if (defined($DB[$dbnum])) {
my $dbhw;
if ($DBCONN_USEDBI) {
$dbhw = emdbi_wrapper::DBI->Wrap($dbname, $dbuser);
}
else {
$dbhw = emdbi_wrapper::Mysql->Wrap($dbname, $dbuser);
}
if (defined($dbhw)) {
$DB[$dbnum] = $dbhw;
last;
}
$maxtries--;
......@@ -150,8 +259,6 @@ sub TBDBConnect($$)
if (! $DBCONN_EXITONERR);
exit(-1);
}
$DB[$dbnum]->{'dbh'}->{'PrintError'} = 0;
$Mysql::QUIET = 1;
return 0;
}
......@@ -173,10 +280,12 @@ sub TBDBReconnect($)
for (my $i = 0; $i < @DB; $i++) {
next
if (!defined($DB[$i]));
my $dbname = $DB[$i]->dbname();
undef($DB[$i]);
return -1
if (TBDBConnect($i, $DBNAMES[$i]) != 0);
if (TBDBConnect($i, $dbname) != 0);
}
if ($retry) {
......@@ -235,7 +344,7 @@ sub DBQueryN($$)
}
# Reconnect to mysqld in child of fork.
if ($DB[$dbnum]->db_pid() != $PID) {
if ($DB[$dbnum]->pid() != $PID) {
if (TBDBReconnect(1) != 0) {
$DBErrorString =
" Query: $query\n".
......@@ -250,9 +359,10 @@ sub DBQueryN($$)
while ($maxtries) {
# Get this each time through the loop since we try reconnect below.
my $db = $DB[$dbnum];
my $dbw = $DB[$dbnum];
my $db = $dbw->dbh();
$result = $db->query($query);
$result = $dbw->query($query);
if (! defined($result)) {
my $err = $db->err;
......@@ -412,14 +522,14 @@ sub DBQuoteSpecial($)
{
my ($string) = @_;
return $DB[0]->quote($string);
return $DB[0]->dbh()->quote($string);
}
sub DBQuoteSpecialN($$)
{
my ($dbnum, $string) = @_;
return $DB[$dbnum]->quote($string);
return $DB[$dbnum]->dbh()->quote($string);
}
#
......@@ -427,11 +537,11 @@ sub DBQuoteSpecialN($$)
#
sub DBErrN($)
{
return $DB[$_[0]]->err;
return $DB[$_[0]]->dbh()->err;
}
sub DBErr()
{
return $DB[0]->err;
return $DB[0]->dbh()->err;
}
#
......
#!/usr/bin/perl -w
#
# EMULAB-COPYRIGHT
# Copyright (c) 2005, 2006, 2010 University of Utah and the Flux Group.
# Copyright (c) 2005-2011 University of Utah and the Flux Group.
# All rights reserved.
#
......@@ -21,7 +21,7 @@ use vars qw(@ISA @EXPORT);
# Must come after package declaration!
use English;
use File::Basename;
require Mysql;
require DBI;
use vars qw($DBQUERY_MAXTRIES $DBCONN_MAXTRIES @EXPORT_OK);
# Configure variables
......@@ -54,6 +54,38 @@ my $tbdbuser;
my $tbdbhost = "localhost";
my $tbdbpasswd = "none";
#############################################################################
# Trivial wrapper for the DBI statement class to avoid a zillion silly
# changes to the rest of the code. These were defined in the Mysql
# wrapper we used to use. Pretty simple stuff, no big deal.
#
package libtbdb_wrapper::DBI::st;
use vars '@ISA';
@ISA = ('DBI::st');
sub dataseek($$) { return $_[0]->func($_[1], 'dataseek'); };
sub numrows($) { return $_[0]->rows(); };
sub num_rows($) { return $_[0]->rows(); };
sub affectedrows($) { return $_[0]->rows(); };
sub insertid($) { return $_[0]->{'mysql_insertid'}; };
sub fetchrow($)
{
my ($self) = @_;
my @row = $self->fetchrow_array();
return (@row ? (wantarray ? @row : $row[0]) : ());
}
sub fetchhash($)
{
my ($self) = @_;
my $ref = $self->fetchrow_hashref();
return ($ref ? %$ref : ());
}
#############################################################################
# Back to the main package.
#
package libtbdb;
#
# Database handle passed by reference as 5th argument ($_[4])
#
......@@ -81,7 +113,9 @@ sub TBDBConnect($;$$$$)
if (defined($dbhost));
while ($maxtries) {
$DB = Mysql->connect($tbdbhost, $tbdbname, $tbdbuser, $tbdbpasswd);
$DB = DBI->connect("DBI:mysql:database=$tbdbname;host=$tbdbhost",
$tbdbuser, $tbdbpasswd,
{'PrintError' => 0});
if (defined($DB)) {
last;
}
......@@ -93,8 +127,6 @@ sub TBDBConnect($;$$$$)
# Ensure consistent error value.
return -1;
}
$DB->{'dbh'}->{'PrintError'} = 0;
$Mysql::QUIET = 1;
if (@_ == 5) {
$_[4]->{'tbdbname'} = $tbdbname;
$_[4]->{'tbdbuser'} = $tbdbuser;
......@@ -114,20 +146,6 @@ sub TBDBDisconnect(;$)
}
}
sub TBdbfork(;$)
{
select(undef, undef, undef, 0.3);
if (defined($_[0])) {
undef($_[0]->{'DB'});
TBDBReConnect($_[0]->{'tbdbname'}, $_[0]->{'tbdbuser'},
$_[0]->{'tbdbpasswd'}, $_[0]->{'tbdbhost'},
$_[0]->{'DB'});
} else {
undef($DB);
TBDBReConnect($tbdbname, $tbdbuser, $tbdbpasswd);
}
}
#
# Record last DB error string.
#
......@@ -164,15 +182,30 @@ sub DBQuery($;$)
}
while ($maxtries) {
$result = $dbhandle->query($query);
if (! defined($result)) {
my $sth = $dbhandle->prepare($query);
if (!$sth) {
my $err = $dbhandle->err;
$DBErrorString =
" Query: $query\n".
" Error: " . $dbhandle->errstr() . " ($err)";
last;
}
my $query_result = $sth->execute();
if (defined($query_result)) {
$sth->{'CompatMode'} = 1;
# See above; we add a couple of extra routines.
bless($sth, "libtbdb_wrapper::DBI::st");
$result = $sth;
}
else {
my $err = $dbhandle->err;
$DBErrorString =
" Query: $query\n".
" Error: " . $dbhandle->errstr . " ($err)";
}
if (defined($result) ||
if (defined($query_result) ||
($dbhandle->err != 2006) && ($dbhandle->err != 1053) &&
($dbhandle->err != 2013) &&
($dbhandle->err != 1046)) {
......@@ -311,7 +344,6 @@ sub DBBinaryQuery {
" Error: " . $dbh->errstr() . " ( " . $dbh->err() . ")";
return undef;
}
$sth->{'PrintError'} = !$Mysql::QUIET;
my $query_result = $sth->execute(@data);
if (!$query_result) {
......@@ -321,6 +353,8 @@ sub DBBinaryQuery {
return undef;
}
$sth->{'CompatMode'} = 1;
# See above; we add a couple of extra routines.
bless($sth, "libtbdb_wrapper::DBI::st");
$sth;
}
......
......@@ -55,6 +55,7 @@ OUTERBOSS_NODENAME=changeme
TBCOOKIESUFFIX=changeme
# XXX hack to work around perl bug
SELFLOADER_DATA=changeme
TBUSEDBI=1
#
# SSL Certificate stuff. Used to customize config files in ssl directory.
# Note that OrganizationalUnit is set in the cnf file.
......
......@@ -374,14 +374,20 @@ sub LookupByExperiment($$)
sub LookupByCreator($$)
{
my ($class, $creator) = @_;
my $creator_uuid = $creator->uuid();
my $query_result =
DBQueryWarn("select idx from geni_slices ".
"where creator_uuid='$creator_uuid'");
return undef unless defined($query_result);
return map( GeniSlice->Lookup( $_ ), $query_result->fetchcol( 0 ) );
my @result = ();
while (my ($idx) = $query_result->fetchrow_array()) {
my $slice = GeniSlice->Lookup($idx);
push(@result, $slice)
if (defined($slice));
}
return @result;
}
#
......
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