use strict;
use warnings;
+
use IO::File;
+use JSON;
use Net::IP;
-use PVE::Tools qw(run_command trim);
-use PVE::Storage::Plugin;
+
+use PVE::CephConfig;
use PVE::JSONSchema qw(get_standard_option);
+use PVE::ProcFSTools;
use PVE::RADOS;
-use PVE::CephConfig;
-use JSON;
+use PVE::Storage::Plugin;
+use PVE::Tools qw(run_command trim);
use base qw(PVE::Storage::Plugin);
};
# needed for volumes created using ceph jewel (or higher)
-my $krbd_feature_disable = sub {
+my $krbd_feature_update = sub {
my ($scfg, $storeid, $name) = @_;
- my ($major, undef, undef, undef) = ceph_version();
- return 1 if $major < 10;
+ my (@disable, @enable);
+ my ($kmajor, $kminor) = PVE::ProcFSTools::kernel_version();
- my $krbd_feature_blacklist = ['deep-flatten', 'fast-diff', 'object-map', 'exclusive-lock'];
- my (undef, undef, undef, undef, $features) = rbd_volume_info($scfg, $storeid, $name);
-
- my $active_features = { map { $_ => 1 } @$features };
- my $incompatible_features = join(',', grep { %$active_features{$_} } @$krbd_feature_blacklist);
-
- if ($incompatible_features) {
- my $feature_cmd = &$rbd_cmd($scfg, $storeid, 'feature', 'disable', $name, $incompatible_features);
- run_rbd_command($feature_cmd, errmsg => "could not disable krbd-incompatible image features of rbd volume $name");
+ if ($kmajor > 5 || $kmajor == 5 && $kminor >= 3) {
+ # 'deep-flatten' can only be disabled, not enabled after image creation
+ push @enable, 'fast-diff', 'object-map';
+ } else {
+ push @disable, 'fast-diff', 'object-map', 'deep-flatten';
}
-};
-my $ceph_version_parser = sub {
- my $line = shift;
- if ($line =~ m/^ceph version ((\d+)\.(\d+)\.(\d+))(?: \([a-fA-F0-9]+\))/) {
- return ($2, $3, $4, $1);
+ if ($kmajor >= 5) {
+ push @enable, 'exclusive-lock';
} else {
- warn "Could not parse Ceph version: '$line'\n";
+ push @disable, 'exclusive-lock';
}
-};
-sub ceph_version {
- my ($cache) = @_;
+ my $active_features_list = (rbd_volume_info($scfg, $storeid, $name))[4];
+ my $active_features = { map { $_ => 1 } @$active_features_list };
- my $version_string = $cache;
+ my $to_disable = join(',', grep { $active_features->{$_} } @disable);
+ my $to_enable = join(',', grep { !$active_features->{$_} } @enable );
- my $major;
- my $minor;
- my $bugfix;
-
- if (defined($version_string)) {
- ($major, $minor, $bugfix, $version_string) = &$ceph_version_parser($version_string);
- } else {
- run_command('ceph --version', outfunc => sub {
- my $line = shift;
- ($major, $minor, $bugfix, $version_string) = &$ceph_version_parser($line);
- });
+ if ($to_disable) {
+ print "disable RBD image features this kernel RBD drivers is not compatible with: $to_disable\n";
+ my $cmd = $rbd_cmd->($scfg, $storeid, 'feature', 'disable', $name, $to_disable);
+ run_rbd_command(
+ $cmd,
+ errmsg => "could not disable krbd-incompatible image features '$to_disable' for rbd image: $name",
+ );
}
- return undef if !defined($version_string);
- return wantarray ? ($major, $minor, $bugfix, $version_string) : $version_string;
-}
+ if ($to_enable) {
+ print "enable RBD image features this kernel RBD drivers supports: $to_enable\n";
+ eval {
+ my $cmd = $rbd_cmd->($scfg, $storeid, 'feature', 'enable', $name, $to_enable);
+ run_rbd_command(
+ $cmd,
+ errmsg => "could not enable krbd-compatible image features '$to_enable' for rbd image: $name",
+ );
+ };
+ warn "$@" if $@;
+ }
+};
sub run_rbd_command {
my ($cmd, %args) = @_;
return $list;
}
+sub rbd_ls_snap {
+ my ($scfg, $storeid, $name) = @_;
+
+ my $cmd = &$rbd_cmd($scfg, $storeid, 'snap', 'ls', $name, '--format', 'json');
+
+ my $raw = '';
+ run_rbd_command($cmd, errmsg => "rbd error", errfunc => sub {}, outfunc => sub { $raw .= shift; });
+
+ my $list;
+ if ($raw =~ m/^(\[.*\])$/s) { # untaint
+ $list = eval { JSON::decode_json($1) };
+ die "invalid JSON output from 'rbd snap ls $name': $@\n" if $@;
+ } else {
+ die "got unexpected data from 'rbd snap ls $name': '$raw'\n";
+ }
+
+ $list = [] if !defined($list);
+
+ my $res = {};
+ foreach my $el (@$list) {
+ my $snap = $el->{name};
+ my $protected = defined($el->{protected}) && $el->{protected} eq "true" ? 1 : undef;
+ $res->{$snap} = {
+ name => $snap,
+ id => $el->{id} // undef,
+ size => $el->{size} // 0,
+ protected => $protected,
+ };
+ }
+ return $res;
+}
+
sub rbd_volume_info {
my ($scfg, $storeid, $volname, $snap) = @_;
return ($path, $vmid, $vtype);
}
-my $find_free_diskname = sub {
- my ($storeid, $scfg, $vmid) = @_;
+sub find_free_diskname {
+ my ($class, $storeid, $scfg, $vmid, $fmt, $add_fmt_suffix) = @_;
my $cmd = &$rbd_cmd($scfg, $storeid, 'ls');
my $disk_list = [];
die $err if $err && $err !~ m/doesn't contain rbd images/;
return PVE::Storage::Plugin::get_next_vm_diskname($disk_list, $storeid, $vmid, undef, $scfg);
-};
+}
sub create_base {
my ($class, $storeid, $scfg, $volname) = @_;
die "$volname is not a base image and snapname is not provided\n"
if !$isBase && !length($snapname);
- my $name = $find_free_diskname->($storeid, $scfg, $vmid);
+ my $name = $class->find_free_diskname($storeid, $scfg, $vmid);
warn "clone $volname: $basename snapname $snap to $name\n";
die "illegal name '$name' - should be 'vm-$vmid-*'\n"
if $name && $name !~ m/^vm-$vmid-/;
- $name = $find_free_diskname->($storeid, $scfg, $vmid) if !$name;
+ $name = $class->find_free_diskname($storeid, $scfg, $vmid) if !$name;
my $cmd = &$rbd_cmd($scfg, $storeid, 'create', '--image-format' , 2, '--size', int(($size+1023)/1024), $name);
run_rbd_command($cmd, errmsg => "rbd create $name' error");
my ($vtype, $name, $vmid, undef, undef, undef) =
$class->parse_volname($volname);
- if ($isBase) {
- my $snap = '__base__';
- my (undef, undef, undef, $protected) = rbd_volume_info($scfg, $storeid, $name, $snap);
- if ($protected){
+ my $snaps = rbd_ls_snap($scfg, $storeid, $name);
+ foreach my $snap (keys %$snaps) {
+ if ($snaps->{$snap}->{protected}) {
my $cmd = &$rbd_cmd($scfg, $storeid, 'snap', 'unprotect', $name, '--snap', $snap);
run_rbd_command($cmd, errmsg => "rbd unprotect $name snap '$snap' error");
}
# max_avail -> max available space for data w/o replication in the pool
# bytes_used -> data w/o replication in the pool
my $free = $d->{stats}->{max_avail};
- my $used = $d->{stats}->{bytes_used};
+ my $used = $d->{stats}->{stored} // $d->{stats}->{bytes_used};
my $total = $used + $free;
my $active = 1;
sub map_volume {
my ($class, $storeid, $scfg, $volname, $snapname) = @_;
- my ($vtype, $name, $vmid) = $class->parse_volname($volname);
+ my ($vtype, $img_name, $vmid) = $class->parse_volname($volname);
+
+ my $name = $img_name;
$name .= '@'.$snapname if $snapname;
my $pool = $scfg->{pool} ? $scfg->{pool} : 'rbd';
return $kerneldev if -b $kerneldev; # already mapped
- &$krbd_feature_disable($scfg, $storeid, $name);
+ # features can only be enabled/disabled for image, not for snapshot!
+ $krbd_feature_update->($scfg, $storeid, $img_name);
my $cmd = &$rbd_cmd($scfg, $storeid, 'map', $name);
run_rbd_command($cmd, errmsg => "can't map rbd volume $name");