]> git.proxmox.com Git - pve-storage.git/blob - PVE/Storage/DRBDPlugin.pm
extend functionality to (de)activate_volumes with snapshots
[pve-storage.git] / PVE / Storage / DRBDPlugin.pm
1 package PVE::Storage::DRBDPlugin;
2
3 use strict;
4 use warnings;
5 use IO::File;
6 use Net::DBus;
7 use Data::Dumper;
8
9 use PVE::Tools qw(run_command trim);
10 use PVE::INotify;
11 use PVE::Storage::Plugin;
12 use PVE::JSONSchema qw(get_standard_option);
13
14 use base qw(PVE::Storage::Plugin);
15
16 # Configuration
17
18 my $default_redundancy = 2;
19
20 sub type {
21 return 'drbd';
22 }
23
24 sub plugindata {
25 return {
26 content => [ {images => 1, rootdir => 1}, { images => 1 }],
27 };
28 }
29
30 sub properties {
31 return {
32 redundancy => {
33 description => "The redundancy count specifies the number of nodes to which the resource should be deployed. It must be at least 1 and at most the number of nodes in the cluster.",
34 type => 'integer',
35 minimum => 1,
36 maximum => 16,
37 default => $default_redundancy,
38 },
39 };
40 }
41
42 sub options {
43 return {
44 redundancy => { optional => 1 },
45 content => { optional => 1 },
46 nodes => { optional => 1 },
47 disable => { optional => 1 },
48 };
49 }
50
51 # helper
52
53 sub get_redundancy {
54 my ($scfg) = @_;
55
56 return $scfg->{redundancy} || $default_redundancy;
57 }
58
59 sub connect_drbdmanage_service {
60
61 my $bus = Net::DBus->system;
62
63 my $service = $bus->get_service("org.drbd.drbdmanaged");
64
65 my $hdl = $service->get_object("/interface", "org.drbd.drbdmanaged");
66
67 return $hdl;
68 }
69
70 sub check_drbd_res {
71 my ($rc) = @_;
72
73 die "got undefined drbd result\n" if !$rc;
74
75 foreach my $res (@$rc) {
76 my ($code, $format, $details) = @$res;
77
78 next if $code == 0;
79
80 my $msg;
81 if (defined($format)) {
82 my @args = ();
83 push @args, $details->{$1} // ""
84 while $format =~ s,\%\((\w+)\),%,;
85
86 $msg = sprintf($format, @args);
87
88 } else {
89 $msg = "drbd error: got error code $code";
90 }
91
92 chomp $msg;
93 die "drbd error: $msg\n";
94 }
95
96 return undef;
97 }
98
99 sub drbd_list_volumes {
100 my ($hdl) = @_;
101
102 $hdl = connect_drbdmanage_service() if !$hdl;
103
104 my ($rc, $res) = $hdl->list_volumes([], 0, {}, []);
105 check_drbd_res($rc);
106
107 my $volumes = {};
108
109 foreach my $entry (@$res) {
110 my ($volname, $properties, $vol_list) = @$entry;
111
112 next if $volname !~ m/^vm-(\d+)-/;
113 my $vmid = $1;
114
115 # fixme: we always use volid 0 ?
116 my $size = 0;
117 foreach my $volentry (@$vol_list) {
118 my ($vol_id, $vol_properties) = @$volentry;
119 next if $vol_id != 0;
120 my $vol_size = $vol_properties->{vol_size} * 1024;
121 $size = $vol_size if $vol_size > $size;
122 }
123
124 $volumes->{$volname} = { format => 'raw', size => $size,
125 vmid => $vmid };
126 }
127
128 return $volumes;
129 }
130
131 # Storage implementation
132
133 sub parse_volname {
134 my ($class, $volname) = @_;
135
136 if ($volname =~ m/^(vm-(\d+)-[a-z][a-z0-9\-\_\.]*[a-z0-9]+)$/) {
137 return ('images', $1, $2, undef, undef, undef, 'raw');
138 }
139
140 die "unable to parse lvm volume name '$volname'\n";
141 }
142
143 sub filesystem_path {
144 my ($class, $scfg, $volname, $snapname) = @_;
145
146 die "drbd snapshot is not implemented\n" if defined($snapname);
147
148 my ($vtype, $name, $vmid) = $class->parse_volname($volname);
149
150 # fixme: always use volid 0?
151 my $path = "/dev/drbd/by-res/$volname/0";
152
153 return wantarray ? ($path, $vmid, $vtype) : $path;
154 }
155
156 sub create_base {
157 my ($class, $storeid, $scfg, $volname) = @_;
158
159 die "can't create base images in drbd storage\n";
160 }
161
162 sub clone_image {
163 my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_;
164
165 die "can't clone images in drbd storage\n";
166 }
167
168 sub alloc_image {
169 my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
170
171 die "unsupported format '$fmt'" if $fmt ne 'raw';
172
173 die "illegal name '$name' - should be 'vm-$vmid-*'\n"
174 if defined($name) && $name !~ m/^vm-$vmid-/;
175
176 my $hdl = connect_drbdmanage_service();
177 my $volumes = drbd_list_volumes($hdl);
178
179 die "volume '$name' already exists\n" if defined($name) && $volumes->{$name};
180
181 if (!defined($name)) {
182 for (my $i = 1; $i < 100; $i++) {
183 my $tn = "vm-$vmid-disk-$i";
184 if (!defined ($volumes->{$tn})) {
185 $name = $tn;
186 last;
187 }
188 }
189 }
190
191 die "unable to allocate an image name for VM $vmid in storage '$storeid'\n"
192 if !defined($name);
193
194 my ($rc, $res) = $hdl->create_resource($name, {});
195 check_drbd_res($rc);
196
197 ($rc, $res) = $hdl->create_volume($name, $size, {});
198 check_drbd_res($rc);
199
200 ($rc, $res) = $hdl->set_drbdsetup_props(
201 {
202 target => "resource",
203 resource => $name,
204 type => 'neto',
205 'allow-two-primaries' => 'yes',
206 });
207 check_drbd_res($rc);
208
209 my $redundancy = get_redundancy($scfg);;
210
211 ($rc, $res) = $hdl->auto_deploy($name, $redundancy, 0, 0);
212 check_drbd_res($rc);
213
214 return $name;
215 }
216
217 sub free_image {
218 my ($class, $storeid, $scfg, $volname, $isBase) = @_;
219
220 my $hdl = connect_drbdmanage_service();
221 my ($rc, $res) = $hdl->remove_resource($volname, 0);
222 check_drbd_res($rc);
223
224 return undef;
225 }
226
227 sub list_images {
228 my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_;
229
230 my $vgname = $scfg->{vgname};
231
232 $cache->{drbd_volumes} = drbd_list_volumes() if !$cache->{drbd_volumes};
233
234 my $res = [];
235
236 my $dat = $cache->{drbd_volumes};
237
238 foreach my $volname (keys %$dat) {
239
240 my $owner = $dat->{$volname}->{vmid};
241
242 my $volid = "$storeid:$volname";
243
244 if ($vollist) {
245 my $found = grep { $_ eq $volid } @$vollist;
246 next if !$found;
247 } else {
248 next if defined ($vmid) && ($owner ne $vmid);
249 }
250
251 my $info = $dat->{$volname};
252 $info->{volid} = $volid;
253
254 push @$res, $info;
255 }
256
257 return $res;
258 }
259
260 sub status {
261 my ($class, $storeid, $scfg, $cache) = @_;
262
263 my ($total, $avail, $used);
264
265 eval {
266 my $hdl = connect_drbdmanage_service();
267 my $redundancy = get_redundancy($scfg);;
268 my ($rc, $res) = $hdl->cluster_free_query($redundancy);
269 check_drbd_res($rc);
270
271 $avail = $res;
272 $used = 0; # fixme
273 $total = $used + $avail;
274
275 };
276 if (my $err = $@) {
277 # ignore error,
278 # assume storage if offline
279
280 return undef;
281 }
282
283 return ($total, $avail, $used, 1);
284 }
285
286 sub activate_storage {
287 my ($class, $storeid, $scfg, $cache) = @_;
288
289 return undef;
290 }
291
292 sub deactivate_storage {
293 my ($class, $storeid, $scfg, $cache) = @_;
294
295 return undef;
296 }
297
298 sub activate_volume {
299 my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
300
301 die "Snapshot not implemented on DRBD\n" if $snapname;
302
303 my $path = $class->path($scfg, $volname);
304
305 my $hdl = connect_drbdmanage_service();
306 my $nodename = PVE::INotify::nodename();
307 my ($rc, $res) = $hdl->list_assignments([$nodename], [], 0, {}, []);
308 check_drbd_res($rc);
309
310 foreach my $entry (@$res) {
311 my ($node, $res_name, $props, $voldata) = @$entry;
312 if (($node eq $nodename) && ($res_name eq $volname)) {
313 return undef; # assignment already exists
314 }
315 }
316
317 # create diskless assignment
318 ($rc, $res) = $hdl->assign($nodename, $volname, { diskless => 'true' });
319 check_drbd_res($rc);
320
321 # wait until device is acessitble
322 my $print_warning = 1;
323 my $max_wait_time = 20;
324 for (my $i = 0;; $i++) {
325 if (1) {
326 # clumsy, but works
327 last if system("dd if=$path of=/dev/null bs=512 count=1 >/dev/null 2>&1") == 0;
328 } else {
329 # correct, but does not work?
330 ($rc, $res) = $hdl->list_assignments([$nodename], [$volname], 0, { "cstate:deploy" => "true" }, []);
331 check_drbd_res($rc);
332 my $len = scalar(@$res);
333 last if $len > 0;
334 }
335 die "aborting wait - device '$path' still not readable\n" if $i > $max_wait_time;
336 print "waiting for device '$path' to become ready...\n" if $print_warning;
337 $print_warning = 0;
338 sleep(1);
339 }
340
341 return undef;
342 }
343
344 sub deactivate_volume {
345 my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
346
347 die "Snapshot not implemented on DRBD\n" if $snapname;
348
349 return undef; # fixme: should we unassign ?
350
351 # remove above return to enable this code
352 my $hdl = connect_drbdmanage_service();
353 my $nodename = PVE::INotify::nodename();
354 my ($rc, $res) = $hdl->list_assignments([$nodename], [$volname], 0,
355 { "cstate:diskless" => "true" }, []);
356 check_drbd_res($rc);
357 if (scalar(@$res)) {
358 my ($rc, $res) = $hdl->unassign($nodename, $volname,0);
359 check_drbd_res($rc);
360 }
361
362 return undef;
363 }
364
365 sub volume_resize {
366 my ($class, $scfg, $storeid, $volname, $size, $running) = @_;
367
368 $size = ($size/1024/1024) . "M";
369
370 my $path = $class->path($scfg, $volname);
371
372 # fixme: howto implement this
373 die "drbd volume_resize is not implemented";
374
375 #my $cmd = ['/sbin/lvextend', '-L', $size, $path];
376 #run_command($cmd, errmsg => "error resizing volume '$path'");
377
378 return 1;
379 }
380
381 sub volume_snapshot {
382 my ($class, $scfg, $storeid, $volname, $snap, $running) = @_;
383
384 die "drbd snapshot is not implemented";
385 }
386
387 sub volume_snapshot_rollback {
388 my ($class, $scfg, $storeid, $volname, $snap) = @_;
389
390 die "drbd snapshot rollback is not implemented";
391 }
392
393 sub volume_snapshot_delete {
394 my ($class, $scfg, $storeid, $volname, $snap) = @_;
395
396 die "drbd snapshot delete is not implemented";
397 }
398
399 sub volume_has_feature {
400 my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
401
402 my $features = {
403 copy => { base => 1, current => 1},
404 };
405
406 my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) =
407 $class->parse_volname($volname);
408
409 my $key = undef;
410 if($snapname){
411 $key = 'snap';
412 }else{
413 $key = $isBase ? 'base' : 'current';
414 }
415 return 1 if $features->{$feature}->{$key};
416
417 return undef;
418 }
419
420 1;