use strict;
use warnings;
+
use IO::File;
+use Net::DBus;
+
use PVE::Tools qw(run_command trim);
+use PVE::INotify;
use PVE::Storage::Plugin;
use PVE::JSONSchema qw(get_standard_option);
# Configuration
+my $default_redundancy = 2;
+
sub type {
return 'drbd';
}
sub plugindata {
return {
- content => [ {images => 1}, { images => 1 }],
+ content => [ {images => 1, rootdir => 1}, { images => 1 }],
};
}
type => 'integer',
minimum => 1,
maximum => 16,
- default => 2,
+ default => $default_redundancy,
},
};
}
sub options {
return {
redundancy => { optional => 1 },
+ content => { optional => 1 },
nodes => { optional => 1 },
disable => { optional => 1 },
+ bwlimit => { optional => 1 },
};
}
+# helper
+
+sub get_redundancy {
+ my ($scfg) = @_;
+
+ return $scfg->{redundancy} || $default_redundancy;
+}
+
+sub connect_drbdmanage_service {
+
+ my $bus = Net::DBus->system;
+
+ my $service = $bus->get_service("org.drbd.drbdmanaged");
+
+ my $hdl = $service->get_object("/interface", "org.drbd.drbdmanaged");
+
+ return $hdl;
+}
+
+sub check_drbd_res {
+ my ($rc) = @_;
+
+ die "got undefined drbd result\n" if !$rc;
+
+ # Messages for return codes 1 to 99 are not considered an error.
+ foreach my $res (@$rc) {
+ my ($code, $format, $details) = @$res;
+
+ next if $code < 100;
+
+ my $msg;
+ if (defined($format)) {
+ my @args = ();
+ push @args, $details->{$1} // ""
+ while $format =~ s,\%\((\w+)\),%,;
+
+ $msg = sprintf($format, @args);
+
+ } else {
+ $msg = "drbd error: got error code $code";
+ }
+
+ chomp $msg;
+ die "drbd error: $msg\n";
+ }
+
+ return undef;
+}
+
+sub drbd_list_volumes {
+ my ($hdl) = @_;
+
+ $hdl = connect_drbdmanage_service() if !$hdl;
+
+ my ($rc, $res) = $hdl->list_volumes([], 0, {}, []);
+ check_drbd_res($rc);
+
+ my $volumes = {};
+
+ foreach my $entry (@$res) {
+ my ($volname, $properties, $vol_list) = @$entry;
+
+ next if $volname !~ m/^vm-(\d+)-/;
+ my $vmid = $1;
+
+ # fixme: we always use volid 0 ?
+ my $size = 0;
+ foreach my $volentry (@$vol_list) {
+ my ($vol_id, $vol_properties) = @$volentry;
+ next if $vol_id != 0;
+ my $vol_size = $vol_properties->{vol_size} * 1024;
+ $size = $vol_size if $vol_size > $size;
+ }
+
+ $volumes->{$volname} = { format => 'raw', size => $size,
+ vmid => $vmid };
+ }
+
+ return $volumes;
+}
+
# Storage implementation
sub parse_volname {
my ($class, $volname) = @_;
if ($volname =~ m/^(vm-(\d+)-[a-z][a-z0-9\-\_\.]*[a-z0-9]+)$/) {
- return ('images', $1, $2);
+ return ('images', $1, $2, undef, undef, undef, 'raw');
}
die "unable to parse lvm volume name '$volname'\n";
}
sub filesystem_path {
- my ($class, $scfg, $volname) = @_;
+ my ($class, $scfg, $volname, $snapname) = @_;
+
+ die "drbd snapshot is not implemented\n" if defined($snapname);
my ($vtype, $name, $vmid) = $class->parse_volname($volname);
- die "fixme";
-
- my $vg = $scfg->{vgname};
-
- my $path = "/dev/$vg/$name";
+ # fixme: always use volid 0?
+ my $path = "/dev/drbd/by-res/$volname/0";
return wantarray ? ($path, $vmid, $vtype) : $path;
}
die "unsupported format '$fmt'" if $fmt ne 'raw';
- die "illegal name '$name' - sould be 'vm-$vmid-*'\n"
- if $name && $name !~ m/^vm-$vmid-/;
+ die "illegal name '$name' - should be 'vm-$vmid-*'\n"
+ if defined($name) && $name !~ m/^vm-$vmid-/;
+ my $hdl = connect_drbdmanage_service();
+ my $volumes = drbd_list_volumes($hdl);
+ my $disk_list = [ keys %$volumes ];
- if (!$name) {
- die "fixme";
-
- my $lvs = {};
-
- for (my $i = 1; $i < 100; $i++) {
- my $tn = "vm-$vmid-disk-$i";
- if (!defined ($lvs->{fixme})) {
- $name = $tn;
- last;
- }
- }
- }
+ die "volume '$name' already exists\n" if defined($name) && $volumes->{$name};
+ $name //= PVE::Storage::Plugin::get_next_vm_diskname($disk_list, $storeid, $vmid, undef, $scfg);
- die "unable to allocate an image name for VM $vmid in storage '$storeid'\n"
- if !$name;
+ my ($rc, $res) = $hdl->create_resource($name, {});
+ check_drbd_res($rc);
- my $cmd = ['drbdmanage', 'new-volume', $name];
+ ($rc, $res) = $hdl->create_volume($name, $size, {});
+ check_drbd_res($rc);
- # fixme: deploy
-
- run_command($cmd, errmsg => "drbdmanage new-volume error");
+ ($rc, $res) = $hdl->set_drbdsetup_props(
+ {
+ target => "resource",
+ resource => $name,
+ type => 'neto',
+ 'allow-two-primaries' => 'yes',
+ });
+ check_drbd_res($rc);
+ my $redundancy = get_redundancy($scfg);;
+
+ ($rc, $res) = $hdl->auto_deploy($name, $redundancy, 0, 0);
+ check_drbd_res($rc);
+
return $name;
}
sub free_image {
my ($class, $storeid, $scfg, $volname, $isBase) = @_;
- my $cmd = ['drbdmanage', 'remove-volume', $volname];
-
- # fixme: undeploy
-
- run_command($cmd, errmsg => "drbdmanage remove-volume error");
+ my $hdl = connect_drbdmanage_service();
+ my ($rc, $res) = $hdl->remove_resource($volname, 0);
+ check_drbd_res($rc);
return undef;
}
my $vgname = $scfg->{vgname};
- #$cache->{lvs} = lvm_lvs() if !$cache->{lvs};
-
+ $cache->{drbd_volumes} = drbd_list_volumes() if !$cache->{drbd_volumes};
+
my $res = [];
- die "fixme";
-
+ my $dat = $cache->{drbd_volumes};
+
+ foreach my $volname (keys %$dat) {
+
+ my $owner = $dat->{$volname}->{vmid};
+
+ my $volid = "$storeid:$volname";
+
+ if ($vollist) {
+ my $found = grep { $_ eq $volid } @$vollist;
+ next if !$found;
+ } else {
+ next if defined ($vmid) && ($owner ne $vmid);
+ }
+
+ my $info = $dat->{$volname};
+ $info->{volid} = $volid;
+
+ push @$res, $info;
+ }
+
return $res;
}
sub status {
my ($class, $storeid, $scfg, $cache) = @_;
- die "fixme";
+ my ($total, $avail, $used);
- return undef;
+ eval {
+ my $hdl = connect_drbdmanage_service();
+ my $redundancy = get_redundancy($scfg);;
+ my ($rc, $free_space, $total_space) = $hdl->cluster_free_query($redundancy);
+ check_drbd_res($rc);
+
+ $avail = $free_space*1024;
+ $total = $total_space*1024;
+ $used = $total - $avail;
+
+ };
+ if (my $err = $@) {
+ # ignore error,
+ # assume storage if offline
+
+ return undef;
+ }
+
+ return ($total, $avail, $used, 1);
}
sub activate_storage {
}
sub activate_volume {
- my ($class, $storeid, $scfg, $volname, $exclusive, $cache) = @_;
+ my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
+
+ die "Snapshot not implemented on DRBD\n" if $snapname;
+
+ my $path = $class->path($scfg, $volname);
+
+ my $hdl = connect_drbdmanage_service();
+ my $nodename = PVE::INotify::nodename();
+ my ($rc, $res) = $hdl->list_assignments([$nodename], [$volname], 0, {}, []);
+ check_drbd_res($rc);
+
+# assignment already exists?
+ return undef if @$res;
+
+ # create diskless assignment
+ ($rc, $res) = $hdl->assign($nodename, $volname, { diskless => 'true' });
+ check_drbd_res($rc);
+
+ # wait until device is accessible
+ my $print_warning = 1;
+ my $max_wait_time = 20;
+ for (my $i = 0;; $i++) {
+ if (1) {
+ # clumsy, but works
+ last if system("dd if=$path of=/dev/null bs=512 count=1 >/dev/null 2>&1") == 0;
+ } else {
+ # correct, but does not work?
+ ($rc, $res) = $hdl->list_assignments([$nodename], [$volname], 0, { "cstate:deploy" => "true" }, []);
+ check_drbd_res($rc);
+ my $len = scalar(@$res);
+ last if $len > 0;
+ }
+ die "aborting wait - device '$path' still not readable\n" if $i > $max_wait_time;
+ print "waiting for device '$path' to become ready...\n" if $print_warning;
+ $print_warning = 0;
+ sleep(1);
+ }
return undef;
}
sub deactivate_volume {
- my ($class, $storeid, $scfg, $volname, $cache) = @_;
+ my ($class, $storeid, $scfg, $volname, $snapname, $cache) = @_;
+ die "Snapshot not implemented on DRBD\n" if $snapname;
+
+ return undef; # fixme: should we unassign ?
+
+ # remove above return to enable this code
+ my $hdl = connect_drbdmanage_service();
+ my $nodename = PVE::INotify::nodename();
+ my ($rc, $res) = $hdl->list_assignments([$nodename], [$volname], 0,
+ { "cstate:diskless" => "true" }, []);
+ check_drbd_res($rc);
+ if (scalar(@$res)) {
+ my ($rc, $res) = $hdl->unassign($nodename, $volname,0);
+ check_drbd_res($rc);
+ }
+
return undef;
}
my $path = $class->path($scfg, $volname);
- die "fixme";
+ # fixme: howto implement this
+ die "drbd volume_resize is not implemented";
#my $cmd = ['/sbin/lvextend', '-L', $size, $path];
#run_command($cmd, errmsg => "error resizing volume '$path'");