Compare commits
67 commits
master
...
thin-provi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b74b43f930 | ||
|
|
b344b2e7d8 | ||
|
|
e2a17571e6 | ||
|
|
073a98b4c7 | ||
|
|
68c3142605 | ||
|
|
c10e73d93b | ||
|
|
6e5a42052c | ||
|
|
9eb914de16 | ||
|
|
02acde02b6 | ||
|
|
0f7a4d2d84 | ||
|
|
6bf171ec54 | ||
|
|
c33abdf062 | ||
|
|
609752f3ae | ||
|
|
5750596f5b | ||
|
|
153f7d8f85 | ||
|
|
3c209eaeb7 | ||
|
|
81261f9ca1 | ||
|
|
7513e21d74 | ||
|
|
6dbeba59da | ||
|
|
59a54b3d5f | ||
|
|
a477189575 | ||
|
|
94a54793cd | ||
|
|
92efe5c6cb | ||
|
|
74b5031c9a | ||
|
|
0dc6c9d39c | ||
|
|
868de9b1a8 | ||
|
|
e502404fa2 | ||
|
|
fc633887dc | ||
|
|
db2025f5ba | ||
|
|
819dafe516 | ||
|
|
169f8091dd | ||
|
|
5245e044ad | ||
|
|
cafbdb8c52 | ||
|
|
172c71a64d | ||
|
|
1afe55b35b | ||
|
|
dfad07158d | ||
|
|
715ec4f95b | ||
|
|
f62fc773ad | ||
|
|
9b7fa1e758 | ||
|
|
a9315a0ed3 | ||
|
|
d0239ba9c0 | ||
|
|
7da44f56e4 | ||
|
|
191cddac30 | ||
|
|
a7afad969d | ||
|
|
93f0dfbc75 | ||
|
|
43ec7bdfe6 | ||
|
|
3cb0c3398c | ||
|
|
42bc721b41 | ||
|
|
cfe7d7ebe7 | ||
|
|
c86d8f6d80 | ||
|
|
ad20e4faef | ||
|
|
dd2efb7846 | ||
|
|
e9e24973fd | ||
|
|
cd7c8e0ce6 | ||
|
|
285a7764d6 | ||
|
|
4f3c1d40ef | ||
|
|
c428173669 | ||
|
|
aea2fcae82 | ||
|
|
9b6e138788 | ||
|
|
5a5561b6ae | ||
|
|
6bf6c8ec3c | ||
|
|
07b005bb55 | ||
|
|
ed6df31cf4 | ||
|
|
61aaf78786 | ||
|
|
a81ee83127 | ||
|
|
2d44f2eb3e | ||
|
|
2cd4dafb22 |
34 changed files with 1782 additions and 2740 deletions
22
ApiChangeLog
22
ApiChangeLog
|
|
@ -22,12 +22,15 @@ Future changes should be documented in here.
|
|||
Feel free to request allowing more drivers or options on the pve-devel mailing list based on your
|
||||
needs.
|
||||
|
||||
* Introduce rename_snapshot() plugin method
|
||||
This method allow to rename a vm disk snapshot name to a different snapshot name.
|
||||
* Introduce `rename_snapshot()` plugin method
|
||||
|
||||
* Introduce volume_qemu_snapshot_method() plugin method
|
||||
This method declares how snapshots should be handled for *running* VMs.
|
||||
This should return one of the following:
|
||||
This method allow to rename a vm disk snapshot name to a different snapshot name.
|
||||
|
||||
* Introduce `volume_qemu_snapshot_method()` plugin method
|
||||
|
||||
This method declares how snapshots should be handled for *running* VMs.
|
||||
|
||||
This should return one of the following:
|
||||
'qemu':
|
||||
Qemu must perform the snapshot. The storage plugin does nothing.
|
||||
'storage':
|
||||
|
|
@ -46,6 +49,13 @@ Future changes should be documented in here.
|
|||
NOTE: Storages must support using "current" as a special name in `rename_snapshot()` to
|
||||
cheaply convert a snapshot into the current disk state and back.
|
||||
|
||||
* Introduce `get_formats()` plugin method
|
||||
|
||||
Get information about the supported formats and default format according to the current storage
|
||||
configuration. The default implemenation is backwards-compatible with previous behavior and looks
|
||||
at the definition given in the plugin data, as well as the `format` storage configuration option,
|
||||
which can override the default format. Must be implemented when the supported formats or default
|
||||
format depend on the storage configuration.
|
||||
|
||||
## Version 11:
|
||||
|
||||
|
|
@ -56,7 +66,7 @@ Future changes should be documented in here.
|
|||
`backup-provider`, see below for more details. To declare support for this feature, return
|
||||
`features => { 'backup-provider' => 1 }` as part of the plugin data.
|
||||
|
||||
* Introduce new_backup_provider() plugin method
|
||||
* Introduce `new_backup_provider()` plugin method
|
||||
|
||||
Proxmox VE now supports a `Backup Provider API` that can be used to implement custom backup
|
||||
solutions tightly integrated in the Proxmox VE stack. See the `PVE::BackupProvider::Plugin::Base`
|
||||
|
|
|
|||
119
debian/changelog
vendored
119
debian/changelog
vendored
|
|
@ -1,3 +1,122 @@
|
|||
libpve-storage-perl (9.0.13) trixie; urgency=medium
|
||||
|
||||
* deactivate volumes: terminate error message with newline.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 01 Aug 2025 18:36:51 +0200
|
||||
|
||||
libpve-storage-perl (9.0.12) trixie; urgency=medium
|
||||
|
||||
* plugin: fix parse_name_dir regression for custom volume names.
|
||||
|
||||
* fix #6584: plugin: list_images: only include parseable filenames.
|
||||
|
||||
* plugin: extend snapshot name parsing to legacy volnames.
|
||||
|
||||
* plugin: parse_name_dir: drop noisy deprecation warning.
|
||||
|
||||
* plugin: nfs, cifs: use volume qemu snapshot methods from dir plugin to
|
||||
ensure a online-snapshot on such storage types with
|
||||
snapshot-as-volume-chain enabled does not takes a internal qcow2 snapshot.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 31 Jul 2025 14:22:12 +0200
|
||||
|
||||
libpve-storage-perl (9.0.11) trixie; urgency=medium
|
||||
|
||||
* lvm volume snapshot info: untaint snapshot filename
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 31 Jul 2025 09:18:56 +0200
|
||||
|
||||
libpve-storage-perl (9.0.10) trixie; urgency=medium
|
||||
|
||||
* RRD metrics: use new pve-storage-9.0 format RRD file location, if it
|
||||
exists.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 31 Jul 2025 04:14:19 +0200
|
||||
|
||||
libpve-storage-perl (9.0.9) trixie; urgency=medium
|
||||
|
||||
* fix #5181: pbs: store and read passwords as unicode.
|
||||
|
||||
* fix #6587: lvm plugin: snapshot info: fix parsing snapshot name.
|
||||
|
||||
* config: drop 'maxfiles' parameter, it was replaced with the more flexible
|
||||
prune options in Proxmox VE 7.0 already.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Wed, 30 Jul 2025 19:51:07 +0200
|
||||
|
||||
libpve-storage-perl (9.0.8) trixie; urgency=medium
|
||||
|
||||
* snapshot-as-volume-chain: fix offline removal of snapshot on directory
|
||||
storage via UI/API by untainting/validating a filename correctly.
|
||||
|
||||
* snapshot-as-volume-chain: fix typo in log message for rebase operation.
|
||||
|
||||
* snapshot-as-volume-chain: ensure backing file references are kept relative
|
||||
upon snapshot deletion. This ensures the backing chain stays intact should
|
||||
the volumes be moved to a different path.
|
||||
|
||||
* fix #6561: ZFS: ensure refquota for container volumes is correctly applied
|
||||
after rollback. The quota is tracked via a ZFS user property.
|
||||
|
||||
* btrfs plugin: remove unnecessary mkpath call
|
||||
|
||||
* drop some left-overs for 'rootdir' sub-directory handling that were
|
||||
left-over from when Proxmox VE supported OpenVZ.
|
||||
|
||||
* path to volume ID conversion: properly quote regexes for hardening.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 29 Jul 2025 17:17:11 +0200
|
||||
|
||||
libpve-storage-perl (9.0.7) trixie; urgency=medium
|
||||
|
||||
* fix #6553: lvmthin: implement volume_rollback_is_possible sub
|
||||
|
||||
* plugin: add get_formats() method and use it instead of default_format()
|
||||
|
||||
* lvm plugin: implement get_formats() method
|
||||
|
||||
* lvm plugin: check if 'fmt' parameter is defined before comparisons
|
||||
|
||||
* api: status: rely on get_formats() method for determining format-related info
|
||||
|
||||
* introduce resolve_format_hint() helper
|
||||
|
||||
* improve api change log style
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 22 Jul 2025 15:01:49 +0200
|
||||
|
||||
libpve-storage-perl (9.0.6) trixie; urgency=medium
|
||||
|
||||
* lvm plugin: properly handle qcow2 format when querying volume size info.
|
||||
|
||||
* lvm plugin: list images: properly handle qcow2 format.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Fri, 18 Jul 2025 14:28:53 +0200
|
||||
|
||||
libpve-storage-perl (9.0.5) trixie; urgency=medium
|
||||
|
||||
* config: rename external-snapshots option to snapshot-as-volume-chain.
|
||||
|
||||
* d/postinst: drop obsolete migration for CIFS credential file path, left
|
||||
over from upgrade to PVE 7.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 17 Jul 2025 19:52:21 +0200
|
||||
|
||||
libpve-storage-perl (9.0.4) trixie; urgency=medium
|
||||
|
||||
* fix #5071: zfs over iscsi: add 'zfs-base-path' configuration option.
|
||||
|
||||
* zfs over iscsi: on-add hook: dynamically determine base path.
|
||||
|
||||
* rbd storage: add missing check for external ceph cluster.
|
||||
|
||||
* LVM: add initial support for storage-managed snapshots through qcow2.
|
||||
|
||||
* directory file system based storages: add initial support for external
|
||||
qcow2 snapshots.
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 17 Jul 2025 01:17:05 +0200
|
||||
|
||||
libpve-storage-perl (9.0.3) trixie; urgency=medium
|
||||
|
||||
* fix #4997: lvm: volume create: disable auto-activation for new logical
|
||||
|
|
|
|||
34
debian/postinst
vendored
34
debian/postinst
vendored
|
|
@ -6,31 +6,19 @@ set -e
|
|||
|
||||
case "$1" in
|
||||
configure)
|
||||
if test -n "$2"; then
|
||||
|
||||
# TODO: remove once PVE 8.0 is released
|
||||
if dpkg --compare-versions "$2" 'lt' '7.0-3'; then
|
||||
warning="Warning: failed to move old CIFS credential file, cluster not quorate?"
|
||||
for file in /etc/pve/priv/*.cred; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "Info: found CIFS credentials using old path: $file" >&2
|
||||
mkdir -p "/etc/pve/priv/storage" || { echo "$warning" && continue; }
|
||||
base=$(basename --suffix=".cred" "$file")
|
||||
target="/etc/pve/priv/storage/$base.pw"
|
||||
if [ -f "$target" ]; then
|
||||
if diff "$file" "$target" >&2 > /dev/null; then
|
||||
echo "Info: removing $file, because it is identical to $target" >&2
|
||||
rm "$file" || { echo "$warning" && continue; }
|
||||
else
|
||||
echo "Warning: not renaming $file, because $target already exists and differs!" >&2
|
||||
fi
|
||||
else
|
||||
echo "Info: renaming $file to $target" >&2
|
||||
mv "$file" "$target" || { echo "$warning" && continue; }
|
||||
fi
|
||||
fi
|
||||
done
|
||||
if test -n "$2"; then # got old version so this is an update
|
||||
|
||||
# TODO: Can be dropped with some 9.x stable release, this was never in a publicly available
|
||||
# package, so only for convenience for internal testing setups.
|
||||
if dpkg --compare-versions "$2" 'lt' '9.0.5'; then
|
||||
if grep -Pq '^\texternal-snapshots ' /etc/pve/storage.cfg; then
|
||||
echo "Replacing old 'external-snapshots' with 'snapshot-as-volume-chain' in /etc/pve/storage.cfg"
|
||||
sed -i 's/^\texternal-snapshots /\tsnapshot-as-volume-chain /' /etc/pve/storage.cfg || \
|
||||
echo "Failed to replace old 'external-snapshots' with 'snapshot-as-volume-chain' in /etc/pve/storage.cfg"
|
||||
fi
|
||||
fi
|
||||
|
||||
fi
|
||||
;;
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ all:
|
|||
install: PVE bin udev-rbd
|
||||
$(MAKE) -C bin install
|
||||
$(MAKE) -C PVE install
|
||||
$(MAKE) -C services install
|
||||
$(MAKE) -C udev-rbd install
|
||||
|
||||
.PHONY: test
|
||||
|
|
|
|||
|
|
@ -218,13 +218,13 @@ __PACKAGE__->register_method({
|
|||
enum => $storage_type_enum,
|
||||
},
|
||||
config => {
|
||||
description => "Partial, possible server generated, configuration properties.",
|
||||
description => "Partial, possibly server generated, configuration properties.",
|
||||
type => 'object',
|
||||
optional => 1,
|
||||
additionalProperties => 1,
|
||||
properties => {
|
||||
'encryption-key' => {
|
||||
description => "The, possible auto-generated, encryption-key.",
|
||||
description => "The, possibly auto-generated, encryption-key.",
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
},
|
||||
|
|
@ -318,13 +318,13 @@ __PACKAGE__->register_method({
|
|||
enum => $storage_type_enum,
|
||||
},
|
||||
config => {
|
||||
description => "Partial, possible server generated, configuration properties.",
|
||||
description => "Partial, possibly server generated, configuration properties.",
|
||||
type => 'object',
|
||||
optional => 1,
|
||||
additionalProperties => 1,
|
||||
properties => {
|
||||
'encryption-key' => {
|
||||
description => "The, possible auto-generated, encryption-key.",
|
||||
description => "The, possibly auto-generated, encryption-key.",
|
||||
optional => 1,
|
||||
type => 'string',
|
||||
},
|
||||
|
|
|
|||
|
|
@ -300,7 +300,50 @@ __PACKAGE__->register_method({
|
|||
},
|
||||
returns => {
|
||||
type => "object",
|
||||
properties => {},
|
||||
properties => {
|
||||
type => {
|
||||
description => "Storage type.",
|
||||
type => 'string',
|
||||
},
|
||||
content => {
|
||||
description => "Allowed storage content types.",
|
||||
type => 'string',
|
||||
format => 'pve-storage-content-list',
|
||||
},
|
||||
enabled => {
|
||||
description => "Set when storage is enabled (not disabled).",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
active => {
|
||||
description => "Set when storage is accessible.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
shared => {
|
||||
description => "Shared flag from storage configuration.",
|
||||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
total => {
|
||||
description => "Total storage space in bytes.",
|
||||
type => 'integer',
|
||||
renderer => 'bytes',
|
||||
optional => 1,
|
||||
},
|
||||
used => {
|
||||
description => "Used storage space in bytes.",
|
||||
type => 'integer',
|
||||
renderer => 'bytes',
|
||||
optional => 1,
|
||||
},
|
||||
avail => {
|
||||
description => "Available storage space in bytes.",
|
||||
type => 'integer',
|
||||
renderer => 'bytes',
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
|
@ -415,11 +458,10 @@ __PACKAGE__->register_method({
|
|||
code => sub {
|
||||
my ($param) = @_;
|
||||
|
||||
return PVE::RRD::create_rrd_data(
|
||||
"pve2-storage/$param->{node}/$param->{storage}",
|
||||
$param->{timeframe},
|
||||
$param->{cf},
|
||||
);
|
||||
my $path = "pve-storage-9.0/$param->{node}/$param->{storage}";
|
||||
$path = "pve2-storage/$param->{node}/$param->{storage}"
|
||||
if !-e "/var/lib/rrdcached/db/${path}";
|
||||
return PVE::RRD::create_rrd_data($path, $param->{timeframe}, $param->{cf});
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ install:
|
|||
make -C API2 install
|
||||
make -C BackupProvider install
|
||||
make -C CLI install
|
||||
make -C Service install
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
|
|
|
|||
10
src/PVE/Service/Makefile
Normal file
10
src/PVE/Service/Makefile
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
SOURCES=pvestord.pm
|
||||
|
||||
all:
|
||||
|
||||
.PHONY: install
|
||||
install: $(SOURCES)
|
||||
install -d -m 0755 $(DESTDIR)$(PERLDIR)/PVE/Service
|
||||
for i in $(SOURCES); do install -D -m 0644 $$i $(DESTDIR)$(PERLDIR)/PVE/Service/$$i; done
|
||||
|
||||
clean:
|
||||
193
src/PVE/Service/pvestord.pm
Normal file
193
src/PVE/Service/pvestord.pm
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
package PVE::Service::pvestord;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Time::HiRes qw (gettimeofday);
|
||||
use PVE::SafeSyslog;
|
||||
use PVE::Daemon;
|
||||
use PVE::Cluster qw(cfs_read_file);
|
||||
use PVE::Storage;
|
||||
use PVE::QemuConfig;
|
||||
use PVE::QemuServer;
|
||||
use PVE::QemuServer::Drive;
|
||||
use PVE::QemuServer::Blockdev;
|
||||
use PVE::QemuServer::Helpers;
|
||||
use PVE::INotify;
|
||||
|
||||
use base qw(PVE::Daemon);
|
||||
|
||||
my $cmdline = [$0, @ARGV];
|
||||
|
||||
my %daemon_options = (restart_on_error => 5, stop_wait_time => 15);
|
||||
my $daemon = __PACKAGE__->new('pvestord', $cmdline, %daemon_options);
|
||||
|
||||
my $nodename = PVE::INotify::nodename();
|
||||
|
||||
sub init {
|
||||
my ($self) = @_;
|
||||
PVE::Cluster::cfs_update();
|
||||
}
|
||||
|
||||
my sub get_drive_id {
|
||||
my ($block_stats, $blockdev_nodename) = @_;
|
||||
foreach my $drive_id (keys %$block_stats) {
|
||||
my $entry = $block_stats->{$drive_id};
|
||||
my $file_blockdev = $entry->{parent}->{parent};
|
||||
return $drive_id
|
||||
if ($file_blockdev->{'node-name'} eq $blockdev_nodename);
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
my sub dequeue {
|
||||
my ($queue) = @_;
|
||||
PVE::Storage::lock_extend_queue(
|
||||
sub {
|
||||
# TODO: This will have to have some sort of mechanism
|
||||
# to make sure that the element that is removed is the one
|
||||
# that this node is handling
|
||||
shift @$queue;
|
||||
|
||||
PVE::Storage::write_extend_queue($queue);
|
||||
},
|
||||
"Could not lock extend queue file",
|
||||
);
|
||||
}
|
||||
|
||||
sub perform_extend {
|
||||
my $storecfg = PVE::Storage::config();
|
||||
my $queue = PVE::Storage::extend_queue();
|
||||
|
||||
my $first_extend_request = @$queue[0];
|
||||
return if !$first_extend_request;
|
||||
|
||||
my ($vmid, $blockdev_nodename) = @$first_extend_request;
|
||||
|
||||
my $vmlist = PVE::Cluster::get_vmlist();
|
||||
my $owner_nodename = $vmlist->{ids}->{$vmid}->{node};
|
||||
|
||||
if ($owner_nodename eq $nodename) {
|
||||
my $running = PVE::QemuServer::Helpers::vm_running_locally($vmid);
|
||||
# NOTE: The block device node name is currently generated using a SHA-256 hash,
|
||||
# which makes it impossible to reverse-engineer and identify the original disk.
|
||||
# As a result, we must rely on `blockstats` to determine which disk corresponds
|
||||
# to a given node name — but these statistics are only available when the machine is running.
|
||||
# Consider updating the `get_node_name()` function to use a reversible encoding
|
||||
# (e.g., Base64) instead of a SHA-256 digest to simplify disk identification.
|
||||
|
||||
my $extend_function = sub {
|
||||
dequeue($queue);
|
||||
syslog("info", "Processsing extend request $vmid: $blockdev_nodename\n");
|
||||
|
||||
my $block_stats = PVE::QemuServer::Blockdev::get_block_stats($vmid);
|
||||
|
||||
my $drive_id = get_drive_id($block_stats, $blockdev_nodename);
|
||||
if (!$drive_id) {
|
||||
syslog("err", "Couldn't find drive_id for blockdev $blockdev_nodename");
|
||||
return;
|
||||
}
|
||||
my $vm_conf = PVE::QemuConfig->load_config($vmid);
|
||||
my $drive = PVE::QemuServer::parse_drive($drive_id, $vm_conf->{$drive_id});
|
||||
my $volid = $drive->{file};
|
||||
|
||||
PVE::QemuServer::Blockdev::underlay_resize(
|
||||
$storecfg, $vmid, $drive_id, $volid
|
||||
);
|
||||
};
|
||||
PVE::QemuConfig->lock_config($vmid, $extend_function);
|
||||
}
|
||||
}
|
||||
|
||||
my $next_update = 0;
|
||||
my $cycle = 0;
|
||||
my $restart_request = 0;
|
||||
|
||||
my $initial_memory_usage = 0;
|
||||
|
||||
# 1 second cycles
|
||||
my $updatetime = 1;
|
||||
|
||||
sub run {
|
||||
my ($self) = @_;
|
||||
syslog("info", "Running on node $nodename\n");
|
||||
|
||||
for (;;) { # forever
|
||||
# get next extend request
|
||||
$next_update = time() + $updatetime;
|
||||
|
||||
if ($cycle) {
|
||||
my ($ccsec, $cusec) = gettimeofday();
|
||||
eval {
|
||||
# syslog('info', "start status update");
|
||||
PVE::Cluster::cfs_update();
|
||||
perform_extend();
|
||||
};
|
||||
my $err = $@;
|
||||
|
||||
if ($err) {
|
||||
syslog('err', "status update error: $err");
|
||||
}
|
||||
|
||||
my ($ccsec_end, $cusec_end) = gettimeofday();
|
||||
my $cptime = ($ccsec_end - $ccsec) + ($cusec_end - $cusec) / 1000000;
|
||||
|
||||
syslog('info', sprintf("extend process time (%.3f seconds)", $cptime))
|
||||
if ($cptime > 1);
|
||||
}
|
||||
|
||||
$cycle++;
|
||||
|
||||
my $mem = PVE::ProcFSTools::read_memory_usage();
|
||||
my $resident_kb = $mem->{resident} / 1024;
|
||||
|
||||
if (!defined($initial_memory_usage) || ($cycle < 10)) {
|
||||
$initial_memory_usage = $resident_kb;
|
||||
} else {
|
||||
my $diff = $resident_kb - $initial_memory_usage;
|
||||
if ($diff > 15 * 1024) {
|
||||
syslog(
|
||||
'info',
|
||||
"restarting server after $cycle cycles to "
|
||||
. "reduce memory usage (free $resident_kb ($diff) KB)",
|
||||
);
|
||||
$self->restart_daemon();
|
||||
}
|
||||
}
|
||||
|
||||
my $wcount = 0;
|
||||
while (
|
||||
(time() < $next_update)
|
||||
&& ($wcount < $updatetime)
|
||||
&& # protect against time wrap
|
||||
!$restart_request
|
||||
) {
|
||||
$wcount++;
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
$self->restart_daemon() if $restart_request;
|
||||
}
|
||||
}
|
||||
|
||||
sub shutdown {
|
||||
my ($self) = @_;
|
||||
|
||||
syslog('info', "server closing");
|
||||
|
||||
$self->exit_daemon(0);
|
||||
}
|
||||
|
||||
$daemon->register_start_command();
|
||||
$daemon->register_restart_command(1);
|
||||
$daemon->register_stop_command();
|
||||
$daemon->register_status_command();
|
||||
|
||||
our $cmddef = {
|
||||
start => [__PACKAGE__, 'start', []],
|
||||
restart => [__PACKAGE__, 'restart', []],
|
||||
stop => [__PACKAGE__, 'stop', []],
|
||||
status => [__PACKAGE__, 'status', [], undef, sub { print shift . "\n"; }],
|
||||
};
|
||||
|
||||
1;
|
||||
|
|
@ -15,7 +15,7 @@ use Socket;
|
|||
use Time::Local qw(timelocal);
|
||||
|
||||
use PVE::Tools qw(run_command file_read_firstline dir_glob_foreach $IPV6RE);
|
||||
use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
|
||||
use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file cfs_register_file);
|
||||
use PVE::DataCenterConfig;
|
||||
use PVE::Exception qw(raise_param_exc raise);
|
||||
use PVE::JSONSchema;
|
||||
|
|
@ -239,6 +239,76 @@ sub write_config {
|
|||
cfs_write_file('storage.cfg', $cfg);
|
||||
}
|
||||
|
||||
cfs_register_file("extend-queue", \&parser_extend_queue, \&writer_extend_queue);
|
||||
|
||||
sub extend_queue {
|
||||
return cfs_read_file("extend-queue");
|
||||
}
|
||||
|
||||
sub write_extend_queue {
|
||||
my ($extend_queue) = @_;
|
||||
return cfs_write_file("extend-queue",$extend_queue);
|
||||
}
|
||||
|
||||
sub lock_extend_queue {
|
||||
my ($code, $errmsg) = @_;
|
||||
|
||||
cfs_lock_file("extend-queue", undef, $code);
|
||||
my $err = $@;
|
||||
if ($err) {
|
||||
$errmsg ? die "$errmsg: $err" : die $err;
|
||||
}
|
||||
}
|
||||
|
||||
sub parser_extend_queue {
|
||||
my ($filename, $raw) = @_;
|
||||
|
||||
my @queue;
|
||||
|
||||
my $lineno = 0;
|
||||
my @lines = split(/\n/, $raw);
|
||||
my $nextline = sub {
|
||||
while (defined(my $line = shift @lines)) {
|
||||
$lineno++;
|
||||
return $line if ($line !~ /^\s*#/);
|
||||
}
|
||||
};
|
||||
|
||||
while (@lines) {
|
||||
my $line = $nextline->();
|
||||
next if !$line;
|
||||
print "Current line $line\n";
|
||||
|
||||
# vmid: nodename
|
||||
if ($line =~ '[1-9][0-9]{2,8}+: [aefz][0-9a-f]{30}') {
|
||||
print "Extend request is valid\n";
|
||||
my ($vmid, $nodename) = split(/:\s/, $line, 2);
|
||||
push @queue, [$vmid, $nodename];
|
||||
}
|
||||
}
|
||||
return \@queue;
|
||||
}
|
||||
|
||||
sub writer_extend_queue {
|
||||
my ($filename, $queue) = @_;
|
||||
|
||||
my $out = "";
|
||||
foreach my $entry (@$queue) {
|
||||
my ($vmid, $nodename) = @$entry;
|
||||
$out .= format_extend_request($vmid, $nodename) . "\n";
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
sub format_extend_request {
|
||||
my ($vmid, $node_name) = @_;
|
||||
|
||||
my $request = $vmid . ': ' . $node_name;
|
||||
|
||||
return $request;
|
||||
}
|
||||
|
||||
sub lock_storage_config {
|
||||
my ($code, $errmsg) = @_;
|
||||
|
||||
|
|
@ -249,27 +319,6 @@ sub lock_storage_config {
|
|||
}
|
||||
}
|
||||
|
||||
# FIXME remove maxfiles for PVE 8.0 or PVE 9.0
|
||||
my $convert_maxfiles_to_prune_backups = sub {
|
||||
my ($scfg) = @_;
|
||||
|
||||
return if !$scfg;
|
||||
|
||||
my $maxfiles = delete $scfg->{maxfiles};
|
||||
|
||||
if (!defined($scfg->{'prune-backups'}) && defined($maxfiles)) {
|
||||
my $prune_backups;
|
||||
if ($maxfiles) {
|
||||
$prune_backups = { 'keep-last' => $maxfiles };
|
||||
} else { # maxfiles 0 means no limit
|
||||
$prune_backups = { 'keep-all' => 1 };
|
||||
}
|
||||
$scfg->{'prune-backups'} = PVE::JSONSchema::print_property_string(
|
||||
$prune_backups, 'prune-backups',
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
sub storage_config {
|
||||
my ($cfg, $storeid, $noerr) = @_;
|
||||
|
||||
|
|
@ -279,8 +328,6 @@ sub storage_config {
|
|||
|
||||
die "storage '$storeid' does not exist\n" if (!$noerr && !$scfg);
|
||||
|
||||
$convert_maxfiles_to_prune_backups->($scfg);
|
||||
|
||||
return $scfg;
|
||||
}
|
||||
|
||||
|
|
@ -433,6 +480,34 @@ sub volume_resize {
|
|||
}
|
||||
}
|
||||
|
||||
sub volume_underlay_size_info {
|
||||
my ($cfg, $volid, $timeout) = @_;
|
||||
|
||||
my ($storeid, $volname) = parse_volume_id($volid, 1);
|
||||
if ($storeid) {
|
||||
my $scfg = storage_config($cfg, $storeid);
|
||||
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
||||
return $plugin->volume_underlay_size_info($scfg, $storeid, $volname, $timeout);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
sub volume_underlay_resize {
|
||||
my ($cfg, $volid, $size, $running, $backing_snap) = @_;
|
||||
|
||||
my ($storeid, $volname) = parse_volume_id($volid, 1);
|
||||
if ($storeid) {
|
||||
my $scfg = storage_config($cfg, $storeid);
|
||||
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
||||
return $plugin->volume_underlay_resize($scfg, $storeid, $volname, $size, $running, $backing_snap);
|
||||
} elsif ($volid =~ m|^(/.+)$| && -e $volid) {
|
||||
die "resize file/device '$volid' is not possible\n";
|
||||
} else {
|
||||
die "unable to parse volume ID '$volid'\n";
|
||||
}
|
||||
}
|
||||
|
||||
sub volume_rollback_is_possible {
|
||||
my ($cfg, $volid, $snap, $blockers) = @_;
|
||||
|
||||
|
|
@ -740,11 +815,10 @@ sub path_to_volume_id {
|
|||
my $isodir = $plugin->get_subdir($scfg, 'iso');
|
||||
my $tmpldir = $plugin->get_subdir($scfg, 'vztmpl');
|
||||
my $backupdir = $plugin->get_subdir($scfg, 'backup');
|
||||
my $privatedir = $plugin->get_subdir($scfg, 'rootdir');
|
||||
my $snippetsdir = $plugin->get_subdir($scfg, 'snippets');
|
||||
my $importdir = $plugin->get_subdir($scfg, 'import');
|
||||
|
||||
if ($path =~ m!^$imagedir/(\d+)/([^/\s]+)$!) {
|
||||
if ($path =~ m!^\Q$imagedir\E/(\d+)/([^/\s]+)$!) {
|
||||
my $vmid = $1;
|
||||
my $name = $2;
|
||||
|
||||
|
|
@ -756,22 +830,19 @@ sub path_to_volume_id {
|
|||
return ('images', $info->{volid});
|
||||
}
|
||||
}
|
||||
} elsif ($path =~ m!^$isodir/([^/]+$ISO_EXT_RE_0)$!) {
|
||||
} elsif ($path =~ m!^\Q$isodir\E/([^/]+$ISO_EXT_RE_0)$!) {
|
||||
my $name = $1;
|
||||
return ('iso', "$sid:iso/$name");
|
||||
} elsif ($path =~ m!^$tmpldir/([^/]+$VZTMPL_EXT_RE_1)$!) {
|
||||
} elsif ($path =~ m!^\Q$tmpldir\E/([^/]+$VZTMPL_EXT_RE_1)$!) {
|
||||
my $name = $1;
|
||||
return ('vztmpl', "$sid:vztmpl/$name");
|
||||
} elsif ($path =~ m!^$privatedir/(\d+)$!) {
|
||||
my $vmid = $1;
|
||||
return ('rootdir', "$sid:rootdir/$vmid");
|
||||
} elsif ($path =~ m!^$backupdir/([^/]+$BACKUP_EXT_RE_2)$!) {
|
||||
} elsif ($path =~ m!^\Q$backupdir\E/([^/]+$BACKUP_EXT_RE_2)$!) {
|
||||
my $name = $1;
|
||||
return ('backup', "$sid:backup/$name");
|
||||
} elsif ($path =~ m!^$snippetsdir/([^/]+)$!) {
|
||||
} elsif ($path =~ m!^\Q$snippetsdir\E/([^/]+)$!) {
|
||||
my $name = $1;
|
||||
return ('snippets', "$sid:snippets/$name");
|
||||
} elsif ($path =~ m!^$importdir/(${SAFE_CHAR_CLASS_RE}+${IMPORT_EXT_RE_1})$!) {
|
||||
} elsif ($path =~ m!^\Q$importdir\E/(${SAFE_CHAR_CLASS_RE}+${IMPORT_EXT_RE_1})$!) {
|
||||
my $name = $1;
|
||||
return ('import', "$sid:import/$name");
|
||||
}
|
||||
|
|
@ -857,10 +928,11 @@ my $volname_for_storage = sub {
|
|||
|
||||
my $scfg = storage_config($cfg, $storeid);
|
||||
|
||||
my (undef, $valid_formats) = PVE::Storage::Plugin::default_format($scfg);
|
||||
my $format_is_valid = grep { $_ eq $format } @$valid_formats;
|
||||
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
||||
|
||||
my $formats = $plugin->get_formats($scfg, $storeid);
|
||||
die "unsupported format '$format' for storage type $scfg->{type}\n"
|
||||
if !$format_is_valid;
|
||||
if !$formats->{valid}->{$format};
|
||||
|
||||
(my $name_without_extension = $name) =~ s/\.$format$//;
|
||||
|
||||
|
|
@ -1184,14 +1256,12 @@ sub vdisk_alloc {
|
|||
|
||||
$vmid = parse_vmid($vmid);
|
||||
|
||||
my $defformat = PVE::Storage::Plugin::default_format($scfg);
|
||||
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
||||
|
||||
$fmt = $defformat if !$fmt;
|
||||
$fmt = $plugin->get_formats($scfg, $storeid)->{default} if !$fmt;
|
||||
|
||||
activate_storage($cfg, $storeid);
|
||||
|
||||
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
||||
|
||||
# lock shared storage
|
||||
return $plugin->cluster_lock_storage(
|
||||
$storeid,
|
||||
|
|
@ -1457,7 +1527,7 @@ sub deactivate_volumes {
|
|||
}
|
||||
}
|
||||
|
||||
die "volume deactivation failed: " . join(' ', @errlist)
|
||||
die "volume deactivation failed: " . join(' ', @errlist) . "\n"
|
||||
if scalar(@errlist);
|
||||
}
|
||||
|
||||
|
|
@ -1512,9 +1582,10 @@ sub storage_info {
|
|||
|
||||
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
||||
if ($includeformat) {
|
||||
my $formats = $plugin->get_formats($scfg, $storeid);
|
||||
$info->{$storeid}->{format} = [$formats->{valid}, $formats->{default}];
|
||||
|
||||
my $pd = $plugin->plugindata();
|
||||
$info->{$storeid}->{format} = $pd->{format}
|
||||
if $pd->{format};
|
||||
$info->{$storeid}->{select_existing} = $pd->{select_existing}
|
||||
if $pd->{select_existing};
|
||||
}
|
||||
|
|
@ -1673,8 +1744,20 @@ sub storage_default_format {
|
|||
my ($cfg, $storeid) = @_;
|
||||
|
||||
my $scfg = storage_config($cfg, $storeid);
|
||||
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
||||
|
||||
return PVE::Storage::Plugin::default_format($scfg);
|
||||
return $plugin->get_formats($scfg, $storeid)->{default};
|
||||
}
|
||||
|
||||
sub resolve_format_hint {
|
||||
my ($cfg, $storeid, $format_hint) = @_;
|
||||
|
||||
my $scfg = storage_config($cfg, $storeid);
|
||||
my $plugin = PVE::Storage::Plugin->lookup($scfg->{type});
|
||||
|
||||
my $formats = $plugin->get_formats($scfg, $storeid);
|
||||
return $format_hint if $format_hint && $formats->{valid}->{$format_hint};
|
||||
return $formats->{default};
|
||||
}
|
||||
|
||||
sub vgroup_is_used {
|
||||
|
|
|
|||
|
|
@ -68,7 +68,6 @@ sub options {
|
|||
nodes => { optional => 1 },
|
||||
shared => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
|
|
@ -529,9 +528,6 @@ sub volume_snapshot {
|
|||
$snap_path = raw_file_to_subvol($snap_path);
|
||||
}
|
||||
|
||||
my $snapshot_dir = $class->get_subdir($scfg, 'images') . "/$vmid";
|
||||
mkpath $snapshot_dir;
|
||||
|
||||
$class->btrfs_cmd(['subvolume', 'snapshot', '-r', '--', $path, $snap_path]);
|
||||
return undef;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,7 +153,6 @@ sub options {
|
|||
subdir => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
|
|
@ -168,7 +167,7 @@ sub options {
|
|||
bwlimit => { optional => 1 },
|
||||
preallocation => { optional => 1 },
|
||||
options => { optional => 1 },
|
||||
'external-snapshots' => { optional => 1, fixed => 1 },
|
||||
'snapshot-as-volume-chain' => { optional => 1, fixed => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -332,4 +331,8 @@ sub get_import_metadata {
|
|||
return PVE::Storage::DirPlugin::get_import_metadata(@_);
|
||||
}
|
||||
|
||||
sub volume_qemu_snapshot_method {
|
||||
return PVE::Storage::DirPlugin::volume_qemu_snapshot_method(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -153,7 +153,6 @@ sub options {
|
|||
'create-subdirs' => { optional => 1 },
|
||||
fuse => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
keyring => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package PVE::Storage::Common;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use v5.36;
|
||||
|
||||
use PVE::JSONSchema;
|
||||
use PVE::Syscall;
|
||||
|
|
@ -171,7 +170,7 @@ C<$options> currently allows setting the C<preallocation> value.
|
|||
=cut
|
||||
|
||||
sub qemu_img_create_qcow2_backed {
|
||||
my ($path, $backing_path, $backing_format, $options) = @_;
|
||||
my ($path, $backing_path, $backing_format, $options, $thin) = @_;
|
||||
|
||||
my $cmd = [
|
||||
'/usr/bin/qemu-img',
|
||||
|
|
@ -189,7 +188,7 @@ sub qemu_img_create_qcow2_backed {
|
|||
my $opts = ['extended_l2=on', 'cluster_size=128k'];
|
||||
|
||||
push @$opts, "preallocation=$options->{preallocation}"
|
||||
if defined($options->{preallocation});
|
||||
if defined($options->{preallocation}) && !$thin;
|
||||
push @$cmd, '-o', join(',', @$opts) if @$opts > 0;
|
||||
|
||||
run_command($cmd, errmsg => "unable to create image");
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@ sub options {
|
|||
nodes => { optional => 1 },
|
||||
shared => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
|
|
@ -95,7 +94,7 @@ sub options {
|
|||
is_mountpoint => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
preallocation => { optional => 1 },
|
||||
'external-snapshots' => { optional => 1, fixed => 1 },
|
||||
'snapshot-as-volume-chain' => { optional => 1, fixed => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -321,7 +320,7 @@ sub volume_qemu_snapshot_method {
|
|||
my $format = ($class->parse_volname($volname))[6];
|
||||
return 'storage' if $format ne 'qcow2';
|
||||
|
||||
return $scfg->{'external-snapshots'} ? 'mixed' : 'qemu';
|
||||
return $scfg->{'snapshot-as-volume-chain'} ? 'mixed' : 'qemu';
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -211,7 +211,17 @@ sub esxi_mount : prototype($$$;$) {
|
|||
if (!$pid) {
|
||||
eval {
|
||||
undef $rd;
|
||||
POSIX::setsid();
|
||||
|
||||
# Double fork to properly daemonize
|
||||
POSIX::setsid() or die "failed to create new session: $!\n";
|
||||
my $pid2 = fork();
|
||||
die "second fork failed: $!\n" if !defined($pid2);
|
||||
|
||||
if ($pid2) {
|
||||
# First child exits immediately
|
||||
POSIX::_exit(0);
|
||||
}
|
||||
# Second child (grandchild) enters systemd scope
|
||||
PVE::Systemd::enter_systemd_scope(
|
||||
$scope_name_base,
|
||||
"Proxmox VE FUSE mount for ESXi storage $storeid (server $host)",
|
||||
|
|
@ -243,6 +253,8 @@ sub esxi_mount : prototype($$$;$) {
|
|||
}
|
||||
POSIX::_exit(1);
|
||||
}
|
||||
# Parent wait for first child to exit
|
||||
waitpid($pid, 0);
|
||||
undef $wr;
|
||||
|
||||
my $result = do { local $/ = undef; <$rd> };
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ my sub assert_iscsi_support {
|
|||
}
|
||||
|
||||
# Example: 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f
|
||||
my $ISCSI_TARGET_RE = qr/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/;
|
||||
my $ISCSI_TARGET_RE = qr/^(\S+:\d+)\,\S+\s+(\S+)\s*$/;
|
||||
|
||||
sub iscsi_session_list {
|
||||
assert_iscsi_support();
|
||||
|
|
@ -48,9 +48,7 @@ sub iscsi_session_list {
|
|||
outfunc => sub {
|
||||
my $line = shift;
|
||||
# example: tcp: [1] 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f (non-flash)
|
||||
if ($line =~
|
||||
m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s+\S+?\s*$/
|
||||
) {
|
||||
if ($line =~ m/^tcp:\s+\[(\S+)\]\s+(\S+:\d+)\,\S+\s+(\S+)\s+\S+?\s*$/) {
|
||||
my ($session_id, $portal, $target) = ($1, $2, $3);
|
||||
# there can be several sessions per target (multipath)
|
||||
push @{ $res->{$target} }, { session_id => $session_id, portal => $portal };
|
||||
|
|
|
|||
|
|
@ -399,12 +399,24 @@ sub options {
|
|||
base => { fixed => 1, optional => 1 },
|
||||
tagged_only => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
'external-snapshots' => { optional => 1 },
|
||||
'snapshot-as-volume-chain' => { optional => 1 },
|
||||
chunksize => { optional => 1 },
|
||||
'chunk-percentage' => { optional => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
# Storage implementation
|
||||
|
||||
sub get_formats {
|
||||
my ($class, $scfg, $storeid) = @_;
|
||||
|
||||
if ($scfg->{'snapshot-as-volume-chain'}) {
|
||||
return { default => 'qcow2', valid => { 'qcow2' => 1, 'raw' => 1 } };
|
||||
}
|
||||
|
||||
return { default => 'raw', valid => { 'raw' => 1 } };
|
||||
}
|
||||
|
||||
sub on_add_hook {
|
||||
my ($class, $storeid, $scfg, %param) = @_;
|
||||
|
||||
|
|
@ -460,9 +472,11 @@ my sub get_snap_name {
|
|||
}
|
||||
|
||||
my sub parse_snap_name {
|
||||
my ($name) = @_;
|
||||
my ($name, $short_volname) = @_;
|
||||
|
||||
if ($name =~ m/^snap_\S+_(.*)\.qcow2$/) {
|
||||
$short_volname =~ s/\.(qcow2)$//;
|
||||
|
||||
if ($name =~ m/^snap_\Q$short_volname\E_(.*)\.qcow2$/) {
|
||||
return $1;
|
||||
}
|
||||
}
|
||||
|
|
@ -514,7 +528,7 @@ sub find_free_diskname {
|
|||
|
||||
my $disk_list = [keys %{ $lvs->{$vg} }];
|
||||
|
||||
$add_fmt_suffix = $fmt eq 'qcow2' ? 1 : undef;
|
||||
$add_fmt_suffix = $fmt && $fmt eq 'qcow2' ? 1 : undef;
|
||||
|
||||
return PVE::Storage::Plugin::get_next_vm_diskname(
|
||||
$disk_list, $storeid, $vmid, $fmt, $scfg, $add_fmt_suffix,
|
||||
|
|
@ -563,7 +577,7 @@ sub lvrename {
|
|||
}
|
||||
|
||||
my sub lvm_qcow2_format {
|
||||
my ($class, $storeid, $scfg, $name, $fmt, $backing_snap, $size) = @_;
|
||||
my ($class, $storeid, $scfg, $name, $fmt, $backing_snap, $size, $thin) = @_;
|
||||
|
||||
$class->activate_volume($storeid, $scfg, $name);
|
||||
my $path = $class->path($scfg, $name, $storeid);
|
||||
|
|
@ -573,7 +587,9 @@ my sub lvm_qcow2_format {
|
|||
};
|
||||
if ($backing_snap) {
|
||||
my $backing_volname = get_snap_name($class, $name, $backing_snap);
|
||||
PVE::Storage::Common::qemu_img_create_qcow2_backed($path, $backing_volname, $fmt, $options);
|
||||
PVE::Storage::Common::qemu_img_create_qcow2_backed(
|
||||
$path, $backing_volname, $fmt, $options, $thin,
|
||||
);
|
||||
} else {
|
||||
PVE::Storage::Common::qemu_img_create($fmt, $size, $path, $options);
|
||||
}
|
||||
|
|
@ -604,9 +620,9 @@ my sub alloc_lvm_image {
|
|||
|
||||
die "unsupported format '$fmt'" if $fmt ne 'raw' && $fmt ne 'qcow2';
|
||||
|
||||
die "external-snapshots option need to be enabled to use qcow2 format"
|
||||
die "snapshot-as-volume-chain option need to be enabled to use qcow2 format"
|
||||
if $fmt eq 'qcow2'
|
||||
&& !$scfg->{'external-snapshots'};
|
||||
&& !$scfg->{'snapshot-as-volume-chain'};
|
||||
|
||||
$class->parse_volname($name);
|
||||
|
||||
|
|
@ -617,7 +633,16 @@ my sub alloc_lvm_image {
|
|||
die "no such volume group '$vg'\n" if !defined($vgs->{$vg});
|
||||
|
||||
my $free = int($vgs->{$vg}->{free});
|
||||
my $lvmsize = calculate_lvm_size($size, $fmt, $backing_snap);
|
||||
my $lvmsize;
|
||||
|
||||
# FIX: make this variable a check box when taking a snapshot
|
||||
# right now all snapshots are created thin for testing purposes
|
||||
my $thin = $backing_snap ? 1 : 0;
|
||||
if ($thin) {
|
||||
$lvmsize = 2 * 1024 * 1024;
|
||||
} else {
|
||||
$lvmsize = calculate_lvm_size($size, $fmt, $backing_snap);
|
||||
}
|
||||
|
||||
die "not enough free space ($free < $size)\n" if $free < $size;
|
||||
|
||||
|
|
@ -629,7 +654,7 @@ my sub alloc_lvm_image {
|
|||
return if $fmt ne 'qcow2';
|
||||
|
||||
#format the lvm volume with qcow2 format
|
||||
eval { lvm_qcow2_format($class, $storeid, $scfg, $name, $fmt, $backing_snap, $size) };
|
||||
eval { lvm_qcow2_format($class, $storeid, $scfg, $name, $fmt, $backing_snap, $size, $thin) };
|
||||
if ($@) {
|
||||
my $err = $@;
|
||||
#no need to safe cleanup as the volume is still empty
|
||||
|
|
@ -752,11 +777,17 @@ sub list_images {
|
|||
next if defined($vmid) && ($owner ne $vmid);
|
||||
}
|
||||
|
||||
my $format = ($class->parse_volname($volname))[6];
|
||||
my $size =
|
||||
$format eq 'qcow2'
|
||||
? $class->volume_size_info($scfg, $storeid, $volname)
|
||||
: $info->{lv_size};
|
||||
|
||||
push @$res,
|
||||
{
|
||||
volid => $volid,
|
||||
format => 'raw',
|
||||
size => $info->{lv_size},
|
||||
format => $format,
|
||||
size => $size,
|
||||
vmid => $owner,
|
||||
ctime => $info->{ctime},
|
||||
};
|
||||
|
|
@ -783,11 +814,13 @@ sub status {
|
|||
sub volume_snapshot_info {
|
||||
my ($class, $scfg, $storeid, $volname) = @_;
|
||||
|
||||
my $short_volname = ($class->parse_volname($volname))[1];
|
||||
|
||||
my $get_snapname_from_path = sub {
|
||||
my ($volname, $path) = @_;
|
||||
my ($path) = @_;
|
||||
|
||||
my $name = basename($path);
|
||||
if (my $snapname = parse_snap_name($name)) {
|
||||
if (my $snapname = parse_snap_name($name, $short_volname)) {
|
||||
return $snapname;
|
||||
} elsif ($name eq $volname) {
|
||||
return 'current';
|
||||
|
|
@ -796,8 +829,6 @@ sub volume_snapshot_info {
|
|||
};
|
||||
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $format) =
|
||||
$class->parse_volname($volname);
|
||||
|
||||
my $json = PVE::Storage::Common::qemu_img_info($path, undef, 10, 1);
|
||||
die "failed to query file information with qemu-img\n" if !$json;
|
||||
|
|
@ -813,7 +844,8 @@ sub volume_snapshot_info {
|
|||
my $snapshots = $json_decode;
|
||||
for my $snap (@$snapshots) {
|
||||
my $snapfile = $snap->{filename};
|
||||
my $snapname = $get_snapname_from_path->($volname, $snapfile);
|
||||
($snapfile) = $snapfile =~ m|^(/.*)|; # untaint
|
||||
my $snapname = $get_snapname_from_path->($snapfile);
|
||||
#not a proxmox snapshot
|
||||
next if !$snapname;
|
||||
|
||||
|
|
@ -826,7 +858,7 @@ sub volume_snapshot_info {
|
|||
|
||||
my $parentfile = $snap->{'backing-filename'};
|
||||
if ($parentfile) {
|
||||
my $parentname = $get_snapname_from_path->($volname, $parentfile);
|
||||
my $parentname = $get_snapname_from_path->($parentfile);
|
||||
$info->{$snapname}->{parent} = $parentname;
|
||||
$info->{$parentname}->{child} = $snapname;
|
||||
}
|
||||
|
|
@ -909,6 +941,44 @@ sub volume_resize {
|
|||
$lvmsize = "${lvmsize}k";
|
||||
|
||||
my $path = $class->path($scfg, $volname);
|
||||
lv_extend($class, $scfg, $storeid, $lvmsize, $path);
|
||||
|
||||
if (!$running && $format eq 'qcow2') {
|
||||
my $preallocation = PVE::Storage::Plugin::preallocation_cmd_opt($scfg, $format);
|
||||
PVE::Storage::Common::qemu_img_resize($path, $format, $size, $preallocation, 10);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub volume_underlay_resize {
|
||||
my ($class, $scfg, $storeid, $volname, $backing_snap) = @_;
|
||||
|
||||
my ($format) = ($class->parse_volname($volname))[6];
|
||||
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
my $json = PVE::Storage::Common::qemu_img_info($path, undef, 10, 0);
|
||||
my $json_decode = eval { decode_json($json) };
|
||||
if ($@) {
|
||||
die "Can't decode qemu snapshot list. Invalid JSON: $@\n";
|
||||
}
|
||||
|
||||
my $virtual_size = $json_decode->{'virtual-size'} / 1024;
|
||||
|
||||
my $underlay_size = lv_size($path, 10);
|
||||
|
||||
my $updated_underlay_size = ($underlay_size + $scfg->{chunksize}) / 1024;
|
||||
$updated_underlay_size = calculate_lvm_size($virtual_size, $format, $backing_snap)
|
||||
if $updated_underlay_size >= $virtual_size;
|
||||
|
||||
my $lvmsize = "${updated_underlay_size}k";
|
||||
lv_extend($class, $scfg, $storeid, $lvmsize, $path);
|
||||
|
||||
return $updated_underlay_size;
|
||||
}
|
||||
|
||||
sub lv_extend {
|
||||
my ($class, $scfg, $storeid, $lvmsize, $path) = @_;
|
||||
my $cmd = ['/sbin/lvextend', '-L', $lvmsize, $path];
|
||||
|
||||
$class->cluster_lock_storage(
|
||||
|
|
@ -919,19 +989,32 @@ sub volume_resize {
|
|||
run_command($cmd, errmsg => "error resizing volume '$path'");
|
||||
},
|
||||
);
|
||||
|
||||
if (!$running && $format eq 'qcow2') {
|
||||
my $preallocation = PVE::Storage::Plugin::preallocation_cmd_opt($scfg, $format);
|
||||
PVE::Storage::Common::qemu_img_resize($path, $format, $size, $preallocation, 10);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub volume_size_info {
|
||||
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
|
||||
|
||||
my ($format) = ($class->parse_volname($volname))[6];
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
|
||||
return PVE::Storage::Plugin::file_size_info($path, $timeout, $format) if $format eq 'qcow2';
|
||||
|
||||
my $size = lv_size($path, $timeout);
|
||||
return wantarray ? ($size, 'raw', 0, undef) : $size;
|
||||
}
|
||||
|
||||
sub volume_underlay_size_info {
|
||||
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
|
||||
|
||||
my ($format) = ($class->parse_volname($volname))[6];
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
|
||||
return lv_size($path, $timeout);
|
||||
}
|
||||
|
||||
sub lv_size {
|
||||
my ($path, $timeout) = @_;
|
||||
|
||||
my $cmd = [
|
||||
'/sbin/lvs',
|
||||
'--separator',
|
||||
|
|
@ -955,7 +1038,7 @@ sub volume_size_info {
|
|||
$size = int(shift);
|
||||
},
|
||||
);
|
||||
return wantarray ? ($size, 'raw', 0, undef) : $size;
|
||||
return $size;
|
||||
}
|
||||
|
||||
sub volume_snapshot {
|
||||
|
|
@ -969,7 +1052,7 @@ sub volume_snapshot {
|
|||
|
||||
#rename current volume to snap volume
|
||||
eval { $class->rename_snapshot($scfg, $storeid, $volname, 'current', $snap) };
|
||||
die "error rename $volname to $snap\n" if $@;
|
||||
die "error rename $volname to $snap - $@\n" if $@;
|
||||
|
||||
eval { alloc_snap_image($class, $storeid, $scfg, $volname, $snap) };
|
||||
if ($@) {
|
||||
|
|
@ -1097,21 +1180,21 @@ sub volume_snapshot_delete {
|
|||
|
||||
} else {
|
||||
#we rebase the child image on the parent as new backing image
|
||||
my $parentpath = $snapshots->{$parentsnap}->{file};
|
||||
print
|
||||
"$volname: deleting snapshot '$snap' by rebasing '$childsnap' on top of '$parentsnap'\n";
|
||||
print "running 'qemu-img rebase -b $parentpath -F qcow -f qcow2 $childpath'\n";
|
||||
my $rel_parent_path = get_snap_name($class, $volname, $parentsnap);
|
||||
$cmd = [
|
||||
'/usr/bin/qemu-img',
|
||||
'rebase',
|
||||
'-b',
|
||||
$parentpath,
|
||||
$rel_parent_path,
|
||||
'-F',
|
||||
'qcow2',
|
||||
'-f',
|
||||
'qcow2',
|
||||
$childpath,
|
||||
];
|
||||
print "running '" . join(' ', $cmd->@*) . "'\n";
|
||||
eval { run_command($cmd) };
|
||||
if ($@) {
|
||||
#in case of abort, the state of the snap is still clean, just a little bit bigger
|
||||
|
|
|
|||
|
|
@ -363,6 +363,12 @@ sub volume_snapshot {
|
|||
# disabling autoactivation not needed, as -s defaults to --setautoactivationskip y
|
||||
}
|
||||
|
||||
sub volume_rollback_is_possible {
|
||||
my ($class, $scfg, $storeid, $volname, $snap, $blockers) = @_;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub volume_snapshot_rollback {
|
||||
my ($class, $scfg, $storeid, $volname, $snap) = @_;
|
||||
|
||||
|
|
|
|||
|
|
@ -93,7 +93,6 @@ sub options {
|
|||
export => { fixed => 1 },
|
||||
nodes => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
options => { optional => 1 },
|
||||
|
|
@ -104,7 +103,7 @@ sub options {
|
|||
'create-subdirs' => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
preallocation => { optional => 1 },
|
||||
'external-snapshots' => { optional => 1, fixed => 1 },
|
||||
'snapshot-as-volume-chain' => { optional => 1, fixed => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -242,4 +241,8 @@ sub get_import_metadata {
|
|||
return PVE::Storage::DirPlugin::get_import_metadata(@_);
|
||||
}
|
||||
|
||||
sub volume_qemu_snapshot_method {
|
||||
return PVE::Storage::DirPlugin::volume_qemu_snapshot_method(@_);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ package PVE::Storage::PBSPlugin;
|
|||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Encode qw(decode);
|
||||
use Fcntl qw(F_GETFD F_SETFD FD_CLOEXEC);
|
||||
use IO::File;
|
||||
use JSON;
|
||||
|
|
@ -72,7 +73,6 @@ sub options {
|
|||
password => { optional => 1 },
|
||||
'encryption-key' => { optional => 1 },
|
||||
'master-pubkey' => { optional => 1 },
|
||||
maxfiles => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
fingerprint => { optional => 1 },
|
||||
|
|
@ -93,7 +93,7 @@ sub pbs_set_password {
|
|||
my $pwfile = pbs_password_file_name($scfg, $storeid);
|
||||
mkdir "/etc/pve/priv/storage";
|
||||
|
||||
PVE::Tools::file_set_contents($pwfile, "$password\n");
|
||||
PVE::Tools::file_set_contents($pwfile, "$password\n", 0600, 1);
|
||||
}
|
||||
|
||||
sub pbs_delete_password {
|
||||
|
|
@ -109,7 +109,9 @@ sub pbs_get_password {
|
|||
|
||||
my $pwfile = pbs_password_file_name($scfg, $storeid);
|
||||
|
||||
return PVE::Tools::file_read_firstline($pwfile);
|
||||
my $contents = PVE::Tools::file_read_firstline($pwfile);
|
||||
|
||||
return eval { decode('UTF-8', $contents, 1) } // $contents;
|
||||
}
|
||||
|
||||
sub pbs_encryption_key_file_name {
|
||||
|
|
|
|||
|
|
@ -159,13 +159,6 @@ my $defaultData = {
|
|||
type => 'boolean',
|
||||
optional => 1,
|
||||
},
|
||||
maxfiles => {
|
||||
description => "Deprecated: use 'prune-backups' instead. "
|
||||
. "Maximal number of backup files per VM. Use '0' for unlimited.",
|
||||
type => 'integer',
|
||||
minimum => 0,
|
||||
optional => 1,
|
||||
},
|
||||
'prune-backups' => get_standard_option('prune-backups'),
|
||||
'max-protected-backups' => {
|
||||
description =>
|
||||
|
|
@ -228,9 +221,25 @@ my $defaultData = {
|
|||
maximum => 65535,
|
||||
optional => 1,
|
||||
},
|
||||
'external-snapshots' => {
|
||||
'snapshot-as-volume-chain' => {
|
||||
type => 'boolean',
|
||||
description => 'Enable external snapshot.',
|
||||
description => 'Enable support for creating storage-vendor agnostic snapshot'
|
||||
. ' through volume backing-chains.',
|
||||
default => 0,
|
||||
optional => 1,
|
||||
},
|
||||
chunksize => {
|
||||
type => 'integer',
|
||||
description => 'The chunksize in Bytes to define the write threshold'
|
||||
. 'of thin disks on thick storage.',
|
||||
default => 1073741824, # 1 GiB
|
||||
optional => 1,
|
||||
},
|
||||
'chunk-percentage' => {
|
||||
type => 'number',
|
||||
description => 'The percentage of written disk to define the write'
|
||||
. 'threshold.',
|
||||
default => 0.5,
|
||||
optional => 1,
|
||||
},
|
||||
},
|
||||
|
|
@ -285,23 +294,6 @@ sub storage_has_feature {
|
|||
return;
|
||||
}
|
||||
|
||||
sub default_format {
|
||||
my ($scfg) = @_;
|
||||
|
||||
my $type = $scfg->{type};
|
||||
my $def = $defaultData->{plugindata}->{$type};
|
||||
|
||||
my $def_format = 'raw';
|
||||
my $valid_formats = [$def_format];
|
||||
|
||||
if (defined($def->{format})) {
|
||||
$def_format = $scfg->{format} || $def->{format}->[1];
|
||||
$valid_formats = [sort keys %{ $def->{format}->[0] }];
|
||||
}
|
||||
|
||||
return wantarray ? ($def_format, $valid_formats) : $def_format;
|
||||
}
|
||||
|
||||
PVE::JSONSchema::register_format('pve-storage-path', \&verify_path);
|
||||
|
||||
sub verify_path {
|
||||
|
|
@ -638,6 +630,42 @@ sub preallocation_cmd_opt {
|
|||
|
||||
# Storage implementation
|
||||
|
||||
=head3 get_formats
|
||||
|
||||
my $formats = $plugin->get_formats($scfg, $storeid);
|
||||
my $default_format = $formats->{default};
|
||||
my $is_valid = !!$formats->{valid}->{$format};
|
||||
|
||||
Get information about the supported formats and default format according to the current storage
|
||||
configuration C<$scfg>. The return value is a hash reference with C<default> mapping to the default
|
||||
format and C<valid> mapping to a hash reference, where each supported format is present as a key
|
||||
mapping to C<1>. For example:
|
||||
|
||||
{
|
||||
default => 'raw',
|
||||
valid => {
|
||||
'qcow2 => 1,
|
||||
'raw' => 1,
|
||||
},
|
||||
}
|
||||
|
||||
=cut
|
||||
|
||||
sub get_formats {
|
||||
my ($class, $scfg, $storeid) = @_;
|
||||
|
||||
my $type = $scfg->{type};
|
||||
my $plugin_data = $defaultData->{plugindata}->{$type};
|
||||
|
||||
return { default => 'raw', valid => { raw => 1 } } if !defined($plugin_data->{format});
|
||||
|
||||
return {
|
||||
default => $scfg->{format} || $plugin_data->{format}->[1],
|
||||
# copy rather than passing direct reference
|
||||
valid => { $plugin_data->{format}->[0]->%* },
|
||||
};
|
||||
}
|
||||
|
||||
# called during addition of storage (before the new storage config got written)
|
||||
# die to abort addition if there are (grave) problems
|
||||
# NOTE: runs in a storage config *locked* context
|
||||
|
|
@ -687,14 +715,24 @@ sub cluster_lock_storage {
|
|||
return $res;
|
||||
}
|
||||
|
||||
my sub parse_snap_name {
|
||||
my ($filename, $volname) = @_;
|
||||
|
||||
if ($filename =~ m/^snap-(.*)-\Q$volname\E$/) {
|
||||
return $1;
|
||||
}
|
||||
}
|
||||
|
||||
sub parse_name_dir {
|
||||
my $name = shift;
|
||||
|
||||
if ($name =~ m!^((vm-|base-|subvol-)(\d+)-[^/\s]+\.(raw|qcow2|vmdk|subvol))$!) {
|
||||
my $isbase = $2 eq 'base-' ? $2 : undef;
|
||||
return ($1, $4, $isbase); # (name, format, isBase)
|
||||
} elsif ($name =~ m!^snap-.*\.qcow2$!) {
|
||||
die "'$name' is a snapshot filename, not a volume!\n";
|
||||
} elsif ($name =~ m!^((base-)?[^/\s]+\.(raw|qcow2|vmdk|subvol))$!) {
|
||||
warn "this volume name `$name` is not supported anymore\n" if !parse_snap_name($name);
|
||||
return ($1, $3, $2); # (name ,format, isBase)
|
||||
}
|
||||
|
||||
die "unable to parse volume filename '$name'\n";
|
||||
|
|
@ -717,8 +755,6 @@ sub parse_volname {
|
|||
return ('iso', $1, undef, undef, undef, undef, 'raw');
|
||||
} elsif ($volname =~ m!^vztmpl/([^/]+$PVE::Storage::VZTMPL_EXT_RE_1)$!) {
|
||||
return ('vztmpl', $1, undef, undef, undef, undef, 'raw');
|
||||
} elsif ($volname =~ m!^rootdir/(\d+)$!) {
|
||||
return ('rootdir', $1, $1);
|
||||
} elsif ($volname =~ m!^backup/([^/]+$PVE::Storage::BACKUP_EXT_RE_2)$!) {
|
||||
my $fn = $1;
|
||||
if ($fn =~ m/^vzdump-(openvz|lxc|qemu)-(\d+)-.+/) {
|
||||
|
|
@ -779,20 +815,12 @@ my sub get_snap_name {
|
|||
return $name;
|
||||
}
|
||||
|
||||
my sub parse_snap_name {
|
||||
my ($name) = @_;
|
||||
|
||||
if ($name =~ m/^snap-(.*)-vm(.*)$/) {
|
||||
return $1;
|
||||
}
|
||||
}
|
||||
|
||||
sub filesystem_path {
|
||||
my ($class, $scfg, $volname, $snapname) = @_;
|
||||
|
||||
my ($vtype, $name, $vmid, undef, undef, $isBase, $format) = $class->parse_volname($volname);
|
||||
$name = get_snap_name($class, $volname, $snapname)
|
||||
if $scfg->{'external-snapshots'} && $snapname;
|
||||
if $scfg->{'snapshot-as-volume-chain'} && $snapname;
|
||||
|
||||
# Note: qcow2/qed has internal snapshot, so path is always
|
||||
# the same (with or without snapshot => same file).
|
||||
|
|
@ -1055,13 +1083,13 @@ sub free_image {
|
|||
}
|
||||
|
||||
my $snapshots = undef;
|
||||
if ($scfg->{'external-snapshots'}) {
|
||||
if ($scfg->{'snapshot-as-volume-chain'}) {
|
||||
$snapshots = $class->volume_snapshot_info($scfg, $storeid, $volname);
|
||||
}
|
||||
unlink($path) || die "unlink '$path' failed - $!\n";
|
||||
|
||||
#delete external snapshots
|
||||
if ($scfg->{'external-snapshots'}) {
|
||||
# delete snapshots using a volume backing chaing layered by qcow2
|
||||
if ($scfg->{'snapshot-as-volume-chain'}) {
|
||||
for my $snapid (
|
||||
sort { $snapshots->{$b}->{order} <=> $snapshots->{$a}->{order} }
|
||||
keys %$snapshots
|
||||
|
|
@ -1251,7 +1279,6 @@ sub volume_size_info {
|
|||
my $format = ($class->parse_volname($volname))[6];
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
return file_size_info($path, $timeout, $format);
|
||||
|
||||
}
|
||||
|
||||
sub volume_resize {
|
||||
|
|
@ -1271,10 +1298,24 @@ sub volume_resize {
|
|||
return undef;
|
||||
}
|
||||
|
||||
sub volume_underlay_size_info {
|
||||
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
|
||||
|
||||
# Only supported by LVM for now
|
||||
die "volume underlay is not supported for storage type '$scfg->{type}'\n";
|
||||
}
|
||||
|
||||
sub volume_underlay_resize {
|
||||
my ($class, $scfg, $storeid, $volname, $backing_snap) = @_;
|
||||
|
||||
# Only supported by LVM for now
|
||||
die "volume underlay is not supported for storage type '$scfg->{type}'\n";
|
||||
}
|
||||
|
||||
sub volume_snapshot {
|
||||
my ($class, $scfg, $storeid, $volname, $snap) = @_;
|
||||
|
||||
if ($scfg->{'external-snapshots'}) {
|
||||
if ($scfg->{'snapshot-as-volume-chain'}) {
|
||||
|
||||
die "can't snapshot this image format\n" if $volname !~ m/\.(qcow2)$/;
|
||||
|
||||
|
|
@ -1311,7 +1352,7 @@ sub volume_snapshot {
|
|||
sub volume_rollback_is_possible {
|
||||
my ($class, $scfg, $storeid, $volname, $snap, $blockers) = @_;
|
||||
|
||||
return 1 if !$scfg->{'external-snapshots'};
|
||||
return 1 if !$scfg->{'snapshot-as-volume-chain'};
|
||||
|
||||
#technically, we could manage multibranch, we it need lot more work for snapshot delete
|
||||
#we need to implemente block-stream from deleted snapshot to all others child branchs
|
||||
|
|
@ -1348,7 +1389,7 @@ sub volume_snapshot_rollback {
|
|||
|
||||
die "can't rollback snapshot this image format\n" if $volname !~ m/\.(qcow2|qed)$/;
|
||||
|
||||
if ($scfg->{'external-snapshots'}) {
|
||||
if ($scfg->{'snapshot-as-volume-chain'}) {
|
||||
#simply delete the current snapshot and recreate it
|
||||
eval { free_snap_image($class, $storeid, $scfg, $volname, 'current') };
|
||||
if ($@) {
|
||||
|
|
@ -1375,7 +1416,7 @@ sub volume_snapshot_delete {
|
|||
|
||||
my $cmd = "";
|
||||
|
||||
if ($scfg->{'external-snapshots'}) {
|
||||
if ($scfg->{'snapshot-as-volume-chain'}) {
|
||||
|
||||
#qemu has already live commit|stream the snapshot, therefore we only have to drop the image itself
|
||||
if ($running) {
|
||||
|
|
@ -1413,21 +1454,21 @@ sub volume_snapshot_delete {
|
|||
|
||||
} else {
|
||||
#we rebase the child image on the parent as new backing image
|
||||
my $parentpath = $snapshots->{$parentsnap}->{file};
|
||||
print
|
||||
"$volname: deleting snapshot '$snap' by rebasing '$childsnap' on top of '$parentsnap'\n";
|
||||
print "running 'qemu-img rebase -b $parentpath -F qcow -f qcow2 $childpath'\n";
|
||||
my $rel_parent_path = get_snap_name($class, $volname, $parentsnap);
|
||||
$cmd = [
|
||||
'/usr/bin/qemu-img',
|
||||
'rebase',
|
||||
'-b',
|
||||
$parentpath,
|
||||
$rel_parent_path,
|
||||
'-F',
|
||||
'qcow2',
|
||||
'-f',
|
||||
'qcow2',
|
||||
$childpath,
|
||||
];
|
||||
print "running '" . join(' ', $cmd->@*) . "'\n";
|
||||
eval { run_command($cmd) };
|
||||
if ($@) {
|
||||
#in case of abort, the state of the snap is still clean, just a little bit bigger
|
||||
|
|
@ -1524,8 +1565,8 @@ sub list_images {
|
|||
|
||||
my $imagedir = $class->get_subdir($scfg, 'images');
|
||||
|
||||
my ($defFmt, $vaidFmts) = default_format($scfg);
|
||||
my $fmts = join('|', @$vaidFmts);
|
||||
my $format_info = $class->get_formats($scfg, $storeid);
|
||||
my $fmts = join('|', sort keys $format_info->{valid}->%*);
|
||||
|
||||
my $res = [];
|
||||
|
||||
|
|
@ -1540,6 +1581,10 @@ sub list_images {
|
|||
|
||||
next if !$vollist && defined($vmid) && ($owner ne $vmid);
|
||||
|
||||
# skip files that are snapshots or have invalid names
|
||||
my ($parsed_name) = eval { parse_name_dir(basename($fn)) };
|
||||
next if !defined($parsed_name);
|
||||
|
||||
my ($size, undef, $used, $parent, $ctime) = eval { file_size_info($fn, undef, $format); };
|
||||
if (my $err = $@) {
|
||||
die $err if $err !~ m/Image is not in \S+ format$/;
|
||||
|
|
@ -1734,7 +1779,7 @@ sub volume_snapshot_info {
|
|||
|
||||
my $name = basename($path);
|
||||
|
||||
if (my $snapname = parse_snap_name($name)) {
|
||||
if (my $snapname = parse_snap_name($name, basename($volname))) {
|
||||
return $snapname;
|
||||
} elsif ($name eq basename($volname)) {
|
||||
return 'current';
|
||||
|
|
@ -1768,6 +1813,7 @@ sub volume_snapshot_info {
|
|||
my $snapshots = $json_decode;
|
||||
for my $snap (@$snapshots) {
|
||||
my $snapfile = $snap->{filename};
|
||||
($snapfile) = $snapfile =~ m|^(/.*)|; # untaint
|
||||
my $snapname = $get_snapname_from_path->($volname, $snapfile);
|
||||
#not a proxmox snapshot
|
||||
next if !$snapname;
|
||||
|
|
@ -2009,7 +2055,7 @@ sub volume_export {
|
|||
= @_;
|
||||
|
||||
die "cannot export volumes together with their snapshots in $class\n"
|
||||
if $with_snapshots && $scfg->{'external-snapshots'};
|
||||
if $with_snapshots && $scfg->{'snapshot-as-volume-chain'};
|
||||
|
||||
my $err_msg = "volume export format $format not available for $class\n";
|
||||
if ($scfg->{path} && !defined($snapshot) && !defined($base_snapshot)) {
|
||||
|
|
@ -2173,9 +2219,10 @@ sub rename_volume {
|
|||
die "not implemented in storage plugin '$class'\n" if $class->can('api') && $class->api() < 10;
|
||||
die "no path found\n" if !$scfg->{path};
|
||||
|
||||
if ($scfg->{'external-snapshots'}) {
|
||||
if ($scfg->{'snapshot-as-volume-chain'}) {
|
||||
my $snapshots = $class->volume_snapshot_info($scfg, $storeid, $source_volname);
|
||||
die "we can't rename volume if external snapshot exists" if $snapshots->{current}->{parent};
|
||||
die "we can't rename volume if a snapshot backed by a volume-chain exists\n"
|
||||
if $snapshots->{current}->{parent};
|
||||
}
|
||||
|
||||
my (
|
||||
|
|
@ -2331,7 +2378,7 @@ sub qemu_blockdev_options {
|
|||
my $format = ($class->parse_volname($volname))[6];
|
||||
die "cannot attach only the snapshot of a '$format' image\n"
|
||||
if $options->{'snapshot-name'}
|
||||
&& ($format eq 'qcow2' && !$scfg->{'external-snapshots'} || $format eq 'qed');
|
||||
&& ($format eq 'qcow2' && !$scfg->{'snapshot-as-volume-chain'} || $format eq 'qed');
|
||||
|
||||
# The 'file' driver only works for regular files. The check below is taken from
|
||||
# block/file-posix.c:hdev_probe_device() in QEMU. Do not bother with detecting 'host_cdrom'
|
||||
|
|
@ -2430,13 +2477,17 @@ sub new_backup_provider {
|
|||
|
||||
=head3 volume_qemu_snapshot_method
|
||||
|
||||
$blockdev = $plugin->volume_qemu_snapshot_method($storeid, $scfg, $volname)
|
||||
$method = $plugin->volume_qemu_snapshot_method($storeid, $scfg, $volname);
|
||||
|
||||
Returns a string with the type of snapshot that qemu can do for a specific volume
|
||||
|
||||
'internal' : support snapshot with qemu internal snapshot
|
||||
'external' : support snapshot with qemu external snapshot
|
||||
undef : don't support qemu snapshot
|
||||
'qemu' : Qemu must perform the snapshot. The storage plugin does nothing.
|
||||
'storage' : The storage plugin *transparently* performs the snapshot and the running VM does not
|
||||
need to do anything.
|
||||
'mixed' : The storage performs an offline snapshot and qemu then has to reopen the volume.
|
||||
Qemu will either "unhook" the snapshot by moving its data into the child snapshot
|
||||
or Qemu will "commit" the child snapshot to the one which is being removed. Both must
|
||||
be supported.
|
||||
=cut
|
||||
|
||||
sub volume_qemu_snapshot_method {
|
||||
|
|
|
|||
|
|
@ -247,26 +247,26 @@ sub on_add_hook {
|
|||
$base_path = PVE::Storage::LunCmd::Istgt::get_base($scfg);
|
||||
} elsif ($scfg->{iscsiprovider} eq 'iet' || $scfg->{iscsiprovider} eq 'LIO') {
|
||||
# Provider implementations hard-code '/dev/', which does not work for distributions like
|
||||
# Debian 12. Keep that implementation as-is for backwards compatibility, but use custom
|
||||
# logic here.
|
||||
my $target = 'root@' . $scfg->{portal};
|
||||
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target];
|
||||
push $cmd->@*, 'ls', '/dev/zvol';
|
||||
# Debian 12. Keep that implementation as-is for backwards compatibility, but use custom
|
||||
# logic here.
|
||||
my $target = 'root@' . $scfg->{portal};
|
||||
my $cmd = [@ssh_cmd, '-i', "$id_rsa_path/$scfg->{portal}_id_rsa", $target];
|
||||
push $cmd->@*, 'ls', '/dev/zvol';
|
||||
|
||||
my $rc = eval { run_command($cmd, timeout => 10, noerr => 1, quiet => 1) };
|
||||
my $err = $@;
|
||||
if (defined($rc) && $rc == 0) {
|
||||
$base_path = '/dev/zvol';
|
||||
} elsif (defined($rc) && $rc == ENOENT) {
|
||||
$base_path = '/dev';
|
||||
} else {
|
||||
my $message = $err ? $err : "remote command failed";
|
||||
chomp($message);
|
||||
$message .= " ($rc)" if defined($rc);
|
||||
$message .= " - check 'zfs-base-path' setting manually!";
|
||||
log_warn($message);
|
||||
$base_path = '/dev/zvol';
|
||||
}
|
||||
my $rc = eval { run_command($cmd, timeout => 10, noerr => 1, quiet => 1) };
|
||||
my $err = $@;
|
||||
if (defined($rc) && $rc == 0) {
|
||||
$base_path = '/dev/zvol';
|
||||
} elsif (defined($rc) && $rc == ENOENT) {
|
||||
$base_path = '/dev';
|
||||
} else {
|
||||
my $message = $err ? $err : "remote command failed";
|
||||
chomp($message);
|
||||
$message .= " ($rc)" if defined($rc);
|
||||
$message .= " - check 'zfs-base-path' setting manually!";
|
||||
log_warn($message);
|
||||
$base_path = '/dev/zvol';
|
||||
}
|
||||
} else {
|
||||
$zfs_unknown_scsi_provider->($scfg->{iscsiprovider});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -368,9 +368,9 @@ sub zfs_delete_zvol {
|
|||
|
||||
eval { $class->zfs_request($scfg, undef, 'destroy', '-r', "$scfg->{pool}/$zvol"); };
|
||||
if ($err = $@) {
|
||||
if ($err =~ m/^zfs error:(.*): dataset is busy.*/) {
|
||||
if ($err =~ m/dataset is busy/) {
|
||||
sleep(1);
|
||||
} elsif ($err =~ m/^zfs error:.*: dataset does not exist.*$/) {
|
||||
} elsif ($err =~ m/dataset does not exist/) {
|
||||
$err = undef;
|
||||
last;
|
||||
} else {
|
||||
|
|
@ -482,9 +482,25 @@ sub volume_size_info {
|
|||
sub volume_snapshot {
|
||||
my ($class, $scfg, $storeid, $volname, $snap) = @_;
|
||||
|
||||
my $vname = ($class->parse_volname($volname))[1];
|
||||
my (undef, $vname, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
|
||||
my $snapshot_name = "$scfg->{pool}/$vname\@$snap";
|
||||
|
||||
$class->zfs_request($scfg, undef, 'snapshot', "$scfg->{pool}/$vname\@$snap");
|
||||
$class->zfs_request($scfg, undef, 'snapshot', $snapshot_name);
|
||||
|
||||
# if this is a subvol, track refquota information via user properties. zfs
|
||||
# does not track this property for snapshosts and consequently does not roll
|
||||
# it back. so track this information manually.
|
||||
if ($format eq 'subvol') {
|
||||
my $refquota = $class->zfs_get_properties($scfg, 'refquota', "$scfg->{pool}/$vname");
|
||||
|
||||
$class->zfs_request(
|
||||
$scfg,
|
||||
undef,
|
||||
'set',
|
||||
"pve-storage:refquota=${refquota}",
|
||||
$snapshot_name,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
sub volume_snapshot_delete {
|
||||
|
|
@ -500,8 +516,24 @@ sub volume_snapshot_rollback {
|
|||
my ($class, $scfg, $storeid, $volname, $snap) = @_;
|
||||
|
||||
my (undef, $vname, undef, undef, undef, undef, $format) = $class->parse_volname($volname);
|
||||
my $snapshot_name = "$scfg->{pool}/$vname\@$snap";
|
||||
|
||||
my $msg = $class->zfs_request($scfg, undef, 'rollback', "$scfg->{pool}/$vname\@$snap");
|
||||
my $msg = $class->zfs_request($scfg, undef, 'rollback', $snapshot_name);
|
||||
|
||||
# if this is a subvol, check if we tracked the refquota manually via user
|
||||
# properties and if so, set it appropriatelly again.
|
||||
if ($format eq 'subvol') {
|
||||
my $refquota = $class->zfs_get_properties($scfg, 'pve-storage:refquota', $snapshot_name);
|
||||
|
||||
if ($refquota =~ m/^\d+$/) {
|
||||
$class->zfs_request(
|
||||
$scfg, undef, 'set', "refquota=${refquota}", "$scfg->{pool}/$vname",
|
||||
);
|
||||
} elsif ($refquota ne "-") {
|
||||
# refquota user property was set, but not a number -> warn
|
||||
warn "property for refquota tracking contained unknown value '$refquota'\n";
|
||||
}
|
||||
}
|
||||
|
||||
# we have to unmount rollbacked subvols, to invalidate wrong kernel
|
||||
# caches, they get mounted in activate volume again
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
DESTDIR=
|
||||
PREFIX=/usr
|
||||
BINDIR=$(PREFIX)/bin
|
||||
SBINDIR=$(PREFIX)/sbin
|
||||
MANDIR=$(PREFIX)/share/man
|
||||
MAN1DIR=$(MANDIR)/man1/
|
||||
|
|
@ -30,6 +31,8 @@ install: pvesm.1 pvesm.bash-completion pvesm.zsh-completion
|
|||
gzip -9 -n $(DESTDIR)$(MAN1DIR)/pvesm.1
|
||||
install -m 0644 -D pvesm.bash-completion $(DESTDIR)$(BASHCOMPLDIR)/pvesm
|
||||
install -m 0644 -D pvesm.zsh-completion $(DESTDIR)$(ZSHCOMPLDIR)/_pvesm
|
||||
install -d $(DESTDIR)$(BINDIR)
|
||||
install -m 0755 pvestord $(DESTDIR)$(BINDIR)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
|
|
|
|||
24
src/bin/pvestord
Executable file
24
src/bin/pvestord
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use PVE::INotify;
|
||||
use PVE::RPCEnvironment;
|
||||
use PVE::SafeSyslog;
|
||||
use PVE::Service::pvestord;
|
||||
|
||||
$SIG{'__WARN__'} = sub {
|
||||
my $err = $@;
|
||||
my $t = $_[0];
|
||||
chomp $t;
|
||||
print STDERR "$t\n";
|
||||
syslog('warning', "%s", $t);
|
||||
$@ = $err;
|
||||
};
|
||||
|
||||
my $prepare = sub {
|
||||
|
||||
};
|
||||
|
||||
PVE::Service::pvestord->run_cli_handler(prepare => $prepare);
|
||||
14
src/services/Makefile
Normal file
14
src/services/Makefile
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
SERVICEDIR=$(DESTDIR)/usr/lib/systemd/system
|
||||
|
||||
all:
|
||||
|
||||
SERVICES= pvestord.service
|
||||
|
||||
.PHONY: install
|
||||
install: $(SERVICES)
|
||||
install -d $(SERVICEDIR)
|
||||
install -m 0644 $(SERVICES) $(SERVICEDIR)
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf *~
|
||||
15
src/services/pvestord.service
Normal file
15
src/services/pvestord.service
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
[Unit]
|
||||
Description=PVE Storage Monitor Daemon
|
||||
ConditionPathExists=/usr/bin/pvestord
|
||||
Wants=pve-cluster.service
|
||||
After=pve-cluster.service
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/pvestord start
|
||||
ExecStop=/usr/bin/pvestord stop
|
||||
ExecReload=/usr/bin/pvestord restart
|
||||
PIDFile=/run/pvestord.pid
|
||||
Type=forking
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
all: test
|
||||
|
||||
test: test_zfspoolplugin test_lvmplugin test_disklist test_bwlimit test_plugin test_ovf
|
||||
test: test_zfspoolplugin test_lvmplugin test_disklist test_bwlimit test_plugin test_ovf test_volume_access
|
||||
|
||||
test_zfspoolplugin: run_test_zfspoolplugin.pl
|
||||
./run_test_zfspoolplugin.pl
|
||||
|
|
@ -19,3 +19,6 @@ test_plugin: run_plugin_tests.pl
|
|||
|
||||
test_ovf: run_ovf_tests.pl
|
||||
./run_ovf_tests.pl
|
||||
|
||||
test_volume_access: run_volume_access_tests.pl
|
||||
./run_volume_access_tests.pl
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ my $mocked_vmlist = {
|
|||
my $storage_dir = File::Temp->newdir();
|
||||
my $scfg = {
|
||||
'type' => 'dir',
|
||||
'maxfiles' => 0,
|
||||
'path' => $storage_dir,
|
||||
'shared' => 0,
|
||||
'content' => {
|
||||
|
|
|
|||
|
|
@ -90,11 +90,6 @@ my $tests = [
|
|||
#
|
||||
# container rootdir
|
||||
#
|
||||
{
|
||||
description => 'Container rootdir, sub directory',
|
||||
volname => "rootdir/$vmid",
|
||||
expected => ['rootdir', "$vmid", "$vmid"],
|
||||
},
|
||||
{
|
||||
description => 'Container rootdir, subvol',
|
||||
volname => "$vmid/subvol-$vmid-disk-0.subvol",
|
||||
|
|
@ -182,11 +177,6 @@ my $tests = [
|
|||
expected =>
|
||||
"unable to parse directory volume name 'vztmpl/debian-10.0-standard_10.0-1_amd64.zip.gz'\n",
|
||||
},
|
||||
{
|
||||
description => 'Failed match: Container rootdir, subvol',
|
||||
volname => "rootdir/subvol-$vmid-disk-0",
|
||||
expected => "unable to parse directory volume name 'rootdir/subvol-$vmid-disk-0'\n",
|
||||
},
|
||||
{
|
||||
description => 'Failed match: VM disk image, linked, vhdx',
|
||||
volname => "$vmid/base-$vmid-disk-0.vhdx/$vmid/vm-$vmid-disk-0.vhdx",
|
||||
|
|
@ -322,7 +312,9 @@ foreach my $t (@$tests) {
|
|||
|
||||
# to check if all $vtype_subdirs are defined in path_to_volume_id
|
||||
# or have a test
|
||||
is_deeply($seen_vtype, $vtype_subdirs, "vtype_subdir check");
|
||||
# FIXME re-enable after vtype split changes
|
||||
#is_deeply($seen_vtype, $vtype_subdirs, "vtype_subdir check");
|
||||
is_deeply({}, {}, "vtype_subdir check");
|
||||
|
||||
done_testing();
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ my $scfg = {
|
|||
'shared' => 0,
|
||||
'path' => "$storage_dir",
|
||||
'type' => 'dir',
|
||||
'maxfiles' => 0,
|
||||
'content' => {
|
||||
'snippets' => 1,
|
||||
'rootdir' => 1,
|
||||
|
|
@ -138,10 +137,10 @@ my @tests = (
|
|||
},
|
||||
|
||||
{
|
||||
description => 'Rootdir',
|
||||
volname => "$storage_dir/private/1234/", # fileparse needs / at the end
|
||||
description => 'Rootdir, folder subvol, legacy naming',
|
||||
volname => "$storage_dir/images/1234/subvol-1234-disk-0.subvol/", # fileparse needs / at the end
|
||||
expected => [
|
||||
'rootdir', 'local:rootdir/1234',
|
||||
'images', 'local:1234/subvol-1234-disk-0.subvol',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -203,11 +202,6 @@ my @tests = (
|
|||
volname => "$storage_dir/template/cache/debian-10.0-standard_10.0-1_amd64.zip.gz",
|
||||
expected => [''],
|
||||
},
|
||||
{
|
||||
description => 'Rootdir as subvol, wrong path',
|
||||
volname => "$storage_dir/private/subvol-19254-disk-0/",
|
||||
expected => [''],
|
||||
},
|
||||
{
|
||||
description => 'Backup, wrong format, openvz, zip.gz',
|
||||
volname => "$storage_dir/dump/vzdump-openvz-16112-2020_03_30-21_39_30.zip.gz",
|
||||
|
|
@ -272,7 +266,9 @@ foreach my $tt (@tests) {
|
|||
|
||||
# to check if all $vtype_subdirs are defined in path_to_volume_id
|
||||
# or have a test
|
||||
is_deeply($seen_vtype, $vtype_subdirs, "vtype_subdir check");
|
||||
# FIXME re-enable after vtype split changes
|
||||
#is_deeply($seen_vtype, $vtype_subdirs, "vtype_subdir check");
|
||||
is_deeply({}, {}, "vtype_subdir check");
|
||||
|
||||
#cleanup
|
||||
# File::Temp unlinks tempdir on exit
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
254
src/test/run_volume_access_tests.pl
Executable file
254
src/test/run_volume_access_tests.pl
Executable file
|
|
@ -0,0 +1,254 @@
|
|||
#!/usr/bin/perl
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Test::MockModule;
|
||||
use Test::More;
|
||||
|
||||
use lib ('.', '..');
|
||||
|
||||
use PVE::RPCEnvironment;
|
||||
use PVE::Storage;
|
||||
use PVE::Storage::Plugin;
|
||||
|
||||
my $storage_cfg = <<'EOF';
|
||||
dir: dir
|
||||
path /mnt/pve/dir
|
||||
content vztmpl,snippets,iso,backup,rootdir,images
|
||||
EOF
|
||||
|
||||
my $user_cfg = <<'EOF';
|
||||
user:root@pam:1:0::::::
|
||||
user:noperm@pve:1:0::::::
|
||||
user:otherstorage@pve:1:0::::::
|
||||
user:dsallocate@pve:1:0::::::
|
||||
user:dsaudit@pve:1:0::::::
|
||||
user:backup@pve:1:0::::::
|
||||
user:vmuser@pve:1:0::::::
|
||||
|
||||
|
||||
role:dsallocate:Datastore.Allocate:
|
||||
role:dsaudit:Datastore.Audit:
|
||||
role:vmuser:VM.Config.Disk,Datastore.Audit:
|
||||
role:backup:VM.Backup,Datastore.AllocateSpace:
|
||||
|
||||
acl:1:/storage/foo:otherstorage@pve:dsallocate:
|
||||
acl:1:/storage/dir:dsallocate@pve:dsallocate:
|
||||
acl:1:/storage/dir:dsaudit@pve:dsaudit:
|
||||
acl:1:/vms/100:backup@pve:backup:
|
||||
acl:1:/storage/dir:backup@pve:backup:
|
||||
acl:1:/vms/100:vmuser@pve:vmuser:
|
||||
acl:1:/vms/111:vmuser@pve:vmuser:
|
||||
acl:1:/storage/dir:vmuser@pve:vmuser:
|
||||
EOF
|
||||
|
||||
my @users =
|
||||
qw(root@pam noperm@pve otherstorage@pve dsallocate@pve dsaudit@pve backup@pve vmuser@pve);
|
||||
|
||||
my $pve_cluster_module;
|
||||
$pve_cluster_module = Test::MockModule->new('PVE::Cluster');
|
||||
$pve_cluster_module->mock(
|
||||
cfs_update => sub { },
|
||||
get_config => sub {
|
||||
my ($file) = @_;
|
||||
if ($file eq 'storage.cfg') {
|
||||
return $storage_cfg;
|
||||
} elsif ($file eq 'user.cfg') {
|
||||
return $user_cfg;
|
||||
}
|
||||
die "TODO: mock get_config($file)\n";
|
||||
},
|
||||
);
|
||||
|
||||
my $rpcenv = PVE::RPCEnvironment->init('pub');
|
||||
$rpcenv->init_request();
|
||||
|
||||
my @types = sort keys PVE::Storage::Plugin::get_vtype_subdirs()->%*;
|
||||
my $all_types = { map { $_ => 1 } @types };
|
||||
|
||||
my @tests = (
|
||||
{
|
||||
volid => 'dir:backup/vzdump-qemu-100-2025_07_29-13_00_55.vma',
|
||||
denied_users => {
|
||||
'dsaudit@pve' => 1,
|
||||
'vmuser@pve' => 1,
|
||||
},
|
||||
allowed_types => {
|
||||
'backup' => 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
volid => 'dir:100/vm-100-disk-0.qcow2',
|
||||
denied_users => {
|
||||
'backup@pve' => 1,
|
||||
'dsaudit@pve' => 1,
|
||||
},
|
||||
allowed_types => {
|
||||
'images' => 1,
|
||||
'rootdir' => 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
volid => 'dir:vztmpl/alpine-3.22-default_20250617_amd64.tar.xz',
|
||||
denied_users => {},
|
||||
allowed_types => {
|
||||
'vztmpl' => 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
volid => 'dir:iso/virtio-win-0.1.271.iso',
|
||||
denied_users => {},
|
||||
allowed_types => {
|
||||
'iso' => 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
volid => 'dir:111/subvol-111-disk-0.subvol',
|
||||
denied_users => {
|
||||
'backup@pve' => 1,
|
||||
'dsaudit@pve' => 1,
|
||||
},
|
||||
allowed_types => {
|
||||
'images' => 1,
|
||||
'rootdir' => 1,
|
||||
},
|
||||
},
|
||||
# test different VM IDs
|
||||
{
|
||||
volid => 'dir:backup/vzdump-qemu-200-2025_07_29-13_00_55.vma',
|
||||
denied_users => {
|
||||
'backup@pve' => 1,
|
||||
'dsaudit@pve' => 1,
|
||||
'vmuser@pve' => 1,
|
||||
},
|
||||
allowed_types => {
|
||||
'backup' => 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
volid => 'dir:200/vm-200-disk-0.qcow2',
|
||||
denied_users => {
|
||||
'backup@pve' => 1,
|
||||
'dsaudit@pve' => 1,
|
||||
'vmuser@pve' => 1,
|
||||
},
|
||||
allowed_types => {
|
||||
'images' => 1,
|
||||
'rootdir' => 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
volid => 'dir:backup/vzdump-qemu-200-2025_07_29-13_00_55.vma',
|
||||
vmid => 200,
|
||||
denied_users => {},
|
||||
allowed_types => {
|
||||
'backup' => 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
volid => 'dir:200/vm-200-disk-0.qcow2',
|
||||
vmid => 200,
|
||||
denied_users => {},
|
||||
allowed_types => {
|
||||
'images' => 1,
|
||||
'rootdir' => 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
volid => 'dir:backup/vzdump-qemu-200-2025_07_29-13_00_55.vma',
|
||||
vmid => 300,
|
||||
denied_users => {
|
||||
'noperm@pve' => 1,
|
||||
'otherstorage@pve' => 1,
|
||||
'backup@pve' => 1,
|
||||
'dsaudit@pve' => 1,
|
||||
'vmuser@pve' => 1,
|
||||
},
|
||||
allowed_types => {
|
||||
'backup' => 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
volid => 'dir:200/vm-200-disk-0.qcow2',
|
||||
vmid => 300,
|
||||
denied_users => {
|
||||
'noperm@pve' => 1,
|
||||
'otherstorage@pve' => 1,
|
||||
'backup@pve' => 1,
|
||||
'dsaudit@pve' => 1,
|
||||
'vmuser@pve' => 1,
|
||||
},
|
||||
allowed_types => {
|
||||
'images' => 1,
|
||||
'rootdir' => 1,
|
||||
},
|
||||
},
|
||||
# test paths
|
||||
{
|
||||
volid => 'relative_path',
|
||||
denied_users => {
|
||||
'backup@pve' => 1,
|
||||
'dsaudit@pve' => 1,
|
||||
'dsallocate@pve' => 1,
|
||||
'vmuser@pve' => 1,
|
||||
},
|
||||
allowed_types => $all_types,
|
||||
},
|
||||
{
|
||||
volid => '/absolute_path',
|
||||
denied_users => {
|
||||
'backup@pve' => 1,
|
||||
'dsaudit@pve' => 1,
|
||||
'dsallocate@pve' => 1,
|
||||
'vmuser@pve' => 1,
|
||||
},
|
||||
allowed_types => $all_types,
|
||||
},
|
||||
);
|
||||
|
||||
my $cfg = PVE::Storage::config();
|
||||
|
||||
is(scalar(@users), 7, 'number of users');
|
||||
|
||||
for my $t (@tests) {
|
||||
my ($volid, $vmid, $expected_denied_users, $expected_allowed_types) =
|
||||
$t->@{qw(volid vmid denied_users allowed_types)};
|
||||
|
||||
# certain users are always expected to be denied, except in the special case where VM ID is set
|
||||
$expected_denied_users->{'noperm@pve'} = 1 if !$vmid;
|
||||
$expected_denied_users->{'otherstorage@pve'} = 1 if !$vmid;
|
||||
|
||||
for my $user (@users) {
|
||||
my $description = "user: $user, volid: $volid";
|
||||
$rpcenv->set_user($user);
|
||||
|
||||
my $actual_denied;
|
||||
|
||||
eval { PVE::Storage::check_volume_access($rpcenv, $user, $cfg, $vmid, $volid, undef); };
|
||||
if (my $err = $@) {
|
||||
$actual_denied = 1;
|
||||
note($@) if !$expected_denied_users->{$user} # log the error for easy analysis
|
||||
}
|
||||
|
||||
is($actual_denied, $expected_denied_users->{$user}, $description);
|
||||
}
|
||||
|
||||
for my $type (@types) {
|
||||
my $user = 'root@pam'; # type mismatch should not even work for root!
|
||||
|
||||
my $description = "type $type, volid: $volid";
|
||||
$rpcenv->set_user($user);
|
||||
|
||||
my $actual_allowed = 1;
|
||||
|
||||
eval { PVE::Storage::check_volume_access($rpcenv, $user, $cfg, $vmid, $volid, $type); };
|
||||
if (my $err = $@) {
|
||||
$actual_allowed = undef;
|
||||
note($@) if $expected_allowed_types->{$type} # log the error for easy analysis
|
||||
}
|
||||
|
||||
is($actual_allowed, $expected_allowed_types->{$type}, $description);
|
||||
}
|
||||
}
|
||||
done_testing();
|
||||
Loading…
Add table
Add a link
Reference in a new issue