On Tue Apr 1, 2025 at 7:34 PM CEST, Fiona Ebner wrote: > The new_backup_provider() method can be used by storage plugins for > external backup providers. If the method returns a provider, Proxmox > VE will use callbacks to that provider for backups and restore instead > of using its usual backup/restore mechanisms.
Really liking this patch as well as all the documentation! :) There are a couple comments inline. > > The backup provider API is split into two parts, both of which again > need different implementations for VM and LXC guests: > > 1. Backup API > > In Proxmox VE, a backup job consists of backup tasks for individual > guests. There are methods for initialization and cleanup of the job, > i.e. job_init() and job_cleanup() and for each guest backup, i.e. > backup_init() and backup_cleanup(). > > The backup_get_mechanism() method is used to decide on the backup > mechanism. Currently, 'file-handle' or 'nbd' for VMs, and 'directory' > for containers is possible. The method also let's the plugin indicate > whether to use a bitmap for incremental VM backup or not. It is enough > to implement one mechanism for VMs and one mechanism for containers. > > Next, there are methods for backing up the guest's configuration and > data, backup_vm() for VM backup and backup_container() for container > backup, with the latter running > > Finally, some helpers like getting the provider name or volume ID for > the backup target, as well as for handling the backup log. > > The backup transaction looks as follows: > > First, job_init() is called that can be used to check backup server > availability and prepare the connection. Then for each guest > backup_init() followed by backup_vm() or backup_container() and finally > backup_cleanup(). Afterwards job_cleanup() is called. For containers, > there is an additional backup_container_prepare() call while still > privileged. The actual backup_container() call happens as the > (unprivileged) container root user, so that the file owner and group IDs > match the container's perspective. > > 1.1 Backup Mechanisms > > VM: > > Access to the data on the VM's disk from the time the backup started > is made available via a so-called "snapshot access". This is either > the full image, or in case a bitmap is used, the dirty parts of the > image since the last time the bitmap was used for a successful backup. > Reading outside of the dirty parts will result in an error. After > backing up each part of the disk, it should be discarded in the export > to avoid unnecessary space usage on the Proxmox VE side (there is an > associated fleecing image). > > VM mechanism 'file-handle': > > The snapshot access is exposed via a file descriptor. A subroutine to > read the dirty regions for incremental backup is provided as well. > > VM mechanism 'nbd': > > The snapshot access and, if used, bitmap are exported via NBD. > > Container mechanism 'directory': > > A copy or snapshot of the container's filesystem state is made > available as a directory. The method is executed inside the user > namespace associated to the container. > > 2. Restore API > > The restore_get_mechanism() method is used to decide on the restore > mechanism. Currently, 'qemu-img' for VMs, and 'directory' or 'tar' for > containers are possible. It is enough to implement one mechanism for > VMs and one mechanism for containers. > > Next, methods for extracting the guest and firewall configuration and > the implementations of the restore mechanism via a pair of methods: an > init method, for making the data available to Proxmox VE and a cleanup > method that is called after restore. > > 2.1. Restore Mechanisms > > VM mechanism 'qemu-img': > > The backup provider gives a path to the disk image that will be > restored. The path needs to be something 'qemu-img' can deal with, > e.g. can also be an NBD URI or similar. > > Container mechanism 'directory': > > The backup provider gives the path to a directory with the full > filesystem structure of the container. > > Container mechanism 'tar': > > The backup provider gives the path to a (potentially compressed) tar > archive with the full filesystem structure of the container. > > See the PVE::BackupProvider::Plugin module for the full API > documentation. > > Signed-off-by: Fiona Ebner <f.eb...@proxmox.com> > --- > > Changes in v7: > * Verify result from backup_init(). Document allowed characters, i.e. > $PVE::Storage::SAFE_CHAR_CLASS_RE + slash + colon > * Move backup_container_perpare() to directly above backup_container() > in the base backup provider plugin. > * Document limitation of backup_container() method not being able to > persistently modify $self, because it runs in a fork(). > * Support per-device bitmap names. Added a new > backup_vm_available_bitmaps() method to the backup provider API. The > backup provider can check on the server which bitmaps can be used in > principle and tell us to try. Acutal bitmap mode still will be > what's passed to backup_vm(), because the requested bitmap might not > exist (anymore). This also means the get_backup_mechanism() method > will return only the mechanism and not the bitmap name. > > src/PVE/BackupProvider/Makefile | 3 + > src/PVE/BackupProvider/Plugin/Base.pm | 1165 ++++++++++++++++++++++++ > src/PVE/BackupProvider/Plugin/Makefile | 5 + > src/PVE/Makefile | 1 + > src/PVE/Storage.pm | 8 + > src/PVE/Storage/Plugin.pm | 15 + > 6 files changed, 1197 insertions(+) > create mode 100644 src/PVE/BackupProvider/Makefile > create mode 100644 src/PVE/BackupProvider/Plugin/Base.pm > create mode 100644 src/PVE/BackupProvider/Plugin/Makefile > > diff --git a/src/PVE/BackupProvider/Makefile b/src/PVE/BackupProvider/Makefile > new file mode 100644 > index 0000000..f018cef > --- /dev/null > +++ b/src/PVE/BackupProvider/Makefile > @@ -0,0 +1,3 @@ > +.PHONY: install > +install: > + make -C Plugin install > diff --git a/src/PVE/BackupProvider/Plugin/Base.pm > b/src/PVE/BackupProvider/Plugin/Base.pm > new file mode 100644 > index 0000000..f837867 > --- /dev/null > +++ b/src/PVE/BackupProvider/Plugin/Base.pm > @@ -0,0 +1,1165 @@ > +package PVE::BackupProvider::Plugin::Base; > + > +use strict; > +use warnings; > + > +=pod > + > +=head1 NAME > + > +PVE::BackupProvider::Plugin::Base - Base Plugin for Backup Provider API > + > +=head1 SYNOPSIS > + > + use base qw(PVE::BackupProvider::Plugin::Base); You can `use parent ...` here, as that's more lightweight. `base` is fine too though. See: https://perldoc.perl.org/parent Only difference is that `use parent ...` doesn't support the `fields` pragma, but that doesn't apply here anyway :P > + > +=head1 DESCRIPTION > + > +This module serves as the base for any module implementing the API that > Proxmox > +VE uses to interface with external backup providers. The API is used for > +creating and restoring backups. A backup provider also needs to provide a > +storage plugin for integration with the front-end. The API here is used by > the > +backup stack in the backend. > + > +1. Backup API These subsections here *could* start with a `=head2` if you want to, but IMO this is just fine otherwise. E.g. =head2 1. Backup API > + > +In Proxmox VE, a backup job consists of backup tasks for individual guests. > +There are methods for initialization and cleanup of the job, i.e. job_init() > and > +job_cleanup() and for each guest backup, i.e. backup_init() and > +backup_cleanup(). > + > +The backup_get_mechanism() method is used to decide on the backup mechanism. > +Currently, 'file-handle' or 'nbd' for VMs, and 'directory' for containers is > +possible. The method also let's the plugin indicate whether to use a bitmap > for > +incremental VM backup or not. It is enough to implement one mechanism for VMs > +and one mechanism for containers. > + > +Next, there are methods for backing up the guest's configuration and data, > +backup_vm() for VM backup and backup_container() for container backup. > + > +Finally, some helpers like provider_name() for getting the name of the backup > +provider and backup_handle_log_file() for handling the backup task log. > + > +The backup transaction looks as follows: > + > +First, job_init() is called that can be used to check backup server > availability > +and prepare the connection. Then for each guest backup_init() followed by > +backup_vm() or backup_container() and finally backup_cleanup(). Afterwards > +job_cleanup() is called. For containers, there is an additional > +backup_container_prepare() call while still privileged. The actual > +backup_container() call happens as the (unprivileged) container root user, so > +that the file owner and group IDs match the container's perspective. > + > +1.1 Backup Mechanisms > + > +VM: > + > +Access to the data on the VM's disk is made available via a "snapshot access" > +abstraction. This is effectively a snapshot of the data from the time the > backup > +is started. New guest writes after the backup started do not affect this. The > +"snapshot access" represents either the full image, or in case a bitmap is > used, > +the dirty parts of the image since the last time the bitmap was used for a > +successful backup. > + > +NOTE: If a bitmap is used, the "snapshot access" is really only the dirty > parts > +of the image. You have to query the bitmap to see which parts of the image > are > +accessible/present. Reading or doing any other operation (like querying the > +block allocation status via NBD) outside of the dirty parts of the image will > +result in an error. In particular, if there were no new writes since the last > +successful backup, i.e. the bitmap is fully clean, then the image cannot be > +accessed at all, you can only query the dirty bitmap. > + > +After backing up each part of the disk, it should be discarded in the export > to > +avoid unnecessary space usage on the Proxmox VE side (there is an associated > +fleecing image). > + > +VM mechanism 'file-handle': > + > +The snapshot access is exposed via a file descriptor. A subroutine to read > the > +dirty regions for incremental backup is provided as well. > + > +VM mechanism 'nbd': > + > +The snapshot access and, if used, bitmap are exported via NBD. For the > +specification of the NBD metadata context for dirty bitmaps, see: > +https://qemu.readthedocs.io/en/master/interop/nbd.html The hyperlink above should be: L<https://qemu.readthedocs.io/en/master/interop/nbd.html> (Yeah I know, it's a POD thing ;P ) > + > +Container mechanism 'directory': > + > +A copy or snapshot of the container's filesystem state is made available as a > +directory. > + > +2. Restore API > + > +The restore_get_mechanism() method is used to decide on the restore > mechanism. > +Currently, 'qemu-img' for VMs, and 'directory' or 'tar' for containers are > +possible. It is enough to implement one mechanism for VMs and one mechanism > for > +containers. > + > +Next, methods for extracting the guest and firewall configuration and the > +implementations of the restore mechanism via a pair of methods: an init > method, > +for making the data available to Proxmox VE and a cleanup method that is > called > +after restore. > + > +2.1. Restore Mechanisms > + > +VM mechanism 'qemu-img': > + > +The backup provider gives a path to the disk image that will be restored. The > +path needs to be something 'qemu-img' can deal with, e.g. can also be an NBD > URI > +or similar. > + > +Container mechanism 'directory': > + > +The backup provider gives the path to a directory with the full filesystem > +structure of the container. > + > +Container mechanism 'tar': > + > +The backup provider gives the path to a (potentially compressed) tar archive > +with the full filesystem structure of the container. > + > +=head1 METHODS > + > +=cut > + > +# plugin methods I'm very happy to see documentation for all of these 🙏 There's just one suggestion I'd like to make: You can slim down a lot of the POD by using a `=head3` instead, e.g.: =head3 new The constructor. [...] =cut POD doesn't have to start with `=pod` actually, any kind of command paragraph (anything that starts with `=`) is fine. You can leave out the C<> format code then too, as it's usually not used when documenting subs via headers. You also wouldn't have to nest lists that often anymore, as that's also kind of... suboptimal in POD (due to, well, POD...) ;P Speaking of nested lists: If there aren't that many parameters that need to be documented, you can also just describe them in prose. To give a full example of all of the above, the `new` method below could look like this: =head3 new The constructor. Returns a blessed instance of the backup provider class. Parameters: =over =item C<$storage_plugin> The associated storage plugin class. =item C<$scfg> The storage configuration of the associated storage. =item C<$storeid> The storage ID of the associated storage. =item C<$log_function> The function signature is C<$log_function($log_level, $message)>. This log function can be used to write to the backup task log in Proxmox VE. The C<$log_level> is either C<info>, C<warn> or C<err> for informational messages, warnings or error messages. C<$message> is the message to be printed. =back =cut > + > +=pod > + > +=over > + > +=item C<new> > + > +The constructor. Returns a blessed instance of the backup provider class. > + > +Parameters: > + > +=over > + > +=item C<$storage_plugin> > + > +The associated storage plugin class. > + > +=item C<$scfg> > + > +The storage configuration of the associated storage. > + > +=item C<$storeid> > + > +The storage ID of the associated storage. > + > +=item C<$log_function> > + > +The function signature is C<$log_function($log_level, $message)>. This log > +function can be used to write to the backup task log in Proxmox VE. > + > +=over > + > +=item C<$log_level> > + > +Either C<info>, C<warn> or C<err> for informational messages, warnings or > error > +messages. > + > +=item C<$message> > + > +The message to be printed. > + > +=back > + > +=back > + > +=back > + > +=cut > +sub new { > + my ($class, $storage_plugin, $scfg, $storeid, $log_function) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<provider_name> > + > +Returns the name of the backup provider. It will be printed in some log > lines. > + > +=back > + > +=cut > +sub provider_name { > + my ($self) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<job_init> > + > +Called when the job is started. Can be used to check the backup server > +availability and prepare for the upcoming backup tasks of individual guests. > For > +example, to establish a connection to be used during C<backup_container()> or > +C<backup_vm()>. > + > +Parameters: > + > +=over > + > +=item C<$start_time> > + > +Unix time-stamp of when the job started. > + > +=back > + > +=back > + > +=cut > +sub job_init { > + my ($self, $start_time) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<job_cleanup> > + > +Called when the job is finished to allow for any potential cleanup related to > +the backup server. Called in both, success and failure scenarios. > + > +=back > + > +=cut > +sub job_cleanup { > + my ($self) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<backup_init> > + > +Called before the backup of the given guest is made. The archive name is > +determined for the backup task and returned to the caller via a hash > reference: > + > + my $res = $backup_provider->backup_init($vmid, $vmtype, $start_time); > + my $archive_name = $res->{'archive-name'}; > + > +The archive name must contain only characters from the > +C<$PVE::Storage::SAFE_CHAR_CLASS_RE> character class as well as forward slash > +C</> and colon C<:>. > + > +Use C<$self> to remember it for the C<backup_container()> or C<backup_vm()> > +method that will be called later. > + > +Parameters: > + > +=over > + > +=item C<$vmid> > + > +The ID of the guest being backed up. > + > +=item C<$vmtype> > + > +The type of the guest being backed up. Currently, either C<qemu> or C<lxc>. > + > +=item C<$start_time> > + > +Unix time-stamp of when the guest backup started. > + > +=back > + > +=back > + > +=cut > +sub backup_init { > + my ($self, $vmid, $vmtype, $start_time) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<backup_cleanup> > + > +Called when the guest backup is finished. Called in both, success and failure > +scenarios. In the success case, statistics about the task after completion of > +the backup are returned via a hash reference. Currently, only the archive > size > +is part of the result: > + > + my $res = $backup_provider->backup_cleanup($vmid, $vmtype, $success, > $info); > + my $stats = $res->{stats}; > + my $archive_size = $stats->{'archive-size'}; > + > +Parameters: > + > +=over > + > +=item C<$vmid> > + > +The ID of the guest being backed up. > + > +=item C<$vmtype> > + > +The type of the guest being backed up. Currently, either C<qemu> or C<lxc>. > +Might be C<undef> in phase C<abort> for certain error scenarios. > + > +=item C<$success> > + > +Boolean indicating whether the job was successful or not. Success means that > all > +individual guest backups were successful. > + > +=item C<$info> > + > +A hash reference with optional information. Currently, the error message in > case > +of a failure. > + > +=over > + > +=item C<< $info->{error} >> > + > +Present if there was a failure. The error message indicating the failure. > + > +=back > + > +=back > + > +=back > + > +=cut > +sub backup_cleanup { > + my ($self, $vmid, $vmtype, $success, $info) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<backup_get_mechanism> > + > +Tell the caller what mechanism to use for backing up the guest. The backup > +method for the guest, i.e. C<backup_vm> for guest type C<qemu> or > +C<backup_container> for guest type C<lxc>, will later be called with > +mechanism-specific information. See those methods for more information. > + > +Returns the mechanism: > + > + my $mechanism = $backup_provider->backup_get_mechanism($vmid, $vmtype); > + > +Currently C<nbd> and C<file-handle> for guest type C<qemu> and C<directory> > for > +guest type C<lxc> are possible. If there is no support for one of the guest > +types, the method should either C<die> or return C<undef>. > + > +Parameters: > + > +=over > + > +=item C<$vmid> > + > +The ID of the guest being backed up. > + > +=item C<$vmtype> > + > +The type of the guest being backed up. Currently, either C<qemu> or C<lxc>. > + > +=back > + > +=back > + > +=cut > +sub backup_get_mechanism { > + my ($self, $vmid, $vmtype) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<backup_handle_log_file> > + > +Handle the backup's log file which contains the task log for the backup. For > +example, a provider might want to upload a copy to the backup server. > + > +Parameters: > + > +=over > + > +=item C<$vmid> > + > +The ID of the guest being backed up. > + > +=item C<$filename> > + > +Path to the file with the backup log. > + > +=back > + > +=back > + > +=cut > +sub backup_handle_log_file { > + my ($self, $vmid, $filename) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<backup_vm_available_bitmaps> > + > +Determines which bitmap name the backup server can use for each volume for > +incremental backup. If incremental backup is not supported, simply return > +without modifying the C<$volumes> parameter. > + > +It cannot be guaranteed that the device on the QEMU-side still has the > bitmap. > +For example, the VM might not be running, or the device might have been > resized > +or detached and re-attached. The C<$volumes> parameter in C<backup_vm()> > +will contain the effective bitmap mode, see the C<backup_vm()> method for > +details. > + > +This method does not have a return value, but should set the bitmap name in > +C<< $volumes->{$device_name}->{'bitmap-name'} >> for each device. > + > +Parameters: > + > +=over > + > +=item C<$vmid> > + > +The ID of the guest being backed up. > + > +=item C<$volumes> > + > +Hash reference with information about the VM's volumes. > + > +=over > + > +=item C<< $volumes->{$device_name} >> > + > +Hash reference with information about the VM volume associated to the device > +C<$device_name>. > + > +=over > + > +=item C<< $volumes->{$device_name}->{size} >> > + > +Size of the volume in bytes. If the size does not match what you expect on > the > +backup server side, the bitmap will not exist anymore on the QEMU side. In > this > +case, it can be decided early to use a new bitmap name, but it is also > possible > +to re-use the same name, in which case a bitmap with that name will be newly > +created on the volume. > + > +=back > + > +=back > + > +=back > + > +=back > + > +=cut > +sub backup_vm_available_bitmaps { > + my ($self, $vmid, $volumes) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<backup_vm> > + > +Used when the guest type is C<qemu>. Back up the virtual machine's > configuration > +and volumes that were made available according to the mechanism returned by > +C<backup_get_mechanism>. Returns when done backing up. Ideally, the method > +should log the progress during backup. > + > +Access to the data on the VM's disk is made available via a "snapshot access" > +abstraction. This is effectively a snapshot of the data from the time the > backup > +is started. New guest writes after the backup started do not affect this. The > +"snapshot access" represents either the full image, or in case a bitmap is > used, > +the dirty parts of the image since the last time the bitmap was used for a > +successful backup. > + > +NOTE: If a bitmap is used, the "snapshot access" is really only the dirty > parts > +of the image. You have to query the bitmap to see which parts of the image > are > +accessible/present. Reading or doing any other operation (like querying the > +block allocation status via NBD) outside of the dirty parts of the image will > +result in an error. In particular, if there were no new writes since the last > +successful backup, i.e. the bitmap is fully clean, then the image cannot be > +accessed at all, you can only query the dirty bitmap. > + > +After backing up each part of the disk, it should be discarded in the export > to > +avoid unnecessary space usage on the Proxmox VE side (there is an associated > +fleecing image). > + > +Parameters: > + > +=over > + > +=item C<$vmid> > + > +The ID of the guest being backed up. > + > +=item C<$guest_config> > + > +The guest configuration as raw data. > + > +=item C<$volumes> > + > +Hash reference with information about the VM's volumes. Some parameters are > +mechanism-specific. > + > +=over > + > +=item C<< $volumes->{$device_name} >> > + > +Hash reference with information about the VM volume associated to the device > +C<$device_name>. The device name needs to be remembered for restoring. The > +device name is also the name of the NBD export when the C<nbd> mechanism is > +used. > + > +=item C<< $volumes->{$device_name}->{size} >> > + > +Size of the volume in bytes. > + > +=item C<< $volumes->{$device_name}->{'bitmap-mode'} >> > + > +How a bitmap is used for the current volume. > + > +=over > + > +=item C<none> > + > +No bitmap is used. > + > +=item C<new> > + > +A bitmap has been newly created on the volume. > + > +=item C<reuse> > + > +The bitmap with the same ID as requested is being re-used. > + > +=back > + > +=back > + > +Mechansims-specific parameters for mechanism: s/Mechansims-specific/Mechanism-specific (or Mechanisms-specific?) > + > +=over > + > +=item C<file-handle> > + > +=over > + > +=item C<< $volumes->{$device_name}->{'file-handle'} >> > + > +File handle the backup data can be read from. Discards should be issued via > the > +C<PVE::Storage::Common::deallocate()> function for ranges that already have > been > +backed-up successfully to reduce space usage on the source-side. > + > +=item C<< $volumes->{$device_name}->{'next-dirty-region'} >> > + > +A function that will return the offset and length of the next dirty region > as a > +two-element list. After the last dirty region, it will return C<undef>. If no > +bitmap is used, it will return C<(0, $size)> and then C<undef>. If a bitmap > is > +used, these are the dirty regions according to the bitmap. > + > +=back > + > +=item C<nbd> > + > +For the specification of the NBD metadata context for dirty bitmaps, see: > +https://qemu.readthedocs.io/en/master/interop/nbd.html Hyperlink should be within L<> here as well ;) > + > +=over > + > +=item C<< $volumes->{$device_name}->{'nbd-path'} >> > + > +The path to the Unix socket providing the NBD export with the backup data > and, > +if a bitmap is used, bitmap data. Discards should be issued after reading the > +data to reduce space usage on the source-side. > + > +=item C<< $volumes->{$device_name}->{'bitmap-name'} >> > + > +The name of the bitmap in case a bitmap is used. > + > +=back > + > +=back > + > +=item C<$info> > + > +A hash reference containing optional parameters. > + > +Optional parameters: > + > +=over > + > +=item C<< $info->{'bandwidth-limit'} >> > + > +The requested bandwith limit. The value is in bytes/second. The backup > provider s/bandwith/bandwidth > +is expected to honor this rate limit for IO on the backup source and network > +traffic. A value of C<0>, C<undef> or if there is no such key in the hash all > +mean that there is no limit. > + > +=item C<< $info->{'firewall-config'} >> > + > +Present if the firewall configuration exists. The guest's firewall > +configuration as raw data. > + > +=back > + > +=back > + > +=back > + > +=cut > +sub backup_vm { > + my ($self, $vmid, $guest_config, $volumes, $info) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<backup_container_prepare> > + > +Called right before C<backup_container()> is called. The method > +C<backup_container()> is called as the ID-mapped root user of the container, > so > +as a potentially unprivileged user. The hook is still called as a privileged > +user to allow for the necessary preparation. > + > +Parameters: > + > +=over > + > +=item C<$vmid> > + > +The ID of the guest being backed up. > + > +=item C<$info> > + > +The same information that's passed along to C<backup_container()>, see the > +description there. > + > +=back > + > +=back > + > +=cut > +sub backup_container_prepare { > + my ($self, $vmid, $info) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<backup_container> > + > +Used when the guest type is C<lxc>. Back up the container filesystem > structure > +that is made available for the mechanism returned by C<backup_get_mechanism>. > +Returns when done backing up. Ideally, the method should log the progress > during > +backup. > + > +Note that this method is executed as the ID-mapped root user of the > container, > +so a potentially unprivileged user. The ID is passed along as part of > C<$info>. > +Use the C<backup_container_prepare()> method for preparation. For example, to > +make credentials available to the potentially unprivileged user. > + > +Note that changes to C<$self> made during this method will not be visible in > +later method calls. This is because the method is executed in a separate > +execution context after forking. Use the C<backup_container_prepare()> method > +if you need persistent changes to C<$self>. > + > +Parameters: > + > +=over > + > +=item C<$vmid> > + > +The ID of the guest being backed up. > + > +=item C<$guest_config> > + > +Guest configuration as raw data. > + > +=item C<$exclude_patterns> > + > +A list of glob patterns of files and directories to be excluded. C<**> is > used > +to match current directory and subdirectories. See also the following (note > +that PBS implements more than required here, like explicit inclusion when > +starting with a C<!>): > +L<vzdump > documentation|https://pve.proxmox.com/pve-docs/chapter-vzdump.html#_file_exclusions> > +and > +L<PBS > documentation|https://pbs.proxmox.com/docs/backup-client.html#excluding-files-directories-from-a-backup> > + > +=item C<$info> > + > +A hash reference containing optional and mechanism-specific parameters. > + > +Optional parameters: > + > +=over > + > +=item C<< $info->{'bandwidth-limit'} >> > + > +The requested bandwith limit. The value is in bytes/second. The backup > provider s/bandwith/bandwidth > +is expected to honor this rate limit for IO on the backup source and network > +traffic. A value of C<0>, C<undef> or if there is no such key in the hash all > +mean that there is no limit. > + > +=item C<< $info->{'firewall-config'} >> > + > +Present if the firewall configuration exists. The guest's firewall > +configuration as raw data. > + > +=back > + > +Mechansims-specific parameters for mechanism: s/Mechansims-specific/Mechanism-specific (or Mechanisms-specific?) > + > +=over > + > +=item C<directory> > + > +=over > + > +=item C<< $info->{directory} >> > + > +Path to the directory with the container's file system structure. > + > +=item C<< $info->{sources} >> > + > +List of paths (for separate mount points, including "." for the root) inside > the > +directory to be backed up. > + > +=item C<< $info->{'backup-user-id'} >> > + > +The user ID of the ID-mapped root user of the container. For example, > C<100000> > +for unprivileged containers by default. > + > +=back > + > +=back > + > +=back > + > +=back > + > +=cut > +sub backup_container { > + my ($self, $vmid, $guest_config, $exclude_patterns, $info) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<restore_get_mechanism> > + > +Tell the caller what mechanism to use for restoring the guest. The restore > +methods for the guest, i.e. C<restore_qemu_img_init> and > +C<restore_qemu_img_cleanup> for guest type C<qemu>, or > C<restore_container_init> > +and C<restore_container_cleanup> for guest type C<lxc> will be called with > +mechanism-specific information and their return value might also depend on > the > +mechanism. See those methods for more information. Returns > +C<($mechanism, $vmtype)>: > + > +=over > + > +=item C<$mechanism> > + > +Currently, C<'qemu-img'> for guest type C<'qemu'> and either C<'tar'> or > +C<'directory'> for type C<'lxc'> are possible. > + > +=item C<$vmtype> > + > +Either C<qemu> or C<lxc> depending on what type the guest in the backed-up > +archive is. > + > +=back > + > +Parameters: > + > +=over > + > +=item C<$volname> > + > +The volume ID of the archive being restored. > + > +=back > + > +=back > + > +=cut > +sub restore_get_mechanism { > + my ($self, $volname) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<archive_get_guest_config> > + > +Extract the guest configuration from the given backup. Returns the raw > contents > +of the backed-up configuration file. Note that this method is called > +independently from C<restore_container_init()> or C<restore_vm_init()>. > + > +Parameters: > + > +=over > + > +=item C<$volname> > + > +The volume ID of the archive being restored. > + > +=back > + > +=back > + > +=cut > +sub archive_get_guest_config { > + my ($self, $volname) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<archive_get_firewall_config> > + > +Extract the guest's firewall configuration from the given backup. Returns the > +raw contents of the backed-up configuration file. Returns C<undef> if there > is > +no firewall config in the archive, C<die> if the configuration can't be > +extracted. Note that this method is called independently from > +C<restore_container_init()> or C<restore_vm_init()>. > + > +Parameters: > + > +=over > + > +=item C<$volname> > + > +The volume ID of the archive being restored. > + > +=back > + > +=back > + > +=cut > +sub archive_get_firewall_config { > + my ($self, $volname) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<restore_vm_init> > + > +Prepare a VM archive for restore. Returns the basic information about the > +volumes in the backup as a hash reference with the following structure: > + > + { > + $device_nameA => { size => $sizeA }, > + $device_nameB => { size => $sizeB }, > + ... > + } > + > +=over > + > +=item C<$device_name> > + > +The device name that was given as an argument to the backup routine when the > +backup was created. > + > +=item C<$size> > + > +The virtual size of the VM volume that was backed up. A volume with this > size is > +created for the restore operation. In particular, for the C<qemu-img> > mechanism, > +this should be the size of the block device referenced by the > C<qemu-img-path> > +returned by C<restore_vm_volume>. > + > +=back > + > +Parameters: > + > +=over > + > +=item C<$volname> > + > +The volume ID of the archive being restored. > + > +=back > + > +=back > + > +=cut > +sub restore_vm_init { > + my ($self, $volname) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<restore_vm_cleanup> > + > +For VM backups, clean up after the restore. Called in both, success and > +failure scenarios. > + > +Parameters: > + > +=over > + > +=item C<$volname> > + > +The volume ID of the archive being restored. > + > +=back > + > +=back > + > +=cut > +sub restore_vm_cleanup { > + my ($self, $volname) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<restore_vm_volume_init> > + > +Prepare a VM volume in the archive for restore. Returns a hash reference with > +the mechanism-specific information for the restore: > + > +=over > + > +=item C<qemu-img> > + > + { 'qemu-img-path' => $path } > + > +The volume will be restored using the C<qemu-img convert> command. > + > +=over > + > +=item C<$path> > + > +A path to the volume that C<qemu-img> can use as a source for the > +C<qemu-img convert> command. For example, the path could also be an NBD URI. > The > +image contents are interpreted as being in C<raw> format and copied verbatim. > +Other formats like C<qcow2> will not be detected currently. > + > +=back > + > +=back > + > +Parameters: > + > +=over > + > +=item C<$volname> > + > +The volume ID of the archive being restored. > + > +=item C<$device_name> > + > +The device name associated to the volume that should be prepared for the > +restore. Same as the argument to the backup routine when the backup was > created. > + > +=item C<$info> > + > +A hash reference with optional and mechanism-specific parameters. Currently > +empty. > + > +=back > + > +=back > + > +=cut > +sub restore_vm_volume_init { > + my ($self, $volname, $device_name, $info) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<restore_vm_volume_cleanup> > + > +For VM backups, clean up after the restore of a given volume. Called in both, > +success and failure scenarios. > + > +Parameters: > + > +=over > + > +=item C<$volname> > + > +The volume ID of the archive being restored. > + > +=item C<$device_name> > + > +The device name associated to the volume that should be prepared for the > +restore. Same as the argument to the backup routine when the backup was > created. > + > +=item C<$info> > + > +A hash reference with optional and mechanism-specific parameters. Currently > +empty. > + > +=back > + > +=back > + > +=cut > +sub restore_vm_volume_cleanup { > + my ($self, $volname, $device_name, $info) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<restore_container_init> > + > +Prepare a container archive for restore. Returns a hash reference with the > +mechanism-specific information for the restore: > + > +=over > + > +=item C<tar> > + > + { 'tar-path' => $path } > + > +The archive will be restored via the C<tar> command. > + > +=over > + > +=item C<$path> > + > +The path to the tar archive containing the full filesystem structure of the > +container. > + > +=back > + > +=item C<directory> > + > + { 'archive-directory' => $path } > + > +The archive will be restored via C<rsync> from a directory containing the > full > +filesystem structure of the container. > + > +=over > + > +=item C<$path> > + > +The path to the directory containing the full filesystem structure of the > +container. > + > +=back > + > +=back > + > +Parameters: > + > +=over > + > +=item C<$volname> > + > +The volume ID of the archive being restored. > + > +=item C<$info> > + > +A hash reference with optional and mechanism-specific parameters. Currently > +empty. > + > +=back > + > +=back > + > +=cut > +sub restore_container_init { > + my ($self, $volname, $info) = @_; > + > + die "implement me in subclass"; > +} > + > +=pod > + > +=over > + > +=item C<restore_container_cleanup> > + > +For container backups, clean up after the restore. Called in both, success > and > +failure scenarios. > + > +Parameters: > + > +=over > + > +=item C<$volname> > + > +The volume ID of the archive being restored. > + > +=item C<$info> > + > +A hash reference with optional and mechanism-specific parameters. Currently > +empty. > + > +=back > + > +=back > + > +=cut > +sub restore_container_cleanup { > + my ($self, $volname, $info) = @_; > + > + die "implement me in subclass"; > +} > + > +1; > diff --git a/src/PVE/BackupProvider/Plugin/Makefile > b/src/PVE/BackupProvider/Plugin/Makefile > new file mode 100644 > index 0000000..bbd7431 > --- /dev/null > +++ b/src/PVE/BackupProvider/Plugin/Makefile > @@ -0,0 +1,5 @@ > +SOURCES = Base.pm > + > +.PHONY: install > +install: > + for i in ${SOURCES}; do install -D -m 0644 $$i > ${DESTDIR}${PERLDIR}/PVE/BackupProvider/Plugin/$$i; done > diff --git a/src/PVE/Makefile b/src/PVE/Makefile > index 0af3081..9e9f6aa 100644 > --- a/src/PVE/Makefile > +++ b/src/PVE/Makefile > @@ -9,6 +9,7 @@ install: > make -C Storage install > make -C GuestImport install > make -C API2 install > + make -C BackupProvider install > make -C CLI install > > .PHONY: test > diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm > index 8cbfb4f..7fd97b7 100755 > --- a/src/PVE/Storage.pm > +++ b/src/PVE/Storage.pm > @@ -2027,6 +2027,14 @@ sub volume_export_start { > PVE::Tools::run_command($cmds, %$run_command_params); > } > > +sub new_backup_provider { > + my ($cfg, $storeid, $log_function) = @_; > + > + my $scfg = storage_config($cfg, $storeid); > + my $plugin = PVE::Storage::Plugin->lookup($scfg->{type}); > + return $plugin->new_backup_provider($scfg, $storeid, $log_function); > +} > + > # bash completion helper > > sub complete_storage { > diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm > index 80daeea..df2ddc5 100644 > --- a/src/PVE/Storage/Plugin.pm > +++ b/src/PVE/Storage/Plugin.pm > @@ -1868,6 +1868,21 @@ sub rename_volume { > return "${storeid}:${base}${target_vmid}/${target_volname}"; > } > > +# Used by storage plugins for external backup providers. See > PVE::BackupProvider::Plugin for the API > +# the provider needs to implement. > +# > +# $scfg - the storage configuration > +# $storeid - the storage ID > +# $log_function($log_level, $message) - this log function can be used to > write to the backup task > +# log in Proxmox VE. $log_level is 'info', 'warn' or 'err', $message is > the message to be printed. > +# > +# Returns a blessed reference to the backup provider class. > +sub new_backup_provider { > + my ($class, $scfg, $storeid, $log_function) = @_; > + > + die "implement me if enabling the feature 'backup-provider' in > plugindata()->{features}\n"; > +} For features, we could in the future check whether a plugin actually implements all of the methods required for that feature. Out of scope for this series, of course! I tried (overengineered) something similar in one old RFC [rfc] of mine -- what we could do is check whether the address of e.g. the `new_backup_provider` symbol in the concrete plugin is the same as the one in `::Plugin`; if it is, throw or refuse to load the plugin. In other words: If the plugin author has overridden the method, the symbol's address will be different. :P Not sure if we actually want to enforce this though; alternatively, we could let some kind of dev tooling check for "conformance" like that. It can quickly get too messy otherwise [rfc]. [rfc]: https://lore.proxmox.com/pve-devel/20250130145124.317745-1-m.carr...@proxmox.com/ > + > sub config_aware_base_mkdir { > my ($class, $scfg, $path) = @_; > _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel