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