[pve-devel] [PATCH v2 storage 1/5] add BTRFS storage plugin
Wolfgang Bumiller
w.bumiller at proxmox.com
Wed Jun 23 14:43:31 CEST 2021
On Wed, Jun 23, 2021 at 02:15:00PM +0200, Fabian Grünbichler wrote:
> On June 22, 2021 2:18 pm, Wolfgang Bumiller wrote:
(...)
> > +sub plugindata {
> > + return {
> > + content => [
> > + {
> > + images => 1,
> > + rootdir => 1,
> > + vztmpl => 1,
> > + iso => 1,
> > + backup => 1,
> > + snippets => 1,
> > + none => 1,
> > + },
> > + { images => 1, rootdir => 1 },
> > + ],
> > + format => [ { raw => 1, qcow2 => 1, vmdk => 1, subvol => 1 }, 'raw', ],
>
> I am not really convinced we need this (or rather, that it doesn't
> cause more confusion than convenience. if I want to use btrfs without
> any btrfs features, I can already use any btrfs-backed dir as dir
> storage (just like with ZFS, or $storage-of-my-choice-providing-a-directory).
>
> e.g., having a qcow2 file on btrfs storage support snapshots but not use
> btrfs functionality for that seems... weird?
It's a directory storage after all.
But I really don't mind much if we just remove that...
>
> > + };
> > +}
> > +
> > +sub options {
> > + return {
> > + path => { fixed => 1 },
> > + nodes => { optional => 1 },
> > + shared => { optional => 1 },
> > + disable => { optional => 1 },
> > + maxfiles => { optional => 1 },
> > + content => { optional => 1 },
> > + format => { optional => 1 },
> > + is_mountpoint => { optional => 1 },
> > + # TODO: The new variant of mkdir with `populate` vs `create`...
> > + };
> > +}
> > +
> > +# Storage implementation
> > +#
> > +# We use the same volume names are directory plugins, but map *raw* disk image file names into a
> > +# subdirectory.
> > +#
> > +# `vm-VMID-disk-ID.raw`
> > +# -> `images/VMID/vm-VMID-disk-ID/disk.raw`
> > +# where the `vm-VMID-disk-ID/` subdirectory is a btrfs subvolume
> > +
> > +# Reuse `DirPlugin`'s `check_config`. This simply checks for invalid paths.
> > +sub check_config {
> > + my ($self, $sectionId, $config, $create, $skipSchemaCheck) = @_;
> > + return PVE::Storage::DirPlugin::check_config($self, $sectionId, $config, $create, $skipSchemaCheck);
> > +}
> > +
> > +sub activate_storage {
> > + my ($class, $storeid, $scfg, $cache) = @_;
> > + return PVE::Storage::DirPlugin::activate_storage($class, $storeid, $scfg, $cache);
>
> shouldn't this check that we can actually do btrfs stuff on $path?
Right. I had a method for this at some point but hadn't used it here.
Should be easily added in a follow up.
>
> > +}
> > +
> > +sub status {
> > + my ($class, $storeid, $scfg, $cache) = @_;
> > + return PVE::Storage::DirPlugin::status($class, $storeid, $scfg, $cache);
> > +}
> > +
> > +# TODO: sub get_volume_notes {}
> > +
> > +# TODO: sub update_volume_notes {}
> > +
> > +# croak would not include the caller from within this module
> > +sub __error {
> > + my ($msg) = @_;
> > + my (undef, $f, $n) = caller(1);
> > + die "$msg at $f: $n\n";
> > +}
>
> this is kind of a new style of error handling - might be worthy of a
> mention somewhere ;)
debug helper... shouldn't be possible to reach this... I should probably
prefix the 2 subs below with a `my` though...
(I mostly ran into such stuff while playing with different ways of
naming/organising snapshot directories and not seeing *where* stuff
happened was annoying...)
>
> > +
> > +# Given a name (eg. `vm-VMID-disk-ID.raw`), take the part up to the format suffix as the name of
> > +# the subdirectory (subvolume).
> > +sub raw_name_to_dir($) {
> > + my ($raw) = @_;
> > +
> > + # For the subvolume directory Strip the `.<format>` suffix:
> > + if ($raw =~ /^(.*)\.raw$/) {
> > + return $1;
> > + }
> > +
> > + __error "internal error: bad disk name: $raw";
> > +}
> > +
> > +sub raw_file_to_subvol($) {
> > + my ($file) = @_;
> > +
> > + if ($file =~ m|^(.*)/disk\.raw$|) {
> > + return "$1";
> > + }
> > +
> > + __error "internal error: bad raw path: $file";
> > +}
> > +
(...)
> > +
> > +sub btrfs_get_subvol_id {
> > + my ($class, $path) = @_;
> > + my $info = $class->btrfs_cmd(['subvolume', 'show', '--', $path]);
> > + if ($info !~ /^\s*(?:Object|Subvolume) ID:\s*(\d+)$/m) {
> > + die "failed to get btrfs subvolume ID from: $info\n";
>
> ugh at yet another parser for CLI tool output.. (not your fault I
> know..)
This one I can actually replace. I just am not sure if I want to do it
all in pure perl or add some btrfs helper to the emerging `pve-rs` lib,
and since this code already existed in the old series, I postponed that
decision.
(...)
> > + my $imagedir = $class->get_subdir($scfg, 'images');
> > + $imagedir .= "/$vmid";
> > + mkpath $imagedir;
> > +
> > + my $path = $class->filesystem_path($scfg, $volname);
> > + my $newname = $class->find_free_diskname($storeid, $scfg, $vmid, $format, 1);
> > +
> > + # For btrfs subvolumes we don't actually need the "link":
> > + #my $newvolname = "$basevmid/$basename/$vmid/$newname";
> > + my $newvolname = "$vmid/$newname";
> > + my $newpath = $class->filesystem_path($scfg, $newvolname);
> > +
> > + my $subvol = $path;
> > + my $newsubvol = $newpath;
> > + if ($format eq 'raw') {
> > + $subvol = raw_file_to_subvol($subvol);
> > + $newsubvol = raw_file_to_subvol($newsubvol);
> > + }
>
> so actually these are two blocks for getting at $subvol and $newsubvol,
> one for raw one for subvol. structuring it as such would make it more
> readable IMHO..
Yeah I suppose those lines could look nicer. Also happens another
time...
>
> > +
> > + $class->btrfs_cmd(['subvolume', 'snapshot', '--', $subvol, $newsubvol]);
> > +
> > + return $newvolname;
> > +}
> > +
> > +sub alloc_image {
> > + my ($class, $storeid, $scfg, $vmid, $fmt, $name, $size) = @_;
> > +
> > + if ($fmt ne 'raw' && $fmt ne 'subvol') {
> > + return PVE::Storage::DirPlugin::alloc_image(@_);
> > + }
> > +
> > + # From Plugin.pm:
> > +
> > + my $imagedir = $class->get_subdir($scfg, 'images') . "/$vmid";
> > +
> > + mkpath $imagedir;
> > +
> > + $name = $class->find_free_diskname($storeid, $scfg, $vmid, $fmt, 1) if !$name;
> > +
> > + my (undef, $tmpfmt) = PVE::Storage::Plugin::parse_name_dir($name);
> > +
> > + die "illegal name '$name' - wrong extension for format ('$tmpfmt != '$fmt')\n"
> > + if $tmpfmt ne $fmt;
> > +
> > + # End copy from Plugin.pm
> > +
> > + my $subvol = "$imagedir/$name";
> > + # .raw is not part of the directory name
> > + $subvol =~ s/\.raw$//;
>
> why not use your helpers for this (raw_.._to_..)
Here it can refer to a `subvol` as well which the helper doesn't allow,
but sure, could be improved.
> in a similar vain, there are three "add '/disk.raw' to path" sites
> that might warrant a helper..
ok
(...)
> > +sub volume_snapshot {
> > + my ($class, $scfg, $storeid, $volname, $snap) = @_;
> > +
> > + my ($name, $vmid, $format) = ($class->parse_volname($volname))[1,2,6];
> > + if ($format ne 'subvol' && $format ne 'raw') {
> > + return PVE::Storage::Plugin::volume_snapshot(@_);
> > + }
> > +
> > + my $path = $class->filesystem_path($scfg, $volname);
> > + my $snap_path = $class->filesystem_path($scfg, $volname, $snap);
> > +
> > + if ($format eq 'raw') {
> > + $path = raw_file_to_subvol($path);
> > + $snap_path = raw_file_to_subvol($snap_path);
> > + }
>
> this is a repeating pattern (volname + optional snapshot name => path(s)
> based on format)
yup, mentioned it above
>
> > +
> > + my $snapshot_dir = $class->get_subdir($scfg, 'images') . "/$vmid";
> > + mkpath $snapshot_dir;
> > +
> > + $class->btrfs_cmd(['subvolume', 'snapshot', '-r', '--', $path, $snap_path]);
> > + return undef;
> > +}
> > +
> > +sub volume_rollback_is_possible {
> > + my ($class, $scfg, $storeid, $volname, $snap) = @_;
> > +
> > + return 1;
>
> snapshot creation was delegated to Plugin.pm for non-raw and non-subvol,
> so if we don't drop that part the same logic would need to apply here..
Ah, good catch.
(...)
> > +sub volume_has_feature {
> > + my ($class, $scfg, $feature, $storeid, $volname, $snapname, $running) = @_;
> > +
> > + my $features = {
> > + snapshot => {
> > + current => { qcow2 => 1, raw => 1, subvol => 1 },
> > + snap => { qcow2 => 1, raw => 1, subvol => 1 }
> > + },
> > + clone => {
> > + base => { qcow2 => 1, raw => 1, subvol => 1, vmdk => 1 },
> > + current => { raw => 1 },
> > + snap => { raw => 1 },
> > + },
> > + template => { current => { qcow2 => 1, raw => 1, vmdk => 1, subvol => 1 } },
> > + copy => {
> > + base => { qcow2 => 1, raw => 1, subvol => 1, vmdk => 1 },
> > + current => { qcow2 => 1, raw => 1, subvol => 1, vmdk => 1 },
> > + snap => { qcow2 => 1, raw => 1, subvol => 1 },
> > + },
> > + sparseinit => { base => {qcow2 => 1, raw => 1, vmdk => 1 },
> > + current => {qcow2 => 1, raw => 1, vmdk => 1 } },
> > + };
>
> some of that is duplicated from the regular plugin, so if we don't drop
> the qcow2/.. support here we need to forward those based on $format..
good point
More information about the pve-devel
mailing list