public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
* [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping
@ 2021-06-21 13:55 Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH cluster 1/1] add nodes/hardware-map.conf Dominik Csapak
                   ` (21 more replies)
  0 siblings, 22 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

this series aims to add a cluster-wide device mapping for (atm) pci and
usb devices. so that an admin can configure a device to be availble
for migration and configuring for uses that are non-root

built-in are some additional safety checks in contrast to current
passthrough, e.g. if pci addresses shift, with the mapping
we can detect that and prevent a vm to boot with the wrong device
(in most cases, there are some edge cases when one has multiple
of the same device, e.g. the same gpu, that we cannot detect)

all in all the series should be functional, but there are some parts
that are not finished/rough:
* the config format
    for now i used a section config with 'hostname:id' as section
    header, but this is not optimal. after some discussion with several
    colleagues, i'll probably settle for a config file with pure json
    in it, since there is no good way to have the necessary nesting
    level (a mapping per node per id + properties per id) and have
    it reasonably integrate in the section config, while maintaining
    ease of use with a normal editor (ideally we would have anoter
    config format like json with comments/trailing commas, or
    rust-object-notation)

    if there are some better ideas, please tell!

* node api
    here the section config helps us, but we have the usual weirdness
    with all options everytime available. also currently the api is
    limited to the node level, and i have no good solution for
    distributing the current state of the devices yet
    (we can use the pvestatd as usual, but it does so many things
    already; the rewrite really becomes a blocker for many things)

* cluster-wide gui
    depends on the cluster wide api call for listing, which i omitted
    for now for the reasons i gave above

* vm editing gui
    not completely happy with the result, but could not come up
    with something better in the time i had until now

* config file location
    for now it lives in pve-common, though after looking at the
    dependency chain, it would be possible to move it to qemu-server
    does not really matter to me, and if we sometime want to extend
    it to containers it needs to be at least in pve-guest-common

* auto-generated roles:
    i did not find a way to have a 'PVEHardwareUser' role without an
    'PVEHardwareAdmin', but the 'Admin' would have the same privs.
    so i settled on only having an Admin. Not sure if that makes sense..

dependencies:
    manager depends on qemu-server,pve-access-control,pve-common
    qemu-server depends on pve-access-control,pve-common
    pve-common depends on pve-cluster

additioanl info:
pve-common 1/3 and 2/3 are necssary but could be independetly applied,
no breaking change


pve-cluster:

Dominik Csapak (1):
  add nodes/hardware-map.conf

 data/PVE/Cluster.pm | 1 +
 data/src/status.c   | 1 +
 2 files changed, 2 insertions(+)

pve-common:

Dominik Csapak (3):
  SysFSTools: add verbose flag to pci_device_info
  SysFSTools: change 'product' to 'device'
  add PVE/HardwareMap and Plugins

 src/Makefile                     |  4 ++
 src/PVE/HardwareMap.pm           | 54 ++++++++++++++++++++
 src/PVE/HardwareMap/PCIPlugin.pm | 87 ++++++++++++++++++++++++++++++++
 src/PVE/HardwareMap/Plugin.pm    | 82 ++++++++++++++++++++++++++++++
 src/PVE/HardwareMap/USBPlugin.pm | 69 +++++++++++++++++++++++++
 src/PVE/SysFSTools.pm            | 33 +++++++++---
 6 files changed, 323 insertions(+), 6 deletions(-)
 create mode 100644 src/PVE/HardwareMap.pm
 create mode 100644 src/PVE/HardwareMap/PCIPlugin.pm
 create mode 100644 src/PVE/HardwareMap/Plugin.pm
 create mode 100644 src/PVE/HardwareMap/USBPlugin.pm

pve-access-control:

Dominik Csapak (2):
  PVE/AccessControl: add Hardware.* privileges and /hardware/ paths
  PVE/RPCEnvironment: add helper for checking hw permissions

 src/PVE/API2/AccessControl.pm |  3 ++-
 src/PVE/AccessControl.pm      | 13 +++++++++++++
 src/PVE/RPCEnvironment.pm     |  8 ++++++++
 3 files changed, 23 insertions(+), 1 deletion(-)

qemu-server:

Dominik Csapak (7):
  PVE/QemuServer: allow mapped usb devices in config
  PVE/QemuServer: allow mapped pci deviced in config
  PVE/API2/Qemu: add permission checks for mapped usb devices
  PVE/API2/Qemu: add permission checks for mapped pci devices
  PVE/QemuServer: extend 'check_local_resources' for mapped resources
  PVE/API2/Qemu: migrate preconditions: use new check_local_resources
    info
  PVE/QemuMigrate: check for mapped resources on migration

 PVE/API2/Qemu.pm      | 108 ++++++++++++++++++++++++++++++++++++++----
 PVE/QemuMigrate.pm    |  13 ++++-
 PVE/QemuServer.pm     |  38 ++++++++++++++-
 PVE/QemuServer/PCI.pm |  22 ++++++++-
 PVE/QemuServer/USB.pm |  22 ++++++++-
 5 files changed, 188 insertions(+), 15 deletions(-)

pve-manager:

Dominik Csapak (8):
  PVE/API2/Hardware: add Mapping.pm
  ui: form/USBSelector: make it more flexible with nodename
  ui: form: add PCIMapSelector
  ui: form: add USBMapSelector
  ui: node: add HardwareView and relevant edit windows
  ui: qemu/PCIEdit: rework panel to add a mapped configuration
  ui: qemu/USBEdit: add 'mapped' device case
  ui: window/Migrate: allow mapped devices

 PVE/API2/Hardware.pm                |   6 +
 PVE/API2/Hardware/Makefile          |   1 +
 PVE/API2/Hardware/Mapping.pm        | 292 +++++++++++++
 www/manager6/Makefile               |   3 +
 www/manager6/form/PCIMapSelector.js |  95 +++++
 www/manager6/form/USBMapSelector.js |  73 ++++
 www/manager6/form/USBSelector.js    |  32 +-
 www/manager6/node/Config.js         |   8 +
 www/manager6/node/HardwareView.js   | 641 ++++++++++++++++++++++++++++
 www/manager6/qemu/PCIEdit.js        | 231 ++++++----
 www/manager6/qemu/USBEdit.js        |  34 +-
 www/manager6/window/Migrate.js      |  37 +-
 12 files changed, 1361 insertions(+), 92 deletions(-)
 create mode 100644 PVE/API2/Hardware/Mapping.pm
 create mode 100644 www/manager6/form/PCIMapSelector.js
 create mode 100644 www/manager6/form/USBMapSelector.js
 create mode 100644 www/manager6/node/HardwareView.js

-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH cluster 1/1] add nodes/hardware-map.conf
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH common 1/3] SysFSTools: add verbose flag to pci_device_info Dominik Csapak
                   ` (20 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

to PVE/Cluster.pm
and status.c

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 data/PVE/Cluster.pm | 1 +
 data/src/status.c   | 1 +
 2 files changed, 2 insertions(+)

diff --git a/data/PVE/Cluster.pm b/data/PVE/Cluster.pm
index 4d09c60..863b90f 100644
--- a/data/PVE/Cluster.pm
+++ b/data/PVE/Cluster.pm
@@ -75,6 +75,7 @@ my $observed = {
     'sdn/dns.cfg' => 1,
     'sdn/.running-config' => 1,
     'virtual-guest/cpu-models.conf' => 1,
+    'nodes/hardware-map.conf' => 1,
 };
 
 sub prepare_observed_file_basedirs {
diff --git a/data/src/status.c b/data/src/status.c
index ff0b1e9..a8481ec 100644
--- a/data/src/status.c
+++ b/data/src/status.c
@@ -105,6 +105,7 @@ static memdb_change_t memdb_change_array[] = {
 	{ .path = "sdn/dns.cfg" },
 	{ .path = "sdn/.running-config" },
 	{ .path = "virtual-guest/cpu-models.conf" },
+	{ .path = "nodes/hardware-map.conf" },
 };
 
 static GMutex mutex;
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH common 1/3] SysFSTools: add verbose flag to pci_device_info
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH cluster 1/1] add nodes/hardware-map.conf Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 15:31   ` [pve-devel] applied: " Thomas Lamprecht
  2021-06-21 13:55 ` [pve-devel] [PATCH common 2/3] SysFSTools: change 'product' to 'device' Dominik Csapak
                   ` (19 subsequent siblings)
  21 siblings, 1 reply; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

to also get the subsystem_vendor and device, as well as the
iommu group and mediated device support

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/PVE/SysFSTools.pm | 29 +++++++++++++++++++++++++----
 1 file changed, 25 insertions(+), 4 deletions(-)

diff --git a/src/PVE/SysFSTools.pm b/src/PVE/SysFSTools.pm
index a8d9a7f..7875b26 100644
--- a/src/PVE/SysFSTools.pm
+++ b/src/PVE/SysFSTools.pm
@@ -197,20 +197,22 @@ sub file_write {
 }
 
 sub pci_device_info {
-    my ($name) = @_;
+    my ($name, $verbose) = @_;
 
     my $res;
 
     return undef if $name !~ m/^${pciregex}$/;
     my ($domain, $bus, $slot, $func) = ($1, $2, $3, $4);
 
-    my $irq = file_read_firstline("$pcisysfs/devices/$name/irq");
+    my $devdir = "$pcisysfs/devices/$name";
+
+    my $irq = file_read_firstline("$devdir/irq");
     return undef if !defined($irq) || $irq !~ m/^\d+$/;
 
-    my $vendor = file_read_firstline("$pcisysfs/devices/$name/vendor");
+    my $vendor = file_read_firstline("$devdir/vendor");
     return undef if !defined($vendor) || $vendor !~ s/^0x//;
 
-    my $product = file_read_firstline("$pcisysfs/devices/$name/device");
+    my $product = file_read_firstline("$devdir/device");
     return undef if !defined($product) || $product !~ s/^0x//;
 
     $res = {
@@ -225,6 +227,25 @@ sub pci_device_info {
 	has_fl_reset => -f "$pcisysfs/devices/$name/reset" || 0,
     };
 
+    if ($verbose) {
+	my $sub_vendor = file_read_firstline("$devdir/subsystem_vendor");
+	$sub_vendor =~ s/^0x// if defined($sub_vendor);
+	my $sub_device = file_read_firstline("$devdir/subsystem_device");
+	$sub_device =~ s/^0x// if defined($sub_device);
+
+	$res->{subsystem_vendor} = $sub_vendor if defined($sub_vendor);
+	$res->{subsystem_device} = $sub_device if defined($sub_device);
+
+	if (-e "$devdir/iommu_group") {
+	    my ($iommugroup) = (readlink("$devdir/iommu_group") =~ m/\/(\d+)$/);
+	    $res->{iommugroup} = int($iommugroup);
+	}
+
+	if (-d "$devdir/mdev_supported_types") {
+	    $res->{mdev} = 1;
+	}
+    }
+
     return $res;
 }
 
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH common 2/3] SysFSTools: change 'product' to 'device'
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH cluster 1/1] add nodes/hardware-map.conf Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH common 1/3] SysFSTools: add verbose flag to pci_device_info Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 15:31   ` [pve-devel] applied: " Thomas Lamprecht
  2021-06-21 13:55 ` [pve-devel] [PATCH common 3/3] add PVE/HardwareMap and Plugins Dominik Csapak
                   ` (18 subsequent siblings)
  21 siblings, 1 reply; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

so it is more consistend with the source (it comes from the file
'device') as well as the subsytem_device field

the only place we use that field is in the same file in pci_dev_bind_to_vfio,
which we also change here, so that should not be a breaking change
(in qemu-server we only really use the existance and the has_fl_reset
flag)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/PVE/SysFSTools.pm | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/PVE/SysFSTools.pm b/src/PVE/SysFSTools.pm
index 7875b26..e595128 100644
--- a/src/PVE/SysFSTools.pm
+++ b/src/PVE/SysFSTools.pm
@@ -218,7 +218,7 @@ sub pci_device_info {
     $res = {
 	name => $name,
 	vendor => $vendor,
-	product => $product,
+	device => $product,
 	domain => $domain,
 	bus => $bus,
 	slot => $slot,
@@ -274,7 +274,7 @@ sub pci_dev_bind_to_vfio {
     my $testdir = "$vfio_basedir/$name";
     return 1 if -d $testdir;
 
-    my $data = "$dev->{vendor} $dev->{product}";
+    my $data = "$dev->{vendor} $dev->{device}";
     return undef if !file_write("$vfio_basedir/new_id", $data);
 
     my $fn = "$pcisysfs/devices/$name/driver/unbind";
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH common 3/3] add PVE/HardwareMap and Plugins
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (2 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH common 2/3] SysFSTools: change 'product' to 'device' Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH access-control 1/2] PVE/AccessControl: add Hardware.* privileges and /hardware/ paths Dominik Csapak
                   ` (17 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

adds the Top level package PVE::HardwareMap that
registers the Plugins for the config, as well
as provides some convenience methods
(find_device_on_current_node, lock/write/get config)

The Plugins themselves are usual SectionConfigs plugins
with (for now) two types: usb, pci

each type gets an 'assert_device_valid' method that
checks the local node for validity of the device

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/Makefile                     |  4 ++
 src/PVE/HardwareMap.pm           | 54 ++++++++++++++++++++
 src/PVE/HardwareMap/PCIPlugin.pm | 87 ++++++++++++++++++++++++++++++++
 src/PVE/HardwareMap/Plugin.pm    | 82 ++++++++++++++++++++++++++++++
 src/PVE/HardwareMap/USBPlugin.pm | 69 +++++++++++++++++++++++++
 5 files changed, 296 insertions(+)
 create mode 100644 src/PVE/HardwareMap.pm
 create mode 100644 src/PVE/HardwareMap/PCIPlugin.pm
 create mode 100644 src/PVE/HardwareMap/Plugin.pm
 create mode 100644 src/PVE/HardwareMap/USBPlugin.pm

diff --git a/src/Makefile b/src/Makefile
index 13de6c6..7cd20d5 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -17,6 +17,10 @@ LIB_SOURCES = \
 	Daemon.pm \
 	Exception.pm \
 	Format.pm \
+	HardwareMap.pm \
+	HardwareMap/Plugin.pm \
+	HardwareMap/PCIPlugin.pm \
+	HardwareMap/USBPlugin.pm \
 	INotify.pm \
 	JSONSchema.pm \
 	LDAP.pm \
diff --git a/src/PVE/HardwareMap.pm b/src/PVE/HardwareMap.pm
new file mode 100644
index 0000000..a0f4a9f
--- /dev/null
+++ b/src/PVE/HardwareMap.pm
@@ -0,0 +1,54 @@
+package PVE::HardwareMap;
+
+use strict;
+use warnings;
+
+use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::HardwareMap::Plugin;
+use PVE::HardwareMap::PCIPlugin;
+use PVE::HardwareMap::USBPlugin;
+use PVE::INotify;
+
+use base qw(Exporter);
+
+our @EXPORT_OK = qw(find_device_on_current_node);
+
+PVE::HardwareMap::PCIPlugin->register();
+PVE::HardwareMap::USBPlugin->register();
+
+PVE::HardwareMap::Plugin->init();
+
+sub find_device_on_current_node {
+    my ($type, $id) = @_;
+
+    my $data = cfs_read_file($PVE::HardwareMap::Plugin::FILENAME);
+
+    my $res = {};
+
+    my $node = PVE::INotify::nodename();
+    my $sectionid = "$node:$id";
+
+    return $data->{ids}->{$sectionid};
+}
+
+sub config {
+    return cfs_read_file($PVE::HardwareMap::Plugin::FILENAME);
+}
+
+sub lock_config {
+    my ($code, $errmsg) = @_;
+
+    cfs_lock_file($PVE::HardwareMap::Plugin::FILENAME, undef, $code);
+    if (my $err = $@) {
+	$errmsg ? die "$errmsg: $err" : die $err;
+    }
+}
+
+sub write_config {
+    my ($cfg) = @_;
+
+    cfs_write_file($PVE::HardwareMap::Plugin::FILENAME, $cfg);
+}
+
+
+1;
diff --git a/src/PVE/HardwareMap/PCIPlugin.pm b/src/PVE/HardwareMap/PCIPlugin.pm
new file mode 100644
index 0000000..eb6d090
--- /dev/null
+++ b/src/PVE/HardwareMap/PCIPlugin.pm
@@ -0,0 +1,87 @@
+package PVE::HardwareMap::PCIPlugin;
+
+use strict;
+use warnings;
+
+use PVE::HardwareMap::Plugin;
+use PVE::SysFSTools;
+
+use base qw(PVE::HardwareMap::Plugin);
+
+sub type {
+    return 'pci';
+}
+
+sub properties {
+    return {
+	pcipath => {
+	    description => "The path to the device. If the function is omitted, the whole device is mapped. In that case use the attrubes of the first device.",
+	    type => 'string',
+	    pattern => qr/^[0-9A-Fa-f]{4}:[0-9A-Fa-f]{2}:[0-9A-Fa-f]{2}(:?.[0-9A-Fa-f])?$/,
+	},
+	mdev => {
+	    description => "The Device supports mediated devices.",
+	    type => 'boolean',
+	},
+	iommugroup => {
+	    type => 'integer',
+	    description => "The IOMMU group in which the device is in.",
+	}
+    };
+}
+
+sub options {
+    return {
+	node => { fixed => 1 },
+	name => { fixed => 1 },
+	pcipath => { },
+	vendor => { },
+	device => { },
+	iommugroup => { },
+	subsystem_vendor => { optional => 1 },
+	subsystem_device => { optional => 1 },
+	mdev => { optional => 1 },
+    };
+}
+
+sub assert_device_valid {
+    my ($class, $cfg) = @_;
+
+    my $path = $cfg->{pcipath};
+
+    if ($path !~ m/\.[a-f0-9]/i) {
+	# whole device, add .0 (must exist)
+	$path = "$path.0";
+    }
+
+    my $info = PVE::SysFSTools::pci_device_info($path, 1);
+    die "pci device '$path' not found\n" if !defined($info);
+
+    my $props = {
+	vendor => $cfg->{vendor},
+	device => $cfg->{device},
+	subsystem_vendor => $cfg->{subsystem_vendor},
+	subsystem_device => $cfg->{subsystem_device},
+	iommugroup => $cfg->{iommugroup},
+	mdev => $cfg->{mdev},
+    };
+
+    for my $prop (keys %$props) {
+	next if !defined($info->{$prop});
+	die "no '$prop' for device '$path'\n"
+	    if defined($info->{$prop}) && !$props->{$prop};
+
+
+	my $correct_prop = $info->{$prop};
+	$correct_prop =~ s/^0x//;
+	my $configured_prop = $props->{$prop};
+	$configured_prop =~ s/^0x//;
+
+	die "'$prop' does not match for '$cfg->{name}' ($correct_prop != $configured_prop)\n"
+	    if $correct_prop ne $configured_prop;
+    }
+
+    return 1;
+}
+
+1;
diff --git a/src/PVE/HardwareMap/Plugin.pm b/src/PVE/HardwareMap/Plugin.pm
new file mode 100644
index 0000000..2d33785
--- /dev/null
+++ b/src/PVE/HardwareMap/Plugin.pm
@@ -0,0 +1,82 @@
+package PVE::HardwareMap::Plugin;
+
+use strict;
+use warnings;
+
+use PVE::Cluster qw(cfs_register_file cfs_read_file);
+use PVE::JSONSchema qw(get_standard_option);
+
+use base qw(PVE::SectionConfig);
+
+our $FILENAME = "nodes/hardware-map.conf";
+cfs_register_file($FILENAME,
+		  sub { PVE::HardwareMap::Plugin->parse_config(@_); },
+		  sub { PVE::HardwareMap::Plugin->write_config(@_); });
+
+my $defaultData = {
+    propertyList => {
+	type => { description => "Hardware Type", },
+	name => {
+	    description => "The custom name for the device",
+	    type => 'string',
+	    format => 'pve-configid',
+	},
+	node => get_standard_option('pve-node'),
+	vendor => {
+	    description => "The vendor ID",
+	    type => 'string',
+	    pattern => qr/^(:?0x)?[0-9A-Fa-f]{4}$/,
+	},
+	device => {
+	    description => "The device ID",
+	    type => 'string',
+	    pattern => qr/^(:?0x)?[0-9A-Fa-f]{4}$/,
+	},
+	subsystem_vendor => {
+	    description => "The subsystem vendor ID",
+	    type => 'string',
+	    pattern => qr/^(:?0x)?[0-9A-Fa-f]{4}$/,
+	    optional => 1,
+	},
+	subsystem_device => {
+	    description => "The subsystem device ID",
+	    type => 'string',
+	    pattern => qr/^(:?0x)?[0-9A-Fa-f]{4}$/,
+	    optional => 1,
+	},
+    },
+};
+
+sub private {
+    return $defaultData;
+}
+
+sub parse_section_header {
+    my ($class, $line) = @_;
+
+    if ($line =~ m/^(\S+):\s*(\S+):(\S+)\s*$/) {
+	my ($type, $node, $name) = ($1, $2, $3);
+	# TODO verify properties
+	my $errmsg = undef;
+	my $config = {
+	    node => $node,
+	    name => $name,
+	};
+	return ($type, "$node:$name", $errmsg, $config);
+    }
+    return undef;
+}
+
+sub format_section_header {
+    my ($class, $type, $sectionId, $scfg, $done_hash) = @_;
+    $done_hash->{name} = 1;
+    $done_hash->{node} = 1;
+    return $class->SUPER::format_section_header($type, $sectionId, $scfg, $done_hash);
+}
+
+sub assert_device_valid {
+    my ($cfg) = @_;
+    die "implement me";
+}
+
+1;
diff --git a/src/PVE/HardwareMap/USBPlugin.pm b/src/PVE/HardwareMap/USBPlugin.pm
new file mode 100644
index 0000000..a1fff77
--- /dev/null
+++ b/src/PVE/HardwareMap/USBPlugin.pm
@@ -0,0 +1,69 @@
+package PVE::HardwareMap::USBPlugin;
+
+use strict;
+use warnings;
+
+use PVE::HardwareMap::Plugin;
+
+use base qw(PVE::HardwareMap::Plugin);
+
+sub type {
+    return 'usb';
+}
+
+sub properties {
+    return {
+	usbpath => {
+	    description => "The path to the usb device.",
+	    type => 'string',
+	    pattern => qr/^(\d+)\-(\d+(\.\d+)*)$/,
+	},
+    };
+}
+
+sub options {
+    return {
+	node => { fixed => 1 },
+	name => { fixed => 1 },
+	vendor => { },
+	device => { },
+	usbpath => { optional => 1 },
+    };
+}
+
+sub assert_device_valid {
+    my ($class, $cfg) = @_;
+
+    my $name = $cfg->{name};
+    my $vendor = $cfg->{vendor};
+    my $device = $cfg->{device};
+
+    my $usb_list = PVE::SysFSTools::scan_usb();
+
+    my $info;
+    if (my $path = $cfg->{usbpath}) {
+	for my $dev (@$usb_list) {
+	    next if !$dev->{usbpath} || !$dev->{busnum};
+	    my $usbpath = "$dev->{busnum}-$dev->{usbpath}";
+	    next if $usbpath ne $path;
+	    $info = $dev;
+	}
+	die "usb device '$path' not found\n" if !defined($info);
+
+	die "'vendor' does not match for '$name'\n"
+	    if $info->{vendid} ne $cfg->{vendor};
+	die "'device' does not match for '$name'\n"
+	    if $info->{prodid} ne $cfg->{device};
+    } else {
+	for my $dev (@$usb_list) {
+	    next if $dev->{vendid} ne $vendor;
+	    next if $dev->{prodid} ne $device;
+	    $info = $dev;
+	}
+	die "usb device '$vendor:$device' not found\n" if !defined($info);
+    }
+
+    return 1;
+}
+
+1;
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH access-control 1/2] PVE/AccessControl: add Hardware.* privileges and /hardware/ paths
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (3 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH common 3/3] add PVE/HardwareMap and Plugins Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH access-control 2/2] PVE/RPCEnvironment: add helper for checking hw permissions Dominik Csapak
                   ` (16 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

so that we can assign privileges on hardware level

this will generate a new role (PVEHardwareAdmin)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/PVE/API2/AccessControl.pm |  3 ++-
 src/PVE/AccessControl.pm      | 13 +++++++++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/src/PVE/API2/AccessControl.pm b/src/PVE/API2/AccessControl.pm
index a77694b..b28ce2c 100644
--- a/src/PVE/API2/AccessControl.pm
+++ b/src/PVE/API2/AccessControl.pm
@@ -178,10 +178,11 @@ my $compute_api_permission = sub {
 	nodes => qr/Sys\.|Permissions\.Modify/,
 	sdn => qr/SDN\.|Permissions\.Modify/,
 	dc => qr/Sys\.Audit|SDN\./,
+	hardware => qr/HW\./,
     };
     map { $res->{$_} = {} } keys %$priv_re_map;
 
-    my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage', '/sdn'];
+    my $required_paths = ['/', '/nodes', '/access/groups', '/vms', '/storage', '/sdn', '/hardware'];
 
     my $checked_paths = {};
     foreach my $path (@$required_paths, keys %{$usercfg->{acl}}) {
diff --git a/src/PVE/AccessControl.pm b/src/PVE/AccessControl.pm
index 2569a35..334b6bd 100644
--- a/src/PVE/AccessControl.pm
+++ b/src/PVE/AccessControl.pm
@@ -822,6 +822,17 @@ my $privgroups = {
 	    'Pool.Audit',
 	],
     },
+    Hardware => {
+	root => [
+	    'Hardware.Configure', # create/edit mappings
+	],
+	admin => [
+	    'Hardware.Use',
+	],
+	audit => [
+	    'Hardware.Audit',
+	],
+    },
 };
 
 my $valid_privs = {};
@@ -948,6 +959,8 @@ sub check_path {
 	|/storage/[[:alnum:]\.\-\_]+
 	|/vms
 	|/vms/[1-9][0-9]{2,}
+	|/hardware
+	|/hardware/[[:alnum:]\.\-\_]+
     )$!xs;
 }
 
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH access-control 2/2] PVE/RPCEnvironment: add helper for checking hw permissions
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (4 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH access-control 1/2] PVE/AccessControl: add Hardware.* privileges and /hardware/ paths Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 1/7] PVE/QemuServer: allow mapped usb devices in config Dominik Csapak
                   ` (15 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

like check_vm_perm, etc.

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 src/PVE/RPCEnvironment.pm | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/PVE/RPCEnvironment.pm b/src/PVE/RPCEnvironment.pm
index 2e5a33b..1fac627 100644
--- a/src/PVE/RPCEnvironment.pm
+++ b/src/PVE/RPCEnvironment.pm
@@ -250,6 +250,14 @@ sub check_vm_perm {
     return $self->check_full($user, "/vms/$vmid", $privs, $any, $noerr);
 };
 
+sub check_hw_perm {
+    my ($self, $user, $id, $privs, $any, $noerr) = @_;
+
+    my $cfg = $self->{user_cfg};
+
+    return $self->check_full($user, "/hardware/$id", $privs, $any, $noerr);
+}
+
 sub is_group_member {
     my ($self, $group, $user) = @_;
 
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH qemu-server 1/7] PVE/QemuServer: allow mapped usb devices in config
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (5 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH access-control 2/2] PVE/RPCEnvironment: add helper for checking hw permissions Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 2/7] PVE/QemuServer: allow mapped pci deviced " Dominik Csapak
                   ` (14 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 PVE/QemuServer.pm     |  2 ++
 PVE/QemuServer/USB.pm | 22 +++++++++++++++++++++-
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index fe31741..5589bc7 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -1016,6 +1016,8 @@ The Host USB device or port or the value 'spice'. HOSTUSBDEVICE syntax is:
 
 You can use the 'lsusb -t' command to list existing usb devices.
 
+Alternatively, you can used an ID of a mapped usb device.
+
 NOTE: This option allows direct access to host hardware. So it is no longer possible to migrate such
 machines - use with special care.
 
diff --git a/PVE/QemuServer/USB.pm b/PVE/QemuServer/USB.pm
index 3c8da2c..ea3193f 100644
--- a/PVE/QemuServer/USB.pm
+++ b/PVE/QemuServer/USB.pm
@@ -4,6 +4,8 @@ use strict;
 use warnings;
 use PVE::QemuServer::PCI qw(print_pci_addr);
 use PVE::JSONSchema;
+use PVE::HardwareMap;
+use PVE::HardwareMap::PCIPlugin;
 use base 'Exporter';
 
 our @EXPORT_OK = qw(
@@ -27,7 +29,25 @@ sub parse_usb_device {
     } elsif ($value =~ m/^spice$/i) {
 	$res->{spice} = 1;
     } else {
-	return;
+	# we have no ordinary usb device, must be a mapping
+	my $device = PVE::HardwareMap::find_device_on_current_node('usb', $value);
+	return undef if !defined($device);
+	eval {
+	    PVE::HardwareMap::USBPlugin->assert_device_valid($device);
+	};
+	if (my $err = $@) {
+	    warn "USB Mapping invalid (hardware probably changed): $err\n";
+	    return;
+	}
+
+	if ($device->{usbpath}) {
+	    $res = parse_usb_device($device->{usbpath});
+	} else {
+	    $res->{vendorid} = $device->{vendor};
+	    $res->{productid} = $device->{device};
+	}
+
+	$res->{mapped} = 1;
     }
 
     return $res;
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH qemu-server 2/7] PVE/QemuServer: allow mapped pci deviced in config
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (6 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 1/7] PVE/QemuServer: allow mapped usb devices in config Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 3/7] PVE/API2/Qemu: add permission checks for mapped usb devices Dominik Csapak
                   ` (13 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

and get the correct pci device during parsing

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 PVE/QemuServer/PCI.pm | 22 ++++++++++++++++++++--
 1 file changed, 20 insertions(+), 2 deletions(-)

diff --git a/PVE/QemuServer/PCI.pm b/PVE/QemuServer/PCI.pm
index 2ee142f..26bad63 100644
--- a/PVE/QemuServer/PCI.pm
+++ b/PVE/QemuServer/PCI.pm
@@ -4,6 +4,8 @@ use warnings;
 use strict;
 
 use PVE::JSONSchema;
+use PVE::HardwareMap;
+use PVE::HardwareMap::PCIPlugin;
 use PVE::SysFSTools;
 
 use base 'Exporter';
@@ -18,12 +20,13 @@ parse_hostpci
 our $MAX_HOSTPCI_DEVICES = 16;
 
 my $PCIRE = qr/([a-f0-9]{4}:)?[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?/;
+
 my $hostpci_fmt = {
     host => {
 	default_key => 1,
 	type => 'string',
-	pattern => qr/$PCIRE(;$PCIRE)*/,
-	format_description => 'HOSTPCIID[;HOSTPCIID2...]',
+	pattern => qr/(:?$PCIRE(;$PCIRE)*)|(:?$PVE::JSONSchema::CONFIGID_RE)/,
+	format_description => 'HOSTPCIID[;HOSTPCIID2...] or configured mapping id',
 	description => <<EODESCR,
 Host PCI device pass through. The PCI ID of a host's PCI device or a list
 of PCI virtual functions of the host. HOSTPCIID syntax is:
@@ -31,6 +34,8 @@ of PCI virtual functions of the host. HOSTPCIID syntax is:
 'bus:dev.func' (hexadecimal numbers)
 
 You can us the 'lspci' command to list existing PCI devices.
+
+Alternatively use the ID of a mapped pci device.
 EODESCR
     },
     rombar => {
@@ -349,6 +354,19 @@ sub parse_hostpci {
 
     my $res = PVE::JSONSchema::parse_property_string($hostpci_fmt, $value);
 
+    if ($res->{host} !~ m/:/) {
+	# we have no ordinary pci id, must be a mapping
+	my $device = PVE::HardwareMap::find_device_on_current_node('pci', $res->{host});
+	die "PCI device mapping not found for '$res->{host}'\n" if !defined($device);
+	eval {
+	    PVE::HardwareMap::PCIPlugin->assert_device_valid($device);
+	};
+	if (my $err = $@) {
+	    die "PCI device mapping invalid (hardware probably changed): $err\n";
+	}
+	$res->{host} = $device->{pcipath};
+    }
+
     my @idlist = split(/;/, $res->{host});
     delete $res->{host};
     foreach my $id (@idlist) {
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH qemu-server 3/7] PVE/API2/Qemu: add permission checks for mapped usb devices
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (7 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 2/7] PVE/QemuServer: allow mapped pci deviced " Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 4/7] PVE/API2/Qemu: add permission checks for mapped pci devices Dominik Csapak
                   ` (12 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 PVE/API2/Qemu.pm | 39 ++++++++++++++++++++++++++++++++++++---
 1 file changed, 36 insertions(+), 3 deletions(-)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index eeb07d1..98d11bc 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -24,6 +24,7 @@ use PVE::QemuServer;
 use PVE::QemuServer::Drive;
 use PVE::QemuServer::CPUConfig;
 use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::USB qw(parse_usb_device);
 use PVE::QemuMigrate;
 use PVE::RPCEnvironment;
 use PVE::AccessControl;
@@ -354,8 +355,12 @@ my $check_vm_create_usb_perm = sub {
 
     foreach my $opt (keys %{$param}) {
 	next if $opt !~ m/^usb\d+$/;
+	my $device = parse_usb_device($param->{$opt});
 
-	if ($param->{$opt} =~ m/spice/) {
+	if ($device->{spice}) {
+	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
+	} elsif ($device->{mapped}) {
+	    $rpcenv->check_hw_perm($authuser, $device->{host}, ['Hardware.Use']);
 	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
 	} else {
 	    die "only root can set '$opt' config for real devices\n";
@@ -1319,7 +1324,12 @@ my $update_vm_api  = sub {
 		    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
 		    PVE::QemuConfig->write_config($vmid, $conf);
 		} elsif ($opt =~ m/^usb\d+$/) {
-		    if ($val =~ m/spice/) {
+		    my $device = PVE::JSONSchema::parse_property_string('pve-qm-usb-device', $val);
+		    my $host = parse_usb_device($device->{host});
+		    if ($host->{spice}) {
+			$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
+		    } elsif ($host->{mapped}) {
+			$rpcenv->check_hw_perm($authuser, $device->{host}, ['Hardware.Use']);
 			$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
 		    } elsif ($authuser ne 'root@pam') {
 			die "only root can delete '$opt' config for real devices\n";
@@ -1359,7 +1369,30 @@ my $update_vm_api  = sub {
 		    }
 		    $conf->{pending}->{$opt} = $param->{$opt};
 		} elsif ($opt =~ m/^usb\d+/) {
-		    if ((!defined($conf->{$opt}) || $conf->{$opt} =~ m/spice/) && $param->{$opt} =~ m/spice/) {
+		    my $olddevice;
+		    my $oldhost;
+		    if (defined($conf->{$opt})) {
+			$olddevice = PVE::JSONSchema::parse_property_string('pve-qm-usb-device', $conf->{$opt});
+			$oldhost = parse_usb_device($olddevice->{host});
+		    }
+		    if (defined($oldhost)) {
+			if ($oldhost->{spice}) {
+			    $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
+			} elsif ($oldhost->{mapped}) {
+			    $rpcenv->check_hw_perm($authuser, $olddevice->{host}, ['Hardware.Use']);
+			    $rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
+			} elsif ($authuser ne 'root@pam') {
+			    die "only root can modify '$opt' config for real devices\n";
+			}
+		    }
+
+		    my $newdevice = PVE::JSONSchema::parse_property_string('pve-qm-usb-device', $param->{$opt});
+		    my $newhost = parse_usb_device($newdevice->{host});
+
+		    if ($newhost->{spice}) {
+			$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
+		    } elsif ($newhost->{mapped}) {
+			$rpcenv->check_hw_perm($authuser, $newdevice->{host}, ['Hardware.Use']);
 			$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
 		    } elsif ($authuser ne 'root@pam') {
 			die "only root can modify '$opt' config for real devices\n";
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH qemu-server 4/7] PVE/API2/Qemu: add permission checks for mapped pci devices
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (8 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 3/7] PVE/API2/Qemu: add permission checks for mapped usb devices Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 5/7] PVE/QemuServer: extend 'check_local_resources' for mapped resources Dominik Csapak
                   ` (11 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 PVE/API2/Qemu.pm | 54 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 52 insertions(+), 2 deletions(-)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index 98d11bc..36b8c31 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -24,6 +24,7 @@ use PVE::QemuServer;
 use PVE::QemuServer::Drive;
 use PVE::QemuServer::CPUConfig;
 use PVE::QemuServer::Monitor qw(mon_cmd);
+use PVE::QemuServer::PCI;
 use PVE::QemuServer::USB qw(parse_usb_device);
 use PVE::QemuMigrate;
 use PVE::RPCEnvironment;
@@ -370,6 +371,26 @@ my $check_vm_create_usb_perm = sub {
     return 1;
 };
 
+my $check_vm_create_hostpci_perm = sub {
+    my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
+
+    return 1 if $authuser eq 'root@pam';
+
+    foreach my $opt (keys %{$param}) {
+	next if $opt !~ m/^hostpci\d+$/;
+
+	my $device = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $param->{$opt});
+	if ($device->{host} !~ m/:/) {
+	    $rpcenv->check_hw_perm($authuser, $device->{host}, ['Hardware.Use']);
+	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
+	} else {
+	    die "only root can set '$opt' config for non-mapped devices\n";
+	}
+    }
+
+    return 1;
+};
+
 my $check_vm_modify_config_perm = sub {
     my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
 
@@ -380,7 +401,7 @@ my $check_vm_modify_config_perm = sub {
 	# else, as there the permission can be value dependend
 	next if PVE::QemuServer::is_valid_drivename($opt);
 	next if $opt eq 'cdrom';
-	next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
+	next if $opt =~ m/^(?:unused|serial|usb|hostpci)\d+$/;
 
 
 	if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
@@ -408,7 +429,7 @@ my $check_vm_modify_config_perm = sub {
 	    # also needs privileges on the storage, that will be checked later
 	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
 	} else {
-	    # catches hostpci\d+, args, lock, etc.
+	    # catches args, lock, etc.
 	    # new options will be checked here
 	    die "only root can set '$opt' config\n";
 	}
@@ -617,6 +638,7 @@ __PACKAGE__->register_method({
 
 	    &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
 	    &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
+	    &$check_vm_create_hostpci_perm($rpcenv, $authuser, $vmid, $pool, $param);
 
 	    &$check_cpu_model_access($rpcenv, $authuser, $param);
 
@@ -1336,6 +1358,16 @@ my $update_vm_api  = sub {
 		    }
 		    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
 		    PVE::QemuConfig->write_config($vmid, $conf);
+		} elsif ($opt =~ m/^hostpci\d+$/) {
+		    my $olddevice = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $val);
+		    if ($olddevice->{host} !~ m/:/) {
+			$rpcenv->check_hw_perm($authuser, $olddevice->{host}, ['Hardware.Use']);
+			$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
+		    } elsif ($authuser ne 'root@pam') {
+			die "only root can set '$opt' config for non-mapped devices\n";
+		    }
+		    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
+		    PVE::QemuConfig->write_config($vmid, $conf);
 		} else {
 		    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
 		    PVE::QemuConfig->write_config($vmid, $conf);
@@ -1397,6 +1429,24 @@ my $update_vm_api  = sub {
 		    } elsif ($authuser ne 'root@pam') {
 			die "only root can modify '$opt' config for real devices\n";
 		    }
+
+		    $conf->{pending}->{$opt} = $param->{$opt};
+		} elsif ($opt =~ m/^hostpci\d+$/) {
+		    my $olddevice;
+		    if (defined($conf->{$opt})) {
+			$olddevice = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $conf->{$opt});
+		    }
+		    my $newdevice = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $param->{$opt});
+		    if ((!defined($olddevice) || $olddevice->{host} !~ m/:/) && $newdevice->{host} !~ m/:/) {
+			if (defined($olddevice)) {
+			    $rpcenv->check_hw_perm($authuser, $olddevice->{host}, ['Hardware.Use']);
+			}
+			$rpcenv->check_hw_perm($authuser, $newdevice->{host}, ['Hardware.Use']);
+			$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
+		    } elsif ($authuser ne 'root@pam') {
+			die "only root can set '$opt' config for non-mapped devices\n";
+		    }
+		    PVE::QemuServer::PCI::parse_hostpci($param->{$opt});
 		    $conf->{pending}->{$opt} = $param->{$opt};
 		} else {
 		    $conf->{pending}->{$opt} = $param->{$opt};
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH qemu-server 5/7] PVE/QemuServer: extend 'check_local_resources' for mapped resources
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (9 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 4/7] PVE/API2/Qemu: add permission checks for mapped pci devices Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 6/7] PVE/API2/Qemu: migrate preconditions: use new check_local_resources info Dominik Csapak
                   ` (10 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

by adding them to their own list, saving the nodes where
they are not allowed, and return those on 'wantarray'

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 PVE/QemuServer.pm | 36 ++++++++++++++++++++++++++++++++++--
 1 file changed, 34 insertions(+), 2 deletions(-)

diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index 5589bc7..5d84ed7 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -2428,6 +2428,22 @@ sub check_local_resources {
     my ($conf, $noerr) = @_;
 
     my @loc_res = ();
+    my $mapped_res = [];
+
+    my $nodelist = PVE::Cluster::get_nodelist();
+    my $hw_map = PVE::HardwareMap::config();
+
+    my $not_allowed_nodes = { map { $_ => [] } @$nodelist };
+
+    my $add_not_allowed_nodes = sub {
+	my ($key, $id) = @_;
+	for my $node (@$nodelist) {
+	    my $hwid = "$node:" . ($id // "");
+	    if (!defined($id) || !defined($hw_map->{ids}->{$hwid})) {
+		push @{$not_allowed_nodes->{$node}}, $key;
+	    }
+	}
+    };
 
     push @loc_res, "hostusb" if $conf->{hostusb}; # old syntax
     push @loc_res, "hostpci" if $conf->{hostpci}; # old syntax
@@ -2435,7 +2451,23 @@ sub check_local_resources {
     push @loc_res, "ivshmem" if $conf->{ivshmem};
 
     foreach my $k (keys %$conf) {
-	next if $k =~ m/^usb/ && ($conf->{$k} =~ m/^spice(?![^,])/);
+	if ($k =~ m/^usb/) {
+	    my $entry = parse_property_string($usb_fmt, $conf->{$k});
+	    next if $entry->{spice};
+	    if (my $id = $entry->{mapped}) {
+		$add_not_allowed_nodes->($k, $id);
+		push @$mapped_res, $k;
+		next;
+	    }
+	}
+	if ($k =~ m/^hostpci/) {
+	    my $entry = parse_property_string('pve-qm-hostpci', $conf->{$k});
+	    if ($entry->{host} !~ m/:/) {
+		$add_not_allowed_nodes->($k, $entry->{host});
+		push @$mapped_res, $k;
+		next;
+	    }
+	}
 	# sockets are safe: they will recreated be on the target side post-migrate
 	next if $k =~ m/^serial/ && ($conf->{$k} eq 'socket');
 	push @loc_res, $k if $k =~ m/^(usb|hostpci|serial|parallel)\d+$/;
@@ -2443,7 +2475,7 @@ sub check_local_resources {
 
     die "VM uses local resources\n" if scalar @loc_res && !$noerr;
 
-    return \@loc_res;
+    return wantarray ? (\@loc_res, $mapped_res, $not_allowed_nodes) : \@loc_res;
 }
 
 # check if used storages are available on all nodes (use by migrate)
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH qemu-server 6/7] PVE/API2/Qemu: migrate preconditions: use new check_local_resources info
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (10 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 5/7] PVE/QemuServer: extend 'check_local_resources' for mapped resources Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 7/7] PVE/QemuMigrate: check for mapped resources on migration Dominik Csapak
                   ` (9 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

restrict the nodes also for mapped devices, and return them in their
own property

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 PVE/API2/Qemu.pm | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index 36b8c31..837185f 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -3624,6 +3624,10 @@ __PACKAGE__->register_method({
 
 	$res->{running} = PVE::QemuServer::check_running($vmid) ? 1:0;
 
+	my ($local_resources, $mapped_resources, $not_allowed_nodes) =
+	    PVE::QemuServer::check_local_resources($vmconf, 1);
+	delete $not_allowed_nodes->{$localnode};
+
 	# if vm is not running, return target nodes where local storage is available
 	# for offline migration
 	if (!$res->{running}) {
@@ -3632,7 +3636,12 @@ __PACKAGE__->register_method({
 	    delete $checked_nodes->{$localnode};
 
 	    foreach my $node (keys %$checked_nodes) {
-		if (!defined $checked_nodes->{$node}->{unavailable_storages}) {
+		if (scalar(@{$not_allowed_nodes->{$node}})) {
+		    $checked_nodes->{$node}->{unavailable_resources} = $not_allowed_nodes->{$node};
+		    next;
+		}
+
+		if (!defined($checked_nodes->{$node}->{unavailable_storages})) {
 		    push @{$res->{allowed_nodes}}, $node;
 		}
 
@@ -3640,13 +3649,11 @@ __PACKAGE__->register_method({
 	    $res->{not_allowed_nodes} = $checked_nodes;
 	}
 
-
 	my $local_disks = &$check_vm_disks_local($storecfg, $vmconf, $vmid);
 	$res->{local_disks} = [ values %$local_disks ];;
 
-	my $local_resources =  PVE::QemuServer::check_local_resources($vmconf, 1);
-
 	$res->{local_resources} = $local_resources;
+	$res->{mapped_resources} = $mapped_resources;
 
 	return $res;
 
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH qemu-server 7/7] PVE/QemuMigrate: check for mapped resources on migration
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (11 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 6/7] PVE/API2/Qemu: migrate preconditions: use new check_local_resources info Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 1/8] PVE/API2/Hardware: add Mapping.pm Dominik Csapak
                   ` (8 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

they can only be migrated to nodes where there exists a mapping,
and if offline

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 PVE/QemuMigrate.pm | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/PVE/QemuMigrate.pm b/PVE/QemuMigrate.pm
index 5f37890..6ae365d 100644
--- a/PVE/QemuMigrate.pm
+++ b/PVE/QemuMigrate.pm
@@ -323,7 +323,7 @@ sub prepare {
 	}
     }
 
-    my $loc_res = PVE::QemuServer::check_local_resources($conf, 1);
+    my ($loc_res, $mapped_res, $not_allowed_nodes) = PVE::QemuServer::check_local_resources($conf, 1);
     if (scalar @$loc_res) {
 	if ($self->{running} || !$self->{opts}->{force}) {
 	    die "can't migrate VM which uses local devices: " . join(", ", @$loc_res) . "\n";
@@ -332,6 +332,17 @@ sub prepare {
 	}
     }
 
+    if (scalar @$mapped_res) {
+	my $not_available = $not_allowed_nodes->{$self->{node}};
+	if ($self->{running}) {
+	    die "can't migrate running VM which uses mapped devices: " . join(", ", @$mapped_res) . "\n";
+	} elsif (scalar @$not_available) {
+	    die "can't migrate to '$self->{node}': missing mapped devices" . join(", ", @$not_available) . "\n";
+	} else {
+	    $self->log('info', "migrating VM which uses mapped local devices");
+	}
+    }
+
     my $vollist = PVE::QemuServer::get_vm_volumes($conf);
 
     foreach my $volid (@$vollist) {
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH manager 1/8] PVE/API2/Hardware: add Mapping.pm
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (12 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 7/7] PVE/QemuMigrate: check for mapped resources on migration Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 2/8] ui: form/USBSelector: make it more flexible with nodename Dominik Csapak
                   ` (7 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

adds the basic api calls to list/get/create/update/delete device
mappings

for now these api calls are only per node, so it only affects
the node specific mapping (thought consistency checks are
done for the whole config, e.g if an id exists already on another
node with a different type)

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 PVE/API2/Hardware.pm         |   6 +
 PVE/API2/Hardware/Makefile   |   1 +
 PVE/API2/Hardware/Mapping.pm | 292 +++++++++++++++++++++++++++++++++++
 3 files changed, 299 insertions(+)
 create mode 100644 PVE/API2/Hardware/Mapping.pm

diff --git a/PVE/API2/Hardware.pm b/PVE/API2/Hardware.pm
index f59bfbe0..ab7b5e63 100644
--- a/PVE/API2/Hardware.pm
+++ b/PVE/API2/Hardware.pm
@@ -8,6 +8,7 @@ use PVE::RESTHandler;
 
 use PVE::API2::Hardware::PCI;
 use PVE::API2::Hardware::USB;
+use PVE::API2::Hardware::Mapping;
 
 use base qw(PVE::RESTHandler);
 
@@ -21,6 +22,10 @@ __PACKAGE__->register_method ({
     path => 'usb',
 });
 
+__PACKAGE__->register_method ({
+    subclass => "PVE::API2::Hardware::Mapping",
+    path => "mapping",
+});
 
 __PACKAGE__->register_method ({
     name => 'index',
@@ -50,6 +55,7 @@ __PACKAGE__->register_method ({
 	my $res = [
 	    { type => 'pci' },
 	    { type => 'usb' },
+	    { type => 'mapping' },
 	];
 
 	return $res;
diff --git a/PVE/API2/Hardware/Makefile b/PVE/API2/Hardware/Makefile
index d27d2201..9f5f3231 100644
--- a/PVE/API2/Hardware/Makefile
+++ b/PVE/API2/Hardware/Makefile
@@ -3,6 +3,7 @@ include ../../../defines.mk
 PERLSOURCE=			\
 	PCI.pm			\
 	USB.pm			\
+	Mapping.pm			\
 
 all:
 
diff --git a/PVE/API2/Hardware/Mapping.pm b/PVE/API2/Hardware/Mapping.pm
new file mode 100644
index 00000000..0a02931f
--- /dev/null
+++ b/PVE/API2/Hardware/Mapping.pm
@@ -0,0 +1,292 @@
+package PVE::API2::Hardware::Mapping;
+
+use strict;
+use warnings;
+
+use Storable qw(dclone);
+
+use PVE::Cluster qw(cfs_lock_file);
+use PVE::HardwareMap;
+use PVE::HardwareMap::Plugin;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::Tools qw(extract_param);
+
+use PVE::RESTHandler;
+
+use base qw(PVE::RESTHandler);
+
+__PACKAGE__->register_method ({
+    name => 'index',
+    path => '',
+    method => 'GET',
+    description => "Hardware Mapping",
+    permissions => {
+	description => "Only lists entries where you have 'Hardware.Audit', 'Hardware.Use', 'Hardware.Configure' permissions on '/hardware/<name>'.",
+	user => 'all',
+    },
+    parameters => {
+	additionalProperties => 0,
+	properties => {
+	    node => get_standard_option('pve-node'),
+	    type => {
+		enum => PVE::HardwareMap::Plugin->lookup_types(),
+		description => "Show only devices of this type.",
+		optional => 1,
+	    },
+	},
+    },
+    returns => {
+	type => 'array',
+	items => {
+	    type => "object",
+	    properties => { name => { type => 'string'} },
+	},
+	links => [ { rel => 'child', href => "{name}" } ],
+    },
+    code => sub {
+	my ($param) = @_;
+
+	my $rpcenv = PVE::RPCEnvironment::get();
+	my $authuser = $rpcenv->get_user();
+
+	my $cfg = PVE::HardwareMap::config();
+
+	my $res = [];
+
+	my $privs = ['Hardware.Audit', 'Hardware.Use', 'Hardware.Configure'];
+
+	for my $id (keys %{$cfg->{ids}}) {
+	    my $entry = $cfg->{ids}->{$id};
+	    next if $entry->{node} ne $param->{node};
+	    next if $param->{type} && $entry->{type} ne $param->{type};
+	    next if !$rpcenv->check_hw_perm($authuser, $entry->{name}, $privs, 1);
+
+	    my $type = $entry->{type};
+	    my $plugin = PVE::HardwareMap::Plugin->lookup($type);
+	    eval {
+		$plugin->assert_device_valid($entry);
+	    };
+	    if (my $err = $@) {
+		$entry->{valid} = 0;
+		$entry->{errmsg} = "$err";
+	    } else {
+		$entry->{valid} = 1;
+	    }
+
+	    push @$res, $entry;
+	}
+
+	return $res;
+    },
+});
+
+__PACKAGE__->register_method ({
+    name => 'get',
+    protected => 1,
+    path => '{name}',
+    method => 'GET',
+    description => "Remove Hardware Mapping.",
+    permissions => {
+	check => [ 'and',
+		    ['perm', '/node/{node}', ['Sys.Modify']],
+		    ['perm', '/hardware/{name}', ['Hardware.Configure']],
+		 ],
+    },
+    parameters => {
+	additionalProperties => 0,
+	properties => {
+	    name => {
+		type => 'string',
+		format => 'pve-configid',
+	    },
+	    node => get_standard_option('pve-node'),
+	}
+    },
+    returns => { type => 'object' },
+    code => sub {
+	my ($param) = @_;
+
+	my $cfg = PVE::HardwareMap::config();
+
+	my $id = "$param->{node}:$param->{name}";
+
+	"mapping '$param->{name}' not found on '$param->{node}'\n"
+	    if !defined($cfg->{ids}->{$id});
+
+	my $data = dclone($cfg->{ids}->{$id});
+
+	$data->{digest} = $cfg->{digest};
+
+	return $data;
+    }});
+
+__PACKAGE__->register_method ({
+    name => 'create',
+    protected => 1,
+    proxyto => 'node',
+    path => '',
+    method => 'POST',
+    description => "Create a new hardware mapping.",
+    permissions => {
+	check => [ 'and',
+		    ['perm', '/node/{node}', ['Sys.Modify']],
+		    ['perm', '/hardware/{name}', ['Hardware.Configure']],
+		 ],
+    },
+    parameters => PVE::HardwareMap::Plugin->createSchema(),
+    returns => {
+	type => 'null',
+    },
+    code => sub {
+	my ($param) = @_;
+
+	my $type = extract_param($param, 'type');
+	my $name = $param->{name};
+	my $node = $param->{node};
+
+	my $mapid = "$node:$name";
+
+	my $plugin = PVE::HardwareMap::Plugin->lookup($type);
+	my $opts = $plugin->check_config($mapid, $param, 1, 1);
+
+	$plugin->assert_device_valid($opts);
+
+	PVE::HardwareMap::lock_config(sub {
+	    my $cfg = PVE::HardwareMap::config();
+
+	    if ($cfg->{ids}->{$mapid}) {
+		die "mapping '$mapid' already defined\n";
+	    }
+
+	    for my $id (keys %{$cfg->{ids}}) {
+		next if $id !~ m/:${name}$/;
+		my $entry = $cfg->{ids}->{$id};
+
+		die "'$name' already defined as type '$entry->{type}'\n"
+		    if $entry->{type} ne $type;
+	    }
+
+	    $cfg->{ids}->{$mapid} = $opts;
+
+	    PVE::HardwareMap::write_config($cfg);
+
+	}, "create hardware mapping failed");
+
+	return;
+    },
+});
+
+__PACKAGE__->register_method ({
+    name => 'update',
+    protected => 1,
+    proxyto => 'node',
+    path => '{name}',
+    method => 'PUT',
+    description => "Update a hardware mapping.",
+    permissions => {
+	check => [ 'and',
+		    ['perm', '/node/{node}', ['Sys.Modify']],
+		    ['perm', '/hardware/{name}', ['Hardware.Configure']],
+		 ],
+    },
+    parameters => PVE::HardwareMap::Plugin->updateSchema(),
+    returns => {
+	type => 'null',
+    },
+    code => sub {
+	my ($param) = @_;
+
+	my $digest = extract_param($param, 'digest');
+	my $delete = extract_param($param, 'delete');
+	my $name = extract_param($param, 'name');
+	my $node = extract_param($param, 'node');
+	if ($delete) {
+	    $delete = [ PVE::Tools::split_list($delete) ];
+	}
+
+	my $mapid = "$node:$name";
+
+
+	PVE::HardwareMap::lock_config(sub {
+	    my $cfg = PVE::HardwareMap::config();
+
+	    PVE::SectionConfig::assert_if_modified($cfg, $digest);
+
+	    my $data = $cfg->{ids}->{$mapid};
+	    die "no mapping  '$mapid'\n" if !$data;
+
+	    my $plugin = PVE::HardwareMap::Plugin->lookup($data->{type});
+	    my $opts = $plugin->check_config($mapid, $param, 0, 1);
+
+	    $plugin->assert_device_valid($opts);
+
+	    for my $k (keys %$opts) {
+		$data->{$k} = $opts->{$k};
+	    }
+
+	    if ($delete) {
+		my $options = $plugin->private()->{options}->{$data->{type}};
+		for my $k (@$delete) {
+		    my $d = $options->{$k} || die "no such option '$k'\n";
+		    die "unable to delete required option '$k'\n" if !$d->{optional};
+		    die "unable to delete fixed option '$k'\n" if $d->{fixed};
+		    die "cannot set and delete property '$k' at the same time!\n"
+			if defined($opts->{$k});
+
+		    delete $data->{$k};
+		}
+	    }
+
+	    PVE::HardwareMap::write_config($cfg);
+
+	}, "update hardware mapping failed");
+
+	return;
+    },
+});
+
+__PACKAGE__->register_method ({
+    name => 'delete',
+    protected => 1,
+    path => '{name}',
+    method => 'DELETE',
+    description => "Remove Hardware Mapping.",
+    permissions => {
+	check => [ 'and',
+		    ['perm', '/node/{node}', ['Sys.Modify']],
+		    ['perm', '/hardware/{name}', ['Hardware.Configure']],
+		 ],
+    },
+    parameters => {
+	additionalProperties => 0,
+	properties => {
+	    node => get_standard_option('pve-node'),
+	    name => {
+		type => 'string',
+		format => 'pve-configid',
+	    },
+	}
+    },
+    returns => { type => 'null' },
+    code => sub {
+	my ($param) = @_;
+
+	PVE::HardwareMap::lock_config(sub {
+	    my $cfg = PVE::HardwareMap::config();
+
+	    my $id = "$param->{node}:$param->{name}";
+
+	    my $plugin_cfg = $cfg->{ids}->{$id};
+
+	    my $plugin = PVE::HardwareMap::Plugin->lookup($plugin_cfg->{type});
+
+	    delete $cfg->{ids}->{$id};
+
+	    PVE::HardwareMap::write_config($cfg);
+
+	}, "delete hardware mapping failed");
+
+	return;
+    }});
+
+1;
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH manager 2/8] ui: form/USBSelector: make it more flexible with nodename
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (13 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 1/8] PVE/API2/Hardware: add Mapping.pm Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 3/8] ui: form: add PCIMapSelector Dominik Csapak
                   ` (6 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

similar to the pciselector, make it accept a plain nodename,
or no node at all and provide a setNodename function

to keep backwards compatibility, also check pveSelNode for the nodename

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/manager6/form/USBSelector.js | 32 ++++++++++++++++++++++++--------
 1 file changed, 24 insertions(+), 8 deletions(-)

diff --git a/www/manager6/form/USBSelector.js b/www/manager6/form/USBSelector.js
index 0d511699..3a2f293e 100644
--- a/www/manager6/form/USBSelector.js
+++ b/www/manager6/form/USBSelector.js
@@ -23,25 +23,39 @@ Ext.define('PVE.form.USBSelector', {
 	return gettext("Invalid Value");
     },
 
-    initComponent: function() {
+    setNodename: function(nodename) {
 	var me = this;
 
-	var nodename = me.pveSelNode.data.node;
+	if (!nodename || me.nodename === nodename) {
+	    return;
+	}
+
+	me.nodename = nodename;
+
+	me.store.setProxy({
+	    type: 'proxmox',
+	    url: `/api2/json/nodes/${me.nodename}/hardware/usb`,
+	});
+
+	me.store.load();
+    },
 
-	if (!nodename) {
-	    throw "no nodename specified";
+    initComponent: function() {
+	var me = this;
+
+	if (me.pveSelNode) {
+	    me.nodename = me.pveSelNode.data.node;
 	}
 
+	var nodename = me.nodename;
+	me.nodename = undefined;
+
 	if (me.type !== 'device' && me.type !== 'port') {
 	    throw "no valid type specified";
 	}
 
 	let store = new Ext.data.Store({
 	    model: `pve-usb-${me.type}`,
-	    proxy: {
-		type: 'proxmox',
-		url: `/api2/json/nodes/${nodename}/hardware/usb`,
-	    },
 	    filters: [
 		({ data }) => !!data.usbpath && !!data.prodid && String(data.class) !== "9",
 	    ],
@@ -99,6 +113,8 @@ Ext.define('PVE.form.USBSelector', {
 
 	me.callParent();
 
+	me.setNodename(nodename);
+
 	store.load();
     },
 
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH manager 3/8] ui: form: add PCIMapSelector
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (14 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 2/8] ui: form/USBSelector: make it more flexible with nodename Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 4/8] ui: form: add USBMapSelector Dominik Csapak
                   ` (5 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

akin to the PCISelector, but uses the api for mapped devices

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/manager6/Makefile               |  1 +
 www/manager6/form/PCIMapSelector.js | 95 +++++++++++++++++++++++++++++
 2 files changed, 96 insertions(+)
 create mode 100644 www/manager6/form/PCIMapSelector.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index ff8ad00f..4168bc4e 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -47,6 +47,7 @@ JSSRC= 							\
 	form/NetworkCardSelector.js			\
 	form/NodeSelector.js				\
 	form/PCISelector.js				\
+	form/PCIMapSelector.js				\
 	form/PermPathSelector.js			\
 	form/PoolSelector.js				\
 	form/PrivilegesSelector.js			\
diff --git a/www/manager6/form/PCIMapSelector.js b/www/manager6/form/PCIMapSelector.js
new file mode 100644
index 00000000..8d68811a
--- /dev/null
+++ b/www/manager6/form/PCIMapSelector.js
@@ -0,0 +1,95 @@
+Ext.define('PVE.form.PCIMapSelector', {
+    extend: 'Proxmox.form.ComboGrid',
+    xtype: 'pvePCIMapSelector',
+
+    store: {
+	fields: ['name', 'pcipath', 'vendor', 'device', 'iommugroup', 'mdev'],
+	filterOnLoad: true,
+	sorters: [
+	    {
+		property: 'name',
+		direction: 'ASC',
+	    },
+	],
+    },
+
+    autoSelect: false,
+    valueField: 'name',
+    displayField: 'name',
+
+    // can contain a load callback for the store
+    // useful to determine the state of the IOMMU
+    onLoadCallBack: undefined,
+
+    listConfig: {
+	width: 800,
+	columns: [
+	    {
+		header: gettext('Name'),
+		dataIndex: 'name',
+		flex: 1,
+	    },
+	    {
+		header: gettext('IOMMU Group'),
+		dataIndex: 'iommugroup',
+		renderer: v => v === undefined ? '-' : v,
+		width: 75,
+	    },
+	    {
+		header: gettext('Path'),
+		dataIndex: 'pcipath',
+		flex: 1,
+	    },
+	    {
+		header: gettext('Vendor'),
+		dataIndex: 'vendor',
+		flex: 1,
+	    },
+	    {
+		header: gettext('Device'),
+		dataIndex: 'device',
+		flex: 1,
+	    },
+	    {
+		header: gettext('Mediated Devices'),
+		dataIndex: 'mdev',
+		flex: 1,
+		renderer: function(val) {
+		    return Proxmox.Utils.format_boolean(!!val);
+		},
+	    },
+	],
+    },
+
+    setNodename: function(nodename) {
+	var me = this;
+
+	if (!nodename || me.nodename === nodename) {
+	    return;
+	}
+
+	me.nodename = nodename;
+
+	me.store.setProxy({
+	    type: 'proxmox',
+	    url: `/api2/json/nodes/${me.nodename}/hardware/mapping?type=pci`,
+	});
+
+	me.store.load();
+    },
+
+    initComponent: function() {
+	var me = this;
+
+	var nodename = me.nodename;
+	me.nodename = undefined;
+
+        me.callParent();
+
+	if (me.onLoadCallBack !== undefined) {
+	    me.mon(me.getStore(), 'load', me.onLoadCallBack);
+	}
+
+	me.setNodename(nodename);
+    },
+});
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH manager 4/8] ui: form: add USBMapSelector
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (15 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 3/8] ui: form: add PCIMapSelector Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 5/8] ui: node: add HardwareView and relevant edit windows Dominik Csapak
                   ` (4 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

similar to PCIMapSelector

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/manager6/Makefile               |  1 +
 www/manager6/form/USBMapSelector.js | 73 +++++++++++++++++++++++++++++
 2 files changed, 74 insertions(+)
 create mode 100644 www/manager6/form/USBMapSelector.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index 4168bc4e..b4e48d33 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -65,6 +65,7 @@ JSSRC= 							\
 	form/TFASelector.js				\
 	form/TokenSelector.js 				\
 	form/USBSelector.js				\
+	form/USBMapSelector.js				\
 	form/UserSelector.js				\
 	form/VLanField.js				\
 	form/VMCPUFlagSelector.js			\
diff --git a/www/manager6/form/USBMapSelector.js b/www/manager6/form/USBMapSelector.js
new file mode 100644
index 00000000..6a96dad6
--- /dev/null
+++ b/www/manager6/form/USBMapSelector.js
@@ -0,0 +1,73 @@
+Ext.define('PVE.form.USBMapSelector', {
+    extend: 'Proxmox.form.ComboGrid',
+    alias: 'widget.pveUSBMapSelector',
+
+    store: {
+	fields: ['name', 'vendor', 'device', 'usbpath'],
+	filterOnLoad: true,
+	sorters: [
+	    {
+		property: 'name',
+		direction: 'ASC',
+	    },
+	],
+    },
+
+    allowBlank: false,
+    autoSelect: false,
+    displayField: 'name',
+    valueField: 'name',
+
+    listConfig: {
+	columns: [
+	    {
+		header: gettext('Name'),
+		dataIndex: 'name',
+		flex: 2,
+	    },
+	    {
+		header: gettext('Path'),
+		dataIndex: 'usbpath',
+		flex: 1,
+	    },
+	    {
+		header: gettext('Vendor'),
+		dataIndex: 'vendor',
+		flex: 1,
+	    },
+	    {
+		header: gettext('Device'),
+		dataIndex: 'device',
+		flex: 1,
+	    },
+	],
+    },
+
+    setNodename: function(nodename) {
+	var me = this;
+
+	if (!nodename || me.nodename === nodename) {
+	    return;
+	}
+
+	me.nodename = nodename;
+
+	me.store.setProxy({
+	    type: 'proxmox',
+	    url: `/api2/json/nodes/${me.nodename}/hardware/mapping?type=usb`,
+	});
+
+	me.store.load();
+    },
+
+    initComponent: function() {
+	var me = this;
+
+	var nodename = me.nodename;
+	me.nodename = undefined;
+
+        me.callParent();
+
+	me.setNodename(nodename);
+    },
+});
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH manager 5/8] ui: node: add HardwareView and relevant edit windows
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (16 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 4/8] ui: form: add USBMapSelector Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 6/8] ui: qemu/PCIEdit: rework panel to add a mapped configuration Dominik Csapak
                   ` (3 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

adds a node specific listing of hardware maps, where the user
can see if a mapping is wrong (wrong vendor/device etc)
and add/edit/delete them

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/manager6/Makefile             |   1 +
 www/manager6/node/Config.js       |   8 +
 www/manager6/node/HardwareView.js | 641 ++++++++++++++++++++++++++++++
 3 files changed, 650 insertions(+)
 create mode 100644 www/manager6/node/HardwareView.js

diff --git a/www/manager6/Makefile b/www/manager6/Makefile
index b4e48d33..e1d7730c 100644
--- a/www/manager6/Makefile
+++ b/www/manager6/Makefile
@@ -188,6 +188,7 @@ JSSRC= 							\
 	node/Subscription.js				\
 	node/Summary.js					\
 	node/ZFS.js					\
+	node/HardwareView.js				\
 	pool/Config.js					\
 	pool/StatusView.js				\
 	pool/Summary.js					\
diff --git a/www/manager6/node/Config.js b/www/manager6/node/Config.js
index 235a7480..fb03c7c2 100644
--- a/www/manager6/node/Config.js
+++ b/www/manager6/node/Config.js
@@ -178,6 +178,14 @@ Ext.define('PVE.node.Config', {
 		    nodename: nodename,
 		    onlineHelp: 'sysadmin_network_configuration',
 		},
+		{
+		    xtype: 'pveNodeHardwareView',
+		    nodename,
+		    itemId: 'hardware',
+		    title: gettext('Hardware'),
+		    iconCls: 'fa fa-desktop',
+		    groups: ['services'],
+		},
 		{
 		    xtype: 'pveCertificatesView',
 		    title: gettext('Certificates'),
diff --git a/www/manager6/node/HardwareView.js b/www/manager6/node/HardwareView.js
new file mode 100644
index 00000000..e6c5ffc2
--- /dev/null
+++ b/www/manager6/node/HardwareView.js
@@ -0,0 +1,641 @@
+Ext.define('pve-node-hardware', {
+    extend: 'Ext.data.Model',
+    fields: [
+	'node', 'type', 'name', 'vendor', 'device', 'pcipath', 'usbpath', 'valid', 'errmsg',
+	{
+	    name: 'path',
+	    calculate: function(data) {
+		if (data.type === 'usb') {
+		    return data.usbpath;
+		} else if (data.type === 'pci') {
+		    return data.pcipath;
+		} else {
+		    return undefined;
+		}
+	    },
+	},
+    ],
+    idProperty: 'name',
+});
+
+Ext.define('PVE.node.HardwareView', {
+    extend: 'Ext.grid.GridPanel',
+
+    alias: 'widget.pveNodeHardwareView',
+
+    onlineHelp: 'pveum_users',
+
+    stateful: true,
+    stateId: 'grid-node-hardware',
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	addPCI: function() {
+	    let me = this;
+	    let nodename = me.getView().nodename;
+	    Ext.create('PVE.node.PCIEditWindow', {
+		url: `/nodes/${nodename}/hardware/mapping/`,
+		nodename,
+		autoShow: true,
+	    });
+	},
+
+	addUSB: function() {
+	    let me = this;
+	    let nodename = me.getView().nodename;
+	    Ext.create('PVE.node.USBEditWindow', {
+		url: `/nodes/${nodename}/hardware/mapping/`,
+		nodename,
+		autoShow: true,
+	    });
+	},
+
+	edit: function() {
+	    let me = this;
+	    let view = me.getView();
+	    let selection = view.getSelection();
+	    if (!selection || !selection.length) {
+		return;
+	    }
+	    let rec = selection[0];
+
+	    let type = 'PVE.node.' + (rec.data.type === 'pci' ? 'PCIEditWindow' : 'USBEditWindow');
+
+	    Ext.create(type, {
+		url: `/nodes/${rec.data.node}/hardware/mapping/${rec.data.name}`,
+		autoShow: true,
+		autoLoad: true,
+		nodename: rec.data.node,
+		name: rec.data.name,
+	    });
+	},
+    },
+
+    columns: [
+	{
+	    header: gettext('Type'),
+	    dataIndex: 'type',
+	},
+	{
+	    header: gettext('Name'),
+	    dataIndex: 'name',
+	},
+	{
+	    header: gettext('Vendor'),
+	    dataIndex: 'vendor',
+	},
+	{
+	    header: gettext('Device'),
+	    dataIndex: 'device',
+	},
+	{
+	    header: gettext('Path'),
+	    dataIndex: 'path',
+	},
+	{
+	    header: gettext('Status'),
+	    dataIndex: 'valid',
+	    flex: 1,
+	    renderer: function(value, mD, record) {
+		let state = value ? 'good' : 'critical';
+		let iconCls = PVE.Utils.get_health_icon(state, true);
+		let status = value ? gettext("OK") : record.data.errmsg || Proxmox.Utils.unknownText;
+		return `<i class="fa ${iconCls}"></i> ${status}`;
+	    },
+	},
+    ],
+
+    store: {
+	type: 'diff',
+	interval: 30*1000,
+	rstore: {
+	    type: 'update',
+	    model: 'pve-node-hardware',
+	},
+    },
+
+    tbar: [
+	{
+	    text: gettext('Add'),
+	    menu: [
+		{
+		    text: gettext('PCI'),
+		    iconCls: 'pve-itype-icon-pci',
+		    handler: 'addPCI',
+		},
+		{
+		    text: gettext('USB'),
+		    iconCls: 'fa fa-fw fa-usb black',
+		    handler: 'addUSB',
+		},
+	    ],
+	},
+	{
+	    xtype: 'proxmoxButton',
+	    text: gettext('Edit'),
+	    disabled: true,
+	    handler: 'edit',
+	},
+	{
+	    xtype: 'proxmoxStdRemoveButton',
+	    getUrl: function(rec) {
+		return `/api2/extjs/nodes/${rec.data.node}/hardware/mapping/${rec.data.name}`;
+	    },
+	    disabled: true,
+	    text: gettext('Remove'),
+	},
+    ],
+
+    initComponent: function() {
+	var me = this;
+
+	if (!me.nodename) {
+	    throw "no nodename given";
+	}
+
+	me.store.rstore.proxy = {
+	    type: 'proxmox',
+	    url: `/api2/json/nodes/${me.nodename}/hardware/mapping`,
+	};
+
+	me.callParent();
+
+	let store = me.getStore();
+	store.rstore.startUpdate();
+
+	Proxmox.Utils.monStoreErrors(me, store);
+    },
+});
+
+Ext.define('PVE.node.PCIEditWindow', {
+    extend: 'Proxmox.window.Edit',
+
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    title: gettext('Add PCI mapping'),
+
+    onlineHelp: 'qm_pci_passthrough',
+
+    method: 'POST',
+
+    cbindData: function(initialConfig) {
+	let me = this;
+	me.isCreate = !me.name;
+	me.method = me.isCreate ? 'POST' : 'PUT';
+	return { name: me.name };
+    },
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	onGetValues: function(values) {
+	    let me = this;
+
+	    if (values.multifunction) {
+		values.pcipath = values.pcipath.substring(0, values.pcipath.indexOf('.')); // skip the '.X'
+		delete values.multifunction;
+	    }
+
+	    return values;
+	},
+
+	checkIommu: function(store, records, success) {
+	    let me = this;
+	    if (!success || !records.length) {
+		return;
+	    }
+	    me.lookup('iommu_warning').setVisible(
+						    records.every((val) => val.data.iommugroup === -1),
+	    );
+	},
+
+	allFunctionsChange: function(_, value) {
+	    let me = this;
+	    if (value) {
+		let pcisel = me.lookup('pciselector');
+		let pcivalue = pcisel.getValue();
+		// replace the function by .0 so that we get the correct vendor/device
+		pcivalue = pcivalue.replace(/.$/, "0");
+		pcisel.setValue(pcivalue);
+	    }
+	},
+
+	pciChange: function(pcisel, value) {
+	    let me = this;
+	    if (!value) {
+		return;
+	    }
+	    let all_functions = !!me.lookup('all_functions').getValue();
+
+	    if (all_functions) {
+		// replace the function by .0 so that we get the correct vendor/device
+		let newvalue = value.replace(/.$/, "0");
+		if (newvalue !== value) {
+		    pcisel.setValue(value);
+		}
+	    }
+
+	    let pciDev = pcisel.getStore().getById(value);
+	    if (!pciDev) {
+		return;
+	    }
+	    let iommu = pciDev.data.iommugroup;
+	    // try to find out if there are more devices in that iommu group
+	    let id = pciDev.data.id.substring(0, 5); // 00:00
+	    let count = 0;
+	    pcisel.getStore().each(({ data }) => {
+		if (data.iommugroup === iommu && data.id.substring(0, 5) !== id) {
+		    count++;
+		    return false;
+		}
+		return true;
+	    });
+
+	    me.lookup('group_warning').setVisible(count > 0);
+
+	    let fields = [
+		'vendor',
+		'device',
+		'subsystem_vendor',
+		'subsystem_device',
+		'iommugroup',
+		'mdev',
+	    ];
+
+	    fields.forEach((fieldName) => {
+		let field = me.lookup(fieldName);
+		let oldValue = field.getValue();
+		if (oldValue !== pciDev.data[fieldName]) {
+		    field.setValue(pciDev.data[fieldName]);
+		}
+	    });
+	},
+
+	init: function(view) {
+	    let me = this;
+
+	    if (!view.nodename) {
+		throw "no nodename given";
+	    }
+	},
+
+	control: {
+	    'field[name=multifunction]': {
+		change: 'allFunctionsChange',
+	    },
+	    'field[name=pcipath]': {
+		change: 'pciChange',
+	    },
+	},
+    },
+
+    items: [
+	{
+	    xtype: 'inputpanel',
+	    onGetValues: function(values) {
+		return this.up('window').getController().onGetValues(values);
+	    },
+
+	    columnT: [
+		{
+		    xtype: 'displayfield',
+		    reference: 'iommu_warning',
+		    hidden: true,
+		    columnWidth: 1,
+		    padding: '0 0 10 0',
+		    value: 'No IOMMU detected, please activate it.' +
+		    'See Documentation for further information.',
+		    userCls: 'pmx-hint',
+		},
+		{
+		    xtype: 'displayfield',
+		    reference: 'group_warning',
+		    hidden: true,
+		    columnWidth: 1,
+		    padding: '0 0 10 0',
+		    itemId: 'iommuwarning',
+		    value: 'The selected Device is not in a seperate IOMMU group, make sure this is intended.',
+		    userCls: 'pmx-hint',
+		},
+	    ],
+
+	    column1: [
+		{
+		    xtype: 'hidden',
+		    name: 'type',
+		    value: 'pci',
+		    cbind: {
+			submitValue: '{isCreate}',
+		    },
+		},
+		{
+		    xtype: 'proxmoxtextfield',
+		    hidden: true,
+		    reference: 'vendor',
+		    name: 'vendor',
+		},
+		{
+		    xtype: 'proxmoxtextfield',
+		    hidden: true,
+		    reference: 'device',
+		    name: 'device',
+		},
+		{
+		    xtype: 'proxmoxtextfield',
+		    hidden: true,
+		    reference: 'subsystem_vendor',
+		    name: 'subsystem_vendor',
+		    cbind: {
+			deleteEmpty: '{!isCreate}',
+		    },
+		},
+		{
+		    xtype: 'proxmoxtextfield',
+		    hidden: true,
+		    reference: 'subsystem_device',
+		    name: 'subsystem_device',
+		    cbind: {
+			deleteEmpty: '{!isCreate}',
+		    },
+		},
+		{
+		    xtype: 'proxmoxtextfield',
+		    hidden: true,
+		    reference: 'iommugroup',
+		    name: 'iommugroup',
+		},
+		{
+		    xtype: 'proxmoxtextfield',
+		    hidden: true,
+		    reference: 'mdev',
+		    name: 'mdev',
+		    cbind: {
+			deleteEmpty: '{!isCreate}',
+		    },
+		},
+		{
+		    xtype: 'displayfield',
+		    fieldLabel: gettext('Node'),
+		    name: 'node',
+		    cbind: {
+			value: '{nodename}',
+		    },
+		    submitValue: true,
+		    allowBlank: false,
+		},
+		{
+		    xtype: 'pmxDisplayEditField',
+		    fieldLabel: gettext('Name'),
+		    cbind: {
+			editable: '{isCreate}',
+			value: '{name}',
+		    },
+		    name: 'name',
+		    allowBlank: false,
+		},
+	    ],
+
+	    column2: [
+		{
+		    xtype: 'pvePCISelector',
+		    fieldLabel: gettext('Device'),
+		    reference: 'pciselector',
+		    name: 'pcipath',
+		    cbind: {
+			nodename: '{nodename}',
+		    },
+		    allowBlank: false,
+		    onLoadCallBack: 'checkIommu',
+		},
+		{
+		    xtype: 'proxmoxcheckbox',
+		    fieldLabel: gettext('All Functions'),
+		    reference: 'all_functions',
+		    name: 'multifunction',
+		},
+	    ],
+	},
+    ],
+});
+
+Ext.define('PVE.node.USBEditWindow', {
+    extend: 'Proxmox.window.Edit',
+
+    mixins: ['Proxmox.Mixin.CBind'],
+
+    cbindData: function(initialConfig) {
+	let me = this;
+	me.isCreate = !me.name;
+	me.method = me.isCreate ? 'POST' : 'PUT';
+	return { name: me.name };
+    },
+
+    title: gettext('Add USB mapping'),
+
+    onlineHelp: 'qm_usb_passthrough',
+
+    method: 'POST',
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	onGetValues: function(values) {
+	    let me = this;
+
+	    var type = me.getView().down('radiofield').getGroupValue();
+
+	    let val = values[type];
+	    delete values[type];
+
+	    let usbsel = me.lookup(type);
+	    let usbDev = usbsel.getStore().findRecord('usbid', val, 0, false, true, true);
+	    if (!usbDev) {
+		return {};
+	    }
+
+	    if (type === 'usbpath') {
+		values.usbpath = val;
+	    } else if (!me.getView().isCreate) {
+		values.delete = 'usbpath';
+	    }
+
+	    values.vendor = usbDev.data.vendid;
+	    values.device = usbDev.data.prodid;
+
+	    return values;
+	},
+
+	usbPathChange: function(usbsel, value) {
+	    let me = this;
+	    if (!value) {
+		return;
+	    }
+
+	    let usbDev = usbsel.getStore().findRecord('usbid', value, 0, false, true, true);
+	    if (!usbDev) {
+		return;
+	    }
+
+	    let usbData = {
+		vendor: usbDev.data.vendid,
+		device: usbDev.data.prodid,
+	    };
+
+	    ['vendor', 'device'].forEach((fieldName) => {
+		let field = me.lookup(fieldName);
+		let oldValue = field.getValue();
+		if (oldValue !== usbData[fieldName]) {
+		    field.setValue(usbData[fieldName]);
+		}
+	    });
+	},
+
+	modeChange: function(field, value) {
+	    let me = this;
+	    let type = field.inputValue;
+	    let usbsel = me.lookup(type);
+	    usbsel.setDisabled(!value);
+	},
+
+	init: function(view) {
+	    let me = this;
+
+	    if (!view.nodename) {
+		throw "no nodename given";
+	    }
+	},
+
+	control: {
+	    'field[name=usbpath]': {
+		change: 'usbPathChange',
+	    },
+	    'radiofield': {
+		change: 'modeChange',
+	    },
+	},
+    },
+
+    items: [
+	{
+	    xtype: 'inputpanel',
+	    onGetValues: function(values) {
+		return this.up('window').getController().onGetValues(values);
+	    },
+
+
+	    column1: [
+		{
+		    xtype: 'hidden',
+		    name: 'type',
+		    value: 'usb',
+		    cbind: {
+			submitValue: '{isCreate}',
+		    },
+		},
+		{
+		    xtype: 'proxmoxtextfield',
+		    hidden: true,
+		    reference: 'vendor',
+		    name: 'vendor',
+		},
+		{
+		    xtype: 'proxmoxtextfield',
+		    hidden: true,
+		    reference: 'device',
+		    name: 'device',
+		},
+		{
+		    xtype: 'displayfield',
+		    fieldLabel: gettext('Node'),
+		    name: 'node',
+		    cbind: {
+			value: '{nodename}',
+		    },
+		    allowBlank: false,
+		},
+		{
+		    xtype: 'pmxDisplayEditField',
+		    fieldLabel: gettext('Name'),
+		    cbind: {
+			editable: '{isCreate}',
+			value: '{name}',
+		    },
+		    name: 'name',
+		    allowBlank: false,
+		},
+	    ],
+
+	    column2: [
+		{
+		    xtype: 'fieldcontainer',
+		    defaultType: 'radiofield',
+		    layout: 'fit',
+		    items: [
+			{
+			    name: 'usb',
+			    inputValue: 'hostdevice',
+			    checked: true,
+			    boxLabel: gettext('Use USB Vendor/Device ID'),
+			    submitValue: false,
+			},
+			{
+			    xtype: 'pveUSBSelector',
+			    type: 'device',
+			    reference: 'hostdevice',
+			    name: 'hostdevice',
+			    cbind: {
+				nodename: '{nodename}',
+			    },
+			    editable: true,
+			    allowBlank: false,
+			    fieldLabel: gettext('Choose Device'),
+			    labelAlign: 'right',
+			},
+			{
+			    name: 'usb',
+			    inputValue: 'usbpath',
+			    boxLabel: gettext('Use USB Port'),
+			    submitValue: false,
+			},
+			{
+			    xtype: 'pveUSBSelector',
+			    disabled: true,
+			    name: 'usbpath',
+			    reference: 'usbpath',
+			    cbind: {
+				nodename: '{nodename}',
+			    },
+			    editable: true,
+			    type: 'port',
+			    allowBlank: false,
+			    fieldLabel: gettext('Choose Port'),
+			    labelAlign: 'right',
+			},
+		    ],
+		},
+	    ],
+	},
+    ],
+
+    initComponent: function() {
+	let me = this;
+	me.callParent();
+
+	if (!me.name) {
+	    return;
+	}
+	me.load({
+	    success: function(response) {
+		let data = response.result.data;
+		if (data.usbpath) {
+		    data.usb = 'usbpath';
+		} else {
+		    data.usb = 'hostdevice';
+		    data.hostdevice = `${data.vendor}:${data.device}`;
+		}
+		me.setValues(data);
+	    },
+	});
+    },
+});
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH manager 6/8] ui: qemu/PCIEdit: rework panel to add a mapped configuration
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (17 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 5/8] ui: node: add HardwareView and relevant edit windows Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 7/8] ui: qemu/USBEdit: add 'mapped' device case Dominik Csapak
                   ` (2 subsequent siblings)
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

reworks the panel to use a controller, so that we can easily
add the selector for mapped pci devices

shows now a selection between 'raw' and 'mapped' devices, where
'raw' ones work like before, and 'mapped' ones take the values
form the hardware map config

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/manager6/qemu/PCIEdit.js | 231 +++++++++++++++++++++++------------
 1 file changed, 155 insertions(+), 76 deletions(-)

diff --git a/www/manager6/qemu/PCIEdit.js b/www/manager6/qemu/PCIEdit.js
index e1c6aea7..920513c3 100644
--- a/www/manager6/qemu/PCIEdit.js
+++ b/www/manager6/qemu/PCIEdit.js
@@ -3,71 +3,106 @@ Ext.define('PVE.qemu.PCIInputPanel', {
 
     onlineHelp: 'qm_pci_passthrough',
 
-    setVMConfig: function(vmconfig) {
-	var me = this;
-	me.vmconfig = vmconfig;
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	setVMConfig: function(vmconfig) {
+	    let me = this;
+	    let view = me.getView();
+	    me.vmconfig = vmconfig;
 
-	var hostpci = me.vmconfig[me.confid] || '';
+	    let hostpci = me.vmconfig[view.confid] || '';
 
-	var values = PVE.Parser.parsePropertyString(hostpci, 'host');
-	if (values.host) {
-	    if (!values.host.match(/^[0-9a-f]{4}:/i)) { // add optional domain
-		values.host = "0000:" + values.host;
+	    let values = PVE.Parser.parsePropertyString(hostpci, 'host');
+	    if (values.host) {
+		if (values.host.includes(':')) {
+		    values.type = 'raw';
+		    if (!values.host.match(/^[0-9a-f]{4}:/i)) { // add optional domain
+			values.host = "0000:" + values.host;
+		    }
+		    if (values.host.length < 11) { // 0000:00:00 format not 0000:00:00.0
+			values.host += ".0";
+			values.multifunction = true;
+		    }
+		} else {
+		    values.hostmapped = values.host;
+		    delete values.host;
+		    values.type = 'mapped';
+		}
 	    }
-	    if (values.host.length < 11) { // 0000:00:00 format not 0000:00:00.0
-		values.host += ".0";
-		values.multifunction = true;
+
+	    values['x-vga'] = PVE.Parser.parseBoolean(values['x-vga'], 0);
+	    values.pcie = PVE.Parser.parseBoolean(values.pcie, 0);
+	    values.rombar = PVE.Parser.parseBoolean(values.rombar, 1);
+
+	    view.setValues(values);
+	    if (!me.vmconfig.machine || me.vmconfig.machine.indexOf('q35') === -1) {
+		// machine is not set to some variant of q35, so we disable pcie
+		let pcie = me.lookup('pcie');
+		pcie.setDisabled(true);
+		pcie.setBoxLabel(gettext('Q35 only'));
 	    }
-	}
 
-	values['x-vga'] = PVE.Parser.parseBoolean(values['x-vga'], 0);
-	values.pcie = PVE.Parser.parseBoolean(values.pcie, 0);
-	values.rombar = PVE.Parser.parseBoolean(values.rombar, 1);
+	    if (values.romfile) {
+		me.lookup('romfile').setVisible(true);
+	    }
+	},
 
-	me.setValues(values);
-	if (!me.vmconfig.machine || me.vmconfig.machine.indexOf('q35') === -1) {
-	    // machine is not set to some variant of q35, so we disable pcie
-	    var pcie = me.down('field[name=pcie]');
-	    pcie.setDisabled(true);
-	    pcie.setBoxLabel(gettext('Q35 only'));
-	}
+	onGetValues: function(values) {
+	    let me = this;
+	    let view = me.getView();
+	    if (!view.confid) {
+		for (let i = 0; i < 5; i++) {
+		    if (!me.vmconfig['hostpci' + i.toString()]) {
+			view.confid = 'hostpci' + i.toString();
+			break;
+		    }
+		}
+		// FIXME: what if no confid was found??
+	    }
 
-	if (values.romfile) {
-	    me.down('field[name=romfile]').setVisible(true);
-	}
-    },
+	    if (values.hostmapped) {
+		values.host = values.hostmapped;
+		delete values.hostmapped;
+	    } else {
+		values.host.replace(/^0000:/, ''); // remove optional '0000' domain
 
-    onGetValues: function(values) {
-	let me = this;
-	if (!me.confid) {
-	    for (let i = 0; i < 5; i++) {
-		if (!me.vmconfig['hostpci' + i.toString()]) {
-		    me.confid = 'hostpci' + i.toString();
-		    break;
+		if (values.multifunction) {
+		    values.host = values.host.substring(0, values.host.indexOf('.')); // skip the '.X'
+		    delete values.multifunction;
 		}
 	    }
-	    // FIXME: what if no confid was found??
-	}
-	values.host.replace(/^0000:/, ''); // remove optional '0000' domain
 
-	if (values.multifunction) {
-	    values.host = values.host.substring(0, values.host.indexOf('.')); // skip the '.X'
-	    delete values.multifunction;
-	}
+	    if (values.rombar) {
+		delete values.rombar;
+	    } else {
+		values.rombar = 0;
+	    }
 
-	if (values.rombar) {
-	    delete values.rombar;
-	} else {
-	    values.rombar = 0;
-	}
+	    if (!values.romfile) {
+		delete values.romfile;
+	    }
 
-	if (!values.romfile) {
-	    delete values.romfile;
-	}
+	    delete values.type;
 
-	let ret = {};
-	ret[me.confid] = PVE.Parser.printPropertyString(values, 'host');
-	return ret;
+	    let ret = {};
+	    ret[view.confid] = PVE.Parser.printPropertyString(values, 'host');
+	    return ret;
+	},
+    },
+
+    viewModel: {
+	data: {
+	    isMapped: true,
+	},
+    },
+
+    setVMConfig: function(vmconfig) {
+	return this.getController().setVMConfig(vmconfig);
+    },
+
+    onGetValues: function(values) {
+	return this.getController().onGetValues(values);
     },
 
     initComponent: function() {
@@ -78,28 +113,77 @@ Ext.define('PVE.qemu.PCIInputPanel', {
 	    throw "no node name specified";
 	}
 
+	me.columnT = [
+	    {
+		xtype: 'displayfield',
+		reference: 'iommu_warning',
+		hidden: true,
+		columnWidth: 1,
+		padding: '0 0 10 0',
+		value: 'No IOMMU detected, please activate it.' +
+		'See Documentation for further information.',
+		userCls: 'pmx-hint',
+	    },
+	    {
+		xtype: 'displayfield',
+		reference: 'group_warning',
+		hidden: true,
+		columnWidth: 1,
+		padding: '0 0 10 0',
+		itemId: 'iommuwarning',
+		value: 'The selected Device is not in a seperate IOMMU group, make sure this is intended.',
+		userCls: 'pmx-hint',
+	    },
+	];
+
 	me.column1 = [
+	    {
+		xtype: 'radiofield',
+		name: 'type',
+		inputValue: 'mapped',
+		boxLabel: gettext('Mapped Device'),
+		bind: {
+		    value: '{isMapped}',
+		},
+	    },
+	    {
+		xtype: 'pvePCIMapSelector',
+		fieldLabel: gettext('Device'),
+		reference: 'mapped_selector',
+		name: 'hostmapped',
+		labelAlign: 'right',
+		nodename: me.nodename,
+		allowBlank: false,
+		bind: {
+		    disabled: '{!isMapped}',
+		},
+	    },
+	    {
+		xtype: 'radiofield',
+		name: 'type',
+		inputValue: 'raw',
+		checked: true,
+		boxLabel: gettext('Raw Device'),
+	    },
 	    {
 		xtype: 'pvePCISelector',
 		fieldLabel: gettext('Device'),
 		name: 'host',
+		reference: 'selector',
 		nodename: me.nodename,
+		labelAlign: 'right',
 		allowBlank: false,
+		disabled: true,
+		bind: {
+		    disabled: '{isMapped}',
+		},
 		onLoadCallBack: function(store, records, success) {
 		    if (!success || !records.length) {
 			return;
 		    }
-		    if (records.every((val) => val.data.iommugroup === -1)) { // no IOMMU groups
-			let warning = Ext.create('Ext.form.field.Display', {
-			    columnWidth: 1,
-			    padding: '0 0 10 0',
-			    value: 'No IOMMU detected, please activate it.' +
-				   'See Documentation for further information.',
-			    userCls: 'pmx-hint',
-			});
-			me.items.insert(0, warning);
-			me.updateLayout(); // insert does not trigger that
-		    }
+		    me.lookup('iommu_warning').setVisible(
+			records.every((val) => val.data.iommugroup === -1),
+		    );
 		},
 		listeners: {
 		    change: function(pcisel, value) {
@@ -129,27 +213,20 @@ Ext.define('PVE.qemu.PCIInputPanel', {
 			    }
 			    return true;
 			});
-			let warning = me.down('#iommuwarning');
-			if (count && !warning) {
-			    warning = Ext.create('Ext.form.field.Display', {
-				columnWidth: 1,
-				padding: '0 0 10 0',
-				itemId: 'iommuwarning',
-				value: 'The selected Device is not in a seperate IOMMU group, make sure this is intended.',
-				userCls: 'pmx-hint',
-			    });
-			    me.items.insert(0, warning);
-			    me.updateLayout(); // insert does not trigger that
-			} else if (!count && warning) {
-			    me.remove(warning);
-			}
+			me.lookup('group_warning').setVisible(count > 0);
 		    },
 		},
 	    },
 	    {
 		xtype: 'proxmoxcheckbox',
 		fieldLabel: gettext('All Functions'),
+		reference: 'all_functions',
+		disabled: true,
+		labelAlign: 'right',
 		name: 'multifunction',
+		bind: {
+		    disabled: '{isMapped}',
+		},
 	    },
 	];
 
@@ -188,6 +265,7 @@ Ext.define('PVE.qemu.PCIInputPanel', {
 		submitValue: true,
 		hidden: true,
 		fieldLabel: 'ROM-File',
+		reference: 'romfile',
 		name: 'romfile',
 	    },
 	];
@@ -196,6 +274,7 @@ Ext.define('PVE.qemu.PCIInputPanel', {
 	    {
 		xtype: 'proxmoxcheckbox',
 		fieldLabel: 'PCI-Express',
+		reference: 'pcie',
 		name: 'pcie',
 	    },
 	];
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH manager 7/8] ui: qemu/USBEdit: add 'mapped' device case
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (18 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 6/8] ui: qemu/PCIEdit: rework panel to add a mapped configuration Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 8/8] ui: window/Migrate: allow mapped devices Dominik Csapak
  2021-06-22  7:07 ` [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

to be able to select 'mapped' usb devices

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/manager6/qemu/USBEdit.js | 34 +++++++++++++++++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/www/manager6/qemu/USBEdit.js b/www/manager6/qemu/USBEdit.js
index a2204584..82af78f9 100644
--- a/www/manager6/qemu/USBEdit.js
+++ b/www/manager6/qemu/USBEdit.js
@@ -5,6 +5,15 @@ Ext.define('PVE.qemu.USBInputPanel', {
     autoComplete: false,
     onlineHelp: 'qm_usb_passthrough',
 
+    cbindData: function(initialConfig) {
+	let me = this;
+	if (!me.pveSelNode) {
+	    throw "no pveSelNode given";
+	}
+
+	return { nodename: me.pveSelNode.data.node };
+    },
+
     viewModel: {
 	data: {},
     },
@@ -31,6 +40,7 @@ Ext.define('PVE.qemu.USBInputPanel', {
 	    case 'spice':
 		val = 'spice';
 		break;
+	    case 'mapped':
 	    case 'hostdevice':
 	    case 'port':
 		val = 'host=' + values[type];
@@ -61,6 +71,23 @@ Ext.define('PVE.qemu.USBInputPanel', {
 		    submitValue: false,
 		    checked: true,
 		},
+		{
+		    name: 'usb',
+		    inputValue: 'mapped',
+		    boxLabel: gettext('Use mapped Device'),
+		    reference: 'mapped',
+		    submitValue: false,
+		},
+		{
+		    xtype: 'pveUSBMapSelector',
+		    disabled: true,
+		    name: 'mapped',
+		    cbind: { nodename: '{nodename}' },
+		    bind: { disabled: '{!mapped.checked}' },
+		    allowBlank: false,
+		    fieldLabel: gettext('Choose Device'),
+		    labelAlign: 'right',
+		},
 		{
 		    name: 'usb',
 		    inputValue: 'hostdevice',
@@ -145,7 +172,7 @@ Ext.define('PVE.qemu.USBEdit', {
 		}
 
 		var data = response.result.data[me.confid].split(',');
-		var port, hostdevice, usb3 = false;
+		var port, hostdevice, mapped, usb3 = false;
 		var type = 'spice';
 
 		for (let i = 0; i < data.length; i++) {
@@ -157,6 +184,10 @@ Ext.define('PVE.qemu.USBEdit', {
 			port = data[i];
 			port = port.replace('host=', '');
 			type = 'port';
+		    } else if (/^(host=)?[a-zA-Z0-9\-_]+$/.test(data[i])) {
+			mapped = data[i];
+			mapped = mapped.replace('host=', '');
+			type = 'mapped';
 		    }
 
 		    if (/^usb3=(1|on|true)$/.test(data[i])) {
@@ -168,6 +199,7 @@ Ext.define('PVE.qemu.USBEdit', {
 		    hostdevice: hostdevice,
 		    port: port,
 		    usb3: usb3,
+		    mapped,
 		};
 
 		ipanel.setValues(values);
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH manager 8/8] ui: window/Migrate: allow mapped devices
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (19 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 7/8] ui: qemu/USBEdit: add 'mapped' device case Dominik Csapak
@ 2021-06-21 13:55 ` Dominik Csapak
  2021-06-22  7:07 ` [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-21 13:55 UTC (permalink / raw)
  To: pve-devel

if the migration is an offline migration and when the mapping on
the target node exists, otherwise not

this does not change the behaviour for 'raw' devices in the config
those can still be forced to be migrated, like before

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 www/manager6/window/Migrate.js | 37 +++++++++++++++++++++++++++-------
 1 file changed, 30 insertions(+), 7 deletions(-)

diff --git a/www/manager6/window/Migrate.js b/www/manager6/window/Migrate.js
index 5122e25f..c1fadfea 100644
--- a/www/manager6/window/Migrate.js
+++ b/www/manager6/window/Migrate.js
@@ -219,14 +219,26 @@ Ext.define('PVE.window.Migrate', {
 		let target = me.lookup('pveNodeSelector').value;
 		if (target.length && !migrateStats.allowed_nodes.includes(target)) {
 		    let disallowed = migrateStats.not_allowed_nodes[target];
-		    let missingStorages = disallowed.unavailable_storages.join(', ');
+		    if (disallowed.unavailable_storages !== undefined) {
+			let missingStorages = disallowed.unavailable_storages.join(', ');
 
-		    migration.possible = false;
-		    migration.preconditions.push({
-			text: 'Storage (' + missingStorages + ') not available on selected target. ' +
-			  'Start VM to use live storage migration or select other target node',
-			severity: 'error',
-		    });
+			migration.possible = false;
+			migration.preconditions.push({
+			    text: 'Storage (' + missingStorages + ') not available on selected target. ' +
+			      'Start VM to use live storage migration or select other target node',
+			    severity: 'error',
+			});
+		    }
+
+		    if (disallowed.unavailable_resources !== undefined) {
+			let missingResources = disallowed.unavailable_resources.join(', ');
+
+			migration.possible = false;
+			migration.preconditions.push({
+			    text: 'Mapped Resources (' + missingResources + ') not available on selected target. ',
+			    severity: 'error',
+			});
+		    }
 		}
 	    }
 
@@ -249,6 +261,17 @@ Ext.define('PVE.window.Migrate', {
 		}
 	    }
 
+	    if (migrateStats.mapped_resources && migrateStats.mapped_resources.length) {
+		if (vm.get('running')) {
+		    migration.possible = false;
+		    migration.preconditions.push({
+			text: Ext.String.format('Can\'t migrate running VM with mapped resources: {0}',
+			migrateStats.mapped_resources.join(', ')),
+			severity: 'error',
+		    });
+		}
+	    }
+
 	    if (migrateStats.local_disks.length) {
 		migrateStats.local_disks.forEach(function(disk) {
 		    if (disk.cdrom && disk.cdrom === 1) {
-- 
2.20.1





^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] applied: [PATCH common 1/3] SysFSTools: add verbose flag to pci_device_info
  2021-06-21 13:55 ` [pve-devel] [PATCH common 1/3] SysFSTools: add verbose flag to pci_device_info Dominik Csapak
@ 2021-06-21 15:31   ` Thomas Lamprecht
  0 siblings, 0 replies; 27+ messages in thread
From: Thomas Lamprecht @ 2021-06-21 15:31 UTC (permalink / raw)
  To: Proxmox VE development discussion, Dominik Csapak

On 21.06.21 15:55, Dominik Csapak wrote:
> to also get the subsystem_vendor and device, as well as the
> iommu group and mediated device support
> 
> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
> ---
>  src/PVE/SysFSTools.pm | 29 +++++++++++++++++++++++++----
>  1 file changed, 25 insertions(+), 4 deletions(-)
> 

applied, thanks - one question below though.


> @@ -225,6 +227,25 @@ sub pci_device_info {
>  	has_fl_reset => -f "$pcisysfs/devices/$name/reset" || 0,
>      };
>  
> +    if ($verbose) {
> +	my $sub_vendor = file_read_firstline("$devdir/subsystem_vendor");
> +	$sub_vendor =~ s/^0x// if defined($sub_vendor);
> +	my $sub_device = file_read_firstline("$devdir/subsystem_device");
> +	$sub_device =~ s/^0x// if defined($sub_device);
> +
> +	$res->{subsystem_vendor} = $sub_vendor if defined($sub_vendor);
> +	$res->{subsystem_device} = $sub_device if defined($sub_device);
> +
> +	if (-e "$devdir/iommu_group") {
> +	    my ($iommugroup) = (readlink("$devdir/iommu_group") =~ m/\/(\d+)$/);
> +	    $res->{iommugroup} = int($iommugroup);
just to be sure: int() warns if it gets an undefined value, but I'd guess that once
"$devdir/iommu_group" exists it's basically guaranteed that we get the integer match
at end-of-string above?




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] applied: [PATCH common 2/3] SysFSTools: change 'product' to 'device'
  2021-06-21 13:55 ` [pve-devel] [PATCH common 2/3] SysFSTools: change 'product' to 'device' Dominik Csapak
@ 2021-06-21 15:31   ` Thomas Lamprecht
  0 siblings, 0 replies; 27+ messages in thread
From: Thomas Lamprecht @ 2021-06-21 15:31 UTC (permalink / raw)
  To: Proxmox VE development discussion, Dominik Csapak

On 21.06.21 15:55, Dominik Csapak wrote:
> so it is more consistend with the source (it comes from the file
> 'device') as well as the subsytem_device field
> 
> the only place we use that field is in the same file in pci_dev_bind_to_vfio,
> which we also change here, so that should not be a breaking change
> (in qemu-server we only really use the existance and the has_fl_reset
> flag)
> 
> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
> ---
>  src/PVE/SysFSTools.pm | 4 ++--
>  1 file changed, 2 insertions(+), 2 deletions(-)
> 
>

applied, thanks!




^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping
  2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
                   ` (20 preceding siblings ...)
  2021-06-21 13:55 ` [pve-devel] [PATCH manager 8/8] ui: window/Migrate: allow mapped devices Dominik Csapak
@ 2021-06-22  7:07 ` Dominik Csapak
  21 siblings, 0 replies; 27+ messages in thread
From: Dominik Csapak @ 2021-06-22  7:07 UTC (permalink / raw)
  To: pve-devel

i just noticed, it seems i forgot to add some hunks/stashes?
usb does not work properly

there is one qemu patch missing
and two would have to be adapted

i can send a v2 for the whole series, only for qemu-server
or only for the patches, depending on what is preferred

the whole diff is rather small:

----8<----
diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index 063b356..bb911b9 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -1346,7 +1346,7 @@ my $update_vm_api  = sub {
                     PVE::QemuConfig->add_to_pending_delete($conf, $opt, 
$force);
                     PVE::QemuConfig->write_config($vmid, $conf);
                 } elsif ($opt =~ m/^usb\d+$/) {
-                   my $device = 
PVE::JSONSchema::parse_property_string('pve-qm-usb-device', $val);
+                   my $device = 
PVE::JSONSchema::parse_property_string('pve-qm-usb', $val);
                     my $host = parse_usb_device($device->{host});
                     if ($host->{spice}) {
                         $rpcenv->check_vm_perm($authuser, $vmid, undef, 
['VM.Config.HWType']);
@@ -1404,7 +1404,7 @@ my $update_vm_api  = sub {
                     my $olddevice;
                     my $oldhost;
                     if (defined($conf->{$opt})) {
-                       $olddevice = 
PVE::JSONSchema::parse_property_string('pve-qm-usb-device', $conf->{$opt});
+                       $olddevice = 
PVE::JSONSchema::parse_property_string('pve-qm-usb', $conf->{$opt});
                         $oldhost = parse_usb_device($olddevice->{host});
                     }
                     if (defined($oldhost)) {
@@ -1418,7 +1418,7 @@ my $update_vm_api  = sub {
                         }
                     }

-                   my $newdevice = 
PVE::JSONSchema::parse_property_string('pve-qm-usb-device', $param->{$opt});
+                   my $newdevice = 
PVE::JSONSchema::parse_property_string('pve-qm-usb', $param->{$opt});
                     my $newhost = parse_usb_device($newdevice->{host});

                     if ($newhost->{spice}) {
diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm
index 5edcb6a..ea824a1 100644
--- a/PVE/QemuServer.pm
+++ b/PVE/QemuServer.pm
@@ -1030,6 +1030,8 @@ EODESCR
      },
  };

+PVE::JSONSchema::register_format('pve-qm-usb', $usb_fmt);
+
  my $usbdesc = {
      optional => 1,
      type => 'string', format => $usb_fmt,
@@ -2438,9 +2440,10 @@ sub check_local_resources {
      foreach my $k (keys %$conf) {
         if ($k =~ m/^usb/) {
             my $entry = parse_property_string($usb_fmt, $conf->{$k});
-           next if $entry->{spice};
-           if (my $id = $entry->{mapped}) {
-               $add_not_allowed_nodes->($k, $id);
+           my $host = parse_usb_device($entry->{host});
+           next if $host->{spice};
+           if ($host->{mapped}) {
+               $add_not_allowed_nodes->($k, $entry->{host});
                 push @$mapped_res, $k;
                 next;
             }
---->8----




^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [pve-devel] [PATCH qemu-server 4/7] PVE/API2/Qemu: add permission checks for mapped pci devices
       [not found]   ` <<20220719114639.3035048-9-d.csapak@proxmox.com>
@ 2022-08-01 13:01     ` Fabian Grünbichler
  0 siblings, 0 replies; 27+ messages in thread
From: Fabian Grünbichler @ 2022-08-01 13:01 UTC (permalink / raw)
  To: Proxmox VE development discussion

On July 19, 2022 1:46 pm, Dominik Csapak wrote:
> Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
> ---
>  PVE/API2/Qemu.pm | 54 ++++++++++++++++++++++++++++++++++++++++++++++--
>  1 file changed, 52 insertions(+), 2 deletions(-)
> 
> diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
> index aa7ddea..a8029c2 100644
> --- a/PVE/API2/Qemu.pm
> +++ b/PVE/API2/Qemu.pm
> @@ -26,6 +26,7 @@ use PVE::QemuServer::Drive;
>  use PVE::QemuServer::ImportDisk;
>  use PVE::QemuServer::Monitor qw(mon_cmd);
>  use PVE::QemuServer::Machine;
> +use PVE::QemuServer::PCI;
>  use PVE::QemuServer::USB qw(parse_usb_device);
>  use PVE::QemuMigrate;
>  use PVE::RPCEnvironment;
> @@ -583,6 +584,26 @@ my $check_vm_create_usb_perm = sub {
>      return 1;
>  };
>  
> +my $check_vm_create_hostpci_perm = sub {
> +    my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
> +
> +    return 1 if $authuser eq 'root@pam';
> +
> +    foreach my $opt (keys %{$param}) {
> +	next if $opt !~ m/^hostpci\d+$/;
> +
> +	my $device = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $param->{$opt});
> +	if ($device->{host} !~ m/:/) {
> +	    $rpcenv->check_hw_perm($authuser, $device->{host}, ['Hardware.Use']);
> +	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
> +	} else {
> +	    die "only root can set '$opt' config for non-mapped devices\n";
> +	}
> +    }
> +
> +    return 1;
> +};
> +
>  my $check_vm_modify_config_perm = sub {
>      my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
>  
> @@ -593,7 +614,7 @@ my $check_vm_modify_config_perm = sub {
>  	# else, as there the permission can be value dependend
>  	next if PVE::QemuServer::is_valid_drivename($opt);
>  	next if $opt eq 'cdrom';
> -	next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
> +	next if $opt =~ m/^(?:unused|serial|usb|hostpci)\d+$/;
>  
>  
>  	if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
> @@ -621,7 +642,7 @@ my $check_vm_modify_config_perm = sub {
>  	    # also needs privileges on the storage, that will be checked later
>  	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
>  	} else {
> -	    # catches hostpci\d+, args, lock, etc.
> +	    # catches args, lock, etc.
>  	    # new options will be checked here
>  	    die "only root can set '$opt' config\n";
>  	}
> @@ -856,6 +877,7 @@ __PACKAGE__->register_method({
>  
>  	    &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
>  	    &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
> +	    &$check_vm_create_hostpci_perm($rpcenv, $authuser, $vmid, $pool, $param);
>  
>  	    &$check_cpu_model_access($rpcenv, $authuser, $param);
>  
> @@ -1569,6 +1591,16 @@ my $update_vm_api  = sub {
>  		    }
>  		    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
>  		    PVE::QemuConfig->write_config($vmid, $conf);
> +		} elsif ($opt =~ m/^hostpci\d+$/) {
> +		    my $olddevice = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $val);
> +		    if ($olddevice->{host} !~ m/:/) {
> +			$rpcenv->check_hw_perm($authuser, $olddevice->{host}, ['Hardware.Use']);
> +			$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
> +		    } elsif ($authuser ne 'root@pam') {
> +			die "only root can set '$opt' config for non-mapped devices\n";
> +		    }
> +		    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
> +		    PVE::QemuConfig->write_config($vmid, $conf);
>  		} else {
>  		    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
>  		    PVE::QemuConfig->write_config($vmid, $conf);
> @@ -1651,6 +1683,24 @@ my $update_vm_api  = sub {
>  		    } elsif ($authuser ne 'root@pam') {
>  			die "only root can modify '$opt' config for real devices\n";
>  		    }
> +
> +		    $conf->{pending}->{$opt} = $param->{$opt};
> +		} elsif ($opt =~ m/^hostpci\d+$/) {
> +		    my $olddevice;
> +		    if (defined($conf->{$opt})) {
> +			$olddevice = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $conf->{$opt});
> +		    }
> +		    my $newdevice = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $param->{$opt});
> +		    if ((!defined($olddevice) || $olddevice->{host} !~ m/:/) && $newdevice->{host} !~ m/:/) {
> +			if (defined($olddevice)) {
> +			    $rpcenv->check_hw_perm($authuser, $olddevice->{host}, ['Hardware.Use']);
> +			}
> +			$rpcenv->check_hw_perm($authuser, $newdevice->{host}, ['Hardware.Use']);
> +			$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
> +		    } elsif ($authuser ne 'root@pam') {
> +			die "only root can set '$opt' config for non-mapped devices\n";
> +		    }

nit: this is a bit confusing to read, and might benefit from splitting the 
check for old and for new, like so

if (defined($old)) {
  parse old
  check old || check root
}
parse new
check new || check root
check VM.Config.HWType

> +		    PVE::QemuServer::PCI::parse_hostpci($param->{$opt});
>  		    $conf->{pending}->{$opt} = $param->{$opt};
>  		} else {
>  		    $conf->{pending}->{$opt} = $param->{$opt};
> -- 
> 2.30.2
> 
> 
> 
> _______________________________________________
> pve-devel mailing list
> pve-devel@lists.proxmox.com
> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
> 
> 
> 




^ permalink raw reply	[flat|nested] 27+ messages in thread

* [pve-devel] [PATCH qemu-server 4/7] PVE/API2/Qemu: add permission checks for mapped pci devices
  2022-07-19 11:46 [pve-devel] [PATCH " Dominik Csapak
@ 2022-07-19 11:46 ` Dominik Csapak
       [not found]   ` <<20220719114639.3035048-9-d.csapak@proxmox.com>
  0 siblings, 1 reply; 27+ messages in thread
From: Dominik Csapak @ 2022-07-19 11:46 UTC (permalink / raw)
  To: pve-devel

Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
 PVE/API2/Qemu.pm | 54 ++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 52 insertions(+), 2 deletions(-)

diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm
index aa7ddea..a8029c2 100644
--- a/PVE/API2/Qemu.pm
+++ b/PVE/API2/Qemu.pm
@@ -26,6 +26,7 @@ use PVE::QemuServer::Drive;
 use PVE::QemuServer::ImportDisk;
 use PVE::QemuServer::Monitor qw(mon_cmd);
 use PVE::QemuServer::Machine;
+use PVE::QemuServer::PCI;
 use PVE::QemuServer::USB qw(parse_usb_device);
 use PVE::QemuMigrate;
 use PVE::RPCEnvironment;
@@ -583,6 +584,26 @@ my $check_vm_create_usb_perm = sub {
     return 1;
 };
 
+my $check_vm_create_hostpci_perm = sub {
+    my ($rpcenv, $authuser, $vmid, $pool, $param) = @_;
+
+    return 1 if $authuser eq 'root@pam';
+
+    foreach my $opt (keys %{$param}) {
+	next if $opt !~ m/^hostpci\d+$/;
+
+	my $device = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $param->{$opt});
+	if ($device->{host} !~ m/:/) {
+	    $rpcenv->check_hw_perm($authuser, $device->{host}, ['Hardware.Use']);
+	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.HWType']);
+	} else {
+	    die "only root can set '$opt' config for non-mapped devices\n";
+	}
+    }
+
+    return 1;
+};
+
 my $check_vm_modify_config_perm = sub {
     my ($rpcenv, $authuser, $vmid, $pool, $key_list) = @_;
 
@@ -593,7 +614,7 @@ my $check_vm_modify_config_perm = sub {
 	# else, as there the permission can be value dependend
 	next if PVE::QemuServer::is_valid_drivename($opt);
 	next if $opt eq 'cdrom';
-	next if $opt =~ m/^(?:unused|serial|usb)\d+$/;
+	next if $opt =~ m/^(?:unused|serial|usb|hostpci)\d+$/;
 
 
 	if ($cpuoptions->{$opt} || $opt =~ m/^numa\d+$/) {
@@ -621,7 +642,7 @@ my $check_vm_modify_config_perm = sub {
 	    # also needs privileges on the storage, that will be checked later
 	    $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]);
 	} else {
-	    # catches hostpci\d+, args, lock, etc.
+	    # catches args, lock, etc.
 	    # new options will be checked here
 	    die "only root can set '$opt' config\n";
 	}
@@ -856,6 +877,7 @@ __PACKAGE__->register_method({
 
 	    &$check_vm_create_serial_perm($rpcenv, $authuser, $vmid, $pool, $param);
 	    &$check_vm_create_usb_perm($rpcenv, $authuser, $vmid, $pool, $param);
+	    &$check_vm_create_hostpci_perm($rpcenv, $authuser, $vmid, $pool, $param);
 
 	    &$check_cpu_model_access($rpcenv, $authuser, $param);
 
@@ -1569,6 +1591,16 @@ my $update_vm_api  = sub {
 		    }
 		    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
 		    PVE::QemuConfig->write_config($vmid, $conf);
+		} elsif ($opt =~ m/^hostpci\d+$/) {
+		    my $olddevice = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $val);
+		    if ($olddevice->{host} !~ m/:/) {
+			$rpcenv->check_hw_perm($authuser, $olddevice->{host}, ['Hardware.Use']);
+			$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
+		    } elsif ($authuser ne 'root@pam') {
+			die "only root can set '$opt' config for non-mapped devices\n";
+		    }
+		    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
+		    PVE::QemuConfig->write_config($vmid, $conf);
 		} else {
 		    PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force);
 		    PVE::QemuConfig->write_config($vmid, $conf);
@@ -1651,6 +1683,24 @@ my $update_vm_api  = sub {
 		    } elsif ($authuser ne 'root@pam') {
 			die "only root can modify '$opt' config for real devices\n";
 		    }
+
+		    $conf->{pending}->{$opt} = $param->{$opt};
+		} elsif ($opt =~ m/^hostpci\d+$/) {
+		    my $olddevice;
+		    if (defined($conf->{$opt})) {
+			$olddevice = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $conf->{$opt});
+		    }
+		    my $newdevice = PVE::JSONSchema::parse_property_string('pve-qm-hostpci', $param->{$opt});
+		    if ((!defined($olddevice) || $olddevice->{host} !~ m/:/) && $newdevice->{host} !~ m/:/) {
+			if (defined($olddevice)) {
+			    $rpcenv->check_hw_perm($authuser, $olddevice->{host}, ['Hardware.Use']);
+			}
+			$rpcenv->check_hw_perm($authuser, $newdevice->{host}, ['Hardware.Use']);
+			$rpcenv->check_vm_perm($authuser, $vmid, undef, ['VM.Config.HWType']);
+		    } elsif ($authuser ne 'root@pam') {
+			die "only root can set '$opt' config for non-mapped devices\n";
+		    }
+		    PVE::QemuServer::PCI::parse_hostpci($param->{$opt});
 		    $conf->{pending}->{$opt} = $param->{$opt};
 		} else {
 		    $conf->{pending}->{$opt} = $param->{$opt};
-- 
2.30.2





^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2022-08-01 13:02 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-06-21 13:55 [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH cluster 1/1] add nodes/hardware-map.conf Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH common 1/3] SysFSTools: add verbose flag to pci_device_info Dominik Csapak
2021-06-21 15:31   ` [pve-devel] applied: " Thomas Lamprecht
2021-06-21 13:55 ` [pve-devel] [PATCH common 2/3] SysFSTools: change 'product' to 'device' Dominik Csapak
2021-06-21 15:31   ` [pve-devel] applied: " Thomas Lamprecht
2021-06-21 13:55 ` [pve-devel] [PATCH common 3/3] add PVE/HardwareMap and Plugins Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH access-control 1/2] PVE/AccessControl: add Hardware.* privileges and /hardware/ paths Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH access-control 2/2] PVE/RPCEnvironment: add helper for checking hw permissions Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 1/7] PVE/QemuServer: allow mapped usb devices in config Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 2/7] PVE/QemuServer: allow mapped pci deviced " Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 3/7] PVE/API2/Qemu: add permission checks for mapped usb devices Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 4/7] PVE/API2/Qemu: add permission checks for mapped pci devices Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 5/7] PVE/QemuServer: extend 'check_local_resources' for mapped resources Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 6/7] PVE/API2/Qemu: migrate preconditions: use new check_local_resources info Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH qemu-server 7/7] PVE/QemuMigrate: check for mapped resources on migration Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH manager 1/8] PVE/API2/Hardware: add Mapping.pm Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH manager 2/8] ui: form/USBSelector: make it more flexible with nodename Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH manager 3/8] ui: form: add PCIMapSelector Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH manager 4/8] ui: form: add USBMapSelector Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH manager 5/8] ui: node: add HardwareView and relevant edit windows Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH manager 6/8] ui: qemu/PCIEdit: rework panel to add a mapped configuration Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH manager 7/8] ui: qemu/USBEdit: add 'mapped' device case Dominik Csapak
2021-06-21 13:55 ` [pve-devel] [PATCH manager 8/8] ui: window/Migrate: allow mapped devices Dominik Csapak
2021-06-22  7:07 ` [pve-devel] [PATCH/RFC cluster/common/... many] add cluster-wide hardware device mapping Dominik Csapak
2022-07-19 11:46 [pve-devel] [PATCH " Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH qemu-server 4/7] PVE/API2/Qemu: add permission checks for mapped pci devices Dominik Csapak
     [not found]   ` <<20220719114639.3035048-9-d.csapak@proxmox.com>
2022-08-01 13:01     ` Fabian Grünbichler

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal