package PVE::Storage::DRBDPlugin; # FIXME: remove with 7.0 use strict; use warnings; use IO::File; # FIXME remove libnet-dbus-perl dependency once this gets removed use Net::DBus; use PVE::Tools qw(run_command trim); use PVE::INotify; use PVE::Storage::Plugin; use PVE::JSONSchema qw(get_standard_option); use base qw(PVE::Storage::Plugin); # Configuration my $default_redundancy = 2; sub type { return 'drbd'; } sub plugindata { return { content => [ {images => 1, rootdir => 1}, { images => 1 }], }; } sub properties { return { redundancy => { 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.", type => 'integer', minimum => 1, maximum => 16, 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, undef, undef, undef, 'raw'); } die "unable to parse lvm volume name '$volname'\n"; } sub filesystem_path { my ($class, $scfg, $volname, $snapname) = @_; die "drbd snapshot is not implemented\n" if defined($snapname); my ($vtype, $name, $vmid) = $class->parse_volname($volname); # fixme: always use volid 0? my $path = "/dev/drbd/by-res/$volname/0"; return wantarray ? ($path, $vmid, $vtype) : $path; } sub create_base { my ($class, $storeid, $scfg, $volname) = @_; die "can't create base images in drbd storage\n"; } sub clone_image { my ($class, $scfg, $storeid, $volname, $vmid, $snap) = @_; die "can't clone images in drbd storage\n"; } sub alloc_image { my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_; die "unsupported format '$fmt'" if $fmt ne 'raw'; 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 ]; 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); my ($rc, $res) = $hdl->create_resource($name, {}); check_drbd_res($rc); ($rc, $res) = $hdl->create_volume($name, $size, {}); check_drbd_res($rc); ($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 $hdl = connect_drbdmanage_service(); my ($rc, $res) = $hdl->remove_resource($volname, 0); check_drbd_res($rc); return undef; } sub list_images { my ($class, $storeid, $scfg, $vmid, $vollist, $cache) = @_; my $vgname = $scfg->{vgname}; $cache->{drbd_volumes} = drbd_list_volumes() if !$cache->{drbd_volumes}; my $res = []; 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) = @_; my ($total, $avail, $used); 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 { my ($class, $storeid, $scfg, $cache) = @_; return undef; } sub deactivate_storage { my ($class, $storeid, $scfg, $cache) = @_; return undef; } sub activate_volume { 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, $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; } sub volume_resize { my ($class, $scfg, $storeid, $volname, $size, $running) = @_; $size = ($size/1024/1024) . "M"; my $path = $class->path($scfg, $volname); # 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'"); return 1; } sub volume_snapshot { my ($class, $scfg, $storeid, $volname, $snap, $running) = @_; die "drbd snapshot is not implemented"; } sub volume_snapshot_rollback { my ($class, $scfg, $storeid, $volname, $snap) = @_; die "drbd snapshot rollback is not implemented"; } sub volume_snapshot_delete { my ($class, $scfg, $storeid, $volname, $snap) = @_; die "drbd snapshot delete is not implemented"; } sub volume_has_feature { my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_; my $features = { copy => { base => 1, current => 1}, }; my ($vtype, $name, $vmid, $basename, $basevmid, $isBase) = $class->parse_volname($volname); my $key = undef; if($snapname){ $key = 'snap'; }else{ $key = $isBase ? 'base' : 'current'; } return 1 if $features->{$feature}->{$key}; return undef; } 1;