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