]> git.proxmox.com Git - librados2-perl.git/blame - PVE/RADOS.pm
allow to specify the userid with rados_create
[librados2-perl.git] / PVE / RADOS.pm
CommitLineData
27bfc7c6
DM
1package PVE::RADOS;
2
3use 5.014002;
4use strict;
5use warnings;
6use Carp;
7use JSON;
9539bd37 8use Socket;
8172535c 9use PVE::Tools;
e2171b36
DM
10use PVE::INotify;
11use PVE::RPCEnvironment;
12
27bfc7c6
DM
13require Exporter;
14
8172535c 15my $rados_default_timeout = 5;
eb351948 16my $ceph_default_conf = '/etc/ceph/ceph.conf';
c0a9abbd 17my $ceph_default_user = 'admin';
8172535c
DM
18
19
27bfc7c6
DM
20our @ISA = qw(Exporter);
21
22# Items to export into callers namespace by default. Note: do not export
23# names by default without a very good reason. Use EXPORT_OK instead.
24# Do not simply export all your public functions/methods/constants.
25
26# This allows declaration use PVE::RADOS ':all';
27# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
28# will save memory.
29our %EXPORT_TAGS = ( 'all' => [ qw(
30
31) ] );
32
33our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
34
35our @EXPORT = qw(
36
37);
38
39our $VERSION = '1.0';
40
41require XSLoader;
42XSLoader::load('PVE::RADOS', $VERSION);
43
9539bd37
DM
44my $writedata = sub {
45 my ($fh, $cmd, $data) = @_;
46
47 local $SIG{PIPE} = 'IGNORE';
fb22f3c5 48
9539bd37
DM
49 my $bin = pack "a L/a*", $cmd, $data || '';
50 my $res = syswrite $fh, $bin;
51
52 die "write data failed - $!\n" if !defined($res);
53};
612779b1 54
9539bd37 55my $readdata = sub {
2eb2d02e 56 my ($fh, $allow_eof) = @_;
27bfc7c6 57
9539bd37 58 my $head = '';
612779b1 59
9539bd37 60 local $SIG{PIPE} = 'IGNORE';
612779b1 61
9539bd37
DM
62 while (length($head) < 5) {
63 last if !sysread $fh, $head, 5 - length($head), length($head);
612779b1 64 }
2eb2d02e
DM
65 return undef if $allow_eof && length($head) == 0;
66
9539bd37 67 die "partial read\n" if length($head) < 5;
fb22f3c5 68
9539bd37 69 my ($cmd, $len) = unpack "a L", $head;
612779b1 70
9539bd37
DM
71 my $data = '';
72 while (length($data) < $len) {
73 last if !sysread $fh, $data, $len - length($data), length($data);
74 }
75 die "partial data read\n" if length($data) < $len;
76
8172535c
DM
77 return wantarray ? ($cmd, $data) : $data;
78};
79
80my $kill_worker = sub {
81 my ($self) = @_;
82
83 return if !$self->{cpid};
84 return if $self->{__already_killed};
85
86 $self->{__already_killed} = 1;
87
88 close($self->{child}) if defined($self->{child});
89
187aea70 90 # only kill if we created the process
fb22f3c5 91 return if $self->{pid} != $$;
187aea70 92
8172535c
DM
93 kill(9, $self->{cpid});
94 waitpid($self->{cpid}, 0);
95};
96
97my $sendcmd = sub {
98 my ($self, $cmd, $data, $expect_tag) = @_;
99
100 $expect_tag = '>' if !$expect_tag;
101
187aea70
DM
102 die "detected forked connection" if $self->{pid} != $$;
103
8172535c
DM
104 my ($restag, $raw);
105 my $code = sub {
106 &$writedata($self->{child}, $cmd, $data) if $expect_tag ne 'S';
107 ($restag, $raw) = &$readdata($self->{child});
108 };
109 eval { PVE::Tools::run_with_timeout($self->{timeout}, $code); };
110 if (my $err = $@) {
111 &$kill_worker($self);
112 die $err;
113 }
114 if ($restag eq 'E') {
115 die $raw if $raw;
116 die "unknown error\n";
9539bd37 117 }
27bfc7c6 118
8172535c
DM
119 die "got unexpected result\n" if $restag ne $expect_tag;
120
121 return $raw;
9539bd37
DM
122};
123
124sub new {
125 my ($class, %params) = @_;
126
e2171b36
DM
127 my $rpcenv = PVE::RPCEnvironment::get();
128
9539bd37
DM
129 socketpair(my $child, my $parent, AF_UNIX, SOCK_STREAM, PF_UNSPEC)
130 || die "socketpair: $!";
131
132 my $cpid = fork();
133
134 die "unable to fork - $!\n" if !defined($cpid);
135
136 my $self = bless {};
137
8172535c
DM
138 my $timeout = delete $params{timeout} || $rados_default_timeout;
139
140 $self->{timeout} = $timeout;
187aea70 141 $self->{pid} = $$;
8172535c 142
9539bd37
DM
143 if ($cpid) { # parent
144 close $parent;
fb22f3c5 145
9539bd37
DM
146 $self->{cpid} = $cpid;
147 $self->{child} = $child;
148
8172535c 149 &$sendcmd($self, undef, undef, 'S'); # wait for sync
9539bd37
DM
150
151 } else { # child
152 $0 = 'pverados';
e2171b36
DM
153
154 PVE::INotify::inotify_close();
155
156 if (my $atfork = $rpcenv->{atfork}) {
157 &$atfork();
158 }
159
9539bd37
DM
160 # fixme: timeout?
161
162 close $child;
163
9539bd37
DM
164 my $conn;
165 eval {
c0a9abbd
AA
166 my $ceph_user = delete $params{userid} || $ceph_default_user;
167 $conn = pve_rados_create($ceph_user) ||
9539bd37
DM
168 die "unable to create RADOS object\n";
169
eb351948
AA
170 if (defined($params{ceph_conf}) && (!-e $params{ceph_conf})) {
171 die "Supplied ceph config doesn't exist, $params{ceph_conf}";
172 }
173
174 my $ceph_conf = delete $params{ceph_conf} || $ceph_default_conf;
175
176 if (-e $ceph_conf) {
177 pve_rados_conf_read_file($conn, $ceph_conf);
178 }
179
9539bd37
DM
180 pve_rados_conf_set($conn, 'client_mount_timeout', $timeout);
181
182 foreach my $k (keys %params) {
183 pve_rados_conf_set($conn, $k, $params{$k});
184 }
185
186 pve_rados_connect($conn);
187 };
188 if (my $err = $@) {
189 &$writedata($parent, 'E', $err);
190 die $err;
191 }
192 &$writedata($parent, 'S');
193
194 $self->{conn} = $conn;
195
196 for (;;) {
2eb2d02e 197 my ($cmd, $data) = &$readdata($parent, 1);
fb22f3c5 198
2eb2d02e 199 last if !$cmd || $cmd eq 'Q';
9539bd37
DM
200
201 my $res;
202 eval {
203 if ($cmd eq 'M') { # rados monitor commands
204 $res = pve_rados_mon_command($self->{conn}, [ $data ]);
205 } elsif ($cmd eq 'C') { # class methods
206 my $aref = decode_json($data);
207 my $method = shift @$aref;
208 $res = encode_json($self->$method(@$aref));
209 } else {
210 die "invalid command\n";
211 }
212 };
213 if (my $err = $@) {
214 &$writedata($parent, 'E', $err);
215 die $err;
216 }
217 &$writedata($parent, '>', $res);
218 }
fb22f3c5 219
9539bd37
DM
220 exit(0);
221 }
27bfc7c6
DM
222
223 return $self;
224}
225
cc2b4dbc
DM
226sub timeout {
227 my ($self, $new_timeout) = @_;
228
229 $self->{timeout} = $new_timeout if $new_timeout;
230
231 return $self->{timeout};
232}
233
27bfc7c6
DM
234sub DESTROY {
235 my ($self) = @_;
236
9539bd37
DM
237 if ($self->{cpid}) {
238 #print "$$: DESTROY WAIT0\n";
8172535c
DM
239 &$kill_worker($self);
240 #print "$$: DESTROY WAIT\n";
9539bd37
DM
241 } else {
242 #print "$$: DESTROY SHUTDOWN\n";
243 pve_rados_shutdown($self->{conn}) if $self->{conn};
244 }
27bfc7c6
DM
245}
246
247sub cluster_stat {
9539bd37
DM
248 my ($self, @args) = @_;
249
250 if ($self->{cpid}) {
251 my $data = encode_json(['cluster_stat', @args]);
8172535c
DM
252 my $raw = &$sendcmd($self, 'C', $data);
253 return decode_json($raw);
9539bd37
DM
254 } else {
255 return pve_rados_cluster_stat($self->{conn});
256 }
27bfc7c6
DM
257}
258
f5996c62
DM
259# example1: { prefix => 'get_command_descriptions'})
260# example2: { prefix => 'mon dump', format => 'json' }
27bfc7c6
DM
261sub mon_command {
262 my ($self, $cmd) = @_;
263
b2a25d5d
DM
264 $cmd->{format} = 'json' if !$cmd->{format};
265
27bfc7c6 266 my $json = encode_json($cmd);
9539bd37 267
8172535c 268 my $raw = &$sendcmd($self, 'M', $json);
9539bd37 269
27bfc7c6 270 if ($cmd->{format} && $cmd->{format} eq 'json') {
23c2cb25 271 return length($raw) ? decode_json($raw) : undef;
27bfc7c6
DM
272 }
273 return $raw;
274}
275
276
2771;
278__END__
279
280=head1 NAME
281
282PVE::RADOS - Perl bindings for librados
283
284=head1 SYNOPSIS
285
286 use PVE::RADOS;
287
288 my $rados = PVE::RADOS::new();
289 my $stat = $rados->cluster_stat();
290 my $res = $rados->mon_command({ prefix => 'mon dump', format => 'json' });
291
292=head1 DESCRIPTION
293
294Perl bindings for librados.
295
296=head2 EXPORT
297
298None by default.
299
300=head1 AUTHOR
301
302Dietmar Maurer, E<lt>dietmar@proxmox.com<gt>
303
304=cut