Upstream proxmox-git/pve-storage state of the touched files before 1860d190cb
This commit is contained in:
1 parent
ab149ea5d1
commit
095cd282f3
3 files changed
+326
-44
No files matched your search
@@ -369,24 +369,6 @@ ovf:Item[rasd:InstanceID='%s']/rasd:ResourceType", $controller_id,
|
||||
};
|
||||
}
|
||||
|
||||
# the address is only unique per controller, so disks on different controllers of the same
|
||||
# type can end up with the same address, move those to the next free one of that bus
|
||||
my $used_addresses = { map { $_->{disk_address} => 1 } @disks };
|
||||
my $assigned_addresses = {};
|
||||
for my $disk (@disks) {
|
||||
if (
|
||||
$assigned_addresses->{ $disk->{disk_address} }
|
||||
&& $disk->{disk_address} =~ m/^([a-z]+)\d+$/
|
||||
) {
|
||||
my $bus = $1;
|
||||
my $index = 0;
|
||||
$index++ while $used_addresses->{"$bus$index"};
|
||||
$disk->{disk_address} = "$bus$index";
|
||||
$used_addresses->{ $disk->{disk_address} } = 1;
|
||||
}
|
||||
$assigned_addresses->{ $disk->{disk_address} } = 1;
|
||||
}
|
||||
|
||||
my $nic_id = dtmf_name_to_id('Ethernet Adapter');
|
||||
my $xpath_find_nics =
|
||||
"/ovf:Envelope/ovf:VirtualSystem/ovf:VirtualHardwareSection/ovf:Item[rasd:ResourceType=${nic_id}]";
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
package PVE::Storage::DirPlugin;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Cwd;
|
||||
use Encode qw(decode encode);
|
||||
use File::Path;
|
||||
use File::Spec;
|
||||
use IO::File;
|
||||
use POSIX;
|
||||
|
||||
use PVE::Storage::Plugin;
|
||||
use PVE::GuestImport::OVF;
|
||||
use PVE::JSONSchema qw(get_standard_option);
|
||||
|
||||
use base qw(PVE::Storage::Plugin);
|
||||
|
||||
# Configuration
|
||||
|
||||
sub type {
|
||||
return 'dir';
|
||||
}
|
||||
|
||||
sub plugindata {
|
||||
return {
|
||||
content => [
|
||||
{
|
||||
images => 1,
|
||||
rootdir => 1,
|
||||
vztmpl => 1,
|
||||
iso => 1,
|
||||
backup => 1,
|
||||
snippets => 1,
|
||||
none => 1,
|
||||
import => 1,
|
||||
},
|
||||
{ images => 1, rootdir => 1 },
|
||||
],
|
||||
format => [{ raw => 1, qcow2 => 1, vmdk => 1, subvol => 1 }, 'raw'],
|
||||
'sensitive-properties' => {},
|
||||
};
|
||||
}
|
||||
|
||||
sub properties {
|
||||
return {
|
||||
path => {
|
||||
description => "File system path.",
|
||||
type => 'string',
|
||||
format => 'pve-storage-path',
|
||||
},
|
||||
mkdir => {
|
||||
description =>
|
||||
"Create the directory if it doesn't exist and populate it with default sub-dirs."
|
||||
. " NOTE: Deprecated, use the 'create-base-path' and 'create-subdirs' options instead.",
|
||||
type => 'boolean',
|
||||
default => 'yes',
|
||||
},
|
||||
'create-base-path' => {
|
||||
description => "Create the base directory if it doesn't exist.",
|
||||
type => 'boolean',
|
||||
default => 'yes',
|
||||
},
|
||||
'create-subdirs' => {
|
||||
description => "Populate the directory with the default structure.",
|
||||
type => 'boolean',
|
||||
default => 'yes',
|
||||
},
|
||||
is_mountpoint => {
|
||||
description => "Assume the given path is an externally managed mountpoint "
|
||||
. "and consider the storage offline if it is not mounted. "
|
||||
. "Using a boolean (yes/no) value serves as a shortcut to using the target path in this field.",
|
||||
type => 'string',
|
||||
default => 'no',
|
||||
},
|
||||
bwlimit => get_standard_option('bwlimit'),
|
||||
};
|
||||
}
|
||||
|
||||
sub options {
|
||||
return {
|
||||
path => { fixed => 1 },
|
||||
'content-dirs' => { optional => 1 },
|
||||
nodes => { optional => 1 },
|
||||
shared => { optional => 1 },
|
||||
disable => { optional => 1 },
|
||||
'prune-backups' => { optional => 1 },
|
||||
'max-protected-backups' => { optional => 1 },
|
||||
content => { optional => 1 },
|
||||
format => { optional => 1 },
|
||||
mkdir => { optional => 1 },
|
||||
'create-base-path' => { optional => 1 },
|
||||
'create-subdirs' => { optional => 1 },
|
||||
is_mountpoint => { optional => 1 },
|
||||
bwlimit => { optional => 1 },
|
||||
preallocation => { optional => 1 },
|
||||
'snapshot-as-volume-chain' => { optional => 1, fixed => 1 },
|
||||
};
|
||||
}
|
||||
|
||||
# Storage implementation
|
||||
#
|
||||
|
||||
# NOTE: should ProcFSTools::is_mounted accept an optional cache like this?
|
||||
sub path_is_mounted {
|
||||
my ($mountpoint, $mountdata) = @_;
|
||||
|
||||
$mountpoint = Cwd::realpath($mountpoint); # symlinks
|
||||
return 0 if !defined($mountpoint); # path does not exist
|
||||
|
||||
$mountdata = PVE::ProcFSTools::parse_proc_mounts() if !$mountdata;
|
||||
return 1 if grep { $_->[1] eq $mountpoint } @$mountdata;
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub parse_is_mountpoint {
|
||||
my ($scfg) = @_;
|
||||
my $is_mp = $scfg->{is_mountpoint};
|
||||
return undef if !defined $is_mp;
|
||||
if (defined(my $bool = PVE::JSONSchema::parse_boolean($is_mp))) {
|
||||
return $bool ? $scfg->{path} : undef;
|
||||
}
|
||||
return $is_mp; # contains a path
|
||||
}
|
||||
|
||||
# FIXME move into 'get_volume_attribute' when removing 'get_volume_notes'
|
||||
my $get_volume_notes_impl = sub {
|
||||
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
|
||||
|
||||
my ($vtype) = $class->parse_volname($volname);
|
||||
return if $vtype ne 'backup';
|
||||
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
$path .= $class->SUPER::NOTES_EXT;
|
||||
|
||||
if (-f $path) {
|
||||
my $data = PVE::Tools::file_get_contents($path);
|
||||
return eval { decode('UTF-8', $data, 1) } // $data;
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
# FIXME remove on the next APIAGE reset.
|
||||
# Deprecated, use get_volume_attribute instead.
|
||||
sub get_volume_notes {
|
||||
my ($class, $scfg, $storeid, $volname, $timeout) = @_;
|
||||
return $get_volume_notes_impl->($class, $scfg, $storeid, $volname, $timeout);
|
||||
}
|
||||
|
||||
# FIXME move into 'update_volume_attribute' when removing 'update_volume_notes'
|
||||
my $update_volume_notes_impl = sub {
|
||||
my ($class, $scfg, $storeid, $volname, $notes, $timeout) = @_;
|
||||
|
||||
my ($vtype) = $class->parse_volname($volname);
|
||||
die "only backups can have notes\n" if $vtype ne 'backup';
|
||||
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
$path .= $class->SUPER::NOTES_EXT;
|
||||
|
||||
if (defined($notes) && $notes ne '') {
|
||||
my $encoded = encode('UTF-8', $notes);
|
||||
PVE::Tools::file_set_contents($path, $encoded);
|
||||
} else {
|
||||
unlink $path or $! == ENOENT or die "could not delete notes - $!\n";
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
# FIXME remove on the next APIAGE reset.
|
||||
# Deprecated, use update_volume_attribute instead.
|
||||
sub update_volume_notes {
|
||||
my ($class, $scfg, $storeid, $volname, $notes, $timeout) = @_;
|
||||
return $update_volume_notes_impl->($class, $scfg, $storeid, $volname, $notes, $timeout);
|
||||
}
|
||||
|
||||
sub get_volume_attribute {
|
||||
my ($class, $scfg, $storeid, $volname, $attribute) = @_;
|
||||
|
||||
if ($attribute eq 'notes') {
|
||||
return $get_volume_notes_impl->($class, $scfg, $storeid, $volname);
|
||||
}
|
||||
|
||||
my ($vtype) = $class->parse_volname($volname);
|
||||
return if $vtype ne 'backup';
|
||||
|
||||
if ($attribute eq 'protected') {
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
return -e PVE::Storage::protection_file_path($path) ? 1 : 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub update_volume_attribute {
|
||||
my ($class, $scfg, $storeid, $volname, $attribute, $value) = @_;
|
||||
|
||||
if ($attribute eq 'notes') {
|
||||
return $update_volume_notes_impl->($class, $scfg, $storeid, $volname, $value);
|
||||
}
|
||||
|
||||
my ($vtype) = $class->parse_volname($volname);
|
||||
die "only backups support attribute '$attribute'\n" if $vtype ne 'backup';
|
||||
|
||||
if ($attribute eq 'protected') {
|
||||
my $path = $class->filesystem_path($scfg, $volname);
|
||||
my $protection_path = PVE::Storage::protection_file_path($path);
|
||||
|
||||
return if !((-e $protection_path) xor $value); # protection status already correct
|
||||
|
||||
if ($value) {
|
||||
my $fh = IO::File->new($protection_path, O_CREAT, 0644)
|
||||
or die "unable to create protection file '$protection_path' - $!\n";
|
||||
close($fh);
|
||||
} else {
|
||||
unlink $protection_path
|
||||
or $! == ENOENT
|
||||
or die "could not delete protection file '$protection_path' - $!\n";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
die "attribute '$attribute' is not supported for storage type '$scfg->{type}'\n";
|
||||
}
|
||||
|
||||
sub status {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
if (defined(my $mp = parse_is_mountpoint($scfg))) {
|
||||
$cache->{mountdata} = PVE::ProcFSTools::parse_proc_mounts()
|
||||
if !$cache->{mountdata};
|
||||
|
||||
return undef if !path_is_mounted($mp, $cache->{mountdata});
|
||||
}
|
||||
|
||||
return $class->SUPER::status($storeid, $scfg, $cache);
|
||||
}
|
||||
|
||||
sub activate_storage {
|
||||
my ($class, $storeid, $scfg, $cache) = @_;
|
||||
|
||||
my $path = $scfg->{path};
|
||||
|
||||
my $mp = parse_is_mountpoint($scfg);
|
||||
if (defined($mp) && !path_is_mounted($mp, $cache->{mountdata})) {
|
||||
die "unable to activate storage '$storeid' - "
|
||||
. "directory is expected to be a mount point but is not mounted: '$mp'\n";
|
||||
}
|
||||
|
||||
$class->config_aware_base_mkdir($scfg, $path);
|
||||
$class->SUPER::activate_storage($storeid, $scfg, $cache);
|
||||
}
|
||||
|
||||
sub check_config {
|
||||
my ($self, $sectionId, $config, $create, $skipSchemaCheck) = @_;
|
||||
my $opts =
|
||||
PVE::SectionConfig::check_config($self, $sectionId, $config, $create, $skipSchemaCheck);
|
||||
return $opts if !$create;
|
||||
if ($opts->{path} !~ m|^/[-/a-zA-Z0-9_.@]+$|) {
|
||||
die "illegal path for directory storage: $opts->{path}\n";
|
||||
}
|
||||
# remove trailing slashes from path
|
||||
$opts->{path} = File::Spec->canonpath($opts->{path});
|
||||
return $opts;
|
||||
}
|
||||
|
||||
sub get_import_metadata {
|
||||
my ($class, $scfg, $volname, $storeid) = @_;
|
||||
|
||||
my ($vtype, $name, undef, undef, undef, undef, $fmt) = $class->parse_volname($volname);
|
||||
die "invalid content type '$vtype'\n" if $vtype ne 'import';
|
||||
die "invalid format\n" if $fmt ne 'ova' && $fmt ne 'ovf';
|
||||
|
||||
# NOTE: all types of warnings must be added to the return schema of the import-metadata API endpoint
|
||||
my $warnings = [];
|
||||
|
||||
my $isOva = 0;
|
||||
if ($fmt =~ m/^ova/) {
|
||||
$isOva = 1;
|
||||
push @$warnings, { type => 'ova-needs-extracting' };
|
||||
}
|
||||
my $path = $class->path($scfg, $volname, $storeid, undef);
|
||||
my $res = PVE::GuestImport::OVF::parse_ovf($path, $isOva);
|
||||
my $disks = {};
|
||||
for my $disk ($res->{disks}->@*) {
|
||||
my $id = $disk->{disk_address};
|
||||
my $size = $disk->{virtual_size};
|
||||
my $path = $disk->{relative_path};
|
||||
my $volid;
|
||||
if ($isOva) {
|
||||
$volid = "$storeid:$volname/$path";
|
||||
} else {
|
||||
$volid = "$storeid:import/$path",;
|
||||
}
|
||||
$disks->{$id} = {
|
||||
volid => $volid,
|
||||
defined($size) ? (size => $size) : (),
|
||||
};
|
||||
}
|
||||
|
||||
if (defined($res->{qm}->{bios}) && $res->{qm}->{bios} eq 'ovmf') {
|
||||
$disks->{efidisk0} = 1;
|
||||
push @$warnings, { type => 'efi-state-lost', key => 'bios', value => 'ovmf' };
|
||||
}
|
||||
|
||||
return {
|
||||
type => 'vm',
|
||||
source => $volname,
|
||||
'create-args' => $res->{qm},
|
||||
'disks' => $disks,
|
||||
warnings => $warnings,
|
||||
net => $res->{net},
|
||||
};
|
||||
}
|
||||
|
||||
sub volume_qemu_snapshot_method {
|
||||
my ($class, $storeid, $scfg, $volname) = @_;
|
||||
|
||||
my $format = ($class->parse_volname($volname))[6];
|
||||
return 'storage' if $format ne 'qcow2';
|
||||
|
||||
return $scfg->{'snapshot-as-volume-chain'} ? 'mixed' : 'qemu';
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -38,15 +38,6 @@ if (my $err = $@) {
|
||||
ok('parse win10 no default rasd NS');
|
||||
}
|
||||
|
||||
my $two_controllers =
|
||||
eval { PVE::GuestImport::OVF::parse_ovf("$test_manifests/two-scsi-controllers.ovf") };
|
||||
if (my $err = $@) {
|
||||
fail('parse two scsi controllers');
|
||||
warn("error: $err\n");
|
||||
} else {
|
||||
ok('parse two scsi controllers');
|
||||
}
|
||||
|
||||
my $xee = eval { PVE::GuestImport::OVF::parse_ovf("$test_manifests/XEE.ovf") };
|
||||
if (my $err = $@) {
|
||||
ok("parsing XEE.ovf failed as expected");
|
||||
@@ -104,17 +95,6 @@ is(
|
||||
'single disk vm (no default rasd NS) has the correct size',
|
||||
);
|
||||
|
||||
is_deeply(
|
||||
[map { $_->{disk_address} } $two_controllers->{disks}->@*],
|
||||
['scsi0', 'scsi2', 'scsi1'],
|
||||
'disks with the same address on different controllers get unique addresses',
|
||||
);
|
||||
is(
|
||||
$two_controllers->{disks}->[1]->{backing_file},
|
||||
"$test_manifests/Win10-Liz-disk1.vmdk",
|
||||
'disk on the second controller has the correct backing device',
|
||||
);
|
||||
|
||||
print "testing nics\n";
|
||||
is($win2008->{net}->{net0}->{model}, 'e1000', 'win2008 has correct nic model');
|
||||
is($win10->{net}->{net0}->{model}, 'e1000e', 'win10 has correct nic model');
|
||||
@@ -129,12 +109,6 @@ is($win2008->{qm}->{memory}, '2048', 'win2008 VM memory is correct');
|
||||
is($win2008->{qm}->{cores}, '1', 'win2008 VM cores are correct');
|
||||
is($win2008->{qm}->{ostype}, 'win7', 'win2008 VM ostype is correcty');
|
||||
|
||||
is(
|
||||
$two_controllers->{qm}->{boot},
|
||||
'order=scsi0;scsi2;scsi1',
|
||||
'VM with two scsi controllers boot is correct',
|
||||
);
|
||||
|
||||
is($win10->{qm}->{boot}, 'order=scsi0', 'win10 VM boot is correct');
|
||||
is($win10->{qm}->{name}, 'Win10-Liz', 'win10 VM name is correct');
|
||||
is($win10->{qm}->{memory}, '6144', 'win10 VM memory is correct');
|
||||
|
||||
Reference in new issue
Block a user