use warnings;
use Carp;
use JSON;
-
+use Socket;
+
require Exporter;
our @ISA = qw(Exporter);
require XSLoader;
XSLoader::load('PVE::RADOS', $VERSION);
-sub new {
- my ($class, %params) = @_;
+# fixme: timeouts??
+
+my $writedata = sub {
+ my ($fh, $cmd, $data) = @_;
+
+ local $SIG{PIPE} = 'IGNORE';
+
+ my $bin = pack "a L/a*", $cmd, $data || '';
+ my $res = syswrite $fh, $bin;
+
+ die "write data failed - $!\n" if !defined($res);
+};
- my $conn = pve_rados_create() ||
- die "unable to create RADOS object\n";
+my $readdata = sub {
+ my ($fh, $expect_result) = @_;
- my $timeout = delete $params{timeout} || 5;
+ my $head = '';
- pve_rados_conf_set($conn, 'client_mount_timeout', $timeout);
+ local $SIG{PIPE} = 'IGNORE';
- foreach my $k (keys %params) {
- pve_rados_conf_set($conn, $k, $params{$k});
+ while (length($head) < 5) {
+ last if !sysread $fh, $head, 5 - length($head), length($head);
}
+ die "partial read\n" if length($head) < 5;
+
+ my ($cmd, $len) = unpack "a L", $head;
- pve_rados_connect($conn);
+ my $data = '';
+ while (length($data) < $len) {
+ last if !sysread $fh, $data, $len - length($data), length($data);
+ }
+ die "partial data read\n" if length($data) < $len;
+
+ if ($expect_result) {
+ die $data if $cmd eq 'E' && $data;
+ die "got unexpected result\n" if $cmd ne '>';
+ }
- my $self = bless { conn => $conn };
+ return wantarray ? ($cmd, $data) : $data;
+};
+
+sub new {
+ my ($class, %params) = @_;
+
+ socketpair(my $child, my $parent, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
+ || die "socketpair: $!";
+
+ my $cpid = fork();
+
+ die "unable to fork - $!\n" if !defined($cpid);
+
+ my $self = bless {};
+
+ if ($cpid) { # parent
+ close $parent;
+
+ $self->{cpid} = $cpid;
+ $self->{child} = $child;
+
+ # wait for sync
+ my ($cmd, $msg) = &$readdata($child);
+ die $msg if $cmd eq 'E';
+ die "internal error- got unexpected result" if $cmd ne 'S';
+
+ } else { # child
+ $0 = 'pverados';
+
+ # fixme: timeout?
+
+ close $child;
+
+ my $timeout = delete $params{timeout} || 5;
+
+ my $conn;
+ eval {
+ $conn = pve_rados_create() ||
+ die "unable to create RADOS object\n";
+
+ pve_rados_conf_set($conn, 'client_mount_timeout', $timeout);
+
+ foreach my $k (keys %params) {
+ pve_rados_conf_set($conn, $k, $params{$k});
+ }
+
+ pve_rados_connect($conn);
+ };
+ if (my $err = $@) {
+ &$writedata($parent, 'E', $err);
+ die $err;
+ }
+ &$writedata($parent, 'S');
+
+ $self->{conn} = $conn;
+
+ for (;;) {
+ my ($cmd, $data) = &$readdata($parent);
+
+ last if $cmd eq 'Q';
+
+ my $res;
+ eval {
+ if ($cmd eq 'M') { # rados monitor commands
+ $res = pve_rados_mon_command($self->{conn}, [ $data ]);
+ } elsif ($cmd eq 'C') { # class methods
+ my $aref = decode_json($data);
+ my $method = shift @$aref;
+ $res = encode_json($self->$method(@$aref));
+ } else {
+ die "invalid command\n";
+ }
+ };
+ if (my $err = $@) {
+ &$writedata($parent, 'E', $err);
+ die $err;
+ }
+ &$writedata($parent, '>', $res);
+ }
+
+ exit(0);
+ }
return $self;
}
sub DESTROY {
my ($self) = @_;
- pve_rados_shutdown($self->{conn});
+ if ($self->{cpid}) {
+ #print "$$: DESTROY WAIT0\n";
+ eval { &$writedata($self->{child}, 'Q'); };
+ my $res = waitpid($self->{cpid}, 0);
+ #print "$$: DESTROY WAIT $res\n";
+ } else {
+ #print "$$: DESTROY SHUTDOWN\n";
+ pve_rados_shutdown($self->{conn}) if $self->{conn};
+ }
}
sub cluster_stat {
- my ($self) = @_;
-
- return pve_rados_cluster_stat($self->{conn});
+ my ($self, @args) = @_;
+
+ if ($self->{cpid}) {
+ my $data = encode_json(['cluster_stat', @args]);
+ &$writedata($self->{child}, 'C', $data);
+ return decode_json(&$readdata($self->{child}, 1));
+ } else {
+ return pve_rados_cluster_stat($self->{conn});
+ }
}
# example1: { prefix => 'get_command_descriptions'})
$cmd->{format} = 'json' if !$cmd->{format};
my $json = encode_json($cmd);
- my $raw = pve_rados_mon_command($self->{conn}, [ $json ]);
+
+ &$writedata($self->{child}, 'M', $json);
+
+ my $raw = &$readdata($self->{child}, 1);
+
if ($cmd->{format} && $cmd->{format} eq 'json') {
return length($raw) ? decode_json($raw) : undef;
}