Index: branches/pap/Nebulous-Server/Build.PL
===================================================================
--- branches/pap/Nebulous-Server/Build.PL	(revision 23948)
+++ branches/pap/Nebulous-Server/Build.PL	(revision 25027)
@@ -10,4 +10,5 @@
     requires            => {
         'Apache2::Const'        => 0,
+        'Cache::Memcached'      => 0,
         'Class::Accessor::Fast' => 0,
         'Config::YAML'          => '1.42',
Index: branches/pap/Nebulous-Server/Changes
===================================================================
--- branches/pap/Nebulous-Server/Changes	(revision 23948)
+++ branches/pap/Nebulous-Server/Changes	(revision 25027)
@@ -2,4 +2,8 @@
 
 0.17
+    - add basic valid key caching via memcached 
+    - add params checking to Nebulous::Key::parse_neb_key()
+    - restrict creation/modification/remove of xattrs to the user.* namespace
+    - add chmod_object() method
     - retry database transactions when a deadlock is detected
     - add log4perl logging to nebdiskd
@@ -16,4 +20,21 @@
     - change 'log_level' param to 'trace'
     - refactor ->find_objects() functionality
+    - rename Nebulous::Key->soft_volume Nebulous::Key->hard_volume and invert
+      the semantics
+    - add nebdiskd mountpoint test retyring
+    - rework delete_instance() to avoid requiring an index on instance.uri
+    - nebdiskd bug fixes: fix debug mode outpoint going to mail, remove db
+      passwd requirement (not all dbs require a password param)
+    - completely rework how mountedvol is populated, drop mount table
+    - infinitely try to get a db handle if the connection fails
+    - add --mountpoint param to neb-voladd
+    - change Nebulous::Server->find_objects() to return dirs and to sort it's
+      output 
+    - add the ability to delete a storage object when it has instances that are
+      offline
+    - change Nebulous::Server->stat_object() to return both the total number of
+      instance and just those that are available
+    - add Nebulous::Server->prune_object() API
+    - don't replicate to the volume being implied by the key being replicated!
       
 0.16
Index: branches/pap/Nebulous-Server/MANIFEST
===================================================================
--- branches/pap/Nebulous-Server/MANIFEST	(revision 23948)
+++ branches/pap/Nebulous-Server/MANIFEST	(revision 25027)
@@ -33,18 +33,20 @@
 t/01_load.t
 t/02_config.t
-t/02_server_setup.t
-t/03_server_create_object.t
-t/04_server_replicate_object.t
-t/05_server_lock_object.t
-t/06_server_unlock_object.t
-t/07_server_find_instances.t
-t/08_server_delete_instance.t
-t/09_server_stat_object.t
-t/10_server_is_valid_volume_name.t
-t/11_server_is_valid_object_key.t
-t/12_server_find_objects.t
-t/13_server_rename_object.t
-t/14_server_xattr.t
-t/15_mounts.t
-t/16_server_swap_objects.t
+t/03_server_setup.t
+t/04_server_create_object.t
+t/05_server_replicate_object.t
+t/06_server_lock_object.t
+t/07_server_unlock_object.t
+t/08_server_find_instances.t
+t/09_server_delete_instance.t
+t/10_server_stat_object.t
+t/11_server_is_valid_volume_name.t
+t/12_server_is_valid_object_key.t
+t/13_server_find_objects.t
+t/14_server_rename_object.t
+t/15_server_xattr.t
+t/16_mounts.t
+t/17_server_swap_objects.t
+t/18_server_chmod_object.t
+t/19_server_prune_object.t
 t/75_parse_neb_key.t
Index: branches/pap/Nebulous-Server/bin/neb-admin
===================================================================
--- branches/pap/Nebulous-Server/bin/neb-admin	(revision 23948)
+++ branches/pap/Nebulous-Server/bin/neb-admin	(revision 25027)
@@ -91,8 +91,7 @@
 {
 # so_id, ext_id, instances, available_instances, need_recovery, recoverable
-    $dbh->do("CREATE TEMPORARY TABLE mymountedvol LIKE mountedvol");
-    $dbh->do("INSERT INTO mymountedvol SELECT * FROM mountedvol");
-    $dbh->do("CREATE TEMPORARY TABLE myvolume LIKE volume");
-    $dbh->do("INSERT INTO myvolume SELECT * FROM volume");
+# XXX don't remove the temp table code -- testing w/o it
+#    $dbh->do("CREATE TEMPORARY TABLE mymountedvol LIKE mountedvol");
+#    $dbh->do("INSERT INTO mymountedvol SELECT * FROM mountedvol");
 
     if (not defined $so_id_start) { $so_id_start = 0; }
@@ -102,46 +101,46 @@
     # XXX check if so_id_start is beyond MAX(so_id): if so, exit with exit status 10
     { 
-	my $query = $dbh->prepare("SELECT MAX(so_id) from storage_object");
-	$query->execute;
+        my $query = $dbh->prepare("SELECT MAX(so_id) from storage_object");
+        $query->execute;
         my $answer = $query->fetchrow_arrayref;
         my $max_so_id = $$answer[0];
-	$query->finish;
-
-	if ($so_id_start > $max_so_id) { 
-	    print STDERR "at end of so_id range, reset please\n";
-	    exit 10;
-	}
-    }
-	
+        $query->finish;
+
+        if ($so_id_start > $max_so_id) { 
+            print STDERR "at end of so_id range, reset please\n";
+            exit 10;
+        }
+    }
+        
     my $query = $dbh->prepare(
-    "        SELECT
-                storage_object.so_id,
-                ext_id,
-                count(ins_id) as instances,
-                myvolume.name as volume_name,
-                myvolume.host as volume_host,
-                count(mymountedvol.vol_id) as available_instances,
-                count(mymountedvol.vol_id) > 0 as recoverable,
-                storage_object_xattr.value as copies
-            FROM storage_object
-            JOIN instance
-                USING(so_id)
-            JOIN myvolume
-                USING(vol_id)
-            LEFT JOIN storage_object_xattr
-                ON storage_object.so_id = storage_object_xattr.so_id
-            JOIN mymountedvol
-                USING(vol_id)
-            WHERE mymountedvol.available = 1
-            AND storage_object_xattr.name = 'user.copies'
-            AND storage_object.so_id >= $so_id_start
-            AND storage_object.so_id <  $so_id_end
-            GROUP BY so_id
-            HAVING available_instances < instances OR instances < copies
-            LIMIT $limit"
+    "
+        SELECT
+            so.so_id,
+            so.ext_id,
+            count(ins_id) as instances,
+            mv.name as volume_name,
+            mv.host as volume_host,
+            count(mv.vol_id) as available_instances,
+            count(mv.vol_id) > 0 as recoverable,
+            xattr.value as copies
+        FROM storage_object AS so
+        LEFT JOIN storage_object_xattr AS xattr
+            ON so.so_id = xattr.so_id
+            AND xattr.name = 'user.copies'
+        JOIN instance AS i
+            ON so.so_id = i.so_id
+        JOIN mountedvol AS mv
+            USING(vol_id)
+        WHERE
+            mv.available = 1
+            AND so.so_id >= $so_id_start
+            AND so.so_id <  $so_id_end
+        GROUP BY so_id
+        HAVING available_instances < instances OR instances < copies
+        LIMIT $limit"
     );
     $query->execute;
 
-    $dbh->do("DROP TABLE IF EXISTS mymountedvol");
+#    $dbh->do("DROP TABLE IF EXISTS mymountedvol");
 
     my @rows;
@@ -174,8 +173,8 @@
         # if the copies xattr is unset and the object has 2 or more instances, we
         # will assume a target of 2 copies
-	
-	# XXX change this: there should be no default value or we will
-	# have a race condition.  user.copies should never get set by
-	# any client until AFTER a file is no longer in use.
+        
+        # XXX change this: there should be no default value or we will
+        # have a race condition.  user.copies should never get set by
+        # any client until AFTER a file is no longer in use.
         my $copies = $obj->{copies} || 2;
 
@@ -207,5 +206,5 @@
     # use a different exit status if we hit the limit (likely more files pending)
     if ($Npending == $limit) {
-	exit 1;
+        exit 1;
     } 
     exit 0;
@@ -225,4 +224,5 @@
 
     $dbh->do("DROP TABLE IF EXISTS mymountedvol");
+    $dbh->do("DROP TABLE IF EXISTS myvolume");
 
     my @rows;
Index: branches/pap/Nebulous-Server/bin/neb-voladd
===================================================================
--- branches/pap/Nebulous-Server/bin/neb-voladd	(revision 23948)
+++ branches/pap/Nebulous-Server/bin/neb-voladd	(revision 25027)
@@ -19,5 +19,5 @@
 use Pod::Usage qw( pod2usage );
 
-my ($db, $dbhost, $dbuser, $dbpass, $vname, $vhost, $uri);
+my ($db, $dbhost, $dbuser, $dbpass, $mountpoint, $vname, $vhost, $uri);
 
 $db     = $ENV{'NEB_DB'} unless $db;
@@ -29,14 +29,15 @@
     'db|d=s'            => \$db,
     'host=s'            => \$dbhost,
+    'mountpoint|m=s'    => \$mountpoint,
+    'pass|p=s'          => \$dbpass,
+    'uri|u=s'           => \$uri,
     'user|u=s'          => \$dbuser,
-    'pass|p=s'          => \$dbpass,
+    'vhost=s'           => \$vhost,
     'vname|n=s'         => \$vname,
-    'vhost=s'           => \$vhost,
-    'uri|u=s'           => \$uri,
 ) || pod2usage( 2 );
 
 pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
-pod2usage( -msg => "Required options: --db --user --pass --vname --vhost --uri", -exitval => 2 )
-    unless $db && $dbuser && $dbpass && $vname && $vhost && $uri;
+pod2usage( -msg => "Required options: --db --user --pass --mountpoint --vname --vhost --uri", -exitval => 2 )
+    unless $db && $dbuser && $dbpass && $mountpoint && $vname && $vhost && $uri;
 
 my $dbh = DBI->connect(
@@ -59,4 +60,7 @@
     die "path: $path dirived from URI: $uri does not exist";
 }
+unless (-d $mountpoint) {
+    die "path: $mountpoint does not exist";
+}
 
 print " OK\n";
@@ -65,5 +69,5 @@
 
 my $query = $dbh->prepare( $sql->new_volume );
-$query->execute( $vname, $vhost, $path );
+$query->execute( $vname, $vhost, $path, $mountpoint);
 
 print " OK\n";
@@ -128,4 +132,7 @@
 Optional.  Defaults to C<localhost>.
 
+=item * --mountpoint <path>
+
+Path to the mountpoint of the filesystem on which C<--uri> resides.
 
 =back
Index: branches/pap/Nebulous-Server/bin/neb-voladm
===================================================================
--- branches/pap/Nebulous-Server/bin/neb-voladm	(revision 23948)
+++ branches/pap/Nebulous-Server/bin/neb-voladm	(revision 25027)
@@ -91,5 +91,4 @@
         my $query = $dbh->prepare($q);
         $query->execute(@bind);
-        $dbh->do("call getmountedvol");
         $dbh->commit;
     };
Index: branches/pap/Nebulous-Server/bin/nebdiskd
===================================================================
--- branches/pap/Nebulous-Server/bin/nebdiskd	(revision 23948)
+++ branches/pap/Nebulous-Server/bin/nebdiskd	(revision 25027)
@@ -1,7 +1,5 @@
 #!/usr/bin/env perl
 
-# Copyright (C) 2007  Joshua Hoblitt
-# 
-# $Id: nebdiskd,v 1.14 2008-10-16 22:51:34 jhoblitt Exp $
+# Copyright (C) 2007-2009  Joshua Hoblitt
 
 use strict;
@@ -35,8 +33,9 @@
     $user,
     $group,
+    $retry,
 );
 
 GetOptions(
-    'dbhost|h=s'    => \$dbhost,
+    'dbhost|H=s'    => \$dbhost,
     'dbpass|p=s'    => \$dbpass,
     'dbuser|u=s'    => \$dbuser,
@@ -46,6 +45,7 @@
     'pidfile=s'     => \$pidfile,
     'restart|r'     => \$restart,
+    'retry=i'       => \$retry,
     'stop|s'        => \$stop,
-    'user=s'        => \$user,
+    'user|U=s'      => \$user,
     'verbose|v'     => \$verbose,
 ) || pod2usage( 2 );
@@ -61,10 +61,11 @@
 $dbhost     ||= $c->get_dbhost  || $ENV{'NEB_DBHOST'} || 'localhost';
 $dbuser     ||= $c->get_dbuser  || $ENV{'NEB_USER'};
-$dbpass     ||= $c->get_dbpass  || $ENV{'NEB_PASS'};
+$dbpass     ||= $c->get_dbpass  || $ENV{'NEB_PASS'} || undef;
 $pidfile    ||= $c->get_pidfile || "/var/tmp/nebdiskd";
 $user       ||= $c->get_user    || $<; # user
 $group      ||= $c->get_group   || $); # group
-
-my $mounts = $c->get_mounts;
+$retry      ||= $c->get_retry   || 1;
+
+#my $mounts = $c->get_mounts;
 my $poll_interval = $c->get_poll_interval || 5;
 
@@ -80,9 +81,10 @@
 $c->set_pidfile($pidfile);
 $c->set_poll_interval($poll_interval);
+$c->set_retry($retry);
 $c->write;
 
 pod2usage( -msg => "Unknown option: @ARGV", -exitval => 2 ) if @ARGV;
-pod2usage( -msg => "Required options: --db --user --pass", -exitval => 2 )
-    unless $db && $dbuser && $dbpass;
+pod2usage( -msg => "Required options: --db --dbuser", -exitval => 2 )
+    unless $db && $dbuser;
 
 # start up logging
@@ -108,7 +110,12 @@
     log4perl.appender.Mailer.to      = ps-ipp-ops@ifa.hawaii.edu
     log4perl.appender.Mailer.subject = nebdiskd alert
-    log4perl.appender.AppError.Filter= MatchWarn
+    log4perl.appender.Mailer.buffered = 0
+    log4perl.appender.Mailer.Filter= MatchWarn
     log4perl.appender.Mailer.layout = Log::Log4perl::Layout::PatternLayout
     log4perl.appender.Mailer.layout.ConversionPattern = %d{yyyy-MM-dd HH:mm:ss} | %H | %p | %M - %m%n
+
+    log4perl.appender.Limiter              = Log::Log4perl::Appender::Limit
+    log4perl.appender.Limiter.appender     = Mailer
+    log4perl.appender.Limiter.block_period = 300
 ';
 Log::Log4perl::init(\$conf);
@@ -136,4 +143,5 @@
                 poll_interval   => $poll_interval,
                 debug           => $debug,
+                retry           => $retry,
         );
     };
@@ -155,7 +163,8 @@
     my $dbhost          = $p{dbhost} or return;
     my $dbuser          = $p{dbuser} or return;
-    my $dbpass          = $p{dbpass} or return;
+#    my $dbpass          = $p{dbpass} or return;
     my $poll_interval   = $p{poll_interval} || 60;
     my $debug           = $p{debug} || 0;
+    my $retry           = $p{retry} || 1;
 
     # setup the db on every pass incase the database died on us
@@ -168,20 +177,41 @@
 
     eval {
-        my $r_query = $dbh->prepare_cached("REPLACE INTO mount VALUES(?, ?, ?)");
-        my $d_query = $dbh->prepare_cached("DELETE FROM mount WHERE mountpoint = ?");
-
+        my $r_query = $dbh->prepare_cached("REPLACE INTO mountedvol SELECT *, ?, ? FROM volume WHERE mountpoint = ?");
+        my $d_query = $dbh->prepare_cached("DELETE FROM mountedvol WHERE mountpoint = ?");
+
+        # get list of mount points
+        my $mounts = [];
+        {
+            # there may be multiple vol_ids per mountpoint but we only need to
+            # check each mointpont once
+            my $query = $dbh->prepare_cached("SELECT DISTINCT mountpoint FROM volume WHERE available = 1");
+            $query->execute;
+            while (my $row = $query->fetchrow_hashref) {
+                push @$mounts, $row;
+            }
+            $query->finish;
+        }
+        
         # determine valid mountpoints
         foreach my $mnt (@$mounts) {
-            $log->debug("checking $mnt");
+            my $mountpoint = $mnt->{'mountpoint'};
+            $log->debug("checking $mountpoint");
             # this /SHOULD/ fail if the mount point is handled by the
             # automounter and it fails to mount
-            eval {
-                unless (is_mountpoint($mnt)) {
+            my $tries = 0;
+            TEST: eval {
+                $tries++;
+                unless (is_mountpoint($mountpoint)) {
                     $log->warn("$mnt is not a valid mountpoint");
                 }
             };
             if ($@) {
+                # try is_mountpoint() again if $retry > 1
+                if ($tries < $retry) {
+                    $log->warn("retrying test of $mountpoint");
+                    goto TEST; 
+                }
                 $log->warn($@);
-                $d_query->execute($mnt);
+                $d_query->execute($mountpoint);
                 next;
             }
@@ -190,23 +220,24 @@
             # we determine if it's a valid mountpoint incase
             # is_mountpoint() invokes the automounter
-            my $dev_info = df($mnt, 1024);
+            my $dev_info = df($mountpoint, 1024);
             unless (defined $dev_info) {
-                $log->error("can't find device info for $mnt");
+                $log->error("can't find device info for $mountpoint");
                 next;
             }
 
-            $r_query->execute($mnt, @$dev_info{qw( blocks used )});
-            $log->debug("adding $mnt to db");
+            # find vol_id(s) for mountpoint
+            $r_query->execute(@$dev_info{qw( blocks used )}, $mountpoint);
+            $log->debug("adding $mountpoint to db");
 
         }
 
-        $dbh->do("call getmountedvol()");
-
-        $dbh->commit;
-        $log->debug("commited to database");
+#        $dbh->do("call getmountedvol()");
+
+#        $dbh->commit;
+#        $log->debug("commited to database");
     };
     if ($@) {
-        $dbh->rollback;
-        $log->debug("rolledback transaction");
+#        $dbh->rollback;
+#        $log->debug("rolledback transaction");
         $log->logdie($@);
     }
@@ -235,8 +266,9 @@
     my %p = @_;
 
+    # $p{dbpass} may be undef;
+
     return unless defined $p{db}
               and defined $p{dbhost}
-              and defined $p{dbuser}
-              and defined $p{dbpass};
+              and defined $p{dbuser};
 
     my $sql = Nebulous::Server::SQL->new;
@@ -249,5 +281,5 @@
             RaiseError => 1,
             PrintError => 1,
-            AutoCommit => 0,
+            AutoCommit => 1, # don't want this to be trasnactional
         },
     );
@@ -255,8 +287,8 @@
     eval {
         $dbh->do( $sql->set_transaction_model );
-        $dbh->commit;
+#        $dbh->commit;
     };
     if ($@) { 
-        $dbh->rollback;
+#        $dbh->rollback;
         $log->logdie($@);
     }
@@ -344,5 +376,8 @@
 =head1 SYNOPSIS
 
-    nebdiskd [--db <db name>] [--user <db username>] [--pass <db password>] [--debug] [--pidfile <filename>] [--stop] [--restart] [--verbose]
+    nebdiskd [--db|-D <db name>] [--dbhost|-H <db hostname>
+    [--dbuser|-u <db username>] [--dbpass|-p <db password>] [--debug|-d]
+    [--user|-U <username>] [--group|-g <groupname>] [--pidfile <filename>]
+    [--retry <n>] [--stop|-s] [--restart|-r] [--verbose|-v]
 
 =head1 DESCRIPTION
@@ -355,5 +390,5 @@
 =over 4
 
-=item * --db|-d <db name>
+=item * --db|-D <db name>
 
 Name of database (C<namespace>) to write too.
@@ -362,5 +397,5 @@
 variable is set.
 
-=item * --user|-u <db username>
+=item * --dbuser|-u <username>
 
 Username to authenticate with.
@@ -369,12 +404,19 @@
 variable is set.
 
-=item * --pass|-p <db password>
+=item * --dbpass|-p <password>
 
 Password to authenticate with.
+
+Optional if defined in the F<.nebdiskdrc> file or the appropriate environment
+variable is set or if the database does not require a password.
+
+=item * --dbhost|-H <hostname>
+
+Database host to connect to.
 
 Optional if defined in the F<.nebdiskdrc> file or the appropriate environment
 variable is set.
 
-=item * --debug
+=item * --debug|-d
 
 This flag prevents the program from "daemonizing" so output can be sent to
@@ -397,5 +439,10 @@
 instance.
 
-=item * --verbose|-r
+=item * --retry
+
+The number of times to test a mountpoint for "mountedness" before giving up on
+it.
+
+=item * --verbose|-v
 
 Turns on informational/debugging messages to be sent to the
@@ -403,4 +450,12 @@
 with C<--debug>.
 
+=item * --user|-U
+
+user account to run daemon as.
+
+=item * --group|-g
+
+group to run daemon as.
+
 =back
 
@@ -415,13 +470,13 @@
 =item * C<NEB_DB>
 
-Equivalent to --db|-d
+Equivalent to --db|-D
 
 =item * C<NEB_USER>
 
-Equivalent to --user|-u
+Equivalent to --dbuser|-u
 
 =item * C<NEB_PASS>
 
-Equivalent to --pass|-p 
+Equivalent to --dbpass|-p 
 
 =back
@@ -439,10 +494,7 @@
     dbpass: '@neb@'
     dbuser: nebulous
-    mounts:
-      - /mnt
-      - /tmp
-      - /usr
     pidfile: /var/tmp/nebdiskd
     poll_interval: 5
+    retry: 3
 
 The values C<db>, C<dbpass>, C<dbuser>, and C<pidfile> have the same semantics
@@ -451,10 +503,4 @@
 =over 4
 
-=item * C<mounts>
-
-A list of "paths" to C<stat(2)> before mounted volumes are polled.  The propose
-of this option is to attempt to keep "automounted" volumes mounted while this
-program is running.
-
 This value may be omitted or left blank.
 
@@ -465,4 +511,8 @@
 This value may be omitted or left blank.  The default value is C<60>s.
 
+=item * C<retry>
+
+Same as C<--retry>.
+
 =back
 
@@ -481,5 +531,5 @@
 =head1 COPYRIGHT
 
-Copyright (C) 2007  Joshua Hoblitt.  All rights reserved.
+Copyright (C) 2007-2009  Joshua Hoblitt.  All rights reserved.
 
 This program is free software; you can redistribute it and/or modify it under
Index: branches/pap/Nebulous-Server/lib/Nebulous/Key.pm
===================================================================
--- branches/pap/Nebulous-Server/lib/Nebulous/Key.pm	(revision 23948)
+++ branches/pap/Nebulous-Server/lib/Nebulous/Key.pm	(revision 25027)
@@ -8,5 +8,5 @@
 use warnings FATAL => qw( all );
 
-our $VERSION = '0.02';
+our $VERSION = '0.03';
 
 use base qw( Exporter Class::Accessor::Fast );
@@ -14,4 +14,5 @@
 use File::Spec;
 use URI::file;
+use Carp qw( croak );
 use URI;
 use overload '""' => \&_stringify_key;
@@ -22,14 +23,16 @@
 );
 
-__PACKAGE__->mk_ro_accessors(qw( path volume soft_volume ));
+__PACKAGE__->mk_ro_accessors(qw( path volume hard_volume ));
 
 sub parse_neb_key
 {
     my ($key, $volume) = @_;
-    return unless defined $key;
+
+    croak "key param is not optional" unless defined $key;
+    croak "too many params" if scalar @_ > 2;
 
     # white space is not allowed
     if ($key =~ qr/\s+/) {
-        die "keys and URIs may not contain whitespace";
+        croak "keys and URIs may not contain whitespace";
     }
 
@@ -39,9 +42,9 @@
     
     my $volume_name;
-    my $soft_volume;
+    my $hard_volume;
     # if this is a valid uri 
     if (defined $scheme) {
         # if so, does it use the neb scheme?
-        die "URI does not use the 'neb' scheme"
+        croak "URI does not use the 'neb' scheme"
             unless $scheme eq 'neb'; 
 
@@ -58,5 +61,5 @@
         # path component to it
         unless (length $path) {
-            die "neb URI scheme requires a path component"; 
+            croak "neb URI scheme requires a path component"; 
         }
         
@@ -68,10 +71,10 @@
         if (defined $volume_info->{volume} and $volume_info->{volume} ne 'any') {
             $volume_name = $volume_info->{volume};
-            $soft_volume = $volume_info->{soft_volume};
+            $hard_volume = $volume_info->{hard_volume};
         }
 
         # require a leading slash if there is no volume name
         if ((not defined $volume_name) and (not $path =~ m|^/|)) {
-            die "neb URI scheme requires a leading slash, eg. neb:/";
+            croak "neb URI scheme requires a leading slash, eg. neb:/";
         }
     } else {
@@ -81,5 +84,5 @@
         if (defined $volume_info->{volume} and $volume_info->{volume} ne 'any') {
             $volume_name = $volume_info->{volume};
-            $soft_volume = $volume_info->{soft_volume};
+            $hard_volume = $volume_info->{hard_volume};
         }
     }
@@ -96,5 +99,5 @@
     return __PACKAGE__->new({
         volume      => $volume_name,
-        soft_volume => $soft_volume,
+        hard_volume => $hard_volume,
         path        => $path,
     });
@@ -107,11 +110,11 @@
     return unless defined $volume;
 
-    my $soft_volume;
+    my $hard_volume;
     # check to see if there is a tilde and remove it if found
-    unless (defined $volume and $volume =~ s/^~//) {
-        $soft_volume = 1;
+    if (defined $volume and $volume =~ s/^~//) {
+        $hard_volume = 1;
     }
 
-    return({ volume => $volume, soft_volume => $soft_volume });
+    return({ volume => $volume, hard_volume => $hard_volume });
 }
 
@@ -122,8 +125,8 @@
 
     my $path        = $self->path;
-    my $volume      = $self->volume || "";
-    my $soft_volume = $self->soft_volume ? '~' : "";
-
-    return "neb://${soft_volume}${volume}/$path";
+    my $volume      = $self->volume || '';
+    my $hard_volume = $self->hard_volume ? '~' : '';
+
+    return "neb://${hard_volume}${volume}/$path";
 }
 
Index: branches/pap/Nebulous-Server/lib/Nebulous/Server.pm
===================================================================
--- branches/pap/Nebulous-Server/lib/Nebulous/Server.pm	(revision 23948)
+++ branches/pap/Nebulous-Server/lib/Nebulous/Server.pm	(revision 25027)
@@ -13,6 +13,8 @@
 use base qw( Class::Accessor::Fast );
 
+use Cache::Memcached;
 use DBI;
 use Digest::SHA1 qw( sha1_hex );
+use Fcntl ':mode';
 use File::Basename qw( basename dirname fileparse );
 use File::ExtAttr qw( setfattr );
@@ -27,8 +29,13 @@
 use URI::file;
 
-__PACKAGE__->mk_accessors(qw( log sql config ));
-
-use constant SUBPATH_DEPTH  => 2;
-use constant NFS_RETRIES    => 100;
+__PACKAGE__->mk_accessors(qw( log sql config cache ));
+
+use constant SUBPATH_DEPTH  	=> 2;
+use constant NFS_RETRIES    	=> 100;
+use constant NFS_RETRY_WAIT 	=> 1;
+use constant TRANS_RETRY_WAIT 	=> 1;
+
+# transaction restart/retry regex
+my $trans_regex = qr/Deadlock Found|Lock wait timeout exceeded|try restarting transaction|Can't connect to MySQL server/i;
 
 sub new
@@ -45,14 +52,15 @@
 sub new_from_config
 {
-    my ($class, $config) = @_;
-
-    # log4perl is not avaliable until we call init()
+    my $class = shift;
+
+    my ($config) = @_;
+
+    # log4perl is not available until we call init()
     Nebulous::Server::Log->init($config);
     my $log = Log::Log4perl::get_logger( "Nebulous::Server" );
     $log->level($config->trace);
+    $log->debug( "entered - @_" );
 
     my $sql = Nebulous::Server::SQL->new;
-
-    $log->debug( "entered - @_" );
 
     my $self = bless {}, ref $class || $class;
@@ -60,4 +68,15 @@
     $self->sql($sql);
     $self->config($config);
+    $self->cache(
+        Cache::Memcached->new({
+	    servers => $config->memcached_servers,
+       })
+    );
+#    $self->cache->set("foo", "bar") or die "set failed";
+#    $self->cache->get("foo") or die "get failed";
+    $log->logdie("at least one database must be defined") unless $config->n_db;
+
+    # cause a db session to be started
+    $self->_db_for_index(0);
 
     $log->debug( "leaving" );
@@ -70,10 +89,15 @@
 sub _db_index_for_key
 {
-    my ($self, $key) = @_;
+    my $self = shift;
+
+    my $log     = $self->log;
+    $log->debug( "entered - @_" );
+
+    my ($key) = @_;
 
     my $config  = $self->config;
 
     my $db_index = 0;
-    die "key not defined" unless defined $key;
+    $log->logdie("key not defined") unless defined $key;
 
     # hash the key to select the correct database instance
@@ -85,4 +109,7 @@
     $db_index = unpack("h8", sha1_hex($path)) % $config->n_db;
 
+    $log->debug("index is $db_index");
+    $log->debug("leaving");
+
     return $db_index;
 }
@@ -90,7 +117,11 @@
 sub _db_for_index
 {
-    my ($self, $db_index) = @_;
+    my $self = shift;
 
     my $log     = $self->log;
+    $log->debug( "entered - @_" );
+
+    my ($db_index) = @_;
+
     my $sql     = $self->sql;
     my $config  = $self->config;
@@ -108,5 +139,5 @@
     # lookup database info
     my $db_config = $config->db($db_index);
-    die "can't find database configuration info for database # $db_index"
+    $log->logdie("can't find database configuration info for db # $db_index")
         unless $db_config;
 
@@ -116,29 +147,39 @@
     # processes and the database might have gone away on us.  Apache::DBI will
     # take care of getting a valid dbh back.
-    eval {
-        $dbh = DBI->connect_cached(
-            $db_config->dsn,
-            $db_config->dbuser,
-            $db_config->dbpasswd,
-            {
-                RaiseError => 1,
-                PrintError => 0,
-                AutoCommit => 0,
-            },
-        );
-
-        $dbh->do( $sql->set_transaction_model );
-        $log->debug( "connected to database: ", sub { $dbh->data_sources; } );
-        $dbh->commit;
-        $log->debug("commit");
-    };
-    if ( $@ ) {
-        $dbh->rollback if $dbh;
-        $log->debug("rollback");
-        $log->logdie( "database error: $@" );
+    TRANS: while (1) {
+        eval {
+            $dbh = DBI->connect_cached(
+                $db_config->dsn,
+                $db_config->dbuser,
+                $db_config->dbpasswd,
+                {
+                    RaiseError => 1,
+                    PrintError => 0,
+                    AutoCommit => 0,
+                },
+            );
+
+            $dbh->do( $sql->set_transaction_model );
+            $log->debug( "connected to database: ", sub { $dbh->data_sources; } );
+            $dbh->commit;
+            $log->debug("commit");
+        };
+        if ($@) {
+            $dbh->rollback if $dbh;
+            $log->debug("rollback") if $dbh;
+            if ($@ =~ qr/Can't connect to MySQL server/) {
+                $log->warn("database error, retrying transaction: $@");
+                sleep TRANS_RETRY_WAIT;
+                redo TRANS;
+            }
+            $log->logdie( "database error: $@" );
+        }
+        last;
     }
 
     $self->{dbs}[$db_index] = $dbh;
 
+    $log->debug("leaving");
+
     return $dbh;
 }
@@ -148,4 +189,7 @@
     my $self = shift;
 
+    my $log     = $self->log;
+    $log->debug( "entered - @_" );
+
     my ($key) = validate_pos(@_,
         {
@@ -154,12 +198,12 @@
     );
 
-    my $log     = $self->log;
     my $sql     = $self->sql;
     my $config  = $self->config;
 
-    die "key not defined" unless defined $key;
+    $log->logdie("key not defined") unless defined $key;
     my $db_index = $self->_db_index_for_key($key);
 
     my $dbh = $self->_db_for_index($db_index);
+
     return $dbh;
 }
@@ -168,4 +212,7 @@
 {
     my $self = shift;
+
+    my $log = $self->log;
+    $log->debug( "entered - @_" );
 
     my ($key, $vol_name) = validate_pos(@_,
@@ -186,13 +233,14 @@
     );
 
+    my $sql = $self->sql;
+
     # vol_name overrides the key implied volume
-    $key = parse_neb_key($key, $vol_name);
+    eval {
+        $key = parse_neb_key($key, $vol_name);
+    };
+    $log->logdie("$@") if $@;
     $vol_name = $key->volume;
 
-    my $log = $self->log;
-    my $sql = $self->sql;
     my $db  = $self->db($key);
-
-    $log->debug( "entered - @_" );
 
     # the key's volume can't be validiated on input for this method so we have
@@ -200,14 +248,14 @@
     if (defined $vol_name
         and not $self->_is_valid_volume_name($key, $key->volume)) {
-        if ($key->soft_volume) {
+        unless ($key->hard_volume) {
             $log->warn( "$vol_name is not a known volume name" );
             $vol_name = undef;
         } else {
-            die "$vol_name is not a valid volume name"
+           $log->logdie("$vol_name is not a valid volume name");
         }
     }
         
     my ($vol_id, $vol_host, $vol_path, $vol_xattr)
-        = $self->_get_storage_volume($key, $vol_name, $key->soft_volume);
+        = $self->_get_storage_volume($key, $vol_name, $key->hard_volume);
 
     my $parent_id = $self->_resolve_dir_parent_id(key => $key, create => 1);
@@ -282,6 +330,7 @@
             $db->rollback;
             $log->debug("rollback");
-            if ($@ =~ /Deadlock found/) {
-                $log->warn("database deadlock retrying transaction: $@");
+            if ($@ =~ $trans_regex) {
+                $log->warn("database error, retrying transaction: $@");
+                sleep TRANS_RETRY_WAIT;
                 redo TRANS;
             }
@@ -291,4 +340,8 @@
     }
 
+    # add new key to the cache
+    $self->cache->set($key->path, 1) if defined $self->cache;
+    $log->debug( "key added to cache" );
+
     $log->debug("leaving");
 
@@ -300,4 +353,7 @@
 {
     my $self = shift;
+
+    my $log = $self->log;
+    $log->debug( "entered - @_" );
 
     my %p = validate(@_,
@@ -322,9 +378,6 @@
     my $key = $p{key};
 
-    my $log = $self->log;
     my $sql = $self->sql;
     my $db  = $self->db($key);
-
-    $log->debug( "entered - @_" );
 
     # no path means '/', which has a dir_id & parent_id of 1
@@ -394,5 +447,5 @@
                     $query->finish;
                 }
-                $log->logdie("failed to get LAST_INSERT_ID()")
+                die("failed to get LAST_INSERT_ID()")
                     unless $parent_id;
 
@@ -403,8 +456,16 @@
             $db->rollback;
             $log->debug("rollback");
-            if ($@ =~ /Deadlock found/) {
-                $log->warn("database deadlock retrying transaction: $@");
+            if ($@ =~ $trans_regex) {
+                $log->warn("database error, retrying transaction: $@");
+                $parent_id = 1;
+                sleep TRANS_RETRY_WAIT;
                 redo TRANS;
             }
+            if ($@ =~ qr/Duplicate entry/) {
+                $log->warn("Duplicate database entry, retrying transaction: $@");
+                $parent_id = 1;
+                sleep TRANS_RETRY_WAIT;
+                redo TRANS;
+            }
             $log->logdie("error: $@");
         }
@@ -421,4 +482,7 @@
 {
     my $self = shift;
+
+    my $log = $self->log;
+    $log->debug("entered - @_");
 
     my ($key, $newkey) = validate_pos(@_,
@@ -438,13 +502,17 @@
     );
 
+    my $sql = $self->sql;
+
     # ignore volumes
-    $key    = parse_neb_key($key);
-    $newkey = parse_neb_key($newkey);
-
-    my $log = $self->log;
-    my $sql = $self->sql;
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+    eval {
+        $newkey = parse_neb_key($newkey);
+    };
+    $log->logdie("$@") if $@;
+
     my $db  = $self->db($key);
-
-    $log->debug("entered - @_");
 
     # XXX this may require database migration in the future
@@ -464,6 +532,9 @@
             unless ($rows == 1) {
                 $query->finish;
-                $log->logdie("affected row count is $rows instead of 1");
-            }
+                die("affected row count is $rows instead of 1");
+            }
+
+            $self->cache->delete($key->path) if defined $self->cache;
+            $self->cache->set($newkey->path, 1) if defined $self->cache;
 
             $db->commit;
@@ -473,6 +544,7 @@
             $db->rollback;
             $log->debug("rollback");
-            if ($@ =~ /Deadlock found/) {
-                $log->warn("database deadlock retrying transaction: $@");
+            if ($@ =~ $trans_regex) {
+                $log->warn("database error, retrying transaction: $@");
+                sleep TRANS_RETRY_WAIT;
                 redo TRANS;
             }
@@ -491,4 +563,7 @@
     my $self = shift;
 
+    my $log = $self->log;
+    $log->debug("entered - @_");
+
     my ($key1, $key2) = validate_pos(@_,
         {
@@ -506,20 +581,25 @@
     );
 
+    my $sql = $self->sql;
+
     # ignore volumes
-    $key1 = parse_neb_key($key1);
-    $key2 = parse_neb_key($key2);
-
-    my $log = $self->log;
-    my $sql = $self->sql;
+    eval {
+        $key1 = parse_neb_key($key1);
+    };
+    $log->logdie("$@") if $@;
+    eval {
+        $key2 = parse_neb_key($key2);
+    };
+    $log->logdie("$@") if $@;
 
     my $dbidx1 = $self->_db_index_for_key($key1);
     my $dbidx2 = $self->_db_index_for_key($key2);
-    die "cannot swap keys not stored on the same database" unless ($dbidx1 == $dbidx2);
+    $log->logdie("cannot swap keys not stored on the same database")
+        unless ($dbidx1 == $dbidx2);
 
     my $dbh1 = $self->_db_for_index($dbidx1);
     my $dbh2 = $self->_db_for_index($dbidx2);
-    die "different db handles for the same db?" unless ($dbh1 == $dbh2);
-
-    $log->debug("entered - @_");
+    $log->logdie("different db handles for the same db?")
+        unless ($dbh1 == $dbh2);
 
     # order of operations for the swap with a single db is:
@@ -543,5 +623,5 @@
               unless ($rows == 1) {
                   $query->finish;
-                  $log->logdie("affected row count is $rows instead of 1");
+                  die("affected row count is $rows instead of 1");
               }
           }
@@ -556,5 +636,5 @@
               unless ($rows == 1) {
                   $query->finish;
-                  $log->logdie("affected row count is $rows instead of 1");
+                  die("affected row count is $rows instead of 1");
               }
           }
@@ -569,5 +649,5 @@
               unless ($rows == 1) {
                   $query->finish;
-                  $log->logdie("affected row count is $rows instead of 1");
+                  die("affected row count is $rows instead of 1");
               }
           }
@@ -579,6 +659,7 @@
             $db->rollback;
             $log->debug("rollback");
-            if ($@ =~ /Deadlock found/) {
-                $log->warn("database deadlock retrying transaction: $@");
+            if ($@ =~ $trans_regex) {
+                $log->warn("database error, retrying transaction: $@");
+                sleep TRANS_RETRY_WAIT;
                 redo TRANS;
             }
@@ -612,5 +693,8 @@
     my $self = shift;
 
-    my ($key, $vol_name) = validate_pos(@_,
+    my $log = $self->log;
+    $log->debug("entered - @_");
+
+    my ($key, $dest_vol_name) = validate_pos(@_,
         {
             type        => SCALAR,
@@ -633,4 +717,6 @@
     );
 
+    my $sql = $self->sql;
+
     # if a volume name is explicity specified then we should make the
     # replication onto that volume (even if there is alread an instance on that
@@ -640,29 +726,35 @@
     # instance on it.  If all avilable volume already have an instance on them
     # then we should throw an error 
-
-    # vol_name overrides the key implied volume
-    $key = parse_neb_key($key, $vol_name);
-    $vol_name = $key->volume;
-
-    my $log = $self->log;
-    my $sql = $self->sql;
+    # volume names implied as part of the key are *IGNORED* as the source and
+    # *SHOULD NOT* be used as the destination either
+
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+
     my $db  = $self->db($key);
 
-    $log->debug("entered - @_");
-
-    if (defined $vol_name
+    # puke if the source volume is bogus, we may want to actually use this as
+    # the instance to be copied later
+    if (defined $key->volume
         and not $self->_is_valid_volume_name($key, $key->volume)) {
-        if ($key->soft_volume) {
-            $log->warn( "$vol_name is not a known volume name" );
-            $vol_name = undef;
+        unless ($key->hard_volume) {
+            $log->warn($key->volume . " not a known volume name");
         } else {
-            die "$vol_name is not a valid volume name"
-        }
+           $log->logdie("$key is not a valid volume name");
+        }
+    }
+    # puke if the source volume is bogus, we may want to actually use this as
+    # the instance to be copied later
+    if (defined $dest_vol_name
+        and not $self->_is_valid_volume_name($key, $dest_vol_name)) {
+           $log->logdie($key->volume . " is not a valid volume name");
     }
         
     my ($vol_id, $vol_host, $vol_path, $vol_xattr);
-    if (defined $vol_name) {
+    if (defined $dest_vol_name) {
         ($vol_id, $vol_host, $vol_path, $vol_xattr)
-            = $self->_get_storage_volume($key, $vol_name);
+            = $self->_get_storage_volume($key, $dest_vol_name);
     } else {
         ($vol_id, $vol_host, $vol_path, $vol_xattr)
@@ -681,5 +773,5 @@
                 unless ( $rows > 0 ) {
                     $query->finish;
-                    $log->logdie( "storage object does not exist" );
+                    die( "storage object does not exist" );
                 }
 
@@ -725,11 +817,12 @@
             $db->rollback;
             # handle soft volumes
-            if (defined $vol_name and defined $key->soft_volume) {
+            if (defined $dest_vol_name and not defined $key->hard_volume) {
                 $log->debug("retrying with 'any' volume");
                 return $self->replicate_object($key->path, 'any');
             }
             $log->debug("rollback");
-            if ($@ =~ /Deadlock found/) {
-                $log->warn("database deadlock retrying transaction: $@");
+            if ($@ =~ $trans_regex) {
+                $log->warn("database error, retrying transaction: $@");
+                sleep TRANS_RETRY_WAIT;
                 redo TRANS;
             }
@@ -739,4 +832,17 @@
     }
 
+    # check to see if the user.mode xattr exists
+    eval {
+        my $mode = $self->getxattr_object("$key", 'user.mode');
+        if (defined $mode) {
+            $self->chmod_object("$key", $mode);
+        }
+    };
+    if ($@) {
+        unless ($@ =~ qr/user.mode does not exist/) {
+            $log->logdie("error: $@");
+        }
+    }
+
     $log->debug("leaving");
 
@@ -745,7 +851,105 @@
 
 
+sub prune_object
+{
+    my $self = shift;
+
+    my $log = $self->log;
+    $log->debug("entered - @_");
+
+    my ($key) = validate_pos(@_,
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key'
+                    => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+    );
+
+    my $sql = $self->sql;
+
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+
+    my $db  = $self->db($key);
+
+    my $rows_removed = 0;
+TRANS: while (1) {
+        eval {
+            # remove key from cache
+            $self->cache->delete($key->path) if defined $self->cache;
+
+            my $so_id;
+            {
+                my $query = $db->prepare_cached( $sql->find_object_by_ext_id );
+                $query->execute( $key->path );
+                $so_id = $query->fetchrow_hashref->{'so_id'};
+                $query->finish;
+            }
+
+            # record the path of the innaccesible files for deferred
+            # deletion
+            my $rows_copied;
+            {
+                my $query = $db->prepare_cached( $sql->copy_dead_instances_to_deleted );
+                $rows_copied = $query->execute( $so_id );
+            }
+
+            # check to see if there is anything to be done
+            unless ($rows_copied > 0) {
+                $db->rollback;
+                return;
+            }
+
+            # In MySQL you can't select from a table your deleting rows from so
+            # we first have to get a list of instances to be removed, and then
+            # removed them.
+            my $rows_found;
+            {
+                my $query = $db->prepare_cached( $sql->find_dead_instances_by_so_id );
+                $rows_found = $query->execute( $so_id );
+                foreach my $row ($query->fetchrow_hashref) {
+                    # remove dead instances
+                    my $query = $db->prepare_cached( $sql->delete_instance_by_ins_id);
+                    $rows_removed += $query->execute( $row->{ins_id} );
+                }
+                $query->finish;
+            }
+
+            # sanity check
+            die("instances inaccessible ($rows_copied) != instances removed ($rows_removed)")
+                unless $rows_copied == $rows_removed;
+            
+            $db->commit;
+            $log->debug("commit");
+        };
+        if ($@) {
+            $db->rollback;
+            $log->debug("rollback");
+            if ($@ =~ $trans_regex) {
+                $log->warn("database error, retrying transaction: $@");
+                sleep TRANS_RETRY_WAIT;
+                redo TRANS;
+            }
+            $log->logdie("error: $@");
+        }
+        last;
+    }
+
+    $log->debug("leaving");
+
+    return $rows_removed;
+}
+
+
 sub lock_object
 {
     my $self = shift;
+
+    my $log = $self->log;
+    $log->debug( "entered - @_" );
 
     my ( $key, $type ) = validate_pos( @_,
@@ -764,12 +968,13 @@
     );
 
+    my $sql = $self->sql;
+
     # ignore volume
-    $key = parse_neb_key($key);
-
-    my $log = $self->log;
-    my $sql = $self->sql;
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+
     my $db  = $self->db($key);
-
-    $log->debug( "entered - @_" );
 
     my $so_id;
@@ -785,5 +990,5 @@
                 unless ( $rows == 1 ) {
                     $query->finish;
-                    $log->logdie( "storage object does not exist" );
+                    die( "storage object does not exist" );
                 }
 
@@ -800,9 +1005,9 @@
                 # can't set a write lock if there are read locks
                 if ($write_lock) {
-                    $log->logdie("can not write lock twice -- retry");
+                    die("can not write lock twice -- retry");
                 }
                 
                 if ($read_lock > 0) {
-                    $log->logdie("can not write lock after read lock -- retry");
+                    die("can not write lock after read lock -- retry");
                 }
 
@@ -813,5 +1018,5 @@
                     # if we affected more then one row something very bad has happened.
                     unless ($rows == 1) {
-                        $log->logdie("affected row count is $rows instead of 1");
+                        die("affected row count is $rows instead of 1");
                     }
 
@@ -820,5 +1025,5 @@
                 # can't set a read lock if there's a write lock
                 if ($write_lock) {
-                    $log->logdie("can not read lock after write lock -- retry");
+                    die("can not read lock after write lock -- retry");
                 }
 
@@ -829,5 +1034,5 @@
                     # if we affected more then one row something very bad has happened.
                     unless ($rows == 1) {
-                        $log->logdie("affected row count is $rows instead of 1");
+                        die("affected row count is $rows instead of 1");
                     }
                 }
@@ -840,6 +1045,7 @@
             $db->rollback;
             $log->debug("rollback");
-            if ($@ =~ /Deadlock found/) {
-                $log->warn("database deadlock retrying transaction: $@");
+            if ($@ =~ $trans_regex) {
+                $log->warn("database error, retrying transaction: $@");
+                sleep TRANS_RETRY_WAIT;
                 redo TRANS;
             }
@@ -858,4 +1064,7 @@
 {
     my $self = shift;
+
+    my $log = $self->log;
+    $log->debug( "entered - @_" );
 
     my ( $key, $type ) = validate_pos( @_,
@@ -874,12 +1083,13 @@
     );
 
+    my $sql = $self->sql;
+
     # ignore volume
-    $key = parse_neb_key($key);
-
-    my $log = $self->log;
-    my $sql = $self->sql;
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+
     my $db  = $self->db($key);
-
-    $log->debug( "entered - @_" );
 
     my $so_id;
@@ -895,5 +1105,5 @@
                 unless ($rows == 1) {
                     $query->finish;
-                    $log->logdie("storage object does not exist");
+                    die("storage object does not exist");
                 }
 
@@ -909,9 +1119,9 @@
                 # can't remove a write lock if it doesn't exist
                 if ($read_lock) {
-                    $log->logdie("can not have a write lock under a read lock");
+                    die("can not have a write lock under a read lock");
                 }
 
                 unless ($write_lock) {
-                    $log->logdie("can not remove non-existant write lock");
+                    die("can not remove non-existant write lock");
                 }
 
@@ -922,5 +1132,5 @@
                     # if we affected more then one row something very bad has happened.
                     unless ($rows == 1) {
-                        $log->logdie("affected row count is $rows instead of 1");
+                        die("affected row count is $rows instead of 1");
                     }
                 }
@@ -929,9 +1139,9 @@
                 # can't remove a read lock if there aren't any
                 if ($write_lock) {
-                    $log->logdie("can not have a read lock under a write lock");
+                    die("can not have a read lock under a write lock");
                 }
                    
                 if ($read_lock == 0) {
-                    $log->logdie("can not remove non-existant read lock");
+                    die("can not remove non-existant read lock");
                 }
 
@@ -942,5 +1152,5 @@
                     # if we affected more then one row something very bad has happened.
                     unless ($rows == 1) {
-                        $log->logdie("affected row count is $rows instead of 1");
+                        die("affected row count is $rows instead of 1");
                     }
 
@@ -953,6 +1163,7 @@
             $db->rollback;
             $log->debug("rollback");
-            if ($@ =~ /Deadlock found/) {
-                $log->warn("database deadlock retrying transaction: $@");
+            if ($@ =~ $trans_regex) {
+                $log->warn("database error, retrying transaction: $@");
+                sleep TRANS_RETRY_WAIT;
                 redo TRANS;
             }
@@ -971,4 +1182,7 @@
 {
     my $self = shift;
+
+    my $log = $self->log;
+    $log->debug("entered - @_");
 
     my ($key, $name, $value, $flags) = validate_pos(@_,
@@ -981,4 +1195,8 @@
         {
             type        => SCALAR,
+            callbacks   => {
+                'xattr is in user. namespace'
+                    => sub { ($_[0]) =~ qr/^user\./ },
+            },
         },
         {
@@ -993,12 +1211,13 @@
     );
 
+    my $sql = $self->sql;
+
     # ignore volume
-    $key = parse_neb_key($key);
-
-    my $log = $self->log;
-    my $sql = $self->sql;
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+
     my $db  = $self->db($key);
-
-    $log->debug("entered - @_");
 
 TRANS: while (1) {
@@ -1020,5 +1239,5 @@
             if ($flags eq 'create') {
                 unless ($rows == 1) {
-                    $log->logdie( "affected row count is $rows instead of 1" );
+                    die( "affected row count is $rows instead of 1" );
                 }
             } else {
@@ -1026,5 +1245,5 @@
                 # the case of a replace and 1 if the xattr didn't already exist.
                 unless ($rows == 1 or $rows == 2) {
-                    $log->logdie( "affected row count is $rows instead of 2" );
+                    die( "affected row count is $rows instead of 2" );
                 }
             }
@@ -1036,6 +1255,7 @@
             $db->rollback;
             $log->debug("rollback");
-            if ($@ =~ /Deadlock found/) {
-                $log->warn("database deadlock retrying transaction: $@");
+            if ($@ =~ $trans_regex) {
+                $log->warn("database error, retrying transaction: $@");
+                sleep TRANS_RETRY_WAIT;
                 redo TRANS;
             }
@@ -1054,4 +1274,7 @@
 {
     my $self = shift;
+
+    my $log = $self->log;
+    $log->debug("entered - @_");
 
     my ($key, $name) = validate_pos(@_,
@@ -1064,15 +1287,20 @@
         {
             type        => SCALAR,
+            callbacks   => {
+                'xattr is in user. namespace'
+                    => sub { ($_[0]) =~ qr/^user\./ },
+            },
         },
     );
 
+    my $sql = $self->sql;
+
     # ignore volume
-    $key = parse_neb_key($key);
-
-    my $log = $self->log;
-    my $sql = $self->sql;
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+
     my $db  = $self->db($key);
-
-    $log->debug("entered - @_");
 
     my $value;
@@ -1085,10 +1313,10 @@
         if ($rows == 0) {
             $query->finish;
-            $log->logdie( "xattr $key:$name does not exist" );
+            die( "xattr $key:$name does not exist" );
         }
         # if we go more then one row bad something very bad has happened.
         unless ($rows == 1) {
             $query->finish;
-            $log->logdie( "affected row count is $rows instead of 1" );
+            die( "affected row count is $rows instead of 1" );
         }
 
@@ -1099,5 +1327,11 @@
         $value = $row->{ 'value' };
     };
-    $log->logdie("database error: $@") if $@;
+    if ($@) {
+        if ($@ =~ /user\..*? does not exist/) {
+            # do not log xattr does not exist messages
+            die $@;
+        }
+        $log->logdie("database error: $@") if $@;
+    }
 
     $log->debug("leaving");
@@ -1110,4 +1344,7 @@
 {
     my $self = shift;
+
+    my $log = $self->log;
+    $log->debug("entered - @_");
 
     my ($key) = validate_pos(@_,
@@ -1120,12 +1357,13 @@
     );
 
+    my $sql = $self->sql;
+
     # ignore volume
-    $key = parse_neb_key($key);
-
-    my $log = $self->log;
-    my $sql = $self->sql;
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+
     my $db  = $self->db($key);
-
-    $log->debug("entered - @_");
 
     my @xattrs;
@@ -1151,4 +1389,7 @@
     my $self = shift;
 
+    my $log = $self->log;
+    $log->debug("entered - @_");
+
     my ($key, $name) = validate_pos(@_,
         {
@@ -1160,15 +1401,20 @@
         {
             type        => SCALAR,
+            callbacks   => {
+                'xattr is in user. namespace'
+                    => sub { ($_[0]) =~ qr/^user\./ },
+            },
         },
     );
 
+    my $sql = $self->sql;
+
     # ignore volume
-    $key = parse_neb_key($key);
-
-    my $log = $self->log;
-    my $sql = $self->sql;
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+
     my $db  = $self->db($key);
-
-    $log->debug("entered - @_");
 
 TRANS: while (1) {
@@ -1179,7 +1425,12 @@
             $query->finish;
 
+            # no rows affected means the xattr did not exist
+            if ($rows == 0) {
+                die( "xattr $key:$name does not exist" );
+            }
+
             # if we affected more then one row something very bad has happened.
-            unless ($rows == 1) {
-                $log->logdie( "affected row count is $rows instead of 1" );
+            if ($rows > 1) {
+                die( "affected row count is $rows instead of 1" );
             }
 
@@ -1190,6 +1441,7 @@
             $db->rollback;
             $log->debug("rollback");
-            if ($@ =~ /Deadlock found/) {
-                $log->warn("database deadlock retrying transaction: $@");
+            if ($@ =~ $trans_regex) {
+                $log->warn("database error, retrying transaction: $@");
+                sleep TRANS_RETRY_WAIT;
                 redo TRANS;
             }
@@ -1209,4 +1461,7 @@
     my $self = shift;
 
+    my $log = $self->log;
+    $log->debug( "entered - @_" );
+
     my ($pattern) = validate_pos( @_,
         {
@@ -1216,9 +1471,9 @@
     );
 
-    $pattern = parse_neb_key($pattern);
-
-    my $log = $self->log;
-
-    $log->debug( "entered - @_" );
+
+    eval {
+        $pattern = parse_neb_key($pattern) if defined $pattern;
+    };
+    $log->logdie("$@") if $@;
 
     unless (defined $pattern) {
@@ -1235,4 +1490,11 @@
     $log->logdie("no keys found") unless ( scalar @keys );
 
+    if (defined $self->cache) {
+    	foreach my $path (@keys) {
+            $self->cache->set($path, 1);
+        	$log->debug("key added to cache as: $path");
+	}
+    }
+
     $log->debug( "leaving" );
 
@@ -1245,12 +1507,14 @@
 
     my $self    = shift;
+
+    my $log = $self->log;
+    $log->debug( "entered - @_" );
+
     my $index   = shift;
     my $key     = shift;
 
-    my $log = $self->log;
     my $sql = $self->sql;
     my $db  = $self->_db_for_index($index);
 
-    $log->debug( "entered - @_" );
 
     # first check to see if the key is an exact match
@@ -1287,6 +1551,27 @@
     }
 
-    eval {
-        $log->debug("trying for a directory match under dir: $dir_id");
+    # find dirs under dir
+    my @dir_keys;
+    eval {
+        $log->debug("looking for directories under dir: $dir_id");
+        my $query = $db->prepare_cached( $sql->find_dir_by_parent_id);
+        $query->execute( $dir_id );
+
+        while ( my $row = $query->fetchrow_hashref ) {
+            next if $row->{'dir_id'} == 1;
+            my $dir = $row->{'dirname'};
+            if ($dir_id == 1) {
+                push @dir_keys, $dir . '/' if $dir;
+            } else {
+                push @dir_keys, $key->path . '/' . $dir . '/' if $dir;
+            }
+            $log->debug( "matched $dir" ) if $dir;
+        }
+    };
+    $log->logdie("database error: $@") if $@;
+
+    # find files under dir
+    eval {
+        $log->debug("looking for objects under dir: $dir_id");
         my $query = $db->prepare_cached( $sql->find_object_by_dir_id );
         $query->execute( $dir_id );
@@ -1301,6 +1586,6 @@
 
     $log->debug( "leaving" );
-
-    return \@keys;
+    
+    return [sort(@dir_keys), sort(@keys)];
 }
 
@@ -1309,4 +1594,7 @@
 {
     my $self = shift;
+
+    my $log = $self->log;
+    $log->debug("entered - @_");
 
     my ($key, $vol_name) = validate_pos(@_,
@@ -1330,13 +1618,19 @@
     );
 
+    my $sql = $self->sql;
+
+#    unless ($key) {
+#        $log->warn("key was undefined after validate_pos(), trying again...");
+#        return $self->find_instances(@_);
+#    }
+
     # vol_name overrides the key implied volume
-    $key = parse_neb_key($key, $vol_name);
+    eval {
+        $key = parse_neb_key($key, $vol_name);
+    };
+    $log->logdie("$@") if $@;
     $vol_name = $key->volume;
 
-    my $log = $self->log;
-    my $sql = $self->sql;
     my $db  = $self->db($key);
-
-    $log->debug("entered - @_");
 
     # the key's volume can't be validiated on input for this method so we have
@@ -1344,9 +1638,9 @@
     if (defined $vol_name
         and not $self->_is_valid_volume_name($key, $key->volume)) {
-        if ($key->soft_volume) {
+        if ($key->hard_volume) {
+            $log->logdie("$vol_name is not a valid volume name");
+        } else {
             $log->warn( "$vol_name is not a known volume name" );
             $vol_name = undef;
-        } else {
-            die "$vol_name is not a valid volume name"
         }
     }
@@ -1361,5 +1655,5 @@
             unless ($rows > 0) {
                 $query->finish;
-                $log->logdie("no instances on storage volume or volume is not avaiable for key: $key volume: $vol_name");
+                die("no instances on storage volume or volume is not avaiable for key: $key volume: $vol_name");
             }
         } else {
@@ -1369,5 +1663,5 @@
             unless ($rows > 0) {
                 $query->finish;
-                $log->logdie("no instances available for key: $key");
+                die("no instances available for key: $key");
             }
         }
@@ -1381,5 +1675,5 @@
         $db->rollback;
         # handle soft volumes
-        if (defined $vol_name and defined $key->soft_volume) {
+        if (defined $vol_name and not defined $key->hard_volume) {
             $log->debug("retrying with 'any' volume");
             return $self->find_instances($key->path, 'any');
@@ -1403,4 +1697,7 @@
     my $self = shift;
 
+    my $log = $self->log;
+    $log->debug( "entered - @_" );
+
     my ($key, $uri) = validate_pos( @_,
         {
@@ -1415,37 +1712,46 @@
     );
 
+    my $sql = $self->sql;
+
     # ignore volume
-    $key = parse_neb_key($key);
-
-    my $log = $self->log;
-    my $sql = $self->sql;
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+
     my $db  = $self->db($key);
-
-    $log->debug( "entered - @_" );
 
 TRANS: while (1) {
         eval {
+            my $total;
+            my $available;
             my $so_id;
-            my $instances;
-            # get so_id
+            my $ins_id;
+            # find so_id for key and get count of instances
             {
-                my $query = $db->prepare_cached( $sql->get_object_from_uri );
-                my $rows = $query->execute( $uri );
-
+                my $query = $db->prepare_cached( $sql->get_instance_count_by_ext_id );
+                my $rows = $query->execute($key->path);
                 unless ( $rows > 0 ) {
                     $query->finish;
-                    $log->logdie( "no instance is associated with uri" );
-                }
-
-                $so_id = $query->fetchrow_hashref->{ 'so_id' };
+                    die( "$key has no associated instances - this should not happen" );
+                }
+
+                my $record = $query->fetchrow_hashref;
+                $so_id      = $record->{ 'so_id' };
+                $total      = $record->{ 'total' };
+                $available  = $record->{ 'available' };
                 $query->finish;
-
-            }
-
+            }
+
+            # find ins_id for uri
             {
-                my $query = $db->prepare_cached( $sql->get_instance_count );
-                $query->execute( $so_id );
-
-                $instances = $query->fetchrow_hashref->{ 'count(ins_id)' };
+                my $query = $db->prepare_cached( $sql->get_instance_by_uri );
+                my $rows = $query->execute($so_id, $uri);
+                unless ( $rows > 0) {
+                    $query->finish;
+                    die( "no instance is associated with uri" );
+                }
+
+                $ins_id = $query->fetchrow_hashref->{ 'ins_id' };
                 $query->finish;
             }
@@ -1453,19 +1759,28 @@
             # remove instance
             {
-                my $query = $db->prepare_cached( $sql->delete_instance );
-                my $rows = $query->execute( $uri );
+                my $query = $db->prepare_cached( $sql->delete_instance_by_ins_id );
+                my $rows = $query->execute( $ins_id );
                 $query->finish;
                 
-                # if we affected something other then two rows something very bad
-                # has happened
+                # if we affected something other then one row something very
+                # bad has happened
                 unless ( $rows == 1 ) {
-                    $log->logdie( "affected row count is $rows instead of 1" );
-                }
-            }
-
-            # if we just deleted the last instance associated with a storage object
-            # remove it too
-            if ( $instances == 1 ) {
-                # we just removed the last instance
+                    die( "affected row count is $rows instead of 1" );
+                }
+                
+            }
+
+            # if we just deleted the last 'available' instance associated with
+            # a storage object remove it too
+            if ( $available == 1 ) {
+                # remove key from cache
+                $self->cache->delete($key->path) if defined $self->cache;
+                
+                if ($total > $available) {
+                    $self->prune_object("$key");
+                }
+                
+                # delete the storage object, remaining instances should be
+                # removed via cascading delete
                 my $query = $db->prepare_cached( $sql->delete_object );
                 my $rows = $query->execute( $so_id );
@@ -1474,5 +1789,5 @@
                 # TODO: this will have to be changed in order to support hardlinks
                 unless ( $rows == 1 ) {
-                    $log->logdie( "affected row count is $rows instead of 2" );
+                    die( "affected row count is $rows instead of 2" );
                 }
             }
@@ -1484,6 +1799,7 @@
             $db->rollback;
             $log->debug("rollback");
-            if ($@ =~ /Deadlock found/) {
-                $log->warn("database deadlock retrying transaction: $@");
+            if ($@ =~ $trans_regex) {
+                $log->warn("database error, retrying transaction: $@");
+                sleep TRANS_RETRY_WAIT;
                 redo TRANS;
             }
@@ -1502,4 +1818,7 @@
 {
     my $self = shift;
+
+    my $log = $self->log;
+    $log->debug("entered - @_");
 
     my ( $key ) = validate_pos( @_,
@@ -1512,12 +1831,13 @@
     );
 
+    my $sql = $self->sql;
+
     # ignore volume
-    $key = parse_neb_key($key);
-
-    my $log = $self->log;
-    my $sql = $self->sql;
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+
     my $db  = $self->db($key);
-
-    $log->debug("entered - @_");
 
     my $stat;
@@ -1527,5 +1847,5 @@
 
         unless ($rows == 1) {
-            $log->logdie("no storage object found");
+            die("no storage object found");
         }
 
@@ -1548,12 +1868,12 @@
     my $self = shift;
 
+    my $log = $self->log;
+    $log->debug("entered - @_");
+
     validate_pos(@_); 
 
-    my $log = $self->log;
     my $sql = $self->sql;
     my $db  = $self->_db_for_index(0); # XXX fix as above
 
-    $log->debug("entered - @_");
-
     my $stats;
     my $query;
@@ -1576,4 +1896,81 @@
 }
 
+sub chmod_object
+{
+    my $self = shift;
+
+    my $log = $self->log;
+    $log->debug("entered - @_");
+
+    my ($key, $mode) = validate_pos( @_, 
+        {
+            type        => SCALAR,
+            callbacks   => {
+                'is valid object key' => sub { $self->_is_valid_object_key($_[0]) },
+            },
+        },
+        {
+            type        => SCALAR,
+            regex       => qr/\d{3,4}/,
+            callbacks   => {
+                'is allowable mode' => sub {
+                    $_[0] == (S_IRUSR | S_IRGRP)
+                },
+            },
+        },
+    );
+
+    my $sql = $self->sql;
+
+    # ignore volume
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+
+    my $db  = $self->db($key);
+
+    # find all instances of this object
+    my $locations;
+    eval {
+        $locations = $self->find_instances("$key");
+    };
+    $log->logdie("error: $@") if $@;
+
+    # update each instances
+    foreach my $inst (@$locations) {
+        my $path = URI->new($inst)->path;
+
+        $self->_retry(sub { chmod($mode, $path) })
+            or $log->logdie("can not chmod() $path: $!");
+
+        # XXX I'm assuming that it's OK to fsync() a filehandle that's only
+        # open for reading?  Opening as w/rw here can fail if the chmod removes
+        # write permissions.
+        my $fh;
+        $self->_retry(sub { open($fh, '<', $path) })
+            or $log->logdie("can not open() $path: $!");
+
+        # fsync(3c)
+        $self->_retry(sub { $fh->sync() })
+            or $log->logdie("can not sync() $path: $!");
+
+        $self->_retry(sub { close($fh) })
+            or $log->logdie("can not close() $path: $!");
+    }
+
+    # stick an xattr on this object with the mode
+    # XXX this would probably be better as a field in the storage_object_attr
+    # table but since we're not planning to use this for very many objects (as
+    # a %) it may not be worth adding the extra field at this time.
+    eval {
+        $self->setxattr_object("$key", 'user.mode', $mode, 'replace');
+    };
+    $log->logdie("error: $@") if $@;
+
+    $log->debug("leaving");
+
+    return $mode;
+}
 
 sub _get_storage_volume
@@ -1581,13 +1978,13 @@
     my $self = shift;
 
-    my ($key, $name, $soft_volume) = @_;
-
-    my $log = $self->log;
-    my $sql = $self->sql;
-    my $db  = $self->db($key);
-
+    my $log = $self->log;
     no warnings qw( uninitialized );
     $log->debug( "entered - @_" );
     use warnings;
+
+    my ($key, $name, $hard_volume) = @_;
+
+    my $sql = $self->sql;
+    my $db  = $self->db($key);
 
     my ($vol_id, $vol_host, $vol_path, $xattr);
@@ -1605,14 +2002,14 @@
                 # if a volume name was specified, and is soft, and we failed to
                 # find it, fall back to any volume
-                if ($soft_volume) {
+                unless ($hard_volume) {
                     ($vol_id, $vol_host, $vol_path, $xattr) = $self->_get_storage_volume($key);
                     return; # this just returns out of the eval not from the subroutine
                 }
-                $log->logdie("storage volume: $name is not available");
+                die("storage volume: $name is not available");
             }
             # when matching by name we shouldn't ever match more than once
             if ($rows > 1) {
                 $query->finish;
-                $log->logdie("affected row count is $rows instead of 1");
+                die("affected row count is $rows instead of 1");
             }
         } else {
@@ -1623,5 +2020,5 @@
             unless ($rows > 0) {
                 $query->finish;
-                $log->logdie("no storage volume is available");
+                die("no storage volume is available for key: $key volume: $name hard_volume: $hard_volume");
             }
         }
@@ -1631,5 +2028,14 @@
         $query->finish;
     };
-    $log->logdie("database error: $@") if $@;
+    if ($@) {
+        if ($@ =~ qr/no storage volume is available/) {
+            # this should not happen unless all volumes are full
+            $log->error($@);
+            $log->debug("retrying...");
+            return $self->_get_storage_volume(@_);
+        } 
+        # else
+        $log->logdie("database error: $@");
+    }
 
     $log->logdie("failed to find a suitable volume" )
@@ -1646,13 +2052,13 @@
     my $self = shift;
 
-    my $key = shift;
-
-    my $log = $self->log;
-    my $sql = $self->sql;
-    my $db  = $self->db($key);
-
+    my $log = $self->log;
     no warnings qw( uninitialized );
     $log->debug( "entered - @_" );
     use warnings;
+
+    my $key = shift;
+
+    my $sql = $self->sql;
+    my $db  = $self->db($key);
 
     my ($vol_id, $vol_host, $vol_path, $xattr);
@@ -1665,10 +2071,10 @@
         unless ($rows > 0) {
             $query->finish;
-            $log->logdie("can't find a suitable storage volume to replicate $key to");
+            die("can't find a suitable storage volume to replicate $key to");
         }
         # when matching by name we shouldn't ever match more than once
         if ($rows > 1) {
             $query->finish;
-            $log->logdie("affected row count is $rows instead of 1");
+            die("affected row count is $rows instead of 1");
         }
 
@@ -1690,10 +2096,28 @@
 sub _is_valid_object_key
 {
-    my ($self, $key) = @_;
-
-    $key = parse_neb_key($key);
-
-    my $log = $self->log;
+    my $self = shift;
+
+    my $log = $self->log;
+    $log->debug( "entered - @_" );
+
+    my ($key) = @_;
+
     my $sql = $self->sql;
+
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
+
+    # check cache first
+    my $cached = $self->cache->get($key->path) if defined $self->cache;
+    if (defined $cached) {
+        $log->debug( "key $key found in cache as ", $key->path );
+        $log->debug( "leaving" );
+        return 1;
+    } else {
+        $log->debug( "key $key not found in cache" );
+    }
+
     my $db  = $self->db($key);
 
@@ -1712,7 +2136,15 @@
 
     if (defined $ext_id) {
+        $log->debug( "key found in db" );
+    	# add key to cache
+        $self->cache->set($key->path, 1) if defined $self->cache;
+        $log->debug( "key added to cache as ", $key->path );
+        $log->debug( "leaving" );
         return 1;
     } 
 
+    $log->debug( "key not found in db" );
+    $log->debug( "leaving" );
+
     return;
 }
@@ -1721,11 +2153,21 @@
 sub _is_valid_volume_name
 {
-    my ($self, $key, $vol_name) = @_;
-
-    $key = parse_neb_key($key);
+    my $self = shift;
+
+    my $log = $self->log;
+    $log->debug( "entered - @_" );
+
+    my ($key, $vol_name) = @_;
+
+    my $sql = $self->sql;
+
+    # the volume name implied by the key is ignored.  $key is only needed to
+    # select a database connection
+    eval {
+        $key = parse_neb_key($key);
+    };
+    $log->logdie("$@") if $@;
     my $volume_info = parse_neb_volume($vol_name);
 
-    my $log = $self->log;
-    my $sql = $self->sql;
     my $db  = $self->db($key);
 
@@ -1734,4 +2176,6 @@
     # handle "any" volume
     if ($vol_name eq 'any') {
+        $log->debug( "found volume name $vol_name" );
+        $log->debug( "leaving" );
         return 1;
     }
@@ -1748,7 +2192,12 @@
 
     if (defined $vol_id and defined $vol_path) {
+        $log->debug( "found volume name $vol_name" );
+        $log->debug( "leaving" );
         return 1;
     } 
 
+    $log->debug( "volume name $vol_name not found" );
+    $log->debug( "leaving" );
+
     return;
 }
@@ -1759,7 +2208,9 @@
     my $self = shift;
 
+    my $log = $self->log;
+    $log->debug( "entered - @_" );
+
     my ($key, $so_id, $ins_id, $vol_path, $xattr) =  @_;
 
-    my $log = $self->log;
     my $sql = $self->sql;
     my $db  = $self->db($key);
@@ -1770,13 +2221,15 @@
         my $storage_filename = $self->_generate_storage_filename($key->path, $ins_id);
         unless (-d $storage_path) {
-            _retry(sub { mkpath(@_) }, $storage_path, 0, 0775)
-                or die "can't create storage path: $storage_path";
+            $self->_retry(sub { mkpath([$storage_path], 0, 0775) })
+                or die("can't create storage path: $storage_path");
         }
         # check to make sure at least the parent directory has the proper
         # permissions
-        my $mode = [_retry(sub { stat($storage_path) } )]->[2] & 07777;
+        my $mode = [$self->_retry(sub { stat($storage_path) } )]->[2] & 07777;
         unless ($mode == 0775) {
-            $log->error("$storage_path has the wrong permissions of: %04x", $mode);
-            _retry(sub { chmod(0775, $storage_path) }) or die "can't chmod $storage_path: $!";
+            # XXX: this problem is so common that it's flooding the logs
+            $log->debug("$storage_path has the wrong permissions of: 0", sprintf("%o", $mode));
+            $self->_retry(sub { chmod(0775, $storage_path) })
+                or die("can not chmod() $storage_path: $!");
         }
 
@@ -1787,4 +2240,8 @@
     };
     if ($@) {
+        if (defined $uri and -e $uri->file) {
+            unlink($uri->file)
+                or $log->error("failed to unlink() $uri: $!");
+        }
         $log->logdie($@);
     }
@@ -1792,5 +2249,5 @@
     if ($xattr) {
         my $path = $uri->file;
-        die "can not set xattr on $path: $!"
+        $log->logdie("can not set xattr on $path: $!")
             unless (setfattr($path, 'user.nebulous_key', $key->path));
     }
@@ -1804,25 +2261,27 @@
     my $self = shift;
 
+    my $log = $self->log;
+    $log->debug( "entered - @_" );
+
     my ($path) = @_;
 
     # perl's open() can't do an O_CREAT | O_EXCL
-    die "file $path already exists" if (-e $path);
+    # XXX is it possible to tell if this system call failed?
+    -e $path
+        and $log->logdie("file $path already exists");
 
     my $fh;
-    die "can not open $path: $!"
-        unless (_retry(sub { open($fh, '>', $path) }));
+    $self->_retry(sub { open($fh, '>', $path) })
+        or $log->logdie("can not open() $path: $!");
 
     # chmod before fsync() to make sure the changed perms hit the disk too
-    die "can not chmod $path: $!"
-        unless (_retry(sub { chmod(0664, $path) }));
-
-    die "can not flush $path: $!"
-        unless ($fh->flush);
-
-    die "can not sync $path: $!"
-        unless ($fh->sync);
-
-    die "can not close $path: $!"
-        unless (_retry(sub { close($fh) }));
+    $self->_retry(sub { chmod(0664, $path) })
+        or $log->logdie("can not chmod() $path: $!");
+
+    $self->_retry(sub { $fh->sync() })
+        or $log->logdie("can not sync() $path: $!");
+
+    $self->_retry(sub { close($fh) })
+        or $log->logdie("can not close() $path: $!");
 
     return $path;
@@ -1861,4 +2320,9 @@
 sub _retry
 {
+    my $self = shift;
+
+    my $log = $self->log;
+    $log->debug( "entered - @_" );
+
     my $func = shift;
 
@@ -1869,6 +2333,6 @@
         };
         if ($@) {
-            die $@;
-            sleep 1;
+            $log->logdie($@);
+            sleep NFS_RETRY_WAIT;
             next;
         }
@@ -1879,5 +2343,5 @@
     # if the loop ended and $@ is set, rethrow the error
     if ($@) {
-        die $@;
+        $log->logdie($@);
     }
 
Index: branches/pap/Nebulous-Server/lib/Nebulous/Server.pod
===================================================================
--- branches/pap/Nebulous-Server/lib/Nebulous/Server.pod	(revision 23948)
+++ branches/pap/Nebulous-Server/lib/Nebulous/Server.pod	(revision 25027)
@@ -29,4 +29,6 @@
     Nebulous::Server->stat_object( $key );
     Nebulous::Server->mounts();
+    Nebulous::Server->chmod_object( $key, $mode);
+    Nebulous::Server->prune_object( $key );
 
 =head1 DESCRIPTION
@@ -181,5 +183,6 @@
     epoch
     mtime
-    number of instances
+    number of available instances
+    total number of instances
 
 =item * mounts( $key );
@@ -203,4 +206,17 @@
     ]
 
+=item * chmod_object( $key, $mode );
+
+Accepts 2 parameters, both mandatory.  C<$key> is the nebulous key to operate
+on and C<$mode> are the POSIX like file permissions to set on all instances of
+C<$key>.  C<$mode> must be specified in oct.  Returns C<$mode> or sucess or an
+exception on failure.
+
+=item * prune_object( $key );
+
+Removes all of the inaccessible instances from an object.
+
+Accepts 1 mandatory parameter.  Returns the number of inaccessible instances actually pruned from the object.
+
 =back
 
@@ -223,5 +239,5 @@
 =head1 COPYRIGHT
 
-Copyright (C) 2004-2008  Joshua Hoblitt.  All rights reserved.
+Copyright (C) 2004-2009  Joshua Hoblitt.  All rights reserved.
 
 This program is free software; you can redistribute it and/or modify it under
Index: branches/pap/Nebulous-Server/lib/Nebulous/Server/Config.pm
===================================================================
--- branches/pap/Nebulous-Server/lib/Nebulous/Server/Config.pm	(revision 23948)
+++ branches/pap/Nebulous-Server/lib/Nebulous/Server/Config.pm	(revision 25027)
@@ -13,5 +13,5 @@
 
 use Log::Log4perl qw( :levels );
-use Params::Validate qw( validate validate_pos SCALAR );
+use Params::Validate qw( validate validate_pos SCALAR ARRAYREF UNDEF );
 
 our %LEVELS = (
@@ -32,5 +32,5 @@
     dsn         => { type => SCALAR },
     dbuser      => { type => SCALAR },
-    dbpasswd    => { type => SCALAR },
+    dbpasswd    => { type => SCALAR | UNDEF },
 };
 
@@ -46,7 +46,8 @@
         },
     },
-    dsn         => { type => SCALAR, optional => 1 },
-    dbuser      => { type => SCALAR, optional => 1 },
-    dbpasswd    => { type => SCALAR, optional => 1 },
+    dsn                 => { type => SCALAR, optional => 1 },
+    dbuser              => { type => SCALAR, optional => 1 },
+    dbpasswd            => { type => SCALAR | UNDEF, optional => 1 },
+    memcached_servers   => { type => ARRAYREF, optional => 1 },
 };
 
@@ -61,5 +62,8 @@
 
     # normalize log levels to lower-case
-    my $self = { trace => $LEVELS{lc($p{trace})} };
+    my $self = {
+        trace             => $LEVELS{lc($p{trace})},
+        memcached_servers => $p{memcached_servers},
+    };
 
     bless $self, $class || ref $class;
Index: branches/pap/Nebulous-Server/lib/Nebulous/Server/SQL.pm
===================================================================
--- branches/pap/Nebulous-Server/lib/Nebulous/Server/SQL.pm	(revision 23948)
+++ branches/pap/Nebulous-Server/lib/Nebulous/Server/SQL.pm	(revision 25027)
@@ -67,4 +67,5 @@
         WHERE parent_id = ?
             AND dirname = ?
+        LOCK IN SHARE MODE
     },
     new_directory       => qq{
@@ -82,18 +83,21 @@
     stat_object          => qq{
         SELECT
-            storage_object.so_id,
-            storage_object.ext_id,
-            read_lock,
-            write_lock,
-            storage_object_attr.epoch,
-            storage_object_attr.mtime,
+            so.so_id,
+            so.ext_id,
+            attr.read_lock,
+            attr.write_lock,
+            attr.epoch,
+            attr.mtime,
+            SUM(available) as available,
             COUNT(instance.so_id) as instances
-        FROM storage_object
-        JOIN storage_object_attr
+        FROM storage_object as so
+        JOIN storage_object_attr as attr
             USING (so_id)
         JOIN instance
             USING (so_id)
-        WHERE ext_id = ?
-        GROUP BY storage_object.so_id
+        LEFT JOIN mountedvol
+            USING(vol_id)
+        WHERE ext_id = ?
+        GROUP BY so.so_id
     },
     # Note: this sets an update lock
@@ -175,7 +179,7 @@
         WHERE ext_id = ?
     },
-    delete_instance     => qq{
+    delete_instance_by_ins_id => qq{
         DELETE FROM instance
-        WHERE uri = ?
+        WHERE ins_id = ?
     },
     get_object_from_uri   => qq{
@@ -184,8 +188,48 @@
         WHERE uri = ?
     },
+    get_instance_by_uri   => qq{
+        SELECT ins_id
+        FROM instance
+        WHERE
+            so_id = ?
+            AND uri = ?
+    },
     get_instance_count   => qq{
         SELECT count(ins_id)
         FROM instance
         WHERE so_id = ?
+    },
+    get_instance_count_by_ext_id   => qq{
+        SELECT 
+            so_id,
+            count(ins_id) as total,
+            sum(available) as available
+        FROM instance
+        JOIN storage_object
+            USING(so_id)
+        LEFT JOIN mountedvol
+            USING(vol_id)
+        WHERE
+            ext_id = ?
+        GROUP BY so_id
+    },
+    copy_dead_instances_to_deleted => qq{
+        INSERT INTO deleted 
+        SELECT vol_id, uri, NULL
+        FROM instance
+        LEFT JOIN mountedvol
+            USING(vol_id)
+        WHERE
+            so_id = ?
+            AND (mountedvol.available IS NULL || mountedvol.available = 0)
+    },
+    find_dead_instances_by_so_id => qq{
+        SELECT so_id, ins_id, vol_id, uri
+        FROM instance
+        LEFT JOIN mountedvol
+            USING(vol_id)
+        WHERE
+            so_id = ?
+            AND (mountedvol.available IS NULL || mountedvol.available = 0)
     },
     get_object_instances    => qq{
@@ -233,41 +277,24 @@
     get_replication_volume_for_ext_id   => qq{
         SELECT
-            vol_id,
+            m.vol_id,
             host,
             path,
             xattr,
             total - used as free
-        FROM (
--- This query works but is slow...
---             SELECT
---                  mountedvol.*
---             FROM mountedvol
---             WHERE
---                 vol_id NOT IN(
---                     SELECT vol_id
---                     FROM storage_object
---                     JOIN instance
---                         USING(so_id)
---                     WHERE ext_id = --
---                  )
-                SELECT
-                    m.*
-                FROM mountedvol AS m
-                LEFT JOIN instance AS i
-                    ON m.vol_id = i.vol_id
-                    AND i.so_id = (
-                        SELECT so_id
-                        FROM storage_object
-                        WHERE ext_id = ?
-                    )
-                WHERE
-                    i.vol_id IS NULL
-        ) as Foo 
-        WHERE
-            used / total < ? 
+        FROM mountedvol AS m
+        LEFT JOIN instance AS i
+            ON m.vol_id = i.vol_id
+            AND i.so_id = (
+                SELECT so_id
+                FROM storage_object
+                WHERE ext_id = ?
+            )
+        WHERE
+            i.vol_id IS NULL
+            AND used / total < ? 
             AND available = ? 
             AND allocate = ? 
-        ORDER BY RAND()
-        LIMIT 1
+            ORDER BY RAND()
+            LIMIT 1
     },
     get_storage_volume          => qq{
@@ -287,6 +314,6 @@
     },
     new_volume          => qq{
-        INSERT INTO volume (name, host, path, allocate, available, xattr)
-        VALUES (?, ?, ?, TRUE, TRUE, FALSE)
+        INSERT INTO volume (name, host, path, allocate, available, xattr, mountpoint)
+        VALUES (?, ?, ?, TRUE, TRUE, FALSE, ?)
     },
     get_volume_by_name => qq{
@@ -310,5 +337,5 @@
     },
     find_object_by_ext_id => qq{
-        SELECT ext_id, ext_id_basename
+        SELECT so_id, ext_id, ext_id_basename
         FROM storage_object
         WHERE ext_id = ?
@@ -318,4 +345,9 @@
         FROM storage_object
         WHERE dir_id = ?
+    },
+    find_dir_by_parent_id => qq{
+        SELECT dir_id, dirname
+        FROM directory
+        WHERE parent_id = ?
     },
     rename_object => qq{
@@ -397,5 +429,5 @@
     },
     get_mounted_volumes => qq{
-        SELECT * FROM mountedvol ORDER BY host, name
+        SELECT mountpoint, total, used, vol_id, name, host, path, allocate, available, xattr FROM mountedvol ORDER BY host, name
     },
 );
@@ -424,11 +456,11 @@
 DROP TABLE IF EXISTS lock_record;
 DROP TABLE IF EXISTS volume;
-DROP TABLE IF EXISTS mount;
+DROP TABLE IF EXISTS mountedvol;
 DROP TABLE IF EXISTS log;
-DROP TABLE IF EXISTS mountedvol;
 DROP TABLE IF EXISTS directory;
-DROP PROCEDURE IF EXISTS getmountedvol;
+DROP TABLE IF EXISTS deleted;
 SET FOREIGN_KEY_CHECKS=1
 END
+#DROP PROCEDURE IF EXISTS getmountedvol;
     $sql{get_db_clear} = \@clear;
 }
@@ -447,4 +479,71 @@
 
 1;
+
+# ###
+# 
+# CREATE PROCEDURE getmountedvol() DETERMINISTIC
+# BEGIN
+#     DECLARE done BOOLEAN DEFAULT FALSE;
+#     DECLARE vol_idvar INT;
+#     DECLARE namevar VARCHAR(255);
+#     DECLARE hostvar VARCHAR(255);
+#     DECLARE pathvar VARCHAR(255);
+#     DECLARE allocatevar BOOLEAN;
+#     DECLARE availablevar BOOLEAN;
+#     DECLARE xattrvar BOOLEAN;
+#     DECLARE trans_level VARCHAR(255);
+#     DECLARE key_checks BOOLEAN;
+#     DECLARE cur1 CURSOR FOR SELECT vol_id, name, host, path, allocate, available, xattr FROM myvolume;
+#     DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = TRUE;
+# 
+#     -- store the okey checking state
+# --    SELECT @@FOREIGN_KEY_CHECKS INTO key_checks; 
+#     -- disable foregin check checks to prevent deadlocks on the mountedvol table
+# --    SET FOREIGN_KEY_CHECKS=0;
+# 
+#     -- make sure the temp table does not already exist... this can happy if the
+#     -- stored proc fails for some reason
+#     DROP TABLE IF EXISTS myvolume;
+#     CREATE TEMPORARY TABLE myvolume LIKE volume;
+#     INSERT INTO myvolume SELECT * FROM volume;
+# 
+#     -- store the current transaction level
+# --    SELECT @@session.tx_isolation INTO trans_level; 
+#     -- set trans level to repeatable-read so the volume table does not change
+#     -- out from under our cursor
+# --    SET @@session.tx_isolation = 'REPEATABLE-READ';
+# 
+#     -- iterate over the volume table finding the coresponding entry in the
+#     -- mount table and inserting union of the volume & mount row into the
+#     -- mountedvol table
+#     OPEN cur1;
+# 
+#     myloop: LOOP
+#         FETCH cur1 INTO vol_idvar, namevar, hostvar, pathvar, allocatevar, availablevar, xattrvar;
+#         IF `done` THEN LEAVE myloop; END IF;
+#         REPLACE INTO mountedvol
+#             SELECT mountpoint, total, used, vol_idvar, namevar, hostvar, pathvar, allocatevar, availablevar, xattrvar
+#             FROM
+#                 (SELECT *, INSTR(pathvar, mountpoint) = 1 as substring
+#                 FROM mount
+#                 HAVING substring = 1
+#                 ORDER BY substring DESC, LENGTH(mountpoint) DESC
+#                 LIMIT 1) as bar;
+#     END LOOP myloop;
+#     
+#     CLOSE cur1;
+# 
+#     -- restore the original transaction level
+# --    SET @@session.tx_isolation = trans_level;
+# 
+#     -- restore the original key checking state
+# --    SET @@FOREIGN_KEY_CHECKS = key_checks; 
+# 
+#     DROP TABLE IF EXISTS myvolume;
+# 
+# --    SET FOREIGN_KEY_CHECKS=1;
+# 
+#     COMMIT;
+# END 
 
 __DATA__
@@ -455,5 +554,7 @@
     FOREIGN KEY(parent_id) REFERENCES directory(dir_id),
     PRIMARY KEY(dir_id),
-    KEY(parent_id)
+    KEY(parent_id),
+    KEY(dirname),
+    UNIQUE(dirname, parent_id)
 ) ENGINE=innodb DEFAULT CHARSET=latin1;
 
@@ -466,5 +567,5 @@
 CREATE TABLE storage_object (
     so_id BIGINT NOT NULL AUTO_INCREMENT,
-    ext_id VARCHAR(255) NOT NULL UNIQUE,
+    ext_id VARCHAR(255) NOT NULL,
     ext_id_basename VARCHAR(255) NOT NULL,
     dir_id BIGINT NOT NULL,
@@ -472,4 +573,5 @@
     type enum('REG_FILE'),
     PRIMARY KEY(so_id),
+    UNIQUE KEY(ext_id),
     KEY(dir_id),
     KEY(type)
@@ -514,5 +616,5 @@
 CREATE TABLE volume (
     vol_id INT NOT NULL AUTO_INCREMENT,
-    name VARCHAR(255) UNIQUE NOT NULL,
+    name VARCHAR(255) NOT NULL,
     host VARCHAR(255) NOT NULL,
     path VARCHAR(255) NOT NULL,
@@ -520,9 +622,38 @@
     available BOOLEAN DEFAULT FALSE,
     xattr BOOLEAN DEFAULT FALSE,
+    mountpoint VARCHAR(255) NOT NULL,
     PRIMARY KEY(vol_id),
+    UNIQUE KEY(name),
+    UNiQUE KEY(path),
     KEY(host(16)),
-    KEY(path(255)),
     KEY(allocate),
-    KEY(available)
+    KEY(available),
+    KEY(mountpoint(255))
+) ENGINE=innodb DEFAULT CHARSET=latin1;
+
+###
+
+CREATE TABLE mountedvol(
+    vol_id INT NOT NULL,
+    FOREIGN KEY(vol_id) REFERENCES volume(vol_id) ON DELETE CASCADE,
+    name VARCHAR(255) NOT NULL,
+    host VARCHAR(255) NOT NULL,
+    path VARCHAR(255) NOT NULL,
+    FOREIGN KEY(path) REFERENCES volume(path) ON DELETE CASCADE,
+    allocate BOOLEAN DEFAULT FALSE,
+    available BOOLEAN DEFAULT FALSE,
+    xattr BOOLEAN DEFAULT FALSE,
+    mountpoint VARCHAR(255) NOT NULL,
+    FOREIGN KEY(mountpoint) REFERENCES volume(mountpoint) ON DELETE CASCADE,
+    total BIGINT NOT NULL,
+    used BIGINT NOT NULL,
+    PRIMARY KEY(vol_id),
+    KEY(name),
+    KEY(host),
+    KEY(path),
+    KEY(allocate),
+    KEY(available),
+    KEY(xattr),
+    KEY(mountpoint(255))
 ) ENGINE=innodb DEFAULT CHARSET=latin1;
 
@@ -532,5 +663,5 @@
     ins_id BIGINT NOT NULL AUTO_INCREMENT,
     so_id BIGINT NOT NULL,
-    FOREIGN KEY(so_id) REFERENCES storage_object(so_id),
+    FOREIGN KEY(so_id) REFERENCES storage_object(so_id) ON DELETE CASCADE,
     vol_id INT NOT NULL,
     FOREIGN KEY(vol_id) REFERENCES volume(vol_id),
@@ -546,13 +677,4 @@
 ###
 
-CREATE TABLE mount (
-    mountpoint VARCHAR(255) NOT NULL,
-    total BIGINT NOT NULL,
-    used BIGINT NOT NULL,
-    PRIMARY KEY(mountpoint)
-) ENGINE=innodb DEFAULT CHARSET=latin1;
-
-###
-
 CREATE TABLE log (
     timestamp TIMESTAMP,
@@ -566,94 +688,11 @@
 ###
 
-CREATE TABLE mountedvol(
-    mountpoint VARCHAR(255) NOT NULL,
-    FOREIGN KEY(mountpoint) REFERENCES mount(mountpoint) ON DELETE CASCADE,
-    total BIGINT NOT NULL,
-    used BIGINT NOT NULL,
+CREATE TABLE deleted (
     vol_id INT NOT NULL,
-    FOREIGN KEY(vol_id) REFERENCES volume(vol_id) ON DELETE CASCADE,
-    name VARCHAR(255) NOT NULL,
-    host VARCHAR(255) NOT NULL,
-    path VARCHAR(255) NOT NULL,
-    FOREIGN KEY(path) REFERENCES volume(path) ON DELETE CASCADE,
-    allocate BOOLEAN DEFAULT FALSE,
-    available BOOLEAN DEFAULT FALSE,
-    xattr BOOLEAN DEFAULT FALSE,
-    PRIMARY KEY(mountpoint),
+    FOREIGN KEY(vol_id) REFERENCES volume(vol_id),
+    uri VARCHAR(255) NOT NULL,
+    timestamp TIMESTAMP,
+    PRIMARY KEY(vol_id, uri),
     KEY(vol_id),
-    KEY(name),
-    KEY(host),
-    KEY(path),
-    KEY(allocate),
-    KEY(available),
-    KEY(xattr)
-) ENGINE=innodb DEFAULT CHARSET=latin1;
-
-###
-
-CREATE PROCEDURE getmountedvol() DETERMINISTIC
-BEGIN
-    DECLARE done BOOLEAN DEFAULT FALSE;
-    DECLARE vol_idvar INT;
-    DECLARE namevar VARCHAR(255);
-    DECLARE hostvar VARCHAR(255);
-    DECLARE pathvar VARCHAR(255);
-    DECLARE allocatevar BOOLEAN;
-    DECLARE availablevar BOOLEAN;
-    DECLARE xattrvar BOOLEAN;
-    DECLARE trans_level VARCHAR(255);
-    DECLARE key_checks BOOLEAN;
-    DECLARE cur1 CURSOR FOR SELECT vol_id, name, host, path, allocate, available, xattr FROM myvolume;
-    DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = TRUE;
-
-    -- store the okey checking state
-    SELECT @@FOREIGN_KEY_CHECKS INTO key_checks; 
-    -- disable foregin check checks to prevent deadlocks on the mountedvol table
-    SET FOREIGN_KEY_CHECKS=0;
-
-    -- make sure the temp table does not already exist... this can happy if the
-    -- stored proc fails for some reason
-    DROP TABLE IF EXISTS myvolume;
-    CREATE TEMPORARY TABLE myvolume LIKE volume;
-    INSERT INTO myvolume SELECT * FROM volume;
-
-    -- store the current transaction level
---    SELECT @@session.tx_isolation INTO trans_level; 
-    -- set trans level to repeatable-read so the volume table does not change
-    -- out from under our cursor
---    SET @@session.tx_isolation = 'REPEATABLE-READ';
-
-    -- iterate over the volume table finding the coresponding entry in the
-    -- mount table and inserting union of the volume & mount row into the
-    -- mountedvol table
-    OPEN cur1;
-
-    DELETE FROM mountedvol;
-
-    myloop: LOOP
-        FETCH cur1 INTO vol_idvar, namevar, hostvar, pathvar, allocatevar, availablevar, xattrvar;
-        IF `done` THEN LEAVE myloop; END IF;
-        INSERT INTO mountedvol
-            SELECT mountpoint, total, used, vol_idvar, namevar, hostvar, pathvar, allocatevar, availablevar, xattrvar
-            FROM
-                (SELECT *, INSTR(pathvar, mountpoint) = 1 as substring
-                FROM mount
-                HAVING substring = 1
-                ORDER BY substring DESC, LENGTH(mountpoint) DESC
-                LIMIT 1) as bar;
-    END LOOP myloop;
-    
-    CLOSE cur1;
-
-    -- restore the original transaction level
---    SET @@session.tx_isolation = trans_level;
-
-    -- restore the original key checking state
-    SET @@FOREIGN_KEY_CHECKS = key_checks; 
-
-    DROP TABLE IF EXISTS myvolume;
-
-    SET FOREIGN_KEY_CHECKS=1;
-
-    COMMIT;
-END 
+    KEY(uri)
+) ENGINE=innodb DEFAULT CHARSET=latin1;
Index: branches/pap/Nebulous-Server/lib/Test/Nebulous.pm
===================================================================
--- branches/pap/Nebulous-Server/lib/Test/Nebulous.pm	(revision 23948)
+++ branches/pap/Nebulous-Server/lib/Test/Nebulous.pm	(revision 25027)
@@ -11,4 +11,5 @@
 use base qw( Exporter );
 
+use Cache::Memcached;
 use DBI;
 use File::Path qw( mkpath rmtree );
@@ -16,9 +17,10 @@
 use Nebulous::Server::SQL;
 
-our @EXPORT = qw( $NEB_DB $NEB_USER $NEB_PASS );
+our @EXPORT = qw( $NEB_DB $NEB_USER $NEB_PASS $NEB_MEMCACHED_SERVERS);
 
 our $NEB_DB     = $ENV{'NEB_DB'}   || "DBI:mysql:database=test:host=localhost";
 our $NEB_USER   = $ENV{'NEB_USER'} || "test";
 our $NEB_PASS   = $ENV{'NEB_PASS'} || '';
+our $NEB_MEMCACHED_SERVERS = ['127.0.0.1:11211'];
 
 my $dbh = DBI->connect( $NEB_DB, $NEB_USER, $NEB_PASS );
@@ -53,34 +55,55 @@
 
     # node01/node02: allocate = TRUE
-    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, allocate, available) VALUES (1, 'node01', 'node01', ?, TRUE, TRUE) }, undef, $dir1);
-    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e7) }, undef, $dir1);
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, mountpoint, allocate, available) VALUES (1, 'node01', 'node01', ?, '/', TRUE, TRUE) }, undef, $dir1);
+    $dbh->do(qq{ INSERT INTO mountedvol SELECT *, 10e10, 10e7 FROM volume WHERE vol_id = ? }, undef, 1);
+#    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e7) }, undef, $dir1);
 
-    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, allocate, available) VALUES (2, 'node02', 'node02', ?, TRUE, TRUE) }, undef, $dir2);
-    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e8) }, undef, $dir2);
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, mountpoint, allocate, available) VALUES (2, 'node02', 'node02', ?, '/', TRUE, TRUE) }, undef, $dir2);
+    $dbh->do(qq{ INSERT INTO mountedvol SELECT *, 10e10, 10e8 FROM volume WHERE vol_id = ? }, undef, 2);
+#    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e8) }, undef, $dir2);
 
     # node03: allocate = TRUE
-    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, allocate, available) VALUES (3, 'node03', 'node03', ?, TRUE, TRUE) }, undef, $dir3);
-    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e8) }, undef, $dir3);
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, mountpoint, allocate, available) VALUES (3, 'node03', 'node03', ?, '/', TRUE, TRUE) }, undef, $dir3);
+    $dbh->do(qq{ INSERT INTO mountedvol SELECT *, 10e10, 10e8 FROM volume WHERE vol_id = ? }, undef, 3);
+#    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e8) }, undef, $dir3);
 
     # node04: allocate = FALSE, available = FALSE
-    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, allocate, available) VALUES (4, 'node04', 'node04', ?, FALSE, FALSE) }, undef, $dir4);
-    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e7) }, undef, $dir4);
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, mountpoint, allocate, available) VALUES (4, 'node04', 'node04', ?, '/', FALSE, FALSE) }, undef, $dir4);
+    $dbh->do(qq{ INSERT INTO mountedvol SELECT *, 10e10, 10e7 FROM volume WHERE vol_id = ? }, undef, 4);
+#    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e7) }, undef, $dir4);
 
     # node05: allocate = FALSE, available = TRUE
-    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, allocate, available) VALUES (5, 'node05', 'node05', ?, FALSE, TRUE) }, undef, $dir5);
-    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e7) }, undef, $dir5);
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, mountpoint, allocate, available) VALUES (5, 'node05', 'node05', ?, '/', FALSE, TRUE) }, undef, $dir5);
+    $dbh->do(qq{ INSERT INTO mountedvol SELECT *, 10e10, 10e7 FROM volume WHERE vol_id = ? }, undef, 5);
+#    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e7) }, undef, $dir5);
 
     # node06: allocate = TRUE, available = FALSE
-    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, allocate, available) VALUES (6, 'node06', 'node06', ?, TRUE, FALSE) }, undef, $dir6);
-    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e7) }, undef, $dir6);
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, mountpoint, allocate, available) VALUES (6, 'node06', 'node06', ?, '/', TRUE, FALSE) }, undef, $dir6);
+    $dbh->do(qq{ INSERT INTO mountedvol SELECT *, 10e10, 10e7 FROM volume WHERE vol_id = ? }, undef, 6);
+#    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e7) }, undef, $dir6);
 
     # node07: full
-    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, allocate, available) VALUES (7, 'node07', 'node07', ?, TRUE, TRUE) }, undef, $dir7);
-    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e10) }, undef, $dir7);
+    $dbh->do(qq{ INSERT INTO volume (vol_id, name, host, path, mountpoint, allocate, available) VALUES (7, 'node07', 'node07', ?, '/', TRUE, TRUE) }, undef, $dir7);
+    $dbh->do(qq{ INSERT INTO mountedvol SELECT *, 10e10, 10e10 FROM volume WHERE vol_id = ? }, undef, 7);
+#    $dbh->do(qq{ INSERT INTO mount VALUES (?, 10e10, 10e10) }, undef, $dir7);
 
-    $dbh->do(qq{ call getmountedvol() });
+#    $dbh->do(qq{ call getmountedvol() });
 }
 
 sub cleanup {
+    # memcached needs to be emptied between test runs
+        Cache::Memcached->new(servers => $NEB_MEMCACHED_SERVERS)->flush_all;
+    # if flush_all() turns out to be useless as feared
+#   my $memd = Cache::Memcached->new(servers => $NEB_MEMCACHED_SERVERS);
+#   my $query = $dbh->prepare("SELECT ext_id FROM storage_object");
+#   if ($query->execute) {
+#        while (my $row = $query->fetchrow_hashref) {
+#            my $ext_id = $row->{ext_id};
+#            warn "found key $ext_id\n" if $memd->get($ext_id);
+#            warn "deleted $ext_id\n" if $memd->delete($ext_id);
+#        }
+#   }
+#   $query->finish;
+
     foreach my $statement (@{ $sql->get_db_clear }) {
         $dbh->do( $statement );
Index: branches/pap/Nebulous-Server/scripts/dirize.pl
===================================================================
--- branches/pap/Nebulous-Server/scripts/dirize.pl	(revision 25027)
+++ branches/pap/Nebulous-Server/scripts/dirize.pl	(revision 25027)
@@ -0,0 +1,75 @@
+#!/usr/bin/env perl
+
+# query to check for duplicate directory entries
+# select count(*) from (select so.so_id, so.ext_id, so.dir_id from storage_object as so join (select dir_id, dirname, parent_id, count(*) from directory group by dirname, parent_id  having count(*) > 1) as foo on so.dir_id = foo.dir_id) as foo;
+
+use strict;
+use warnings;
+
+use Nebulous::Key qw( parse_neb_key );
+use Nebulous::Server;
+use Nebulous::Server::Config;
+use File::Basename qw( basename );
+
+my $config = Nebulous::Server::Config->new(
+    trace       => 'warn',
+);
+$config->add_db(
+    dbindex     => 0,
+    dsn         => 'DBI:mysql:database=nebulous:host=localhost',
+    dbuser      => 'nebulous',
+    dbpasswd    => '@neb@',
+);
+
+my $neb = Nebulous::Server->new_from_config($config);
+
+my $db = $neb->_db_for_index(0);
+
+my $n = 0;
+#{
+#    my $query = $db->prepare("SELECT COUNT(*) as n FROM storage_object");
+#    $query->execute;
+#    $n = $query->fetchrow_hashref->{'n'};
+#}
+
+# repair directory duplication
+#my $query = $db->prepare_cached("select so.so_id, so.ext_id, so.dir_id from storage_object as so join (select dir_id, dirname, parent_id, count(*) from directory group by dirname, parent_id  having count(*) > 1) as foo on so.dir_id = foo.dir_id limit 1000");
+
+# initial directory fill in
+my $work_query = $db->prepare_cached("SELECT so_id, ext_id, dir_id FROM storage_object AS so WHERE so.dir_id = 0 LIMIT 1000");
+
+my $update_query = $db->prepare_cached("UPDATE storage_object SET ext_id_basename = ?, dir_id = ? WHERE so_id = ?");
+
+# turn of fkeys, otherwise we can change storage_object.dir_id to a
+# non-existant value
+$db->do("SET FOREIGN_KEY_CHECKS=0");
+#$db->do("UPDATE storage_object SET dir_id = 0");
+
+# completely reset the directory table
+#$db->do("DELETE FROM directory");
+#$db->do("ALTER TABLE directory AUTO_INCREMENT = 1");
+# make sure these duplicates can't happen again
+#$db->do("alter table directory add unique key(dirname,parent_id)");
+# seed the root ('/') directory
+#$db->do("INSERT INTO directory VALUES(1, '/', 1)");
+
+my $i = 0;
+while ($work_query->execute and $work_query->rows) {
+    while (my $row = $work_query->fetchrow_hashref) {
+        $i++;
+        my $key = parse_neb_key($row->{'ext_id'});
+        my $parent_id = $neb->_resolve_dir_parent_id(key => $key, create => 1);
+
+#printf("dirizing %20s basename: %20s parent_id %10d\n", $key, basename($row->{'ext_id'}), $parent_id);
+#        printf("$i dirizing %s\n", $key->path);
+
+        $update_query->execute(basename($row->{'ext_id'}), $parent_id, $row->{'so_id'});
+    }
+    $db->commit;
+    printf("### COMMIT ###\n");
+    $work_query->finish;
+    printf("dirized $i\n");
+}
+
+$db->do("SET FOREIGN_KEY_CHECKS=1");
+$db->commit;
Index: branches/pap/Nebulous-Server/t/02_config.t
===================================================================
--- branches/pap/Nebulous-Server/t/02_config.t	(revision 23948)
+++ branches/pap/Nebulous-Server/t/02_config.t	(revision 25027)
@@ -8,5 +8,5 @@
 use warnings;
 
-use Test::More tests => 21;
+use Test::More tests => 22;
 
 use lib qw( ./t ./lib );
@@ -109,2 +109,11 @@
     is($config_db1->dbpasswd, "boo2", "dbpasswd");
 }
+
+# memcached_servers
+{
+    my $config = Nebulous::Server::Config->new(
+            memcached_servers => ['127.0.0.1:11211'],
+        );
+
+    is_deeply($config->memcached_servers, ['127.0.0.1:11211'], "memcached_servers");
+}
Index: branches/pap/Nebulous-Server/t/02_server_setup.t
===================================================================
--- branches/pap/Nebulous-Server/t/02_server_setup.t	(revision 23948)
+++ 	(revision )
@@ -1,65 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2004-2005  Joshua Hoblitt
-#
-# $Id: 02_server_setup.t,v 1.8 2008-12-14 22:54:25 eugene Exp $
-
-use strict;
-use warnings;
-
-use Test::More tests => 6;
-
-use lib qw( ./t ./lib );
-
-use Nebulous::Server;
-use Test::Nebulous;
-
-Test::Nebulous->setup;
-
-isa_ok(Nebulous::Server->new(), "Nebulous::Server");
-
-Test::Nebulous->setup;
-
-# ->new()
-{
-    my $neb = Nebulous::Server->new( trace => 'off' );
-
-    ok($neb, "set log level");
-}
-
-Test::Nebulous->setup;
-
-{
-    my $neb = Nebulous::Server->new(
-        dsn         => "DBI:mysql:database=foobar:host=localhost",
-        dbuser      => "baz",
-        dbpasswd    =>"boo",
-    );
-
-    ok($neb, "set database");
-}
-
-Test::Nebulous->setup;
-
-# add dbs after ->new()
-{
-    my $neb = Nebulous::Server->new;
-
-    ok($neb->config->add_db(
-        dbindex    => 0,
-        dsn         => "DBI:mysql:database=foobar:host=localhost",
-        dbuser      => "baz",
-        dbpasswd    =>"boo",
-    ), "add db");
-    
-    ok($neb->config->add_db(
-        dbindex    => 1,
-        dsn         => "DBI:mysql:database=foobar:host=localhost",
-        dbuser      => "baz",
-        dbpasswd    =>"boo",
-    ), "add dbs");
-
-    is($neb->config->n_db, 2, "n dbs")
-}
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/03_server_create_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/03_server_create_object.t	(revision 23948)
+++ 	(revision )
@@ -1,615 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2004-2005  Joshua Hoblitt
-#
-# $Id: 03_server_create_object.t,v 1.30 2008-09-11 22:35:52 jhoblitt Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 99;
-
-use lib qw( ./t ./lib );
-
-use File::Basename qw( basename );
-use File::ExtAttr qw( getfattr );
-use Nebulous::Server;
-use Test::Nebulous;
-use Test::URI;
-use URI::Split qw( uri_split );
-
-my $test_xattr = undef;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-use Test::DBUnit dsn => $NEB_DB, username => $NEB_USER, password => $NEB_PASS;
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri = $neb->create_object("foo");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri = $neb->create_object("neb:/foo");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri = $neb->create_object("neb://node01/foo");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri = $neb->create_object("neb://node02/foo");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri = $neb->create_object("/foo");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), '/foo', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-{
-    # key
-    my $uri = $neb->create_object("neb:///foo");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri = $neb->create_object("/foo/");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), '/foo/', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri = $neb->create_object("neb:/foo/");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), '/foo/', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri = $neb->create_object("foo/");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), 'foo/', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri = $neb->create_object("foo/bar");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo/bar"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), 'foo/bar', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri = $neb->create_object("/foo/bar");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo/bar"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), '/foo/bar', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key, volume
-    my $uri = $neb->create_object("foo", "node01");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-}
-
-Test::Nebulous->setup;
-
-{
-    # volume name override
-    my $uri = $neb->create_object("neb://node02/foo", "node01");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-}
-
-Test::Nebulous->setup;
-
-{
-    # volume name override 
-    # OK because the volume arg overrides the key's  implied volume
-    my $uri = $neb->create_object("neb://99/foo", "node01");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-}
-
-Test::Nebulous->setup;
-
-{
-    # undef volume name
-    # OK because the undef is ignored
-    my $uri = $neb->create_object("neb://node01/foo", undef);
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-}
-
-Test::Nebulous->setup;
-
-{
-    # soft volume name request
-    my $uri = $neb->create_object("neb://~node01/foo");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-}
-
-Test::Nebulous->setup;
-
-{
-    # soft volume name request
-    my $uri = $neb->create_object("neb://node01/foo", "~node02");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-}
-
-Test::Nebulous->setup;
-
-{
-    # any volume name request
-    my $uri = $neb->create_object("neb://any/foo");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-}
-
-Test::Nebulous->setup;
-
-{
-    # any volume name request
-    my $uri = $neb->create_object("neb://~any/foo");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-}
-
-Test::Nebulous->setup;
-
-{
-    # any volume name request
-    my $uri = $neb->create_object("neb://node01/foo", "any");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-}
-
-Test::Nebulous->setup;
-
-{
-    # any volume name request
-    my $uri = $neb->create_object("neb://node01/foo", "~any");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    ok($neb->find_instances("foo"), 'object key exists');
-    uri_scheme_ok($uri, 'file');
-}
-
-# test for properly row creation in the directories table
-
-Test::Nebulous->setup;
-
-{
-    my $key = "foo";
-    $neb->create_object($key);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        storage_object  => [so_id => 1, ext_id => $key, dir_id => 1],
-    );
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key = "a/foo";
-    $neb->create_object($key);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
-        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 2],
-    );
-
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key = "a/b/foo";
-    $neb->create_object($key);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
-        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
-        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 3],
-    );
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key = "a/b/c/foo";
-    $neb->create_object($key);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
-        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
-        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
-        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 4],
-    );
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key1 = "a/b/c/foo";
-    my $key2 = "foo";
-    $neb->create_object($key1);
-    $neb->create_object($key2);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 1],
-        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
-        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
-        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
-        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
-    );
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key1 = "a/b/c/foo";
-    my $key2 = "a/foo";
-    $neb->create_object($key1);
-    $neb->create_object($key2);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
-        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 2],
-        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
-        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
-        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
-    );
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key1 = "a/b/c/foo";
-    my $key2 = "d/foo";
-    $neb->create_object($key1);
-    $neb->create_object($key2);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
-        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
-        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
-        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
-        directory       => [dir_id => 5, dirname => 'd', parent_id => 1],
-        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 5],
-    );
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key1 = "a/b/c/foo";
-    my $key2 = "a/d/foo";
-    $neb->create_object($key1);
-    $neb->create_object($key2);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
-        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
-        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
-        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
-        directory       => [dir_id => 5, dirname => 'd', parent_id => 2],
-        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 5],
-    );
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key1 = "a/b/c/foo";
-    my $key2 = "d/a/foo";
-    $neb->create_object($key1);
-    $neb->create_object($key2);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
-        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
-        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
-        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
-        directory       => [dir_id => 5, dirname => 'd', parent_id => 1],
-        directory       => [dir_id => 6, dirname => 'a', parent_id => 5],
-        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 6],
-    );
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key1 = "a/b/c/foo";
-    my $key2 = "a/b/c/d/foo";
-    $neb->create_object($key1);
-    $neb->create_object($key2);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
-        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
-        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
-        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
-        directory       => [dir_id => 5, dirname => 'd', parent_id => 4],
-        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 5],
-    );
-}
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-    $neb->create_object("foo");
-};
-like($@, qr/Duplicate entry/, "object already exists");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("neb:/foo");
-    $neb->create_object("neb:/foo");
-};
-like($@, qr/Duplicate entry/, "object already exists");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo", '~node07');
-};
-like($@, qr/node07 is not available/, "request volume this is full");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("neb://~node07/foo");
-};
-like($@, qr/node07 is not available/, "request volume this is full");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo", '~node04');
-};
-like($@, qr/node04 is not available/, "request volume with allocate = FALSE, available = FALSE");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("neb://~node04/foo");
-};
-like($@, qr/node04 is not available/, "request volume with allocate = FALSE, available = FALSE");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo", '~node05');
-};
-like($@, qr/node05 is not available/, "request volume with allocate = FALSE , available = TRUE");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("neb://~node05/foo");
-};
-like($@, qr/node05 is not available/, "request volume with allocate = FALSE , available = TRUE");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo", '~node06');
-};
-like($@, qr/node06 is not available/, "request volume with allocate = TRUE, available = FALSE");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("neb://~node06/foo");
-};
-like($@, qr/node06 is not available/, "request volume with allocate = TRUE, available = FALSE");
-
-Test::Nebulous->setup;
-
-{
-    ok($neb->create_object("foo", 99));
-}
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo", "~99");
-};
-like($@, qr/is not a valid volume name/, "volume name doesn't exist");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("neb://~99/foo");
-};
-like($@, qr/is not a valid volume name/, "volume name doesn't exist");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object();
-};
-like($@, qr/1 - 2 were expected/, "no params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object(1, "node01", 3);
-};
-like($@, qr/1 - 2 were expected/, "too many params");
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/03_server_setup.t
===================================================================
--- branches/pap/Nebulous-Server/t/03_server_setup.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/03_server_setup.t	(revision 25027)
@@ -0,0 +1,71 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 02_server_setup.t,v 1.8 2008-12-14 22:54:25 eugene Exp $
+
+use strict;
+use warnings;
+
+use Test::More tests => 6;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+Test::Nebulous->setup;
+
+# ->new()
+
+{
+    my $neb = Nebulous::Server->new(
+        dsn         => $NEB_DB,
+        dbuser      => $NEB_USER,
+        dbpasswd    => $NEB_PASS,
+    );
+
+    ok($neb, "set database");
+}
+
+Test::Nebulous->setup;
+
+{
+    my $neb = Nebulous::Server->new(
+        dsn         => $NEB_DB,
+        dbuser      => $NEB_USER,
+        dbpasswd    => $NEB_PASS,
+        trace       => 'off',
+    );
+
+    ok($neb, "set log level");
+}
+
+Test::Nebulous->setup;
+
+# add dbs after ->new()
+{
+    my $config = Nebulous::Server::Config->new;
+
+    ok($config->add_db(
+        dbindex    => 0,
+        dsn         => $NEB_DB,
+        dbuser      => $NEB_USER,
+        dbpasswd    => $NEB_PASS,
+    ), "add db");
+    
+    ok($config->add_db(
+        dbindex    => 1,
+        dsn         => $NEB_DB,
+        dbuser      => $NEB_USER,
+        dbpasswd    => $NEB_PASS,
+    ), "add dbs");
+
+    is($config->n_db, 2, "n dbs");
+
+    my $neb = Nebulous::Server->new_from_config($config);
+     
+    isa_ok($neb, 'Nebulous::Server');
+}
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/04_server_create_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/04_server_create_object.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/04_server_create_object.t	(revision 25027)
@@ -0,0 +1,615 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 03_server_create_object.t,v 1.30 2008-09-11 22:35:52 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 99;
+
+use lib qw( ./t ./lib );
+
+use File::Basename qw( basename );
+use File::ExtAttr qw( getfattr );
+use Nebulous::Server;
+use Test::Nebulous;
+use Test::URI;
+use URI::Split qw( uri_split );
+
+my $test_xattr = undef;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+use Test::DBUnit dsn => $NEB_DB, username => $NEB_USER, password => $NEB_PASS;
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("neb:/foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("neb://node01/foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("neb://node02/foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("/foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), '/foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+{
+    # key
+    my $uri = $neb->create_object("neb:///foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("/foo/");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), '/foo/', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("neb:/foo/");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), '/foo/', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("foo/");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo/', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("foo/bar");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo/bar"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo/bar', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("/foo/bar");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo/bar"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), '/foo/bar', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, volume
+    my $uri = $neb->create_object("foo", "node01");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+{
+    # volume name override
+    my $uri = $neb->create_object("neb://node02/foo", "node01");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+{
+    # volume name override 
+    # OK because the volume arg overrides the key's  implied volume
+    my $uri = $neb->create_object("neb://99/foo", "node01");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+{
+    # undef volume name
+    # OK because the undef is ignored
+    my $uri = $neb->create_object("neb://node01/foo", undef);
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+{
+    # soft volume name request
+    my $uri = $neb->create_object("neb://~node01/foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+{
+    # soft volume name request
+    my $uri = $neb->create_object("neb://node01/foo", "~node02");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+{
+    # any volume name request
+    my $uri = $neb->create_object("neb://any/foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+{
+    # any volume name request
+    my $uri = $neb->create_object("neb://~any/foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+{
+    # any volume name request
+    my $uri = $neb->create_object("neb://node01/foo", "any");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+Test::Nebulous->setup;
+
+{
+    # any volume name request
+    my $uri = $neb->create_object("neb://node01/foo", "~any");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    ok($neb->find_instances("foo"), 'object key exists');
+    uri_scheme_ok($uri, 'file');
+}
+
+# test for properly row creation in the directories table
+
+Test::Nebulous->setup;
+
+{
+    my $key = "foo";
+    $neb->create_object($key);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        storage_object  => [so_id => 1, ext_id => $key, dir_id => 1],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key = "a/foo";
+    $neb->create_object($key);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 2],
+    );
+
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key = "a/b/foo";
+    $neb->create_object($key);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 3],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key = "a/b/c/foo";
+    $neb->create_object($key);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 4],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "a/b/c/foo";
+    my $key2 = "foo";
+    $neb->create_object($key1);
+    $neb->create_object($key2);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "a/b/c/foo";
+    my $key2 = "a/foo";
+    $neb->create_object($key1);
+    $neb->create_object($key2);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 2],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "a/b/c/foo";
+    my $key2 = "d/foo";
+    $neb->create_object($key1);
+    $neb->create_object($key2);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
+        directory       => [dir_id => 5, dirname => 'd', parent_id => 1],
+        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 5],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "a/b/c/foo";
+    my $key2 = "a/d/foo";
+    $neb->create_object($key1);
+    $neb->create_object($key2);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
+        directory       => [dir_id => 5, dirname => 'd', parent_id => 2],
+        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 5],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "a/b/c/foo";
+    my $key2 = "d/a/foo";
+    $neb->create_object($key1);
+    $neb->create_object($key2);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
+        directory       => [dir_id => 5, dirname => 'd', parent_id => 1],
+        directory       => [dir_id => 6, dirname => 'a', parent_id => 5],
+        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 6],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "a/b/c/foo";
+    my $key2 = "a/b/c/d/foo";
+    $neb->create_object($key1);
+    $neb->create_object($key2);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        directory       => [dir_id => 3, dirname => 'b', parent_id => 2],
+        directory       => [dir_id => 4, dirname => 'c', parent_id => 3],
+        storage_object  => [so_id => 1, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 4],
+        directory       => [dir_id => 5, dirname => 'd', parent_id => 4],
+        storage_object  => [so_id => 2, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 5],
+    );
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+    $neb->create_object("foo");
+};
+like($@, qr/Duplicate entry/, "object already exists");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("neb:/foo");
+    $neb->create_object("neb:/foo");
+};
+like($@, qr/Duplicate entry/, "object already exists");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo", '~node07');
+};
+like($@, qr/node07 is not available/, "request volume this is full");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("neb://~node07/foo");
+};
+like($@, qr/node07 is not available/, "request volume this is full");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo", '~node04');
+};
+like($@, qr/node04 is not available/, "request volume with allocate = FALSE, available = FALSE");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("neb://~node04/foo");
+};
+like($@, qr/node04 is not available/, "request volume with allocate = FALSE, available = FALSE");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo", '~node05');
+};
+like($@, qr/node05 is not available/, "request volume with allocate = FALSE , available = TRUE");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("neb://~node05/foo");
+};
+like($@, qr/node05 is not available/, "request volume with allocate = FALSE , available = TRUE");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo", '~node06');
+};
+like($@, qr/node06 is not available/, "request volume with allocate = TRUE, available = FALSE");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("neb://~node06/foo");
+};
+like($@, qr/node06 is not available/, "request volume with allocate = TRUE, available = FALSE");
+
+Test::Nebulous->setup;
+
+{
+    ok($neb->create_object("foo", 99));
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo", "~99");
+};
+like($@, qr/is not a valid volume name/, "volume name doesn't exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("neb://~99/foo");
+};
+like($@, qr/is not a valid volume name/, "volume name doesn't exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object();
+};
+like($@, qr/1 - 2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object(1, "node01", 3);
+};
+like($@, qr/1 - 2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/04_server_replicate_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/04_server_replicate_object.t	(revision 23948)
+++ 	(revision )
@@ -1,185 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2004-2005  Joshua Hoblitt
-#
-# $Id: 04_server_replicate_object.t,v 1.16 2008-09-11 22:35:52 jhoblitt Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 26;
-
-use lib qw( ./t ./lib );
-
-use File::ExtAttr qw( getfattr );
-use Nebulous::Server;
-use Test::Nebulous;
-use Test::URI;
-use URI::Split qw( uri_split );
-
-my $test_xattr = undef;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-Test::Nebulous->setup;
-
-{
-    # key
-    $neb->create_object("foo");
-    my $uri = $neb->replicate_object("foo");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key, $volume
-    $neb->create_object("foo");
-    my $uri = $neb->replicate_object("foo", "node01");
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key, $volume is undef
-    $neb->create_object("foo");
-    my $uri = $neb->replicate_object("foo", undef);
-
-    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
-    ok(-e $path, "file exists");
-    uri_scheme_ok($uri, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-}
-
-Test::Nebulous->setup;
-
-{
-    # key, $volume
-    $neb->create_object("foo");
-    my $uri1 = $neb->replicate_object("foo", "~node01");
-    my $uri2 = $neb->replicate_object("foo", "~node02");
-
-    {
-        my ($scheme, $auth, $path, $query, $frag) = uri_split($uri1);
-        ok(-e $path, "file exists");
-        uri_scheme_ok($uri1, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-        is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-    }
-
-    {
-        my ($scheme, $auth, $path, $query, $frag) = uri_split($uri2);
-        ok(-e $path, "file exists");
-        uri_scheme_ok($uri2, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-        is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-    }
-}
-
-Test::Nebulous->setup;
-
-{
-    # key, $volume
-    $neb->create_object("foo");
-    my $uri1 = $neb->replicate_object("foo", "any");
-
-    {
-        my ($scheme, $auth, $path, $query, $frag) = uri_split($uri1);
-        ok(-e $path, "file exists");
-        uri_scheme_ok($uri1, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-        is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-    }
-}
-
-Test::Nebulous->setup;
-
-{
-    # key, $volume
-    $neb->create_object("foo");
-    my $uri1 = $neb->replicate_object("foo", "~any");
-
-    {
-        my ($scheme, $auth, $path, $query, $frag) = uri_split($uri1);
-        ok(-e $path, "file exists");
-        uri_scheme_ok($uri1, 'file');
-
-SKIP: {
-    skip "requires xattr support", 1 unless $test_xattr;
-        is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
-}
-    }
-}
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->replicate_object('foo');
-};
-like($@, qr/is valid object key/, 'storage object does not exist');
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-    ok($neb->replicate_object('foo', 'bar'),'soft fake storage volume');
-};
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-    $neb->replicate_object('foo', '~bar');
-};
-like($@, qr/is not a valid volume name/, 'storage volume does not exist');
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->replicate_object();
-};
-like($@, qr/1 - 2 were expected/, 'no params');
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-    $neb->replicate_object('foo', 'node01', 3);
-};
-like($@, qr/1 - 2 were expected/, 'too many params');
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/05_server_lock_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/05_server_lock_object.t	(revision 23948)
+++ 	(revision )
@@ -1,126 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2004-2005  Joshua Hoblitt
-#
-# $Id: 05_server_lock_object.t,v 1.10 2008-03-20 21:10:14 jhoblitt Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 13;
-
-use lib qw( ./t ./lib );
-
-use Nebulous::Server;
-use Test::Nebulous;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("foo");
-
-    ok($neb->lock_object("foo", "read"), "read lock");
-}
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("foo");
-
-    ok($neb->lock_object("foo", "read"), "read lock");
-    ok($neb->lock_object("foo", "read"), "read lock");
-}
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("foo");
-
-    ok($neb->lock_object("foo", "write"), "write lock");
-}
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->lock_object("foo", "read");
-};
-like($@, qr/is valid object key/, "storage object does not exist");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->lock_object("foo", "write");
-};
-like($@, qr/is valid object key/, "storage object does not exist");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->lock_object("foo", "write");
-    $neb->lock_object("foo", "write");
-};
-like($@, qr/can not write lock twice/, "can not write lock twice");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->lock_object("foo", "read");
-    $neb->lock_object("foo", "write");
-};
-like($@, qr/can not write lock after read lock/, "can not write lock after read lock");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->lock_object("foo", "write");
-    $neb->lock_object("foo", "read");
-};
-like($@, qr/can not read lock after write lock/, "can not read lock after write lock");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->lock_object();
-};
-like($@, qr/2 were expected/, "no params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->lock_object("foo");
-};
-like($@, qr/2 were expected/, "not enough params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->lock_object("foo", "both");
-};
-like($@, qr/is read or write/, "not read or write");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->lock_object("foo", 'read', 3);
-};
-like($@, qr/2 were expected/, "too many params");
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/05_server_replicate_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/05_server_replicate_object.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/05_server_replicate_object.t	(revision 25027)
@@ -0,0 +1,186 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 04_server_replicate_object.t,v 1.16 2008-09-11 22:35:52 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 25;
+
+use lib qw( ./t ./lib );
+
+use File::ExtAttr qw( getfattr );
+use Nebulous::Server;
+use Test::Nebulous;
+use Test::URI;
+use URI::Split qw( uri_split );
+
+my $test_xattr = undef;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+    memcached_servers => $NEB_MEMCACHED_SERVERS,
+);
+
+Test::Nebulous->setup;
+
+{
+    # key
+    $neb->create_object("foo");
+    my $uri = $neb->replicate_object("foo");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, $volume
+    $neb->create_object("foo");
+    my $uri = $neb->replicate_object("foo", "node01");
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, $volume is undef
+    $neb->create_object("foo");
+    my $uri = $neb->replicate_object("foo", undef);
+
+    my ($scheme, $auth, $path, $query, $frag) = uri_split($uri);
+    ok(-e $path, "file exists");
+    uri_scheme_ok($uri, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+    is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, $volume
+    $neb->create_object("foo");
+    my $uri1 = $neb->replicate_object("foo", "~node01");
+    my $uri2 = $neb->replicate_object("foo", "~node02");
+
+    {
+        my ($scheme, $auth, $path, $query, $frag) = uri_split($uri1);
+        ok(-e $path, "file exists");
+        uri_scheme_ok($uri1, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+        is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+    }
+
+    {
+        my ($scheme, $auth, $path, $query, $frag) = uri_split($uri2);
+        ok(-e $path, "file exists");
+        uri_scheme_ok($uri2, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+        is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+    }
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, $volume
+    $neb->create_object("foo");
+    my $uri1 = $neb->replicate_object("foo", "any");
+
+    {
+        my ($scheme, $auth, $path, $query, $frag) = uri_split($uri1);
+        ok(-e $path, "file exists");
+        uri_scheme_ok($uri1, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+        is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+    }
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, $volume
+    $neb->create_object("foo");
+    my $uri1 = $neb->replicate_object("foo", "~any");
+
+    {
+        my ($scheme, $auth, $path, $query, $frag) = uri_split($uri1);
+        ok(-e $path, "file exists");
+        uri_scheme_ok($uri1, 'file');
+
+SKIP: {
+    skip "requires xattr support", 1 unless $test_xattr;
+        is(getfattr($path, 'user.nebulous_key'), 'foo', 'user.nebulous_key xattr');
+}
+    }
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->replicate_object('foo');
+};
+like($@, qr/is valid object key/, 'storage object does not exist');
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+    ok($neb->replicate_object('foo', 'bar'),'soft fake storage volume');
+};
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+    $neb->replicate_object('foo', '~bar');
+};
+like($@, qr/is not a valid volume name/, 'storage volume does not exist');
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->replicate_object();
+};
+like($@, qr/1 - 2 were expected/, 'no params');
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+    $neb->replicate_object('foo', 'node01', 3);
+};
+like($@, qr/1 - 2 were expected/, 'too many params');
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/06_server_lock_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/06_server_lock_object.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/06_server_lock_object.t	(revision 25027)
@@ -0,0 +1,126 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 05_server_lock_object.t,v 1.10 2008-03-20 21:10:14 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 13;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    ok($neb->lock_object("foo", "read"), "read lock");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    ok($neb->lock_object("foo", "read"), "read lock");
+    ok($neb->lock_object("foo", "read"), "read lock");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    ok($neb->lock_object("foo", "write"), "write lock");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->lock_object("foo", "read");
+};
+like($@, qr/is valid object key/, "storage object does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->lock_object("foo", "write");
+};
+like($@, qr/is valid object key/, "storage object does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "write");
+    $neb->lock_object("foo", "write");
+};
+like($@, qr/can not write lock twice/, "can not write lock twice");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "read");
+    $neb->lock_object("foo", "write");
+};
+like($@, qr/can not write lock after read lock/, "can not write lock after read lock");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "write");
+    $neb->lock_object("foo", "read");
+};
+like($@, qr/can not read lock after write lock/, "can not read lock after write lock");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->lock_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo");
+};
+like($@, qr/2 were expected/, "not enough params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "both");
+};
+like($@, qr/is read or write/, "not read or write");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", 'read', 3);
+};
+like($@, qr/2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/06_server_unlock_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/06_server_unlock_object.t	(revision 23948)
+++ 	(revision )
@@ -1,141 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2004-2005  Joshua Hoblitt
-#
-# $Id: 06_server_unlock_object.t,v 1.10 2008-03-20 21:10:14 jhoblitt Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 14;
-
-use lib qw( ./t ./lib );
-
-use Nebulous::Server;
-use Test::Nebulous;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("foo");
-
-    $neb->lock_object("foo", "read");
-
-    ok($neb->unlock_object("foo", "read"), "read unlock");
-}
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("foo");
-
-    $neb->lock_object("foo", "read");
-    $neb->lock_object("foo", "read");
-
-    ok($neb->unlock_object("foo", "read"), "read unlock");
-    ok($neb->unlock_object("foo", "read"), "read unlock");
-}
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("foo");
-
-    $neb->lock_object("foo", "write");
-
-    ok($neb->unlock_object("foo", "write"), "write unlock");
-}
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->unlock_object("foo", "read");
-};
-like($@, qr/is valid object key/, "storage object does not exist");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->unlock_object("foo", "write");
-};
-like($@, qr/is valid object key/, "storage object does not exist");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->unlock_object("foo", "read");
-};
-like($@, qr/can not remove non-existant read lock/, "no lock set");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->unlock_object("foo", "write");
-};
-like($@, qr/can not remove non-existant write lock/, "no lock set");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-    $neb->lock_object("foo", "write");
-
-    $neb->unlock_object("foo", "read");
-};
-like($@, qr/can not have a read lock under a write lock/, "read unlock under write lock");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-    $neb->lock_object("foo", "read");
-
-    $neb->unlock_object("foo", "write");
-};
-like($@, qr/can not have a write lock under a read lock/, "write unlock under read lock");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->unlock_object();
-};
-like($@, qr/2 were expected/, "no params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->unlock_object("foo");
-};
-like($@, qr/2 were expected/, "not enough params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->unlock_object("foo", "both");
-};
-like($@, qr/is read or write/, "not read or write");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->unlock_object("foo", 'read', 3);
-};
-like($@, qr/2 were expected/, "too many params");
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/07_server_find_instances.t
===================================================================
--- branches/pap/Nebulous-Server/t/07_server_find_instances.t	(revision 23948)
+++ 	(revision )
@@ -1,142 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2004-2005  Joshua Hoblitt
-#
-# $Id: 07_server_find_instances.t,v 1.16 2008-09-11 22:35:52 jhoblitt Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 19;
-
-use lib qw( ./t ./lib );
-
-use Nebulous::Server;
-use Test::URI;
-use Test::Nebulous;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri = $neb->create_object("foo");
-
-    my $locations = $neb->find_instances("foo");
-
-    uri_scheme_ok($locations->[0], 'file');
-    is($uri, $locations->[0], "URIs match");
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri1 = $neb->create_object("foo");
-    my $uri2 = $neb->replicate_object("foo");
-
-    my $locations = $neb->find_instances("foo");
-
-    uri_scheme_ok($locations->[0], 'file');
-    uri_scheme_ok($locations->[1], 'file');
-    ok(eq_set([$uri1, $uri2], $locations), "URIs match");
-}
-
-Test::Nebulous->setup;
-
-{
-    # key, volume
-    my $uri = $neb->create_object('foo', 'node01');
-
-    my $locations = $neb->find_instances('foo', 'node01');
-
-    uri_scheme_ok($locations->[0], 'file');
-    is($uri, $locations->[0], "URIs match");
-}
-
-Test::Nebulous->setup;
-
-{
-    # key, volume
-    my $uri = $neb->create_object('foo', 'node01');
-
-    my $locations = $neb->find_instances('foo', undef);
-
-    uri_scheme_ok($locations->[0], 'file');
-    is($uri, $locations->[0], "URIs match");
-}
-
-Test::Nebulous->setup;
-
-{
-    # key, volume
-    my $uri = $neb->create_object('foo', 'node01');
-
-    my $locations = $neb->find_instances('neb://node02/foo', "~any");
-
-    uri_scheme_ok($locations->[0], 'file');
-    is($uri, $locations->[0], "URIs match");
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $uri1 = $neb->create_object("foo");
-    my $uri2 = $neb->replicate_object("foo");
-
-    my $locations = $neb->find_instances("foo", "~any");
-
-    uri_scheme_ok($locations->[0], 'file');
-    uri_scheme_ok($locations->[1], 'file');
-    ok(eq_set([$uri1, $uri2], $locations), "URIs match");
-}
-
-# object exists but instance is on a different volume
-Test::Nebulous->setup;
-
-eval {
-    # key, volume
-    my $uri = $neb->create_object('foo', '~node01');
-
-    my $locations = $neb->find_instances('foo', '~node02');
-};
-like($@, qr/no instances on storage volume/, 'instances on a different volume');
-
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->find_instances('foo');
-};
-like($@, qr/is valid object key/, "storage object does not exist");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-    $neb->find_instances('foo', '~bar');
-};
-like($@, qr/is not a valid volume name/, "storage volume does not exist");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->find_instances();
-};
-like($@, qr/1 - 2 were expected/, "no params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-    $neb->find_instances('foo', 'node01', 3);
-};
-like($@, qr/1 - 2 were expected/, "too many params");
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/07_server_unlock_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/07_server_unlock_object.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/07_server_unlock_object.t	(revision 25027)
@@ -0,0 +1,141 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 06_server_unlock_object.t,v 1.10 2008-03-20 21:10:14 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 14;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "read");
+
+    ok($neb->unlock_object("foo", "read"), "read unlock");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "read");
+    $neb->lock_object("foo", "read");
+
+    ok($neb->unlock_object("foo", "read"), "read unlock");
+    ok($neb->unlock_object("foo", "read"), "read unlock");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    $neb->lock_object("foo", "write");
+
+    ok($neb->unlock_object("foo", "write"), "write unlock");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->unlock_object("foo", "read");
+};
+like($@, qr/is valid object key/, "storage object does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->unlock_object("foo", "write");
+};
+like($@, qr/is valid object key/, "storage object does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->unlock_object("foo", "read");
+};
+like($@, qr/can not remove non-existant read lock/, "no lock set");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->unlock_object("foo", "write");
+};
+like($@, qr/can not remove non-existant write lock/, "no lock set");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+    $neb->lock_object("foo", "write");
+
+    $neb->unlock_object("foo", "read");
+};
+like($@, qr/can not have a read lock under a write lock/, "read unlock under write lock");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+    $neb->lock_object("foo", "read");
+
+    $neb->unlock_object("foo", "write");
+};
+like($@, qr/can not have a write lock under a read lock/, "write unlock under read lock");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->unlock_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->unlock_object("foo");
+};
+like($@, qr/2 were expected/, "not enough params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->unlock_object("foo", "both");
+};
+like($@, qr/is read or write/, "not read or write");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->unlock_object("foo", 'read', 3);
+};
+like($@, qr/2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/08_server_delete_instance.t
===================================================================
--- branches/pap/Nebulous-Server/t/08_server_delete_instance.t	(revision 23948)
+++ 	(revision )
@@ -1,80 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2004-2005  Joshua Hoblitt
-#
-# $Id: 08_server_delete_instance.t,v 1.10.22.1 2008-12-14 22:52:37 eugene Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 8;
-
-use lib qw( ./t ./lib );
-
-use Nebulous::Server;
-use Test::Nebulous;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-Test::Nebulous->setup;
-
-{
-    my $key = "foo";
-    my $uri = $neb->create_object($key);
-
-    ok($neb->delete_instance($key, $uri), "delete instance");
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key = "foo";
-    my $uri1 = $neb->create_object($key);
-    my $uri2 = $neb->replicate_object($key);
-
-    ok($neb->delete_instance($key, $uri1), "delete instance");
-
-    my $locations = $neb->find_instances($key);
-
-    is($locations->[0], $uri2, "instance remains");
-
-    ok($neb->delete_instance($key, $uri2), "delete instance");
-
-    eval {
-        $neb->find_instances($key);
-    };
-    like($@, qr/is valid object key/, "storage object was deleted");
-}
-
-Test::Nebulous->setup;
-
-eval {
-    my $key = "foo";
-    my $uri1 = $neb->create_object($key);
-
-    $neb->delete_instance($key, "file:/foo");
-};
-like($@, qr/no instance is associated with uri/, "uri does not exist");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->delete_instance();
-};
-like($@, qr/2 were expected/, "no params");
-
-Test::Nebulous->setup;
-
-eval {
-    my $key = "foo";
-    my $uri1 = $neb->create_object($key);
-
-    $neb->delete_instance("foo", 2, 3);
-};
-like($@, qr/2 were expected/, "too many params");
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/08_server_find_instances.t
===================================================================
--- branches/pap/Nebulous-Server/t/08_server_find_instances.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/08_server_find_instances.t	(revision 25027)
@@ -0,0 +1,141 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 07_server_find_instances.t,v 1.16 2008-09-11 22:35:52 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 19;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::URI;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri = $neb->create_object("foo");
+
+    my $locations = $neb->find_instances("foo");
+
+    uri_scheme_ok($locations->[0], 'file');
+    is($uri, $locations->[0], "URIs match");
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri1 = $neb->create_object("foo");
+    my $uri2 = $neb->replicate_object("foo");
+
+    my $locations = $neb->find_instances("foo");
+
+    uri_scheme_ok($locations->[0], 'file');
+    uri_scheme_ok($locations->[1], 'file');
+    ok(eq_set([$uri1, $uri2], $locations), "URIs match");
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, volume
+    my $uri = $neb->create_object('foo', 'node01');
+
+    my $locations = $neb->find_instances('foo', 'node01');
+
+    uri_scheme_ok($locations->[0], 'file');
+    is($uri, $locations->[0], "URIs match");
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, volume
+    my $uri = $neb->create_object('foo', 'node01');
+
+    my $locations = $neb->find_instances('foo', undef);
+
+    uri_scheme_ok($locations->[0], 'file');
+    is($uri, $locations->[0], "URIs match");
+}
+
+Test::Nebulous->setup;
+
+{
+    # key, volume
+    my $uri = $neb->create_object('foo', 'node01');
+
+    my $locations = $neb->find_instances('neb://node02/foo', "~any");
+
+    uri_scheme_ok($locations->[0], 'file');
+    is($uri, $locations->[0], "URIs match");
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $uri1 = $neb->create_object("foo");
+    my $uri2 = $neb->replicate_object("foo");
+
+    my $locations = $neb->find_instances("foo", "~any");
+
+    uri_scheme_ok($locations->[0], 'file');
+    uri_scheme_ok($locations->[1], 'file');
+    ok(eq_set([$uri1, $uri2], $locations), "URIs match");
+}
+
+# object exists but instance is on a different volume
+Test::Nebulous->setup;
+
+eval {
+    # key, volume
+    my $uri = $neb->create_object('foo', '~node01');
+
+    my $locations = $neb->find_instances('foo', '~node02');
+};
+like($@, qr/no instances on storage volume/, 'instances on a different volume');
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->find_instances('foo');
+};
+like($@, qr/is valid object key/, "storage object does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+    $neb->find_instances('foo', '~bar');
+};
+like($@, qr/is not a valid volume name/, "storage volume does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->find_instances();
+};
+like($@, qr/1 - 2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+    $neb->find_instances('foo', 'node01', 3);
+};
+like($@, qr/1 - 2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/09_server_delete_instance.t
===================================================================
--- branches/pap/Nebulous-Server/t/09_server_delete_instance.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/09_server_delete_instance.t	(revision 25027)
@@ -0,0 +1,105 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 08_server_delete_instance.t,v 1.10.22.1 2008-12-14 22:52:37 eugene Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 11;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+use Test::DBUnit dsn => $NEB_DB, username => $NEB_USER, password => $NEB_PASS;
+
+Test::Nebulous->setup;
+
+{
+    my $key = "foo";
+    my $uri = $neb->create_object($key);
+
+    ok($neb->delete_instance($key, $uri), "delete instance");
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key = "foo";
+    my $uri1 = $neb->create_object($key);
+    my $uri2 = $neb->replicate_object($key);
+
+    ok($neb->delete_instance($key, $uri1), "delete instance");
+
+    my $locations = $neb->find_instances($key);
+
+    is($locations->[0], $uri2, "instance remains");
+
+    ok($neb->delete_instance($key, $uri2), "delete instance");
+
+    eval {
+        $neb->find_instances($key);
+    };
+    like($@, qr/is valid object key/, "storage object was deleted");
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key = "foo";
+    my $uri1 = $neb->create_object($key, 'node01');
+    my $uri2 = $neb->replicate_object($key, 'node02');
+
+    # make one of the instances unavailable
+    my $dbh = test_dbh();
+    $dbh->do("UPDATE mountedvol SET available = 0 WHERE name = 'node01'");
+
+    ok($neb->delete_instance($key, $uri2), "delete instance");
+
+    expected_dataset_ok(
+        deleted => [vol_id => 1, uri => $uri1],
+    );
+
+    eval {
+        $neb->find_instances($key);
+    };
+    like($@, qr/is valid object key/, "storage object was deleted");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    my $key = "foo";
+    my $uri1 = $neb->create_object($key);
+
+    $neb->delete_instance($key, "file:/foo");
+};
+like($@, qr/no instance is associated with uri/, "uri does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->delete_instance();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    my $key = "foo";
+    my $uri1 = $neb->create_object($key);
+
+    $neb->delete_instance("foo", 2, 3);
+};
+like($@, qr/2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/09_server_stat_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/09_server_stat_object.t	(revision 23948)
+++ 	(revision )
@@ -1,71 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2004-2005  Joshua Hoblitt
-#
-# $Id: 09_server_stat_object.t,v 1.15 2008-05-16 20:29:19 jhoblitt Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 12;
-
-use lib qw( ./t ./lib );
-
-use Nebulous::Server;
-use Test::Nebulous;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-Test::Nebulous->setup;
-{
-    $neb->create_object("foo");
-
-    my $info = $neb->stat_object("foo");
-
-    is(scalar @$info, 7, "number of columns");
-}
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("foo", "node01");
-
-    my $info = $neb->stat_object("foo");
-
-    is(scalar @$info, 7,                       "number of columns");
-    is(@$info[0], 1,                           "so_id");
-    is(@$info[1], "foo",                       "ext_id");
-    is(@$info[2], 0,                           "read lock");
-    is(@$info[3], undef,                       "write lock");
-    like(@$info[4], qr/....-..-.. ..:..:../,   "epoch");
-    like(@$info[5], qr/....-..-.. ..:..:../,   "mtime");
-    is(@$info[6], 1,                           "instances");
-}
-
-Test::Nebulous->setup;
-
-eval {
-    my $stat = $neb->stat_object("foo");
-};
-like($@, qr/is valid object key/, "no params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->stat_object();
-};
-like($@, qr/1 was expected/, "no params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-    $neb->stat_object("foo", 2);
-};
-like($@, qr/1 was expected/, "too many params");
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/10_server_is_valid_volume_name.t
===================================================================
--- branches/pap/Nebulous-Server/t/10_server_is_valid_volume_name.t	(revision 23948)
+++ 	(revision )
@@ -1,35 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2004-2005  Joshua Hoblitt
-#
-# $Id: 10_server_is_valid_volume_name.t,v 1.7 2008-12-14 22:54:25 eugene Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 3;
-
-use lib qw( ./t ./lib );
-
-use Nebulous::Server;
-use Test::Nebulous;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-Test::Nebulous->setup;
-
-ok($neb->_is_valid_volume_name('foo', 'node01'), "valid volume name");
-
-Test::Nebulous->setup;
-
-ok($neb->_is_valid_volume_name('foo', 'node02'), "valid volume name");
-
-Test::Nebulous->setup;
-
-is($neb->_is_valid_volume_name('foo', 'node99'), undef, "invalid volume name");
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/10_server_stat_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/10_server_stat_object.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/10_server_stat_object.t	(revision 25027)
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 09_server_stat_object.t,v 1.15 2008-05-16 20:29:19 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 13;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+{
+    $neb->create_object("foo");
+
+    my $info = $neb->stat_object("foo");
+
+    is(scalar @$info, 8, "number of columns");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo", "node01");
+
+    my $info = $neb->stat_object("foo");
+
+    is(scalar @$info, 8,                       "number of columns");
+    is(@$info[0], 1,                           "so_id");
+    is(@$info[1], "foo",                       "ext_id");
+    is(@$info[2], 0,                           "read lock");
+    is(@$info[3], undef,                       "write lock");
+    like(@$info[4], qr/....-..-.. ..:..:../,   "epoch");
+    like(@$info[5], qr/....-..-.. ..:..:../,   "mtime");
+    is(@$info[6], 1,                           "available instances");
+    is(@$info[7], 1,                           "total instances");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    my $stat = $neb->stat_object("foo");
+};
+like($@, qr/is valid object key/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->stat_object();
+};
+like($@, qr/1 was expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+    $neb->stat_object("foo", 2);
+};
+like($@, qr/1 was expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/11_server_is_valid_object_key.t
===================================================================
--- branches/pap/Nebulous-Server/t/11_server_is_valid_object_key.t	(revision 23948)
+++ 	(revision )
@@ -1,37 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2004-2005  Joshua Hoblitt
-#
-# $Id: 11_server_is_valid_object_key.t,v 1.3 2008-03-20 21:10:14 jhoblitt Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 2;
-
-use lib qw( ./t ./lib );
-
-use Nebulous::Server;
-use Test::Nebulous;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object('foo');
-
-    ok($neb->_is_valid_object_key('foo'), "valid object key");
-}
-
-Test::Nebulous->setup;
-
-{
-    ok(!$neb->_is_valid_object_key('foo'), "invalid object key");
-}
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/11_server_is_valid_volume_name.t
===================================================================
--- branches/pap/Nebulous-Server/t/11_server_is_valid_volume_name.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/11_server_is_valid_volume_name.t	(revision 25027)
@@ -0,0 +1,35 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 10_server_is_valid_volume_name.t,v 1.7 2008-12-14 22:54:25 eugene Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 3;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+ok($neb->_is_valid_volume_name('foo', 'node01'), "valid volume name");
+
+Test::Nebulous->setup;
+
+ok($neb->_is_valid_volume_name('foo', 'node02'), "valid volume name");
+
+Test::Nebulous->setup;
+
+is($neb->_is_valid_volume_name('foo', 'node99'), undef, "invalid volume name");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/12_server_find_objects.t
===================================================================
--- branches/pap/Nebulous-Server/t/12_server_find_objects.t	(revision 23948)
+++ 	(revision )
@@ -1,180 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2004-2005  Joshua Hoblitt
-#
-# $Id: 12_server_find_objects.t,v 1.4 2008-05-16 20:29:19 jhoblitt Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 28;
-
-use lib qw( ./t ./lib );
-
-use Nebulous::Server;
-use Test::Nebulous;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-Test::Nebulous->setup;
-
-# search for a regex of '' should match nothing
-eval {
-    $neb->create_object("foo");
-
-    my $keys = $neb->find_objects();
-};
-like($@, qr/no keys found/, "no keys found");
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("foo");
-
-    my $keys = $neb->find_objects("foo");
-
-    is(scalar @$keys, 1, 'number of keys found');
-    is($keys->[0], "foo", "key name");
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    $neb->create_object("foo");
-    $neb->replicate_object("foo");
-
-    my $keys = $neb->find_objects("foo");
-
-    is(scalar @$keys, 1, 'number of keys found');
-    is($keys->[0], "foo", "key name");
-}
-
-Test::Nebulous->setup;
-
-{
-    # key
-    $neb->create_object("foo");
-    $neb->create_object("bar");
-
-    my $keys = $neb->find_objects("foo");
-
-    is(scalar @$keys, 1, 'number of keys found');
-    is($keys->[0], "foo", "key name");
-}
-
-# test recursive dir searching
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("a/foo");
-
-    my $keys = $neb->find_objects("a");
-
-    is(scalar @$keys, 1, 'number of keys found');
-    is($keys->[0], "a/foo", "key name");
-}
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("a/foo");
-    $neb->create_object("b/foo");
-
-    my $keys = $neb->find_objects("a");
-
-    is(scalar @$keys, 1, 'number of keys found');
-    is($keys->[0], "a/foo", "key name");
-}
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("a/foo");
-    $neb->create_object("a/b/foo");
-
-    my $keys = $neb->find_objects("a");
-
-    is(scalar @$keys, 1, 'number of keys found');
-    is($keys->[0], "a/foo", "key name");
-}
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("a/foo");
-    $neb->create_object("a/bar");
-
-    my $keys = $neb->find_objects("a");
-
-    is(scalar @$keys, 2, 'number of keys found');
-    is($keys->[0], "a/foo", "key name");
-    is($keys->[1], "a/bar", "key name");
-}
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("a/foo");
-    $neb->create_object("bar");
-
-    my $keys = $neb->find_objects("a");
-
-    is(scalar @$keys, 1, 'number of keys found');
-    is($keys->[0], "a/foo", "key name");
-}
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("a/foo");
-    $neb->create_object("foo");
-    $neb->create_object("bar");
-
-    my $keys = $neb->find_objects("/");
-
-    is(scalar @$keys, 2, 'number of keys found');
-    is($keys->[0], "foo", "key name");
-    is($keys->[1], "bar", "key name");
-}
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("a/foo");
-    $neb->create_object("foo");
-    $neb->create_object("bar");
-
-    my $keys = $neb->find_objects(".");
-
-    is(scalar @$keys, 2, 'number of keys found');
-    is($keys->[0], "foo", "key name");
-    is($keys->[1], "bar", "key name");
-}
-
-Test::Nebulous->setup;
-
-{
-    $neb->create_object("a/foo");
-    $neb->create_object("foo");
-    $neb->create_object("bar");
-
-    my $keys = $neb->find_objects("..");
-
-    is(scalar @$keys, 2, 'number of keys found');
-    is($keys->[0], "foo", "key name");
-    is($keys->[1], "bar", "key name");
-}
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->find_objects("foo", 3);
-};
-like($@, qr/1 was expected/, "too many params");
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/12_server_is_valid_object_key.t
===================================================================
--- branches/pap/Nebulous-Server/t/12_server_is_valid_object_key.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/12_server_is_valid_object_key.t	(revision 25027)
@@ -0,0 +1,37 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 11_server_is_valid_object_key.t,v 1.3 2008-03-20 21:10:14 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 2;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object('foo');
+
+    ok($neb->_is_valid_object_key('foo'), "valid object key");
+}
+
+Test::Nebulous->setup;
+
+{
+    ok(!$neb->_is_valid_object_key('foo'), "invalid object key");
+}
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/13_server_find_objects.t
===================================================================
--- branches/pap/Nebulous-Server/t/13_server_find_objects.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/13_server_find_objects.t	(revision 25027)
@@ -0,0 +1,199 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 12_server_find_objects.t,v 1.4 2008-05-16 20:29:19 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 36;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+# search for a regex of '' should match nothing
+eval {
+    $neb->create_object("foo");
+
+    my $keys = $neb->find_objects();
+};
+like($@, qr/no keys found/, "no keys found");
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("foo");
+
+    my $keys = $neb->find_objects("foo");
+
+    is(scalar @$keys, 1, 'number of keys found');
+    is($keys->[0], "foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    $neb->create_object("foo");
+    $neb->replicate_object("foo");
+
+    my $keys = $neb->find_objects("foo");
+
+    is(scalar @$keys, 1, 'number of keys found');
+    is($keys->[0], "foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+{
+    # key
+    $neb->create_object("foo");
+    $neb->create_object("bar");
+
+    my $keys = $neb->find_objects("foo");
+
+    is(scalar @$keys, 1, 'number of keys found');
+    is($keys->[0], "foo", "key name");
+}
+
+# test recursive dir searching
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("a/foo");
+
+    my $keys = $neb->find_objects("a");
+
+    is(scalar @$keys, 1, 'number of keys found');
+    is($keys->[0], "a/foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("a/foo");
+    $neb->create_object("b/foo");
+
+    my $keys = $neb->find_objects("a");
+
+    is(scalar @$keys, 1, 'number of keys found');
+    is($keys->[0], "a/foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("a/foo");
+    $neb->create_object("a/b/foo");
+
+    my $keys = $neb->find_objects("a");
+
+    is(scalar @$keys, 2, 'number of keys found');
+    is($keys->[0], "a/b/", "key name");
+    is($keys->[1], "a/foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("a/foo");
+    $neb->create_object("a/bar");
+
+    my $keys = $neb->find_objects("a");
+
+    is(scalar @$keys, 2, 'number of keys found');
+    is($keys->[0], "a/bar", "key name");
+    is($keys->[1], "a/foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("a/foo");
+    $neb->create_object("bar");
+
+    my $keys = $neb->find_objects("a");
+
+    is(scalar @$keys, 1, 'number of keys found');
+    is($keys->[0], "a/foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("a/foo");
+    $neb->create_object("foo");
+    $neb->create_object("bar");
+
+    my $keys = $neb->find_objects("/");
+
+    is(scalar @$keys, 3, 'number of keys found');
+    is($keys->[0], "a/", "key name");
+    is($keys->[1], "bar", "key name");
+    is($keys->[2], "foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("a/foo");
+    $neb->create_object("foo");
+    $neb->create_object("bar");
+
+    my $keys = $neb->find_objects(".");
+
+    is(scalar @$keys, 3, 'number of keys found');
+    is($keys->[0], "a/", "key name");
+    is($keys->[1], "bar", "key name");
+    is($keys->[2], "foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("a/foo");
+    $neb->create_object("foo");
+    $neb->create_object("bar");
+
+    my $keys = $neb->find_objects("..");
+
+    is(scalar @$keys, 3, 'number of keys found');
+    is($keys->[0], "a/", "key name");
+    is($keys->[1], "bar", "key name");
+    is($keys->[2], "foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+{
+    $neb->create_object("a/bar");
+    $neb->create_object("b/foo");
+    $neb->create_object("foo");
+
+    my $keys = $neb->find_objects("/");
+
+    is(scalar @$keys, 3, 'number of keys found');
+    is($keys->[0], "a/", "key name");
+    is($keys->[1], "b/", "key name");
+    is($keys->[2], "foo", "key name");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->find_objects("foo", 3);
+};
+like($@, qr/1 was expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/13_server_rename_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/13_server_rename_object.t	(revision 23948)
+++ 	(revision )
@@ -1,122 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2007  Joshua Hoblitt
-#
-# $Id: 13_server_rename_object.t,v 1.5 2008-05-16 20:29:19 jhoblitt Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 8;
-
-use lib qw( ./t ./lib );
-
-use File::Basename qw( basename );
-use Nebulous::Server;
-use Test::Nebulous;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-use Test::DBUnit dsn => $NEB_DB, username => $NEB_USER, password => $NEB_PASS;
-
-Test::Nebulous->setup;
-
-{
-    my $key = "bar";
-    my $uri = $neb->create_object("foo");
-
-    $neb->rename_object("foo", $key);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 1],
-    );
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key = "a/bar";
-    my $uri = $neb->create_object("foo");
-
-    $neb->rename_object("foo", $key);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
-        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 2],
-    );
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key = "bar";
-    my $uri = $neb->create_object("a/foo");
-
-    $neb->rename_object("a/foo", $key);
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
-        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 1],
-    );
-}
-
-Test::Nebulous->setup;
-
-# destination key exists
-eval {
-    $neb->create_object('foo');
-    $neb->create_object('bar');
-
-    $neb->rename_object('foo', 'bar');
-};
-like($@, qr/is not valid object key/, "too few params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->rename_object();
-};
-like($@, qr/2 were expected/, "no params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->rename_object("foo");
-};
-like($@, qr/2 were expected/, "too few params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->rename_object("foo", "bar", "baz");
-};
-like($@, qr/2 were expected/, "too many params");
-
-# test attempting to rename a key in such a way to cause the distributed
-# storage db to change
-# this must be the last test as we're messing with the $neb object
-eval {
-    $neb->config->add_db(
-        dbindex     => 1,
-        dsn         => $NEB_DB,
-        dbuser      => $NEB_USER,
-        dbpasswd    => $NEB_PASS,
-    );
-
-    $neb->create_object("a/foo");
-    $neb->rename_object("a/foo", "g/bar");
-};
-like($@, qr/rename objects across distributed database boundaries/, "rename between databases");
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/14_server_rename_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/14_server_rename_object.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/14_server_rename_object.t	(revision 25027)
@@ -0,0 +1,122 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2007  Joshua Hoblitt
+#
+# $Id: 13_server_rename_object.t,v 1.5 2008-05-16 20:29:19 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 8;
+
+use lib qw( ./t ./lib );
+
+use File::Basename qw( basename );
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+use Test::DBUnit dsn => $NEB_DB, username => $NEB_USER, password => $NEB_PASS;
+
+Test::Nebulous->setup;
+
+{
+    my $key = "bar";
+    my $uri = $neb->create_object("foo");
+
+    $neb->rename_object("foo", $key);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 1],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key = "a/bar";
+    my $uri = $neb->create_object("foo");
+
+    $neb->rename_object("foo", $key);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 2],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key = "bar";
+    my $uri = $neb->create_object("a/foo");
+
+    $neb->rename_object("a/foo", $key);
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        storage_object  => [so_id => 1, ext_id => $key, ext_id_basename => basename($key), dir_id => 1],
+    );
+}
+
+Test::Nebulous->setup;
+
+# destination key exists
+eval {
+    $neb->create_object('foo');
+    $neb->create_object('bar');
+
+    $neb->rename_object('foo', 'bar');
+};
+like($@, qr/is not valid object key/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->rename_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->rename_object("foo");
+};
+like($@, qr/2 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->rename_object("foo", "bar", "baz");
+};
+like($@, qr/2 were expected/, "too many params");
+
+# test attempting to rename a key in such a way to cause the distributed
+# storage db to change
+# this must be the last test as we're messing with the $neb object
+eval {
+    $neb->config->add_db(
+        dbindex     => 1,
+        dsn         => $NEB_DB,
+        dbuser      => $NEB_USER,
+        dbpasswd    => $NEB_PASS,
+    );
+
+    $neb->create_object("a/foo");
+    $neb->rename_object("a/foo", "g/bar");
+};
+like($@, qr/rename objects across distributed database boundaries/, "rename between databases");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/14_server_xattr.t
===================================================================
--- branches/pap/Nebulous-Server/t/14_server_xattr.t	(revision 23948)
+++ 	(revision )
@@ -1,214 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2007  Joshua Hoblitt
-#
-# $Id: 14_server_xattr.t,v 1.8 2008-07-09 23:32:35 jhoblitt Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 36;
-
-use lib qw( ./t ./lib );
-
-use Nebulous::Server;
-use Test::Nebulous;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-# 1 key / xattr
-
-Test::Nebulous->setup;
-
-{
-    my $uri = $neb->create_object('foo');
-
-    ok($neb->setxattr_object('foo', 'bar', 'baz', 'create'), 'set object xattr');
-    {
-        my $xattrs = $neb->listxattr_object('foo');
-        is(scalar @$xattrs, 1, 'number of xattrs');
-        is(@$xattrs[0], 'bar', 'xattr name');
-    }
-
-    my $value = $neb->getxattr_object('foo', 'bar');
-    is($value, 'baz', 'xattr value');
-
-    ok($neb->removexattr_object('foo', 'bar'), "remove object xattr");
-    {
-        my $xattrs = $neb->listxattr_object('foo');
-        is(scalar @$xattrs, 0, 'number of xattrs');
-    }
-}
-
-# multiple xattrs
-
-Test::Nebulous->setup;
-
-{
-    my $uri = $neb->create_object('foo');
-
-    ok($neb->setxattr_object('foo', 'bar', 'baz', 'create'), 'set object xattr');
-    ok($neb->setxattr_object('foo', 'bonk', 'quix', 'create'), 'set object xattr');
-    
-    {
-        my $xattrs = $neb->listxattr_object('foo');
-        is(scalar @$xattrs, 2, 'number of xattrs');
-        is(@$xattrs[0], 'bar', 'xattr name');
-        is(@$xattrs[1], 'bonk', 'xattr name');
-    }
-
-    my $value = $neb->getxattr_object('foo', 'bar');
-    is($value, 'baz', 'xattr value');
-    $value = $neb->getxattr_object('foo', 'bonk');
-    is($value, 'quix', 'xattr value');
-
-    ok($neb->removexattr_object('foo', 'bar'), "remove object xattr");
-    ok($neb->removexattr_object('foo', 'bonk'), "remove object xattr");
-    {
-        my $xattrs = $neb->listxattr_object('foo');
-        is(scalar @$xattrs, 0, 'number of xattrs');
-    }
-}
-
-# replace xattrs
-
-Test::Nebulous->setup;
-
-{
-    my $uri = $neb->create_object('foo');
-
-    ok($neb->setxattr_object('foo', 'bar', 'baz', 'create'), 'set object xattr');
-    ok($neb->setxattr_object('foo', 'bar', 'quix', 'replace'), 're-set object xattr');
-    
-    {
-        my $xattrs = $neb->listxattr_object('foo');
-        is(scalar @$xattrs, 1, 'number of xattrs');
-        is(@$xattrs[0], 'bar', 'xattr name');
-    }
-
-    my $value = $neb->getxattr_object('foo', 'bar');
-    is($value, 'quix', 'xattr value');
-
-    ok($neb->removexattr_object('foo', 'bar'), "remove object xattr");
-    {
-        my $xattrs = $neb->listxattr_object('foo');
-        is(scalar @$xattrs, 0, 'number of xattrs');
-    }
-}
-
-# setxattr_object
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->setxattr_object('foo', 'bar', 'baz', 'create');
-};
-like($@, qr/is valid object key/, "create xattr on non-existant key");
-
-eval {
-    $neb->setxattr_object('foo', 'bar', 'baz', 'replace');
-};
-like($@, qr/is valid object key/, "replace xattr on non-existant key");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->setxattr_object();
-};
-like($@, qr/4 were expected/, "no params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-
-    $neb->setxattr_object('foo', 'bar');
-};
-like($@, qr/4 were expected/, "too few params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-
-    $neb->setxattr_object('foo', 'bar', 'baz');
-};
-like($@, qr/4 were expected/, "too few params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-
-    $neb->setxattr_object('foo', 'bar', 'baz', 'create', 'quix');
-};
-like($@, qr/4 were expected/, "too many params");
-
-# getxattr_object
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->getxattr_object('foo', 'bar');
-};
-like($@, qr/is valid object key/, "get xattr from non-existant nebulous key");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-    $neb->getxattr_object('foo', 'bar');
-};
-like($@, qr|xattr neb:///foo:bar does not exist|,
-    "get xattr from non-existant xattr key");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->getxattr_object();
-};
-like($@, qr/2 were expected/, "no params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-
-    $neb->getxattr_object('foo');
-};
-like($@, qr/2 were expected/, "too few params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-
-    $neb->getxattr_object('foo', 'bar', 'baz');
-};
-like($@, qr/2 were expected/, "too many params");
-
-# listxattr_object
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-
-    $neb->listxattr_object();
-};
-like($@, qr/1 was expected/, "no params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-
-    $neb->listxattr_object('foo', 'bar');
-};
-like($@, qr/1 was expected/, "too many params");
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/15_mounts.t
===================================================================
--- branches/pap/Nebulous-Server/t/15_mounts.t	(revision 23948)
+++ 	(revision )
@@ -1,67 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2008  Joshua Hoblitt
-#
-# $Id: 15_mounts.t,v 1.3 2008-09-11 22:35:52 jhoblitt Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 18;
-
-use lib qw( ./t ./lib );
-
-use Nebulous::Server;
-use Test::URI;
-use Test::Nebulous;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-Test::Nebulous->setup;
-
-{
-    # key
-    my $mounts = $neb->mounts();
-
-    is(scalar @$mounts, 7, "number of rows");
-
-    my %row;
-    # first row
-    @row{qw(mountpoint total used vol_id name host path allocate available xattr)}
-        = @{$mounts->[0]};
-    
-    is($row{total},     100000000000);
-    is($row{used},      100000000);
-    is($row{vol_id},    1);
-    is($row{name},      "node01");
-    is($row{host},      "node01");
-    is($row{allocate},  1);
-    is($row{available}, 1);
-    is($row{xattr},     0);
-
-    # 2nd row
-    @row{qw(mountpoint total used vol_id name host path allocate available xattr)}
-        = @{$mounts->[1]};
-
-    is($row{total},     100000000000);
-    is($row{used},      1000000000);
-    is($row{vol_id},    2);
-    is($row{name},      "node02");
-    is($row{host},      "node02");
-    is($row{allocate},  1);
-    is($row{available}, 1);
-    is($row{xattr},     0);
-}
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->mounts("foo");
-};
-like($@, qr/0 were expected/, "no params");
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/15_server_xattr.t
===================================================================
--- branches/pap/Nebulous-Server/t/15_server_xattr.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/15_server_xattr.t	(revision 25027)
@@ -0,0 +1,286 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2007  Joshua Hoblitt
+#
+# $Id: 14_server_xattr.t,v 1.8 2008-07-09 23:32:35 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 44;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+# 1 key / xattr
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object('foo');
+
+    ok($neb->setxattr_object('foo', 'user.bar', 'baz', 'create'), 'set object xattr');
+    {
+        my $xattrs = $neb->listxattr_object('foo');
+        is(scalar @$xattrs, 1, 'number of xattrs');
+        is(@$xattrs[0], 'user.bar', 'xattr name');
+    }
+
+    my $value = $neb->getxattr_object('foo', 'user.bar');
+    is($value, 'baz', 'xattr value');
+
+    ok($neb->removexattr_object('foo', 'user.bar'), "remove object xattr");
+    {
+        my $xattrs = $neb->listxattr_object('foo');
+        is(scalar @$xattrs, 0, 'number of xattrs');
+    }
+}
+
+# multiple xattrs
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object('foo');
+
+    ok($neb->setxattr_object('foo', 'user.bar', 'baz', 'create'), 'set object xattr');
+    ok($neb->setxattr_object('foo', 'user.bonk', 'quix', 'create'), 'set object xattr');
+    
+    {
+        my $xattrs = $neb->listxattr_object('foo');
+        is(scalar @$xattrs, 2, 'number of xattrs');
+        is(@$xattrs[0], 'user.bar', 'xattr name');
+        is(@$xattrs[1], 'user.bonk', 'xattr name');
+    }
+
+    my $value = $neb->getxattr_object('foo', 'user.bar');
+    is($value, 'baz', 'xattr value');
+    $value = $neb->getxattr_object('foo', 'user.bonk');
+    is($value, 'quix', 'xattr value');
+
+    ok($neb->removexattr_object('foo', 'user.bar'), "remove object xattr");
+    ok($neb->removexattr_object('foo', 'user.bonk'), "remove object xattr");
+    {
+        my $xattrs = $neb->listxattr_object('foo');
+        is(scalar @$xattrs, 0, 'number of xattrs');
+    }
+}
+
+# replace xattrs
+
+Test::Nebulous->setup;
+
+{
+    my $uri = $neb->create_object('foo');
+
+    ok($neb->setxattr_object('foo', 'user.bar', 'baz', 'create'), 'set object xattr');
+    ok($neb->setxattr_object('foo', 'user.bar', 'quix', 'replace'), 're-set object xattr');
+    
+    {
+        my $xattrs = $neb->listxattr_object('foo');
+        is(scalar @$xattrs, 1, 'number of xattrs');
+        is(@$xattrs[0], 'user.bar', 'xattr name');
+    }
+
+    my $value = $neb->getxattr_object('foo', 'user.bar');
+    is($value, 'quix', 'xattr value');
+
+    ok($neb->removexattr_object('foo', 'user.bar'), "remove object xattr");
+    {
+        my $xattrs = $neb->listxattr_object('foo');
+        is(scalar @$xattrs, 0, 'number of xattrs');
+    }
+}
+
+# setxattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->setxattr_object('foo', 'user.bar', 'baz', 'create');
+};
+like($@, qr/is valid object key/, "create xattr on non-existant key");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->setxattr_object('foo', 'luser.bar', 'baz', 'create');
+};
+like($@, qr/xattr is in user. namespace/, "user. namspace");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->setxattr_object('foo', 'user.bar', 'baz', 'replace');
+};
+like($@, qr/is valid object key/, "replace xattr on non-existant key");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->setxattr_object();
+};
+like($@, qr/4 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->setxattr_object('foo', 'user.bar');
+};
+like($@, qr/4 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->setxattr_object('foo', 'user.bar', 'baz');
+};
+like($@, qr/4 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->setxattr_object('foo', 'user.bar', 'baz', 'create', 'quix');
+};
+like($@, qr/4 were expected/, "too many params");
+
+# getxattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->getxattr_object('foo', 'bar');
+};
+like($@, qr/is valid object key/, "get xattr from non-existant nebulous key");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->getxattr_object('foo', 'luser.bar');
+};
+like($@, qr/xattr is in user. namespace/, "user. namespace");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+    $neb->getxattr_object('foo', 'user.bar');
+};
+like($@, qr|xattr neb:///foo:user.bar does not exist|,
+    "get xattr from non-existant xattr key");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->getxattr_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->getxattr_object('foo');
+};
+like($@, qr/2 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->getxattr_object('foo', 'user.bar', 'baz');
+};
+like($@, qr/2 were expected/, "too many params");
+
+# listxattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->listxattr_object();
+};
+like($@, qr/1 was expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->listxattr_object('foo', 'bar');
+};
+like($@, qr/1 was expected/, "too many params");
+
+# removexattr_object
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->removexattr_object('foo', 'user.bar');
+};
+like($@, qr/is valid object key/, "remove xattr from non-existant nebulous key");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->getxattr_object('foo', 'luser.bar');
+};
+like($@, qr/xattr is in user. namespace/, "user. namespace");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+    $neb->removexattr_object('foo', 'user.bar');
+};
+like($@, qr|xattr neb:///foo:user.bar does not exist|,
+    "remove xattr from non-existant xattr key");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->removexattr_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->removexattr_object('foo');
+};
+like($@, qr/2 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->removexattr_object('foo', 'user.bar', 'baz');
+};
+like($@, qr/2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/16_mounts.t
===================================================================
--- branches/pap/Nebulous-Server/t/16_mounts.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/16_mounts.t	(revision 25027)
@@ -0,0 +1,67 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2008  Joshua Hoblitt
+#
+# $Id: 15_mounts.t,v 1.3 2008-09-11 22:35:52 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 18;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::URI;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+{
+    # key
+    my $mounts = $neb->mounts();
+
+    is(scalar @$mounts, 7, "number of rows");
+
+    my %row;
+    # first row
+    @row{qw(mountpoint total used vol_id name host path allocate available xattr)}
+        = @{$mounts->[0]};
+    
+    is($row{total},     100000000000);
+    is($row{used},      100000000);
+    is($row{vol_id},    1);
+    is($row{name},      "node01");
+    is($row{host},      "node01");
+    is($row{allocate},  1);
+    is($row{available}, 1);
+    is($row{xattr},     0);
+
+    # 2nd row
+    @row{qw(mountpoint total used vol_id name host path allocate available xattr)}
+        = @{$mounts->[1]};
+
+    is($row{total},     100000000000);
+    is($row{used},      1000000000);
+    is($row{vol_id},    2);
+    is($row{name},      "node02");
+    is($row{host},      "node02");
+    is($row{allocate},  1);
+    is($row{available}, 1);
+    is($row{xattr},     0);
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->mounts("foo");
+};
+like($@, qr/0 were expected/, "no params");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/16_server_swap_objects.t
===================================================================
--- branches/pap/Nebulous-Server/t/16_server_swap_objects.t	(revision 23948)
+++ 	(revision )
@@ -1,117 +1,0 @@
-#!/usr/bin/perl
-
-# Copryight (C) 2007  Joshua Hoblitt
-#
-# $Id: 16_server_swap_objects.t,v 1.1 2008-10-13 20:41:17 jhoblitt Exp $
-
-use strict;
-use warnings FATAL => qw( all );
-
-use Test::More tests => 13;
-
-use lib qw( ./t ./lib );
-
-use File::Basename qw( basename );
-use Nebulous::Server;
-use Test::Nebulous;
-
-my $neb = Nebulous::Server->new(
-    dsn         => $NEB_DB,
-    dbuser      => $NEB_USER,
-    dbpasswd    => $NEB_PASS,
-);
-
-use Test::DBUnit dsn => $NEB_DB, username => $NEB_USER, password => $NEB_PASS;
-
-Test::Nebulous->setup;
-
-{
-    my $key1 = "foo1";
-    my $key2 = "foo2";
-    my $uri1 = $neb->create_object($key1);
-    my $uri2 = $neb->create_object($key2);
-
-    ok($neb->swap_objects($key1, $key2), "swap succeeded");
-
-    my $new_uri1 = ($neb->find_instances($key1))->[0];
-    my $new_uri2 = ($neb->find_instances($key2))->[0];
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        storage_object  => [so_id => 1, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 1],
-        storage_object  => [so_id => 2, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 1],
-    );
-
-    is($uri1, $new_uri2, "key1 -> key2");
-    is($uri2, $new_uri1, "key2 -> key1");
-}
-
-Test::Nebulous->setup;
-
-{
-    my $key1 = "foo1";
-    my $key2 = "a/foo2";
-    my $uri1 = $neb->create_object($key1);
-    my $uri2 = $neb->create_object($key2);
-
-    ok($neb->swap_objects($key1, $key2), "swap succeeded");
-
-    my $new_uri1 = ($neb->find_instances($key1))->[0];
-    my $new_uri2 = ($neb->find_instances($key2))->[0];
-
-    expected_dataset_ok(
-        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
-        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
-        storage_object  => [so_id => 1, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 2],
-        storage_object  => [so_id => 2, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 1],
-    );
-
-    is($uri1, $new_uri2, "key1 -> key2");
-    is($uri2, $new_uri1, "key2 -> key1");
-}
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('bar');
-
-    $neb->swap_objects('foo', 'bar');
-};
-like($@, qr/is valid object key/, "key1 doesn't exist");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object('foo');
-
-    $neb->swap_objects('foo', 'bar');
-};
-like($@, qr/is valid object key/, "key2 doesn't exist");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->swap_objects();
-};
-like($@, qr/2 were expected/, "no params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-
-    $neb->swap_objects("foo");
-};
-like($@, qr/2 were expected/, "too few params");
-
-Test::Nebulous->setup;
-
-eval {
-    $neb->create_object("foo");
-    $neb->create_object("bar");
-
-    $neb->swap_objects("foo", "bar", "baz");
-};
-like($@, qr/2 were expected/, "too many params");
-
-Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/17_server_swap_objects.t
===================================================================
--- branches/pap/Nebulous-Server/t/17_server_swap_objects.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/17_server_swap_objects.t	(revision 25027)
@@ -0,0 +1,117 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2007  Joshua Hoblitt
+#
+# $Id: 16_server_swap_objects.t,v 1.1 2008-10-13 20:41:17 jhoblitt Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 13;
+
+use lib qw( ./t ./lib );
+
+use File::Basename qw( basename );
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+use Test::DBUnit dsn => $NEB_DB, username => $NEB_USER, password => $NEB_PASS;
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "foo1";
+    my $key2 = "foo2";
+    my $uri1 = $neb->create_object($key1);
+    my $uri2 = $neb->create_object($key2);
+
+    ok($neb->swap_objects($key1, $key2), "swap succeeded");
+
+    my $new_uri1 = ($neb->find_instances($key1))->[0];
+    my $new_uri2 = ($neb->find_instances($key2))->[0];
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        storage_object  => [so_id => 1, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 1],
+        storage_object  => [so_id => 2, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 1],
+    );
+
+    is($uri1, $new_uri2, "key1 -> key2");
+    is($uri2, $new_uri1, "key2 -> key1");
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key1 = "foo1";
+    my $key2 = "a/foo2";
+    my $uri1 = $neb->create_object($key1);
+    my $uri2 = $neb->create_object($key2);
+
+    ok($neb->swap_objects($key1, $key2), "swap succeeded");
+
+    my $new_uri1 = ($neb->find_instances($key1))->[0];
+    my $new_uri2 = ($neb->find_instances($key2))->[0];
+
+    expected_dataset_ok(
+        directory       => [dir_id => 1, dirname => '/', parent_id => 1],
+        directory       => [dir_id => 2, dirname => 'a', parent_id => 1],
+        storage_object  => [so_id => 1, ext_id => $key2, ext_id_basename => basename($key2), dir_id => 2],
+        storage_object  => [so_id => 2, ext_id => $key1, ext_id_basename => basename($key1), dir_id => 1],
+    );
+
+    is($uri1, $new_uri2, "key1 -> key2");
+    is($uri2, $new_uri1, "key2 -> key1");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('bar');
+
+    $neb->swap_objects('foo', 'bar');
+};
+like($@, qr/is valid object key/, "key1 doesn't exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object('foo');
+
+    $neb->swap_objects('foo', 'bar');
+};
+like($@, qr/is valid object key/, "key2 doesn't exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->swap_objects();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->swap_objects("foo");
+};
+like($@, qr/2 were expected/, "too few params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+    $neb->create_object("bar");
+
+    $neb->swap_objects("foo", "bar", "baz");
+};
+like($@, qr/2 were expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/18_server_chmod_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/18_server_chmod_object.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/18_server_chmod_object.t	(revision 25027)
@@ -0,0 +1,111 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2009  Joshua Hoblitt
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 13;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::URI;
+use Test::Nebulous;
+use URI;
+use File::stat;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+Test::Nebulous->setup;
+
+# object with one instance
+{
+    my $key = "foo";
+    my $uri = $neb->create_object($key);
+
+    my $path = URI->new($uri)->path;
+    my $st1 = stat($path);
+
+    my $mode = $neb->chmod_object($key, 0440);
+    my $st2 = stat($path);
+
+    is($st2->mode &07777, 0440, "chmod() single instance");
+    is($mode, 0440, "returned mode");
+    is($neb->getxattr_object($key, 'user.mode'), 0440, "xattr user.mode");
+}
+
+Test::Nebulous->setup;
+
+# object with two instances
+{
+    my $key = "foo";
+    my $uri1 = $neb->create_object($key);
+    my $uri2 = $neb->replicate_object($key);
+
+    my $mode = $neb->chmod_object($key, 0440);
+
+    my $st1 = stat(URI->new($uri1)->path);
+    my $st2 = stat(URI->new($uri2)->path);
+
+    is($st1->mode &07777, 0440, "chmod() first instance");
+    is($st2->mode &07777, 0440, "chmod() second instance");
+    is($mode, 0440, "returned mode");
+    is($neb->getxattr_object($key, 'user.mode'), 0440, "xattr user.mode");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->chmod_object("foo", 0644);
+};
+like($@, qr/valid object/, "object does not exist");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->chmod_object("foo", 0640);
+};
+like($@, qr/allowable mode/, "mode is not 0400");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->chmod_object("foo", 99999);
+};
+like($@, qr/allowable mode/, "bad mode");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->chmod_object();
+};
+like($@, qr/2 were expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->chmod_object("foo");
+};
+like($@, qr/2 were expected/, "one param");
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->create_object("foo");
+
+    $neb->chmod_object("foo", 0440, 2);
+};
+like($@, qr/2 were expected/, "three params");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/19_server_prune_object.t
===================================================================
--- branches/pap/Nebulous-Server/t/19_server_prune_object.t	(revision 25027)
+++ branches/pap/Nebulous-Server/t/19_server_prune_object.t	(revision 25027)
@@ -0,0 +1,73 @@
+#!/usr/bin/perl
+
+# Copryight (C) 2004-2005  Joshua Hoblitt
+#
+# $Id: 08_server_delete_instance.t,v 1.10.22.1 2008-12-14 22:52:37 eugene Exp $
+
+use strict;
+use warnings FATAL => qw( all );
+
+use Test::More tests => 6;
+
+use lib qw( ./t ./lib );
+
+use Nebulous::Server;
+use Test::Nebulous;
+
+my $neb = Nebulous::Server->new(
+    dsn         => $NEB_DB,
+    dbuser      => $NEB_USER,
+    dbpasswd    => $NEB_PASS,
+);
+
+use Test::DBUnit dsn => $NEB_DB, username => $NEB_USER, password => $NEB_PASS;
+
+Test::Nebulous->setup;
+
+{
+    my $key = "foo";
+    my $uri1 = $neb->create_object($key, 'node01');
+    my $uri2 = $neb->replicate_object($key, 'node02');
+
+    # make one of the instances unavailable
+    my $dbh = test_dbh();
+    $dbh->do("UPDATE mountedvol SET available = 0 WHERE name = 'node01'");
+
+    ok($neb->prune_object($key), "prune object");
+
+    expected_dataset_ok(
+        deleted => [vol_id => 1, uri => $uri1],
+    );
+    
+    expected_dataset_ok(
+        instance => [ins_id => 2, so_id => 1, vol_id => 2, uri => $uri2],
+    );
+}
+
+Test::Nebulous->setup;
+
+{
+    my $key = "foo";
+    my $uri1 = $neb->create_object($key);
+
+    is($neb->prune_object($key), 0, "no dead instances to remove");
+}
+
+Test::Nebulous->setup;
+
+eval {
+    $neb->prune_object();
+};
+like($@, qr/1 was expected/, "no params");
+
+Test::Nebulous->setup;
+
+eval {
+    my $key = "foo";
+    my $uri1 = $neb->create_object($key);
+
+    $neb->prune_object("foo", 2);
+};
+like($@, qr/1 was expected/, "too many params");
+
+Test::Nebulous->cleanup;
Index: branches/pap/Nebulous-Server/t/75_parse_neb_key.t
===================================================================
--- branches/pap/Nebulous-Server/t/75_parse_neb_key.t	(revision 23948)
+++ branches/pap/Nebulous-Server/t/75_parse_neb_key.t	(revision 25027)
@@ -10,5 +10,5 @@
 use Test::More;
 
-plan tests => 99;
+plan tests => 106;
 
 use lib qw( ./t ./lib );
@@ -28,5 +28,5 @@
     is($key->path, 'foo/bar/baz/quix', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -36,5 +36,5 @@
     is($key->path, 'foo/bar/baz/quix', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -44,5 +44,5 @@
     is($key->path, 'foo/bar/baz/quix', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -52,5 +52,5 @@
     is($key->path, 'foo/bar/baz/quix', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -60,5 +60,5 @@
     is($key->path, 'foo/bar/baz/quix', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -68,5 +68,5 @@
     is($key->path, 'foo/bar/baz/quix', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -77,5 +77,5 @@
     is($key->path, 'foo/bar/baz/quix', 'path');
     is($key->volume, 'boing', 'volume name');
-    is($key->soft_volume, 1, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -85,5 +85,5 @@
     is($key->path, 'foo/bar/baz/quix', 'path');
     is($key->volume, 'boing', 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, 1, 'soft volume name');
 }
 
@@ -94,5 +94,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, 'foo', 'volume name');
-    is($key->soft_volume, 1, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -102,5 +102,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, 'foo', 'volume name');
-    is($key->soft_volume, 1, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -110,5 +110,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, 'foo', 'volume name');
-    is($key->soft_volume, 1, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -118,5 +118,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, 'foo', 'volume name');
-    is($key->soft_volume, 1, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -126,5 +126,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, 'foo', 'volume name');
-    is($key->soft_volume, 1, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -135,5 +135,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, 'foo', 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, 1, 'soft volume name');
 }
 
@@ -144,5 +144,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, 'boing', 'volume name');
-    is($key->soft_volume, 1, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -152,5 +152,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, 'boing', 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, 1, 'soft volume name');
 }
 
@@ -160,5 +160,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, 'foo', 'volume name');
-    is($key->soft_volume, 1, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -169,5 +169,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -178,5 +178,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -186,5 +186,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -195,5 +195,5 @@
     is($key->path, 'bar/baz/quix', 'path');
     is($key->volume, 'foo', 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, 1, 'soft volume name');
 }
 
@@ -205,5 +205,5 @@
     is($key->path, 'foo/bar/baz/quix', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -213,5 +213,5 @@
     is($key->volume, undef, 'volume name');
     is($key->path, 'foo/bar/baz/quix', 'path');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -221,5 +221,5 @@
     is($key->path, 'foo/bar/baz/quix', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -230,5 +230,5 @@
     is($key->path, '', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -238,5 +238,5 @@
     is($key->path, '', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -246,5 +246,5 @@
     is($key->path, '', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -254,5 +254,5 @@
     is($key->path, '', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -262,5 +262,5 @@
     is($key->path, '', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
 }
 
@@ -270,5 +270,28 @@
     is($key->path, '', 'path');
     is($key->volume, undef, 'volume name');
-    is($key->soft_volume, undef, 'soft volume name');
+    is($key->hard_volume, undef, 'soft volume name');
+}
+
+# stringification
+
+{
+    my $txt = 'neb:///bar/baz';
+    my $key = parse_neb_key($txt);
+
+    is("$key", $txt, "stringified");
+}
+
+{
+    my $txt = 'neb://foo.0/bar/baz';
+    my $key = parse_neb_key($txt);
+
+    is("$key", $txt, "stringified");
+}
+
+{
+    my $txt = 'neb://~foo.0/bar/baz';
+    my $key = parse_neb_key($txt);
+
+    is("$key", $txt, "stringified");
 }
 
@@ -322,2 +345,23 @@
 };
 like( $@, qr/requires a path/, "no path" );
+
+# params
+eval {
+    my $key = parse_neb_key();
+};
+like( $@, qr/key param is not optional/, "no params" );
+
+eval {
+    my $key = parse_neb_key(undef);
+};
+like( $@, qr/key param is not optional/, "key is undef" );
+
+eval {
+    my $key = parse_neb_key(undef, 'bar');
+};
+like( $@, qr/key param is not optional/, "key is undef" );
+
+eval {
+    my $key = parse_neb_key('foo', 'bar', 'foo');
+};
+like( $@, qr/too many params/, "too many params" );
