From: Dominik Csapak <d.csapak@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH common 1/1] add PVE/HardwareMap
Date: Tue, 19 Jul 2022 13:46:20 +0200 [thread overview]
Message-ID: <20220719114639.3035048-5-d.csapak@proxmox.com> (raw)
In-Reply-To: <20220719114639.3035048-1-d.csapak@proxmox.com>
this adds functionality for the hardwaremap config (as json)
the format of the config is like this:
{
usb => {
name => {
nodename1 => { /* mapping object */ },
nodename2 => { /* mapping object */ }
}
},
pci => {
/* same as above */
},
digest => "<DIGEST-STRING>"
}
it also adds some helpers for the api schema & asserting that the
device mappings are valid (by checking the saved properties
against the ones found on the current available devices)
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
---
src/Makefile | 1 +
src/PVE/HardwareMap.pm | 363 +++++++++++++++++++++++++++++++++++++++++
2 files changed, 364 insertions(+)
create mode 100644 src/PVE/HardwareMap.pm
diff --git a/src/Makefile b/src/Makefile
index 13de6c6..8527704 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -17,6 +17,7 @@ LIB_SOURCES = \
Daemon.pm \
Exception.pm \
Format.pm \
+ HardwareMap.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..1b94abc
--- /dev/null
+++ b/src/PVE/HardwareMap.pm
@@ -0,0 +1,363 @@
+package PVE::HardwareMap;
+
+use strict;
+use warnings;
+
+use Digest::SHA;
+use JSON;
+use Storable qw(dclone);
+
+use PVE::Cluster qw(cfs_register_file cfs_read_file cfs_write_file cfs_lock_file);
+use PVE::INotify;
+use PVE::JSONSchema qw(get_standard_option);
+use PVE::SysFSTools;
+
+use base qw(Exporter);
+
+our @EXPORT_OK = qw(find_device_on_current_node);
+
+my $FILENAME = "nodes/hardware-map.conf";
+cfs_register_file($FILENAME, \&read_hardware_map, \&write_hardware_map);
+
+# a mapping format per type
+my $format = {
+ usb => {
+ 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,
+ },
+ path => {
+ description => "The path to the usb device.",
+ type => 'string',
+ optional => 1,
+ pattern => qr/^(\d+)\-(\d+(\.\d+)*)$/,
+ },
+ comment => {
+ description => "Description.",
+ type => 'string',
+ optional => 1,
+ maxLength => 4096,
+ },
+ },
+ pci => {
+ 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,
+ },
+ path => {
+ 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 => "(?:[a-f0-9]{4,}:)?[a-f0-9]{2}:[a-f0-9]{2}(?:\.[a-f0-9])?",
+ },
+ mdev => {
+ description => "The Device supports mediated devices.",
+ type => 'boolean',
+ optional => 1,
+ default => 0,
+ },
+ iommugroup => {
+ type => 'integer',
+ description => "The IOMMU group in which the device is in.",
+ optional => 1,
+ },
+ comment => {
+ description => "Description.",
+ type => 'string',
+ optional => 1,
+ maxLength => 4096,
+ },
+ },
+};
+
+my $name_format = {
+ description => "The custom name for the device",
+ type => 'string',
+ format => 'pve-configid',
+};
+
+sub find_device_on_current_node {
+ my ($type, $id) = @_;
+
+ my $cfg = config();
+ my $node = PVE::INotify::nodename();
+
+ return undef if !defined($cfg->{$type}->{$id}) || !defined($cfg->{$type}->{$id}->{$node});
+ return $cfg->{$type}->{$id}->{$node};
+}
+
+sub config {
+ return cfs_read_file($FILENAME);
+}
+
+sub lock_config {
+ my ($code, $errmsg) = @_;
+
+ cfs_lock_file($FILENAME, undef, $code);
+ if (my $err = $@) {
+ $errmsg ? die "$errmsg: $err" : die $err;
+ }
+}
+
+sub write_config {
+ my ($cfg) = @_;
+
+ cfs_write_file($FILENAME, $cfg);
+}
+
+sub check_prop {
+ my ($schema, $key, $value) = @_;
+ my $errors = {};
+ PVE::JSONSchema::check_prop($value, $schema, '', $errors);
+ if (scalar(keys %$errors)) {
+ die "$errors->{$key}\n" if $errors->{$key};
+ die "$errors->{_root}\n" if $errors->{_root};
+ die "unknown error\n";
+ }
+}
+
+sub check_config {
+ my ($cfg) = @_;
+
+ for my $type (keys %$format) {
+ my $type_cfg = $cfg->{$type};
+ my $type_format = $format->{$type};
+
+ for my $name (keys %$type_cfg) {
+ check_prop($name_format, 'name', $name);
+
+ for my $node (keys $type_cfg->{$name}->%*) {
+ check_prop(get_standard_option('pve-node'), 'node', $node);
+ my $entry = $type_cfg->{$name}->{$node};
+
+ # check required props
+ for my $prop (keys %$type_format) {
+ next if $type_format->{$prop}->{optional};
+ die "missing property '$prop' for $type entry '$name'\n"
+ if !defined($entry->{$prop});
+ }
+
+ for my $prop (keys %$entry) {
+ check_prop($type_format->{$prop}, $prop, $entry->{$prop});
+ }
+ }
+ }
+ }
+}
+
+sub read_hardware_map {
+ my ($filename, $raw) = @_;
+
+ my $digest = Digest::SHA::sha1_hex($raw);
+
+ if (!defined($raw) || $raw eq '') {
+ return {
+ digest => $digest,
+ };
+ }
+
+ my $cfg = from_json($raw);
+ check_config($cfg);
+ $cfg->{digest} = $digest;
+
+ return $cfg;
+}
+
+sub write_hardware_map {
+ my ($filename, $cfg) = @_;
+
+ check_config($cfg);
+
+ return to_json($cfg);
+}
+
+my $pci_valid = sub {
+ my ($cfg) = @_;
+
+ my $path = $cfg->{path} // '';
+
+ 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 $correct_props = {
+ vendor => $info->{vendor},
+ device => $info->{device},
+ 'subsystem-vendor' => $info->{'subsystem_vendor'},
+ 'subsystem-device' => $info->{'subsystem_device'},
+ mdev => $info->{mdev},
+ iommugroup => $info->{iommugroup},
+ };
+
+ for my $prop (sort keys %$correct_props) {
+ next if !defined($correct_props->{$prop}) && !defined($cfg->{$prop});
+ die "no '$prop' for device '$path'\n"
+ if defined($correct_props->{$prop}) && !defined($cfg->{$prop});
+ die "'$prop' configured but should not be\n"
+ if !defined($correct_props->{$prop}) && defined($cfg->{$prop});
+
+ my $correct_prop = $correct_props->{$prop};
+ $correct_prop =~ s/^0x//;
+ my $configured_prop = $cfg->{$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;
+};
+
+my $usb_valid = sub {
+ my ($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->{path}) {
+ 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;
+};
+
+sub assert_device_valid {
+ my ($type, $cfg) = @_;
+
+ if ($type eq 'usb') {
+ return $usb_valid->($cfg);
+ } elsif ($type eq 'pci') {
+ return $pci_valid->($cfg);
+ }
+
+ die "invalid type $type\n";
+}
+
+sub createSchema {
+ my ($type) = @_;
+
+ my $schema = {};
+
+ $schema->{name} = $name_format;
+ $schema->{node} = get_standard_option('pve-node');
+
+ for my $opt (sort keys $format->{$type}->%*) {
+ $schema->{$opt} = $format->{$type}->{$opt};
+ }
+
+ return {
+ additionalProperties => 0,
+ properties => $schema,
+ };
+
+}
+
+sub updateSchema {
+ my ($type) = @_;
+
+ my $schema = {};
+
+ $schema->{name} = $name_format;
+ $schema->{node} = get_standard_option('pve-node');
+
+ my $deletable = [];
+
+ for my $opt (sort keys $format->{$type}->%*) {
+ $schema->{$opt} = dclone($format->{$type}->{$opt});
+ $schema->{$opt}->{optional} = 1;
+ if ($format->{$type}->{$opt}->{optional}) {
+ push @$deletable, $opt;
+ }
+ }
+
+ my $deletable_pattern = join('|', @$deletable);
+
+ $schema->{delete} = {
+ type => 'string', format => 'pve-configid-list',
+ description => "A list of settings you want to delete.",
+ maxLength => 4096,
+ optional => 1,
+ };
+
+ $schema->{digest} = get_standard_option('pve-config-digest');
+
+ return {
+ additionalProperties => 0,
+ properties => $schema,
+ };
+}
+
+sub options {
+ my ($type) = @_;
+
+ my $opts = {};
+
+ for my $opt (sort keys $format->{$type}->%*) {
+ $opts->{$opt}->{optional} = $format->{$type}->{$opt}->{optional};
+ }
+
+ return $opts;
+}
+
+1;
--
2.30.2
next prev parent reply other threads:[~2022-07-19 11:47 UTC|newest]
Thread overview: 37+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-07-19 11:46 [pve-devel] [PATCH many] add cluster-wide hardware device mapping Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH cluster 1/1] add nodes/hardware-map.conf Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH access-control 1/2] PVE/AccessControl: add Hardware.* privileges and /hardware/ paths Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH access-control 2/2] PVE/RPCEnvironment: add helper for checking hw permissions Dominik Csapak
2022-08-01 12:01 ` Fabian Grünbichler
2022-08-09 6:55 ` Dominik Csapak
2022-07-19 11:46 ` Dominik Csapak [this message]
[not found] ` <<20220719114639.3035048-5-d.csapak@proxmox.com>
2022-08-01 12:58 ` [pve-devel] [PATCH common 1/1] add PVE/HardwareMap Fabian Grünbichler
2022-08-09 7:29 ` Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH qemu-server 1/7] PVE/QemuServer: allow mapped usb devices in config Dominik Csapak
[not found] ` <<20220719114639.3035048-6-d.csapak@proxmox.com>
2022-08-01 12:59 ` Fabian Grünbichler
2022-07-19 11:46 ` [pve-devel] [PATCH qemu-server 2/7] PVE/QemuServer: allow mapped pci deviced " Dominik Csapak
[not found] ` <<20220719114639.3035048-7-d.csapak@proxmox.com>
2022-08-01 12:59 ` Fabian Grünbichler
2022-07-19 11:46 ` [pve-devel] [PATCH qemu-server 3/7] PVE/API2/Qemu: add permission checks for mapped usb devices Dominik Csapak
[not found] ` <<20220719114639.3035048-8-d.csapak@proxmox.com>
2022-08-01 13:01 ` Fabian Grünbichler
2022-08-09 7:32 ` 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
2022-07-19 11:46 ` [pve-devel] [PATCH qemu-server 5/7] PVE/QemuServer: extend 'check_local_resources' for mapped resources Dominik Csapak
[not found] ` <<<20220719114639.3035048-10-d.csapak@proxmox.com>
2022-08-01 13:02 ` Fabian Grünbichler
2022-07-19 11:46 ` [pve-devel] [PATCH qemu-server 6/7] PVE/API2/Qemu: migrate preconditions: use new check_local_resources info Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH qemu-server 7/7] PVE/QemuMigrate: check for mapped resources on migration Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH manager 01/12] PVE/API2/Hardware: add Mapping.pm Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH manager 02/12] PVE/API2/Cluster: add Hardware mapping list api call Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH manager 03/12] ui: form/USBSelector: make it more flexible with nodename Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH manager 04/12] ui: form: add PCIMapSelector Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH manager 05/12] ui: form: add USBMapSelector Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH manager 06/12] ui: qemu/PCIEdit: rework panel to add a mapped configuration Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH manager 07/12] ui: qemu/USBEdit: add 'mapped' device case Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH manager 08/12] ui: add window/PCIEdit: edit window for pci mappings Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH manager 09/12] ui: add window/USBEdit: edit window for usb mappings Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH manager 10/12] ui: add dc/HardwareView: a CRUD interface for hardware mapping Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH manager 11/12] ui: window/Migrate: allow mapped devices Dominik Csapak
2022-07-19 11:46 ` [pve-devel] [PATCH manager 12/12] ui: improve permission handling for hardware Dominik Csapak
2022-07-19 13:26 ` [pve-devel] [PATCH many] add cluster-wide hardware device mapping Dominik Csapak
[not found] ` <mailman.329.1658406652.464.pve-devel@lists.proxmox.com>
2022-07-21 14:48 ` Dominik Csapak
2022-08-02 15:59 ` DERUMIER, Alexandre
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20220719114639.3035048-5-d.csapak@proxmox.com \
--to=d.csapak@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox