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