package MogileFS::Test;
use strict;
use warnings;
use DBI;
use FindBin qw($Bin);
use IO::Socket::INET;
use MogileFS::Server;
use LWP::UserAgent;
use base 'Exporter';
our @EXPORT = qw(&find_mogclient_or_skip &temp_store &create_mogstored &create_temp_tracker &try_for &want);
sub find_mogclient_or_skip {
# needed for running "make test" from project root directory, with
# full svn 'mogilefs' repo checked out, without installing
# MogileFS::Client to normal system locations...
#
# then, second path is when running "make disttest", which is another
# directory below.
foreach my $dir ("$Bin/../../api/perl/MogileFS-Client/lib",
"$Bin/../../../api/perl/MogileFS-Client/lib",
) {
next unless -d $dir;
unshift @INC, $dir;
$ENV{PERL5LIB} = $dir . ($ENV{PERL5LIB} ? ":$ENV{PERL5LIB}" : "");
}
unless (eval "use MogileFS::Client; 1") {
warn "Can't find MogileFS::Client: $@\n";
Test::More::plan('skip_all' => "Can't find MogileFS::Client library, necessary for testing.");
}
unless (eval { TrackerHandle::_mogadm_exe() }) {
warn "Can't find mogadm utility $@\n";
Test::More::plan('skip_all' => "Can't find mogadm executable, necessary for testing.");
}
return 1;
}
sub temp_store {
my $type = $ENV{MOGTEST_DBTYPE};
my $host = $ENV{MOGTEST_DBHOST} || '';
my $port = $ENV{MOGTEST_DBPORT} || '';
my $user = $ENV{MOGTEST_DBUSER} || '';
my $pass = $ENV{MOGTEST_DBPASS} || '';
my $name = $ENV{MOGTEST_DBNAME} || '';
my $rootuser = $ENV{MOGTEST_DBROOTUSER} || '';
my $rootpass = $ENV{MOGTEST_DBROOTPASS} || '';
# default to mysql, but make sure DBD::MySQL is installed
unless ($type) {
$type = "MySQL";
eval "use DBD::mysql; 1" or
die "DBD::mysql isn't installed. Please install it or define MOGTEST_DBTYPE env. variable";
}
die "Bogus type" unless $type =~ /^\w+$/;
my $store = "MogileFS::Store::$type";
eval "use $store; 1;";
if ($@) {
die "Failed to load $store: $@\n";
}
my %opts = ( dbhost => $host, dbport => $port,
dbuser => $user, dbpass => $pass,
dbname => $name);
$opts{dbrootuser} = $rootuser unless $rootuser eq '';
$opts{dbrootpass} = $rootpass unless $rootpass eq '';
my $sto = $store->new_temp(%opts);
Mgd::set_store($sto);
return $sto;
}
sub create_temp_tracker {
my $sto = shift;
my $opts = shift || [];
my $pid = fork();
my $whoami = `whoami`;
chomp $whoami;
my $connect = sub {
return IO::Socket::INET->new(PeerAddr => "127.0.0.1:7001",
Timeout => 2);
};
my $conn = $connect->();
die "Failed: tracker already running on port 7001?\n" if $conn;
unless ($pid) {
exec("$Bin/../mogilefsd",
($whoami eq "root" ? "--user=root" : ()),
"--skipconfig",
"--workers=2",
"--dsn=" . $sto->dsn,
"--dbuser=" . $sto->user,
"--dbpass=" . $sto->pass,
@$opts,
);
}
for (1..3) {
if ($connect->()) {
return TrackerHandle->new(pid => $pid);
}
sleep 1;
}
return undef;
}
sub create_mogstored {
my ($ip, $root, $daemonize) = @_;
my $connect = sub {
return IO::Socket::INET->new(PeerAddr => "$ip:7500",
Timeout => 2);
};
my $conn = $connect->();
die "Failed: tracker already running on port 7500?\n" if $conn;
$ENV{PERL5LIB} .= ":$Bin/../lib";
my @args = ("$Bin/../mogstored",
"--skipconfig",
"--httplisten=$ip:7500",
"--mgmtlisten=$ip:7501",
"--maxconns=1000", # because we're not root, put it below 1024
"--docroot=$root");
my $pid;
if ($daemonize) {
# don't set pid. since our fork fid would just
# go away, once perlbal daemonized itself.
push @args, "--daemonize";
system(@args) and die "Failed to start daemonized mogstored.";
} else {
$pid = fork();
die "failed to fork: $!" unless defined $pid;
unless ($pid) {
exec(@args);
}
}
for (1..12) {
if ($connect->()) {
return MogstoredHandle->new(pid => $pid, ip => $ip, root => $root);
}
select undef, undef, undef, 0.25;
}
return undef;
}
sub try_for {
my ($tries, $code) = @_;
for (1..$tries) {
return 1 if $code->();
sleep 1;
}
return 0;
}
sub want {
my ($admin, $count, $jobclass) = @_;
my $req = "!want $count $jobclass\r\n";
syswrite($admin, $req) or die "syswrite: $!\n";
my $r = <$admin>;
if ($r =~ /Now desiring $count children doing '$jobclass'/ && <$admin> eq ".\r\n") {
my $rcount;
try_for(30, sub {
$rcount = -1;
syswrite($admin, "!jobs\r\n");
MogileFS::Util::wait_for_readability(fileno($admin), 10);
while (1) {
my $line = <$admin>;
if ($line =~ /\A$jobclass count (\d+)/) {
$rcount = $1;
}
last if $line eq ".\r\n";
}
$rcount == $count;
});
return 1 if $rcount == $count;
die "got $jobclass count $rcount (expected=$count)\n";
}
die "got bad response for $req: $r\n";
}
############################################################################
package ProcessHandle;
sub new {
my ($class, %args) = @_;
bless \%args, $class;
}
sub pid { return $_[0]{pid} }
sub DESTROY {
my $self = shift;
return unless $self->{pid};
kill 15, $self->{pid};
}
############################################################################
package TrackerHandle;
use base 'ProcessHandle';
sub ipport {
my $self = shift;
return "127.0.0.1:7001";
}
my $_mogadm_exe_cache;
sub _mogadm_exe {
return $_mogadm_exe_cache if $_mogadm_exe_cache;
for my $dir ("$FindBin::Bin/../../utils",
"$FindBin::Bin/../../../utils",
split(/:/, $ENV{PATH}),
"/usr/bin",
"/usr/sbin",
"/usr/local/bin",
"/usr/local/sbin",
) {
my $exe = $dir . '/mogadm';
return $_mogadm_exe_cache = $exe if -x $exe;
}
die "mogadm executable not found.\n";
}
sub mogadm {
my $self = shift;
my $rv = system(_mogadm_exe(), "--trackers=" . $self->ipport, @_);
return !$rv;
}
############################################################################
package MogstoredHandle;
use base 'ProcessHandle';
# this space intentionally left blank. all in super class for now.
############################################################################
package MogPath;
sub new {
my ($class, $url) = @_;
return bless {
url => $url,
}, $class;
}
sub host {
my $self = shift;
my ($host1) = $self->{url} =~ m!^http://(.+:\d+)!;
return $host1
}
sub device {
my $self = shift;
my ($dev) = $self->{url} =~ m!dev(\d+)!;
return $dev
}
sub path {
my $self = shift;
my $path = $self->{url};
$path =~ s!^http://(.+:\d+)!!;
return $path;
}
1;