From c.ebner at proxmox.com Mon Oct 2 09:16:42 2023 From: c.ebner at proxmox.com (Christian Ebner) Date: Mon, 2 Oct 2023 09:16:42 +0200 (CEST) Subject: [pve-devel] [PATCH pve-manager] website: update external links to www.proxmox.com In-Reply-To: References: <20230811104654.56916-1-c.ebner@proxmox.com> Message-ID: <52833257.5525.1696231002479@webmail.proxmox.com> Okay, thanks for the clarification. > On 30.09.2023 10:07 CEST Thomas Lamprecht wrote: > > > Am 11/08/2023 um 12:46 schrieb Christian Ebner: > > During the redesign of www.proxmox.com the menu structure and therefore > > some url changed. Update the external link in order to avoid an > > unneccessary redirect > > > > Signed-off-by: Christian Ebner > > --- > > PVE/API2/Subscription.pm | 2 +- > > aplinfo/aplinfo.dat | 4 ++-- > > www/manager6/Utils.js | 2 +- > > www/manager6/dc/Summary.js | 2 +- > > www/mobile/WidgetToolkitUtils.js | 1 - > > 5 files changed, 5 insertions(+), 6 deletions(-) > > > > diff --git a/PVE/API2/Subscription.pm b/PVE/API2/Subscription.pm > > index c7b81ee9..96fcd4e5 100644 > > --- a/PVE/API2/Subscription.pm > > +++ b/PVE/API2/Subscription.pm > > @@ -128,7 +128,7 @@ __PACKAGE__->register_method ({ > > my $has_permission = $rpcenv->check($authuser, "/nodes/$node", ['Sys.Audit'], 1); > > > > my $server_id = PVE::API2Tools::get_hwaddress(); > > - my $url = "https://www.proxmox.com/proxmox-ve/pricing"; > > + my $url = "https://www.proxmox.com/en/proxmox-virtual-environment/pricing"; > > > > my $info = read_etc_subscription(); > > if (!$info) { > > diff --git a/aplinfo/aplinfo.dat b/aplinfo/aplinfo.dat > > index 95a8be5a..8382bd7d 100644 > > --- a/aplinfo/aplinfo.dat > > +++ b/aplinfo/aplinfo.dat > > @@ -135,7 +135,7 @@ Architecture: amd64 > > Location: mail/proxmox-mailgateway-7.3-standard_7.3-1_amd64.tar.zst > > md5sum: 6c130003f9880ae66dca0603d7b7ca87 > > sha512sum: 2fdf1dc24306bbaa2ef9a0f322416ca15b97b7d19f84b83743c7afc896095c398241fbc2eb41a33a69f3f275ce4c4cb6425edc5538831b4650d39a5e44fdbc25 > > -Infopage: https://www.proxmox.com/de/proxmox-mail-gateway > > +Infopage: https://www.proxmox.com/en/proxmox-mail-gateway/overview > > Description: Proxmox Mailgateway 7.3 > > A full featured mail proxy for spam and virus filtering, optimized for container environment. > > > > @@ -149,7 +149,7 @@ Architecture: amd64 > > Location: mail/proxmox-mailgateway-8.0-standard_8.0-1_amd64.tar.zst > > md5sum: 7d321e5dfc6e1005231586d1871e3625 > > sha512sum: be5efcb8ee97f2bb1c638360191eda19f49e2063acb88da55c948c90c091063972cc9ea29e6aeaa4a85733e0fb2c99ea905d665ac693cb2bf06b091c4baf781f > > -Infopage: https://www.proxmox.com/de/proxmox-mail-gateway > > +Infopage: https://www.proxmox.com/en/proxmox-mail-gateway/overview > > Description: Proxmox Mailgateway 8.0 > > A full featured mail proxy for spam and virus filtering, optimized for container environment. > > > > yeah no, those are only tracked here to provide an initial list of available > templates after installation, i.e., before the daily update service does the > equivalent of `pveam update` and overrides these again. > > The next dab appliance update will "fix" these, but it shouldn't matter, > those links are not easily accessible by users and we (hopefully) keep > old URLs reachable for a long time.. From l.wagner at proxmox.com Mon Oct 2 09:40:49 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 09:40:49 +0200 Subject: [pve-devel] [PATCH proxmox-perl-rs] notify context: fix 'default_sendmail_from' context method Message-ID: <20231002074049.157507-1-l.wagner@proxmox.com> The name of the configuration option in datacenter.cfg is `email_from` and not `mail_from`. Signed-off-by: Lukas Wagner --- Reported in our forum: https://forum.proxmox.com/threads/mail-alerts-not-sent-with-datacenter-default.134305/ pve-rs/src/notify_context.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pve-rs/src/notify_context.rs b/pve-rs/src/notify_context.rs index 48623fd..3cf3e18 100644 --- a/pve-rs/src/notify_context.rs +++ b/pve-rs/src/notify_context.rs @@ -58,7 +58,7 @@ impl Context for PVEContext { fn default_sendmail_from(&self) -> String { let content = attempt_file_read("/etc/pve/datacenter.cfg"); content - .and_then(|content| lookup_datacenter_config_key(&content, "mail_from")) + .and_then(|content| lookup_datacenter_config_key(&content, "email_from")) .unwrap_or_else(|| String::from("root")) } -- 2.39.2 From l.wagner at proxmox.com Mon Oct 2 10:06:16 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 10:06:16 +0200 Subject: [pve-devel] [PATCH v2 proxmox 03/11] notify: introduce Error::Generic In-Reply-To: <20231002080624.198759-1-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> Message-ID: <20231002080624.198759-4-l.wagner@proxmox.com> ... as leaf error-type for anything for which we do not necessarily want a separate enum variant. Signed-off-by: Lukas Wagner --- proxmox-notify/src/lib.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/proxmox-notify/src/lib.rs b/proxmox-notify/src/lib.rs index 7500778..f7d480c 100644 --- a/proxmox-notify/src/lib.rs +++ b/proxmox-notify/src/lib.rs @@ -25,13 +25,22 @@ mod config; #[derive(Debug)] pub enum Error { + /// There was an error serializing the config ConfigSerialization(Box), + /// There was an error deserializing the config ConfigDeserialization(Box), + /// An endpoint failed to send a notification NotifyFailed(String, Box), + /// A target does not exist TargetDoesNotExist(String), + /// Testing one or more notification targets failed TargetTestFailed(Vec>), + /// A filter could not be applied FilterFailed(String), + /// The notification's template string could not be rendered RenderError(Box), + /// Generic error for anything else + Generic(String), } impl Display for Error { @@ -60,6 +69,7 @@ impl Display for Error { write!(f, "could not apply filter: {message}") } Error::RenderError(err) => write!(f, "could not render notification template: {err}"), + Error::Generic(message) => f.write_str(message), } } } @@ -74,6 +84,7 @@ impl StdError for Error { Error::TargetTestFailed(errs) => Some(&*errs[0]), Error::FilterFailed(_) => None, Error::RenderError(err) => Some(&**err), + Error::Generic(_) => None, } } } -- 2.39.2 From l.wagner at proxmox.com Mon Oct 2 10:06:15 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 10:06:15 +0200 Subject: [pve-devel] [PATCH v2 proxmox 02/11] sys: email: add `forward` In-Reply-To: <20231002080624.198759-1-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> Message-ID: <20231002080624.198759-3-l.wagner@proxmox.com> This new function forwards an email to new recipients. Signed-off-by: Lukas Wagner --- proxmox-sys/src/email.rs | 52 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/proxmox-sys/src/email.rs b/proxmox-sys/src/email.rs index 8b3a1b6..c94f634 100644 --- a/proxmox-sys/src/email.rs +++ b/proxmox-sys/src/email.rs @@ -3,7 +3,7 @@ use std::io::Write; use std::process::{Command, Stdio}; -use anyhow::{bail, Error}; +use anyhow::{bail, format_err, Error}; /// Sends multi-part mail with text and/or html to a list of recipients /// @@ -110,6 +110,56 @@ pub fn sendmail( Ok(()) } +/// Forwards an email message to a given list of recipients. +/// +/// ``sendmail`` is used for sending the mail, thus `message` must be +/// compatible with that (the message is piped into stdin unmodified). +pub fn forward( + mailto: &[&str], + mailfrom: &str, + message: &[u8], + uid: Option, +) -> Result<(), Error> { + use std::os::unix::process::CommandExt; + + if mailto.is_empty() { + bail!("At least one recipient has to be specified!") + } + + let mut builder = Command::new("/usr/sbin/sendmail"); + + builder + .args([ + "-N", "never", // never send DSN (avoid mail loops) + "-f", mailfrom, "--", + ]) + .args(mailto) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + + if let Some(uid) = uid { + builder.uid(uid); + } + + let mut process = builder + .spawn() + .map_err(|err| format_err!("could not spawn sendmail process: {err}"))?; + + process + .stdin + .take() + .unwrap() + .write_all(message) + .map_err(|err| format_err!("couldn't write to sendmail stdin: {err}"))?; + + process + .wait() + .map_err(|err| format_err!("sendmail did not exit successfully: {err}"))?; + + Ok(()) +} + #[cfg(test)] mod test { use crate::email::sendmail; -- 2.39.2 From l.wagner at proxmox.com Mon Oct 2 10:06:24 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 10:06:24 +0200 Subject: [pve-devel] [PATCH v2 pve-docs 11/11] notification: add docs for system mail forwarding In-Reply-To: <20231002080624.198759-1-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> Message-ID: <20231002080624.198759-12-l.wagner@proxmox.com> Signed-off-by: Lukas Wagner --- notifications.adoc | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/notifications.adoc b/notifications.adoc index c4d2931..0b00b1e 100644 --- a/notifications.adoc +++ b/notifications.adoc @@ -19,9 +19,10 @@ such as: | `fencing` | The {pve} HA manager has fenced a node | `error` | `replication` | A storage replication job has failed | `error` | `vzdump` | vzdump backup finished | `info` (`error` on failure) +| `system-mail` | An email was sent to the local root user| `error` |=========================================================================== -In the 'Notification' panel of the datacenter view, the system's behavior can be +In the 'Notifications' panel of the datacenter view, the system's behavior can be configured for all events except backup jobs. For backup jobs, the settings can be found in the respective backup job configuration. For every notification event there is an option to configure the notification @@ -151,9 +152,22 @@ included in the group. If a group/endpoint is configured to use a filter, the user must have the `Mapping.Use` permission for the filter as well. - - - - - - +System Mail Forwarding +--------------------- + +Certain local system daemons, such as `smartd`, generate notification emails +that are initially directed to the local `root` user. {pve} will +forward these emails to a customizable notification target. + +By default, these emails are sent to the email address that is configured for +the `root at pam` user using the predefined `mail-to-root` notification target. +However, similar to any other notification event, this behavior can be +adjusted within the 'Notifications' panel by making changes to the +'System Mail' setting. + +When the forwarding process involves an email-based target +(like `sendmail` or `smtp`), the email is forwarded exactly as received, with all +original mail headers remaining intact. For all other targets, +the system tries to extract both a subject line and the main text body +from the email content. In instances where emails solely consist of HTML +content, they will be transformed into plain text format during this process. -- 2.39.2 From l.wagner at proxmox.com Mon Oct 2 10:06:22 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 10:06:22 +0200 Subject: [pve-devel] [PATCH v2 pve-manager 09/11] ui: notify: add system-mail settings, configuring mail forwarding In-Reply-To: <20231002080624.198759-1-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> Message-ID: <20231002080624.198759-10-l.wagner@proxmox.com> The 'Notifications' panel in Datacenter view now features a new entry 'System mail', allowing the user to configure target and policy for mails sent to the local root user. Signed-off-by: Lukas Wagner --- www/manager6/dc/NotificationEvents.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/www/manager6/dc/NotificationEvents.js b/www/manager6/dc/NotificationEvents.js index 18816290..40e9a65f 100644 --- a/www/manager6/dc/NotificationEvents.js +++ b/www/manager6/dc/NotificationEvents.js @@ -240,6 +240,33 @@ Ext.define('PVE.dc.NotificationEvents', { ], }); + me.addInputPanelRow('system-mail', 'notify', gettext('System Mail'), { + renderer: (value, metaData, record, rowIndex, colIndex, store) => + render_value(store, 'target-system-mail', 'system-mail', gettext('Always')), + url: "/api2/extjs/cluster/options", + items: [ + { + xtype: 'pveNotificationEventsPolicySelector', + name: 'system-mail', + fieldLabel: gettext('Notify'), + comboItems: [ + ['__default__', `${Proxmox.Utils.defaultText} (${gettext('Always')})`], + ['always', gettext('Always')], + ['never', gettext('Never')], + ], + warningRef: 'warning', + warnIfValIs: 'never', + }, + { + xtype: 'pveNotificationEventsTargetSelector', + name: 'target-system-mail', + }, + { + xtype: 'pveNotificationEventDisabledWarning', + reference: 'warning', + }, + ], + }); // Hack: Also load the notify property to make it accessible // for our render functions. me.rows.notify = { -- 2.39.2 From l.wagner at proxmox.com Mon Oct 2 10:06:13 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 10:06:13 +0200 Subject: [pve-devel] [PATCH v2 many 00/11] notifications: feed system mails into proxmox_notify Message-ID: <20231002080624.198759-1-l.wagner@proxmox.com> The aim of this patch series is to adapt `proxmox-mail-forward` so that it forwards emails that were sent to the local root user through the `proxmox_notify` crate. A short summary of the status quo: Any mail that is sent to the local `root` user is forwarded by postfix to the `proxmox-mail-forward` binary, which receives the mail via STDIN. `proxmox-mail-forward` looks up the email address configured for the `root at pam` user in /etc/{proxmox-backup,pve}/user.cfg and then forwards the mail to this address by calling `sendmail` This patch series modifies `proxmox-mail-forward` in the following way: `proxmox-mail-forward` instantiates the configuration for `proxmox_notify` by reading `/etc/{proxmox-backup,pve}/notifications.cfg. Also, it looks up the policy for system mail (target/if to forward at all) in `node.cfg/datacenter.cfg`. Following that, the mail is passed to `proxmox_notify`, which sends it to the specified target(s). If no target is configured/configuration files do not exist, then the mail is forwarded using the `mail-to-root` target, which always exists. In this way the changes should be 100% backwards compatible. One small change in behavior can occur if PBS is co-installed on a PVE host. Here it could happen that a mail is forwarded twice: Once for for notification configuration for PVE, and once for the config for PBS. Unfortunately there is no easy way to perform any useful 'deduplication' there (by target name does not really work, since they could have different configuration/recipients; by 'mail-address' would work for mail-based targets, however this involves some pretty invasive changes and still does not work for targets that are not mail-based). Personally I feel that we should just add a section about this behavior in the docs (once proxmox_notify is fully integrated in PBS), instructing the user to set `system-mail` to `never` in `node.cfg` (don't forward mails). Alternatively we could try to detect co-installations and only forward for the target of one of both products. However, I prefer the first option. `proxmox-notify` now depends on a new crate `mail-parser` to parse email headers (something I *really* don't want to implement myself from scratch). The new dependency is not packaged yet, the necessary debcargo-conf changes are included in the first patch. @TESTERS: I can provide a pre-built deb for `mail-parser`. Changelog: - v1 -> v2: - Rebased - Apply the same fix for the PVE context as in [1] [1] https://lists.proxmox.com/pipermail/pve-devel/2023-October/059294.html debcargo-conf: Lukas Wagner (1): package mail-parser 0.8.2 src/mail-parser/debian/changelog | 6 ++ src/mail-parser/debian/copyright | 49 ++++++++++++ .../debian/copyright.debcargo.hint | 77 +++++++++++++++++++ src/mail-parser/debian/debcargo.toml | 2 + 4 files changed, 134 insertions(+) create mode 100644 src/mail-parser/debian/changelog create mode 100644 src/mail-parser/debian/copyright create mode 100644 src/mail-parser/debian/copyright.debcargo.hint create mode 100644 src/mail-parser/debian/debcargo.toml proxmox: Lukas Wagner (4): sys: email: add `forward` notify: introduce Error::Generic notify: add mechanisms for email message forwarding notify: add PVE/PBS context Cargo.toml | 1 + proxmox-notify/Cargo.toml | 5 +- proxmox-notify/src/context/common.rs | 27 ++++ .../src/{context.rs => context/mod.rs} | 14 +- proxmox-notify/src/context/pbs.rs | 130 ++++++++++++++++++ proxmox-notify/src/context/pve.rs | 82 +++++++++++ proxmox-notify/src/endpoints/gotify.rs | 21 +-- proxmox-notify/src/endpoints/sendmail.rs | 62 +++++---- proxmox-notify/src/filter.rs | 8 +- proxmox-notify/src/lib.rs | 109 +++++++++++++-- proxmox-sys/src/email.rs | 52 ++++++- 11 files changed, 451 insertions(+), 60 deletions(-) create mode 100644 proxmox-notify/src/context/common.rs rename proxmox-notify/src/{context.rs => context/mod.rs} (54%) create mode 100644 proxmox-notify/src/context/pbs.rs create mode 100644 proxmox-notify/src/context/pve.rs proxmox-perl-rs: Lukas Wagner (2): notify: construct Notification via constructor pve-rs: notify: remove notify_context for PVE common/src/notify.rs | 8 +-- pve-rs/Cargo.toml | 2 +- pve-rs/src/lib.rs | 7 ++- pve-rs/src/notify_context.rs | 117 ----------------------------------- 4 files changed, 6 insertions(+), 128 deletions(-) delete mode 100644 pve-rs/src/notify_context.rs pve-cluster: Lukas Wagner (1): datacenter config: add new parameters for system mail forwarding src/PVE/DataCenterConfig.pm | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) pve-manager: Lukas Wagner (1): ui: notify: add system-mail settings, configuring mail forwarding www/manager6/dc/NotificationEvents.js | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) proxmox-mail-forward: Lukas Wagner (1): feed forwarded mails into proxmox_notify Cargo.toml | 8 +- src/main.rs | 348 +++++++++++++++++++++++++++++++++++----------------- 2 files changed, 238 insertions(+), 118 deletions(-) pve-docs: Lukas Wagner (1): notification: add docs for system mail forwarding notifications.adoc | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) Summary over all repositories: 24 files changed, 899 insertions(+), 313 deletions(-) -- murpp v0.4.0 From l.wagner at proxmox.com Mon Oct 2 10:06:17 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 10:06:17 +0200 Subject: [pve-devel] [PATCH v2 proxmox 04/11] notify: add mechanisms for email message forwarding In-Reply-To: <20231002080624.198759-1-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> Message-ID: <20231002080624.198759-5-l.wagner@proxmox.com> As preparation for the integration of `proxmox-mail-foward` into the notification system, this commit makes a few changes that allow us to forward raw email messages (as passed from postfix). For mail-based notification targets, the email will be forwarded as-is, including all headers. The only thing that changes is the message envelope. For other notification targets, the mail is parsed using the `mail-parser` crate, which allows us to extract a subject and a body. As a body we use the plain-text version of the mail. If an email is HTML-only, the `mail-parser` crate will automatically attempt to transform the HTML into readable plain text. Signed-off-by: Lukas Wagner --- Cargo.toml | 1 + proxmox-notify/Cargo.toml | 2 + proxmox-notify/src/endpoints/gotify.rs | 21 +++-- proxmox-notify/src/endpoints/sendmail.rs | 62 ++++++++------- proxmox-notify/src/filter.rs | 8 +- proxmox-notify/src/lib.rs | 98 ++++++++++++++++++++---- 6 files changed, 138 insertions(+), 54 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e334ac1..9adfe59 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ lazy_static = "1.4" ldap3 = { version = "0.11", default-features = false } libc = "0.2.107" log = "0.4.17" +mail-parser = "0.8.2" native-tls = "0.2" nix = "0.26.1" once_cell = "1.3.1" diff --git a/proxmox-notify/Cargo.toml b/proxmox-notify/Cargo.toml index 1541b8b..441b6e1 100644 --- a/proxmox-notify/Cargo.toml +++ b/proxmox-notify/Cargo.toml @@ -11,6 +11,7 @@ exclude.workspace = true handlebars = { workspace = true } lazy_static.workspace = true log.workspace = true +mail-parser = { workspace = true, optional = true } once_cell.workspace = true openssl.workspace = true proxmox-http = { workspace = true, features = ["client-sync"], optional = true } @@ -26,5 +27,6 @@ serde_json.workspace = true [features] default = ["sendmail", "gotify"] +mail-forwarder = ["dep:mail-parser"] sendmail = ["dep:proxmox-sys"] gotify = ["dep:proxmox-http"] diff --git a/proxmox-notify/src/endpoints/gotify.rs b/proxmox-notify/src/endpoints/gotify.rs index 83df41f..261573b 100644 --- a/proxmox-notify/src/endpoints/gotify.rs +++ b/proxmox-notify/src/endpoints/gotify.rs @@ -11,7 +11,7 @@ use proxmox_schema::{api, Updater}; use crate::context::context; use crate::renderer::TemplateRenderer; use crate::schema::ENTITY_NAME_SCHEMA; -use crate::{renderer, Endpoint, Error, Notification, Severity}; +use crate::{renderer, Content, Endpoint, Error, Notification, Severity}; fn severity_to_priority(level: Severity) -> u32 { match level { @@ -87,13 +87,18 @@ impl Endpoint for GotifyEndpoint { fn send(&self, notification: &Notification) -> Result<(), Error> { let properties = notification.properties.as_ref(); - let title = renderer::render_template( - TemplateRenderer::Plaintext, - ¬ification.title, - properties, - )?; - let message = - renderer::render_template(TemplateRenderer::Plaintext, ¬ification.body, properties)?; + let (title, message) = match ¬ification.content { + Content::Template { title, body } => { + let rendered_title = + renderer::render_template(TemplateRenderer::Plaintext, title, properties)?; + let rendered_message = + renderer::render_template(TemplateRenderer::Plaintext, body, properties)?; + + (rendered_title, rendered_message) + } + #[cfg(feature = "mail-forwarder")] + Content::ForwardedMail { title, body, .. } => (title.clone(), body.clone()), + }; // We don't have a TemplateRenderer::Markdown yet, so simply put everything // in code tags. Otherwise tables etc. are not formatted properly diff --git a/proxmox-notify/src/endpoints/sendmail.rs b/proxmox-notify/src/endpoints/sendmail.rs index 26e2a17..9cc3f31 100644 --- a/proxmox-notify/src/endpoints/sendmail.rs +++ b/proxmox-notify/src/endpoints/sendmail.rs @@ -8,7 +8,7 @@ use proxmox_schema::{api, Updater}; use crate::context::context; use crate::renderer::TemplateRenderer; use crate::schema::{EMAIL_SCHEMA, ENTITY_NAME_SCHEMA, USER_SCHEMA}; -use crate::{renderer, Endpoint, Error, Notification}; +use crate::{renderer, Content, Endpoint, Error, Notification}; pub(crate) const SENDMAIL_TYPENAME: &str = "sendmail"; @@ -103,40 +103,44 @@ impl Endpoint for SendmailEndpoint { } let properties = notification.properties.as_ref(); - - let subject = renderer::render_template( - TemplateRenderer::Plaintext, - ¬ification.title, - properties, - )?; - let html_part = - renderer::render_template(TemplateRenderer::Html, ¬ification.body, properties)?; - let text_part = - renderer::render_template(TemplateRenderer::Plaintext, ¬ification.body, properties)?; - - let author = self - .config - .author - .clone() - .unwrap_or_else(|| context().default_sendmail_author()); - + let recipients_str: Vec<&str> = recipients.iter().map(String::as_str).collect(); let mailfrom = self .config .from_address .clone() .unwrap_or_else(|| context().default_sendmail_from()); - let recipients_str: Vec<&str> = recipients.iter().map(String::as_str).collect(); - - proxmox_sys::email::sendmail( - &recipients_str, - &subject, - Some(&text_part), - Some(&html_part), - Some(&mailfrom), - Some(&author), - ) - .map_err(|err| Error::NotifyFailed(self.config.name.clone(), err.into())) + match ¬ification.content { + Content::Template { title, body } => { + let subject = + renderer::render_template(TemplateRenderer::Plaintext, title, properties)?; + let html_part = + renderer::render_template(TemplateRenderer::Html, body, properties)?; + let text_part = + renderer::render_template(TemplateRenderer::Plaintext, body, properties)?; + + let author = self + .config + .author + .clone() + .unwrap_or_else(|| context().default_sendmail_author()); + + proxmox_sys::email::sendmail( + &recipients_str, + &subject, + Some(&text_part), + Some(&html_part), + Some(&mailfrom), + Some(&author), + ) + .map_err(|err| Error::NotifyFailed(self.config.name.clone(), err.into())) + } + #[cfg(feature = "mail-forwarder")] + Content::ForwardedMail { raw, uid, .. } => { + proxmox_sys::email::forward(&recipients_str, &mailfrom, raw, *uid) + .map_err(|err| Error::NotifyFailed(self.config.name.clone(), err.into())) + } + } } fn name(&self) -> &str { diff --git a/proxmox-notify/src/filter.rs b/proxmox-notify/src/filter.rs index 748ec4e..d052512 100644 --- a/proxmox-notify/src/filter.rs +++ b/proxmox-notify/src/filter.rs @@ -160,7 +160,7 @@ impl<'a> FilterMatcher<'a> { #[cfg(test)] mod tests { use super::*; - use crate::config; + use crate::{config, Content}; fn parse_filters(config: &str) -> Result, Error> { let (config, _) = config::config(config)?; @@ -169,8 +169,10 @@ mod tests { fn empty_notification_with_severity(severity: Severity) -> Notification { Notification { - title: String::new(), - body: String::new(), + content: Content::Template { + title: String::new(), + body: String::new(), + }, severity, properties: Default::default(), } diff --git a/proxmox-notify/src/lib.rs b/proxmox-notify/src/lib.rs index f7d480c..eebc57a 100644 --- a/proxmox-notify/src/lib.rs +++ b/proxmox-notify/src/lib.rs @@ -116,17 +116,79 @@ pub trait Endpoint { fn filter(&self) -> Option<&str>; } +#[derive(Debug, Clone)] +pub enum Content { + /// Title and body will be rendered as a template + Template { title: String, body: String }, + /// A special content type for forwarded mails. Contains the raw content of the original mail + /// as well as a (non-template) fallback title and body for endpoints that are not based + /// on email. + #[cfg(feature = "mail-forwarder")] + ForwardedMail { + /// Raw mail contents + raw: Vec, + /// Fallback title + title: String, + /// Fallback body + body: String, + /// UID to use when calling sendmail + #[allow(dead_code)] // Unused in some feature flag permutations + uid: Option, + }, +} + #[derive(Debug, Clone)] /// Notification which can be sent pub struct Notification { /// Notification severity - pub severity: Severity, - /// The title of the notification - pub title: String, - /// Notification text - pub body: String, + severity: Severity, + /// Notification content + #[allow(dead_code)] // Unused in some feature flag permutations + content: Content, /// Additional metadata for the notification - pub properties: Option, + #[allow(dead_code)] // Unused in some feature flag permutations + properties: Option, +} + +impl Notification { + pub fn new_templated>( + severity: Severity, + title: S, + body: S, + properties: Option, + ) -> Self { + Self { + severity, + content: Content::Template { + title: title.as_ref().to_string(), + body: body.as_ref().to_string(), + }, + properties, + } + } + + #[cfg(feature = "mail-forwarder")] + pub fn new_forwarded_mail(raw_mail: &[u8], uid: Option) -> Result { + let message = mail_parser::Message::parse(raw_mail) + .ok_or_else(|| Error::Generic("could not parse forwarded email".to_string()))?; + + let title = message.subject().unwrap_or_default().into(); + let body = message.body_text(0).unwrap_or_default().into(); + + Ok(Self { + // Unfortunately we cannot reasonably infer the severity from the + // mail contents, so just set it to the highest for now so that + // it is not filtered out. + severity: Severity::Error, + content: Content::ForwardedMail { + raw: raw_mail.into(), + title, + body, + uid, + }, + properties: None, + }) + } } /// Notification configuration @@ -384,8 +446,10 @@ impl Bus { pub fn test_target(&self, target: &str) -> Result<(), Error> { let notification = Notification { severity: Severity::Info, - title: "Test notification".into(), - body: "This is a test of the notification target '{{ target }}'".into(), + content: Content::Template { + title: "Test notification".into(), + body: "This is a test of the notification target '{{ target }}'".into(), + }, properties: Some(json!({ "target": target })), }; @@ -474,8 +538,10 @@ mod tests { bus.send( "endpoint", &Notification { - title: "Title".into(), - body: "Body".into(), + content: Content::Template { + title: "Title".into(), + body: "Body".into(), + }, severity: Severity::Info, properties: Default::default(), }, @@ -514,8 +580,10 @@ mod tests { bus.send( channel, &Notification { - title: "Title".into(), - body: "Body".into(), + content: Content::Template { + title: "Title".into(), + body: "Body".into(), + }, severity: Severity::Info, properties: Default::default(), }, @@ -582,8 +650,10 @@ mod tests { bus.send( "channel1", &Notification { - title: "Title".into(), - body: "Body".into(), + content: Content::Template { + title: "Title".into(), + body: "Body".into(), + }, severity, properties: Default::default(), }, -- 2.39.2 From l.wagner at proxmox.com Mon Oct 2 10:06:21 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 10:06:21 +0200 Subject: [pve-devel] [PATCH v2 pve-cluster 08/11] datacenter config: add new parameters for system mail forwarding In-Reply-To: <20231002080624.198759-1-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> Message-ID: <20231002080624.198759-9-l.wagner@proxmox.com> This commit adds two new paramters to the 'notify' property string: - 'system-mail': Determine whether mails to root should be forwarded by the notification system - 'system-mail-target': Determine the target to which the notification should be forwarded to. Signed-off-by: Lukas Wagner --- src/PVE/DataCenterConfig.pm | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/PVE/DataCenterConfig.pm b/src/PVE/DataCenterConfig.pm index 09be6eb..e2619c5 100644 --- a/src/PVE/DataCenterConfig.pm +++ b/src/PVE/DataCenterConfig.pm @@ -116,6 +116,28 @@ my $notification_format = { . " to root via a 'sendmail' notification endpoint.", optional => 1, }, + 'system-mail' => { + type => 'string', + enum => ['always', 'never'], + description => "Control if mails to the 'root' user should be forwarded.", + verbose_description => "Control if mails to the 'root' user should be forwarded.\n" + . "* 'always' forward always\n" + . "* 'never' forward never.\n" + . "For production systems, turning off mail forwarding is not" + . "recommended!\n", + default => 'always', + optional => 1, + }, + 'target-system-mail' => { + type => 'string', + format_description => 'TARGET', + description => "Control where mails to the 'root' user should be forwarded to.", + verbose_description => "Control where mails to the 'root' user should be forwarded to." + . " Has to be the name of a notification target (endpoint or notification group)." + . " If the 'target-system-mail' parameter is not set, the system will send mails" + . " to root via a 'sendmail' notification endpoint.", + optional => 1, + }, }; register_standard_option('pve-ha-shutdown-policy', { -- 2.39.2 From l.wagner at proxmox.com Mon Oct 2 10:06:14 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 10:06:14 +0200 Subject: [pve-devel] [PATCH v2 debcargo-conf 01/11] package mail-parser 0.8.2 In-Reply-To: <20231002080624.198759-1-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> Message-ID: <20231002080624.198759-2-l.wagner@proxmox.com> Signed-off-by: Lukas Wagner --- src/mail-parser/debian/changelog | 6 ++ src/mail-parser/debian/copyright | 49 ++++++++++++ .../debian/copyright.debcargo.hint | 77 +++++++++++++++++++ src/mail-parser/debian/debcargo.toml | 2 + 4 files changed, 134 insertions(+) create mode 100644 src/mail-parser/debian/changelog create mode 100644 src/mail-parser/debian/copyright create mode 100644 src/mail-parser/debian/copyright.debcargo.hint create mode 100644 src/mail-parser/debian/debcargo.toml diff --git a/src/mail-parser/debian/changelog b/src/mail-parser/debian/changelog new file mode 100644 index 000000000..2ed51934d --- /dev/null +++ b/src/mail-parser/debian/changelog @@ -0,0 +1,6 @@ +rust-mail-parser (0.8.2-1) UNRELEASED-FIXME-AUTOGENERATED-DEBCARGO; urgency=medium + + * Team upload. + * Package mail-parser 0.8.2 from crates.io using debcargo 2.6.0 + + -- Lukas Wagner Fri, 25 Aug 2023 14:10:10 +0200 diff --git a/src/mail-parser/debian/copyright b/src/mail-parser/debian/copyright new file mode 100644 index 000000000..28fc2722c --- /dev/null +++ b/src/mail-parser/debian/copyright @@ -0,0 +1,49 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: mail-parser +Upstream-Contact: Stalwart Labs +Source: https://github.com/stalwartlabs/mail-parser + +Files: * +Copyright: 2020-2022 Stalwart Labs +License: Apache-2.0 or MIT + +Files: tests/legacy/* +Copyright: 2010 Hunny Software, Inc. +License: UNKNOWN-LICENSE; + +Files: tests/malformed/* +Copyright: 2003-2018 Dovecot authors, licensed under MIT. +License: MIT + +Files: tests/thirdparty/* +Copyright: 2003-2018 Dovecot authors, licensed under MIT. +License: MIT + +Files: debian/* +Copyright: + 2023 Debian Rust Maintainers + 2023 Lukas Wagner +License: Apache-2.0 or MIT + +License: Apache-2.0 + Debian systems provide the Apache 2.0 license in + /usr/share/common-licenses/Apache-2.0 + +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/src/mail-parser/debian/copyright.debcargo.hint b/src/mail-parser/debian/copyright.debcargo.hint new file mode 100644 index 000000000..f0379206a --- /dev/null +++ b/src/mail-parser/debian/copyright.debcargo.hint @@ -0,0 +1,77 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: mail-parser +Upstream-Contact: Stalwart Labs +Source: https://github.com/stalwartlabs/mail-parser + +Files: * +Copyright: FIXME (overlay) UNKNOWN-YEARS Stalwart Labs +License: Apache-2.0 or MIT +Comment: + FIXME (overlay): Since upstream copyright years are not available in + Cargo.toml, they were extracted from the upstream Git repository. This may not + be correct information so you should review and fix this before uploading to + the archive. + +Files: README.md +Copyright: 2020-2022, Stalwart Labs Ltd. +License: UNKNOWN-LICENSE; FIXME (overlay) +Comment: + FIXME (overlay): These notices are extracted from files. Please review them + before uploading to the archive. + +Files: src/decoders/base64.rs +Copyright: 2005, 2006, 2007 Nick Galbreath -- nickg [at] modp [dot] com +License: UNKNOWN-LICENSE; FIXME (overlay) +Comment: + FIXME (overlay): These notices are extracted from files. Please review them + before uploading to the archive. + +Files: tests/legacy/COPYING +Copyright: 2010 Hunny Software, Inc. +License: UNKNOWN-LICENSE; FIXME (overlay) +Comment: + FIXME (overlay): These notices are extracted from files. Please review them + before uploading to the archive. + +Files: tests/malformed/COPYING +Copyright: 2003-2018 Dovecot authors, licensed under MIT. +License: UNKNOWN-LICENSE; FIXME (overlay) +Comment: + FIXME (overlay): These notices are extracted from files. Please review them + before uploading to the archive. + +Files: tests/thirdparty/COPYING +Copyright: 2003-2018 Dovecot authors, licensed under MIT. +License: UNKNOWN-LICENSE; FIXME (overlay) +Comment: + FIXME (overlay): These notices are extracted from files. Please review them + before uploading to the archive. + +Files: debian/* +Copyright: + 2023 Debian Rust Maintainers + 2023 Lukas Wagner +License: Apache-2.0 or MIT + +License: Apache-2.0 + Debian systems provide the Apache 2.0 license in + /usr/share/common-licenses/Apache-2.0 + +License: MIT + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + . + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + . + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/src/mail-parser/debian/debcargo.toml b/src/mail-parser/debian/debcargo.toml new file mode 100644 index 000000000..d69a1a27b --- /dev/null +++ b/src/mail-parser/debian/debcargo.toml @@ -0,0 +1,2 @@ +overlay = "." +uploaders = ["Lukas Wagner "] -- 2.39.2 From l.wagner at proxmox.com Mon Oct 2 10:06:19 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 10:06:19 +0200 Subject: [pve-devel] [PATCH v2 proxmox-perl-rs 06/11] notify: construct Notification via constructor In-Reply-To: <20231002080624.198759-1-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> Message-ID: <20231002080624.198759-7-l.wagner@proxmox.com> This keeps us isolated from any further changes in the proxmox_notify::Notification struct. Signed-off-by: Lukas Wagner --- common/src/notify.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/common/src/notify.rs b/common/src/notify.rs index 9f44225..203acca 100644 --- a/common/src/notify.rs +++ b/common/src/notify.rs @@ -94,13 +94,7 @@ mod export { properties: Option, ) -> Result<(), HttpError> { let config = this.config.lock().unwrap(); - - let notification = Notification { - severity, - title, - body, - properties, - }; + let notification = Notification::new_templated(severity, title, body, properties); api::common::send(&config, channel, ¬ification) } -- 2.39.2 From l.wagner at proxmox.com Mon Oct 2 10:06:20 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 10:06:20 +0200 Subject: [pve-devel] [PATCH v2 proxmox-perl-rs 07/11] pve-rs: notify: remove notify_context for PVE In-Reply-To: <20231002080624.198759-1-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> Message-ID: <20231002080624.198759-8-l.wagner@proxmox.com> The context has now been moved to `proxmox-notify` due to the fact that we also need it in `proxmox-mail-forward` now. Signed-off-by: Lukas Wagner --- pve-rs/Cargo.toml | 2 +- pve-rs/src/lib.rs | 7 ++- pve-rs/src/notify_context.rs | 117 ----------------------------------- 3 files changed, 5 insertions(+), 121 deletions(-) delete mode 100644 pve-rs/src/notify_context.rs diff --git a/pve-rs/Cargo.toml b/pve-rs/Cargo.toml index f9e3291..f734b3b 100644 --- a/pve-rs/Cargo.toml +++ b/pve-rs/Cargo.toml @@ -36,7 +36,7 @@ perlmod = { version = "0.13", features = [ "exporter" ] } proxmox-apt = "0.10.6" proxmox-http = { version = "0.9", features = ["client-sync", "client-trait"] } proxmox-http-error = "0.1.0" -proxmox-notify = "0.2" +proxmox-notify = { version = "0.2", features = ["pve-context"] } proxmox-openid = "0.10" proxmox-resource-scheduling = "0.3.0" proxmox-subscription = "0.4" diff --git a/pve-rs/src/lib.rs b/pve-rs/src/lib.rs index d1915c9..42be39e 100644 --- a/pve-rs/src/lib.rs +++ b/pve-rs/src/lib.rs @@ -4,18 +4,19 @@ pub mod common; pub mod apt; -pub mod notify_context; pub mod openid; pub mod resource_scheduling; pub mod tfa; #[perlmod::package(name = "Proxmox::Lib::PVE", lib = "pve_rs")] mod export { - use crate::{common, notify_context}; + use proxmox_notify::context::pve::PVE_CONTEXT; + + use crate::common; #[export] pub fn init() { common::logger::init("PVE_LOG", "info"); - notify_context::init(); + proxmox_notify::context::set_context(&PVE_CONTEXT) } } diff --git a/pve-rs/src/notify_context.rs b/pve-rs/src/notify_context.rs deleted file mode 100644 index 48623fd..0000000 --- a/pve-rs/src/notify_context.rs +++ /dev/null @@ -1,117 +0,0 @@ -use log; -use std::path::Path; - -use proxmox_notify::context::Context; - -// Some helpers borrowed and slightly adapted from `proxmox-mail-forward` - -fn normalize_for_return(s: Option<&str>) -> Option { - match s?.trim() { - "" => None, - s => Some(s.to_string()), - } -} - -fn attempt_file_read>(path: P) -> Option { - match proxmox_sys::fs::file_read_optional_string(path) { - Ok(contents) => contents, - Err(err) => { - log::error!("{err}"); - None - } - } -} - -fn lookup_mail_address(content: &str, user: &str) -> Option { - normalize_for_return(content.lines().find_map(|line| { - let fields: Vec<&str> = line.split(':').collect(); - #[allow(clippy::get_first)] // to keep expression style consistent - match fields.get(0)?.trim() == "user" && fields.get(1)?.trim() == user { - true => fields.get(6).copied(), - false => None, - } - })) -} - -fn lookup_datacenter_config_key(content: &str, key: &str) -> Option { - let key_prefix = format!("{key}:"); - normalize_for_return( - content - .lines() - .find_map(|line| line.strip_prefix(&key_prefix)), - ) -} - -#[derive(Debug)] -struct PVEContext; - -impl Context for PVEContext { - fn lookup_email_for_user(&self, user: &str) -> Option { - let content = attempt_file_read("/etc/pve/user.cfg"); - content.and_then(|content| lookup_mail_address(&content, user)) - } - - fn default_sendmail_author(&self) -> String { - "Proxmox VE".into() - } - - fn default_sendmail_from(&self) -> String { - let content = attempt_file_read("/etc/pve/datacenter.cfg"); - content - .and_then(|content| lookup_datacenter_config_key(&content, "mail_from")) - .unwrap_or_else(|| String::from("root")) - } - - fn http_proxy_config(&self) -> Option { - let content = attempt_file_read("/etc/pve/datacenter.cfg"); - content.and_then(|content| lookup_datacenter_config_key(&content, "http_proxy")) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - const USER_CONFIG: &str = " -user:root at pam:1:0:::root at example.com::: -user:test at pve:1:0:::test at example.com::: -user:no-mail at pve:1:0:::::: - "; - - #[test] - fn test_parse_mail() { - assert_eq!( - lookup_mail_address(USER_CONFIG, "root at pam"), - Some("root at example.com".to_string()) - ); - assert_eq!( - lookup_mail_address(USER_CONFIG, "test at pve"), - Some("test at example.com".to_string()) - ); - assert_eq!(lookup_mail_address(USER_CONFIG, "no-mail at pve"), None); - } - - const DC_CONFIG: &str = " -email_from: user at example.com -http_proxy: http://localhost:1234 -keyboard: en-us -"; - #[test] - fn test_parse_dc_config() { - assert_eq!( - lookup_datacenter_config_key(DC_CONFIG, "email_from"), - Some("user at example.com".to_string()) - ); - assert_eq!( - lookup_datacenter_config_key(DC_CONFIG, "http_proxy"), - Some("http://localhost:1234".to_string()) - ); - assert_eq!(lookup_datacenter_config_key(DC_CONFIG, "foo"), None); - } -} - -static CONTEXT: PVEContext = PVEContext; - -pub fn init() { - proxmox_notify::context::set_context(&CONTEXT) -} -- 2.39.2 From l.wagner at proxmox.com Mon Oct 2 10:06:18 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 10:06:18 +0200 Subject: [pve-devel] [PATCH v2 proxmox 05/11] notify: add PVE/PBS context In-Reply-To: <20231002080624.198759-1-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> Message-ID: <20231002080624.198759-6-l.wagner@proxmox.com> This commit moves PVEContext from `proxmox-perl-rs` into the `proxmox-notify` crate, since we now also need to access it from `promxox-mail-forward`. The context is now hidden behind a feature flag `pve-context`, ensuring that we only compile it when needed. This commit adds PBSContext, since we now require it for `proxmox-mail-forward`. This commit also changes the global context from being stored in a `once_cell` to a regular `Mutex`, since we now need to set/reset the context in `proxmox-mail-forward`. Signed-off-by: Lukas Wagner --- proxmox-notify/Cargo.toml | 3 +- proxmox-notify/src/context/common.rs | 27 ++++ .../src/{context.rs => context/mod.rs} | 14 +- proxmox-notify/src/context/pbs.rs | 130 ++++++++++++++++++ proxmox-notify/src/context/pve.rs | 82 +++++++++++ 5 files changed, 251 insertions(+), 5 deletions(-) create mode 100644 proxmox-notify/src/context/common.rs rename proxmox-notify/src/{context.rs => context/mod.rs} (54%) create mode 100644 proxmox-notify/src/context/pbs.rs create mode 100644 proxmox-notify/src/context/pve.rs diff --git a/proxmox-notify/Cargo.toml b/proxmox-notify/Cargo.toml index 441b6e1..8d8caaf 100644 --- a/proxmox-notify/Cargo.toml +++ b/proxmox-notify/Cargo.toml @@ -12,7 +12,6 @@ handlebars = { workspace = true } lazy_static.workspace = true log.workspace = true mail-parser = { workspace = true, optional = true } -once_cell.workspace = true openssl.workspace = true proxmox-http = { workspace = true, features = ["client-sync"], optional = true } proxmox-http-error.workspace = true @@ -30,3 +29,5 @@ default = ["sendmail", "gotify"] mail-forwarder = ["dep:mail-parser"] sendmail = ["dep:proxmox-sys"] gotify = ["dep:proxmox-http"] +pve-context = ["dep:proxmox-sys"] +pbs-context = ["dep:proxmox-sys"] diff --git a/proxmox-notify/src/context/common.rs b/proxmox-notify/src/context/common.rs new file mode 100644 index 0000000..7580bd1 --- /dev/null +++ b/proxmox-notify/src/context/common.rs @@ -0,0 +1,27 @@ +use std::path::Path; + +pub(crate) fn attempt_file_read>(path: P) -> Option { + match proxmox_sys::fs::file_read_optional_string(path) { + Ok(contents) => contents, + Err(err) => { + log::error!("{err}"); + None + } + } +} + +pub(crate) fn lookup_datacenter_config_key(content: &str, key: &str) -> Option { + let key_prefix = format!("{key}:"); + normalize_for_return( + content + .lines() + .find_map(|line| line.strip_prefix(&key_prefix)), + ) +} + +pub(crate) fn normalize_for_return(s: Option<&str>) -> Option { + match s?.trim() { + "" => None, + s => Some(s.to_string()), + } +} diff --git a/proxmox-notify/src/context.rs b/proxmox-notify/src/context/mod.rs similarity index 54% rename from proxmox-notify/src/context.rs rename to proxmox-notify/src/context/mod.rs index 370c7ee..00de2b0 100644 --- a/proxmox-notify/src/context.rs +++ b/proxmox-notify/src/context/mod.rs @@ -1,6 +1,12 @@ use std::fmt::Debug; +use std::sync::Mutex; -use once_cell::sync::OnceCell; +#[cfg(any(feature = "pve-context", feature = "pbs-context"))] +pub mod common; +#[cfg(feature = "pbs-context")] +pub mod pbs; +#[cfg(feature = "pve-context")] +pub mod pve; pub trait Context: Send + Sync + Debug { fn lookup_email_for_user(&self, user: &str) -> Option; @@ -9,13 +15,13 @@ pub trait Context: Send + Sync + Debug { fn http_proxy_config(&self) -> Option; } -static CONTEXT: OnceCell<&'static dyn Context> = OnceCell::new(); +static CONTEXT: Mutex> = Mutex::new(None); pub fn set_context(context: &'static dyn Context) { - CONTEXT.set(context).expect("context has already been set"); + *CONTEXT.lock().unwrap() = Some(context); } #[allow(unused)] // context is not used if all endpoint features are disabled pub(crate) fn context() -> &'static dyn Context { - *CONTEXT.get().expect("context has not been yet") + (*CONTEXT.lock().unwrap()).expect("context for proxmox-notify has not been set yet") } diff --git a/proxmox-notify/src/context/pbs.rs b/proxmox-notify/src/context/pbs.rs new file mode 100644 index 0000000..1e79566 --- /dev/null +++ b/proxmox-notify/src/context/pbs.rs @@ -0,0 +1,130 @@ +use serde::Deserialize; + +use proxmox_schema::{ObjectSchema, Schema, StringSchema}; +use proxmox_section_config::{SectionConfig, SectionConfigPlugin}; + +use crate::context::{common, Context}; + +const PBS_USER_CFG_FILENAME: &str = "/etc/proxmox-backup/user.cfg"; +const PBS_NODE_CFG_FILENAME: &str = "/etc/proxmox-backup/node.cfg"; + +// FIXME: Switch to the actual schema when possible in terms of dependency. +// It's safe to assume that the config was written with the actual schema restrictions, so parsing +// it with the less restrictive schema should be enough for the purpose of getting the mail address. +const DUMMY_ID_SCHEMA: Schema = StringSchema::new("dummy ID").min_length(3).schema(); +const DUMMY_EMAIL_SCHEMA: Schema = StringSchema::new("dummy email").schema(); +const DUMMY_USER_SCHEMA: ObjectSchema = ObjectSchema { + description: "minimal PBS user", + properties: &[ + ("userid", false, &DUMMY_ID_SCHEMA), + ("email", true, &DUMMY_EMAIL_SCHEMA), + ], + additional_properties: true, + default_key: None, +}; + +#[derive(Deserialize)] +struct DummyPbsUser { + pub email: Option, +} + +/// Extract the root user's email address from the PBS user config. +fn lookup_mail_address(content: &str, username: &str) -> Option { + let mut config = SectionConfig::new(&DUMMY_ID_SCHEMA).allow_unknown_sections(true); + let user_plugin = SectionConfigPlugin::new( + "user".to_string(), + Some("userid".to_string()), + &DUMMY_USER_SCHEMA, + ); + config.register_plugin(user_plugin); + + match config.parse(PBS_USER_CFG_FILENAME, content) { + Ok(parsed) => { + parsed.sections.get(username)?; + match parsed.lookup::("user", username) { + Ok(user) => common::normalize_for_return(user.email.as_deref()), + Err(err) => { + log::error!("unable to parse {} - {}", PBS_USER_CFG_FILENAME, err); + None + } + } + } + Err(err) => { + log::error!("unable to parse {} - {}", PBS_USER_CFG_FILENAME, err); + None + } + } +} + +#[derive(Debug)] +pub struct PBSContext; + +pub static PBS_CONTEXT: PBSContext = PBSContext; + +impl Context for PBSContext { + fn lookup_email_for_user(&self, user: &str) -> Option { + let content = common::attempt_file_read(PBS_USER_CFG_FILENAME); + content.and_then(|content| lookup_mail_address(&content, user)) + } + + fn default_sendmail_author(&self) -> String { + "Proxmox Backup Server".into() + } + + fn default_sendmail_from(&self) -> String { + let content = common::attempt_file_read(PBS_NODE_CFG_FILENAME); + content + .and_then(|content| common::lookup_datacenter_config_key(&content, "email-from")) + .unwrap_or_else(|| String::from("root")) + } + + fn http_proxy_config(&self) -> Option { + let content = common::attempt_file_read(PBS_NODE_CFG_FILENAME); + content.and_then(|content| common::lookup_datacenter_config_key(&content, "http-proxy")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + const USER_CONFIG: &str = " +user: root at pam + email root at example.com + +user: test at pbs + enable true + expire 0 + "; + + #[test] + fn test_parse_mail() { + assert_eq!( + lookup_mail_address(USER_CONFIG, "root at pam"), + Some("root at example.com".to_string()) + ); + assert_eq!(lookup_mail_address(USER_CONFIG, "test at pbs"), None); + } + + const NODE_CONFIG: &str = " +default-lang: de +email-from: root at example.com +http-proxy: http://localhost:1234 + "; + + #[test] + fn test_parse_node_config() { + assert_eq!( + common::lookup_datacenter_config_key(NODE_CONFIG, "email-from"), + Some("root at example.com".to_string()) + ); + assert_eq!( + common::lookup_datacenter_config_key(NODE_CONFIG, "http-proxy"), + Some("http://localhost:1234".to_string()) + ); + assert_eq!( + common::lookup_datacenter_config_key(NODE_CONFIG, "foo"), + None + ); + } +} diff --git a/proxmox-notify/src/context/pve.rs b/proxmox-notify/src/context/pve.rs new file mode 100644 index 0000000..f263c95 --- /dev/null +++ b/proxmox-notify/src/context/pve.rs @@ -0,0 +1,82 @@ +use crate::context::{common, Context}; + +fn lookup_mail_address(content: &str, user: &str) -> Option { + common::normalize_for_return(content.lines().find_map(|line| { + let fields: Vec<&str> = line.split(':').collect(); + #[allow(clippy::get_first)] // to keep expression style consistent + match fields.get(0)?.trim() == "user" && fields.get(1)?.trim() == user { + true => fields.get(6).copied(), + false => None, + } + })) +} + +#[derive(Debug)] +pub struct PVEContext; + +impl Context for PVEContext { + fn lookup_email_for_user(&self, user: &str) -> Option { + let content = common::attempt_file_read("/etc/pve/user.cfg"); + content.and_then(|content| lookup_mail_address(&content, user)) + } + + fn default_sendmail_author(&self) -> String { + "Proxmox VE".into() + } + + fn default_sendmail_from(&self) -> String { + let content = common::attempt_file_read("/etc/pve/datacenter.cfg"); + content + .and_then(|content| common::lookup_datacenter_config_key(&content, "email_from")) + .unwrap_or_else(|| String::from("root")) + } + + fn http_proxy_config(&self) -> Option { + let content = common::attempt_file_read("/etc/pve/datacenter.cfg"); + content.and_then(|content| common::lookup_datacenter_config_key(&content, "http_proxy")) + } +} + +pub static PVE_CONTEXT: PVEContext = PVEContext; + +#[cfg(test)] +mod tests { + use super::*; + + const USER_CONFIG: &str = " +user:root at pam:1:0:::root at example.com::: +user:test at pve:1:0:::test at example.com::: +user:no-mail at pve:1:0:::::: + "; + + #[test] + fn test_parse_mail() { + assert_eq!( + lookup_mail_address(USER_CONFIG, "root at pam"), + Some("root at example.com".to_string()) + ); + assert_eq!( + lookup_mail_address(USER_CONFIG, "test at pve"), + Some("test at example.com".to_string()) + ); + assert_eq!(lookup_mail_address(USER_CONFIG, "no-mail at pve"), None); + } + + const DC_CONFIG: &str = " +email_from: user at example.com +http_proxy: http://localhost:1234 +keyboard: en-us +"; + #[test] + fn test_parse_dc_config() { + assert_eq!( + common::lookup_datacenter_config_key(DC_CONFIG, "email_from"), + Some("user at example.com".to_string()) + ); + assert_eq!( + common::lookup_datacenter_config_key(DC_CONFIG, "http_proxy"), + Some("http://localhost:1234".to_string()) + ); + assert_eq!(common::lookup_datacenter_config_key(DC_CONFIG, "foo"), None); + } +} -- 2.39.2 From l.wagner at proxmox.com Mon Oct 2 10:06:23 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 2 Oct 2023 10:06:23 +0200 Subject: [pve-devel] [PATCH v2 proxmox-mail-forward 10/11] feed forwarded mails into proxmox_notify In-Reply-To: <20231002080624.198759-1-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> Message-ID: <20231002080624.198759-11-l.wagner@proxmox.com> This allows us to send notifications for events from daemons that are not under our control, e.g. zed, smartd, cron. etc... For mail-based notification targets (sendmail, soon smtp) the mail is forwarded as is, including all headers. All other target types will try to parse the email to extra subject and text body. On PBS, where proxmox-notify is not yet fully integrated, we also use proxmox-notify. There, we simply fall back to the default target/policy (mail-to-root, which looks up root at pam's mail address). Once notification support is built into PBS, proxmox-mail-forward should automatically work in the same way as for PVE. Signed-off-by: Lukas Wagner --- Cargo.toml | 8 +- src/main.rs | 348 +++++++++++++++++++++++++++++++++++----------------- 2 files changed, 238 insertions(+), 118 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c68e802..64f8d47 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ name = "proxmox-mail-forward" version = "0.2.0" authors = [ "Fiona Ebner ", + "Lukas Wagner ", "Proxmox Support Team ", ] edition = "2021" @@ -17,9 +18,10 @@ anyhow = "1.0" log = "0.4.17" nix = "0.26" serde = { version = "1.0", features = ["derive"] } -#serde_json = "1.0" +serde_json = "1.0" syslog = "6.0" -proxmox-schema = "1.3" -proxmox-section-config = "1.0.2" +proxmox-schema = { version = "2.0", features = ["api-macro"] } +proxmox-section-config = "2.0" proxmox-sys = "0.5" +proxmox-notify = {version = "0.2", features = ["mail-forwarder", "pve-context", "pbs-context"] } diff --git a/src/main.rs b/src/main.rs index f3d4193..d3b7b6a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,39 +1,58 @@ +use std::io::Read; use std::path::Path; -use std::process::Command; -use anyhow::{bail, format_err, Error}; +use anyhow::Error; use serde::Deserialize; -use proxmox_schema::{ObjectSchema, Schema, StringSchema}; -use proxmox_section_config::{SectionConfig, SectionConfigPlugin}; +use proxmox_notify::context::pbs::PBS_CONTEXT; +use proxmox_notify::context::pve::PVE_CONTEXT; +use proxmox_notify::endpoints::sendmail::SendmailConfig; +use proxmox_notify::Config; +use proxmox_schema::{api, ApiType}; use proxmox_sys::fs; -const PBS_USER_CFG_FILENAME: &str = "/etc/proxmox-backup/user.cfg"; -const PBS_ROOT_USER: &str = "root at pam"; - -// FIXME: Switch to the actual schema when possible in terms of dependency. -// It's safe to assume that the config was written with the actual schema restrictions, so parsing -// it with the less restrictive schema should be enough for the purpose of getting the mail address. -const DUMMY_ID_SCHEMA: Schema = StringSchema::new("dummy ID").min_length(3).schema(); -const DUMMY_EMAIL_SCHEMA: Schema = StringSchema::new("dummy email").schema(); -const DUMMY_USER_SCHEMA: ObjectSchema = ObjectSchema { - description: "minimal PBS user", - properties: &[ - ("userid", false, &DUMMY_ID_SCHEMA), - ("email", true, &DUMMY_EMAIL_SCHEMA), - ], - additional_properties: true, - default_key: None, -}; +const PVE_CFG_PATH: &str = "/etc/pve"; +const PVE_DATACENTER_CFG_FILENAME: &str = "/etc/pve/datacenter.cfg"; +const PVE_PUB_NOTIFICATION_CFG_FILENAME: &str = "/etc/pve/notifications.cfg"; +const PVE_PRIV_NOTIFICATION_CFG_FILENAME: &str = "/etc/pve/priv/notifications.cfg"; + +const PBS_CFG_PATH: &str = "/etc/proxmox-backup"; +const PBS_NODE_CFG_FILENAME: &str = "/etc/proxmox-backup/node.cfg"; +const PBS_PUB_NOTIFICATION_CFG_FILENAME: &str = "/etc/proxmox-backup/notifications.cfg"; +const PBS_PRIV_NOTIFICATION_CFG_FILENAME: &str = "/etc/proxmox-backup/notifications-priv.cfg"; -#[derive(Deserialize)] -struct DummyPbsUser { - pub email: Option, +#[api] +#[derive(Deserialize, Default, Debug, PartialEq)] +#[serde(rename_all = "kebab-case")] +enum Policy { + #[default] + /// Always send a notification. + Always, + /// Never send a notification. + Never, } -const PVE_USER_CFG_FILENAME: &str = "/etc/pve/user.cfg"; -const PVE_DATACENTER_CFG_FILENAME: &str = "/etc/pve/datacenter.cfg"; -const PVE_ROOT_USER: &str = "root at pam"; +#[api( + properties: {}, + additional_properties: true, +)] +#[derive(Deserialize, Default, Debug)] +#[serde(rename_all = "kebab-case")] +/// Parsed property string for the `notify` key in `datacenter.cfg` +struct NotifyPropertyString { + /// Target for redirected system mails. + target_system_mail: Option, + /// Notification policy for system mails + system_mail: Option, +} + +/// Forwarding policy for system mail +struct NotificationPolicy { + /// Target for redirected system mails. + target: String, + /// Notification policy for system mails + policy: Policy, +} /// Convenience helper to get the trimmed contents of an optional &str, mapping blank ones to `None` /// and creating a String from it for returning. @@ -44,89 +63,143 @@ fn normalize_for_return(s: Option<&str>) -> Option { } } -/// Extract the root user's email address from the PBS user config. -fn get_pbs_mail_to(content: &str) -> Option { - let mut config = SectionConfig::new(&DUMMY_ID_SCHEMA).allow_unknown_sections(true); - let user_plugin = SectionConfigPlugin::new( - "user".to_string(), - Some("userid".to_string()), - &DUMMY_USER_SCHEMA, - ); - config.register_plugin(user_plugin); - - match config.parse(PBS_USER_CFG_FILENAME, content) { - Ok(parsed) => { - parsed.sections.get(PBS_ROOT_USER)?; - match parsed.lookup::("user", PBS_ROOT_USER) { - Ok(user) => normalize_for_return(user.email.as_deref()), - Err(err) => { - log::error!("unable to parse {} - {}", PBS_USER_CFG_FILENAME, err); - None - } - } - } +/// Wrapper around `proxmox_sys::fs::file_read_optional_string` which also returns `None` upon error +/// after logging it. +fn attempt_file_read>(path: P) -> Option { + match fs::file_read_optional_string(path) { + Ok(contents) => contents, Err(err) => { - log::error!("unable to parse {} - {}", PBS_USER_CFG_FILENAME, err); + log::error!("{}", err); None } } } -/// Extract the root user's email address from the PVE user config. -fn get_pve_mail_to(content: &str) -> Option { - normalize_for_return(content.lines().find_map(|line| { - let fields: Vec<&str> = line.split(':').collect(); - #[allow(clippy::get_first)] // to keep expression style consistent - match fields.get(0)?.trim() == "user" && fields.get(1)?.trim() == PVE_ROOT_USER { - true => fields.get(6).copied(), - false => None, - } - })) -} - -/// Extract the From-address configured in the PVE datacenter config. -fn get_pve_mail_from(content: &str) -> Option { +/// Look up a the value of a given key in PVE datacenter config +fn lookup_datacenter_config_key(content: &str, key: &str) -> Option { + let key_prefix = format!("{key}:"); normalize_for_return( content .lines() - .find_map(|line| line.strip_prefix("email_from:")), + .find_map(|line| line.strip_prefix(&key_prefix)), ) } -/// Executes sendmail as a child process with the specified From/To-addresses, expecting the mail -/// contents to be passed via stdin inherited from this program. -fn forward_mail(mail_from: String, mail_to: Vec) -> Result<(), Error> { - if mail_to.is_empty() { - bail!("user 'root at pam' does not have an email address"); +/// Read data from stdin, until EOF is encountered. +fn read_stdin() -> Result, Error> { + let mut input = Vec::new(); + let stdin = std::io::stdin(); + let mut handle = stdin.lock(); + + handle.read_to_end(&mut input)?; + Ok(input) +} + +/// Parse node.cfg/datacenter.cfg and extract system mail target/policy. +fn system_mail_target_policy(datacenter_config: Option<&str>) -> NotificationPolicy { + let cfg: Result<_, Error> = datacenter_config + .and_then(|cfg| lookup_datacenter_config_key(cfg, "notify")) + .map(|val| { + let val = NotifyPropertyString::API_SCHEMA.parse_property_string(&val)?; + let config: NotifyPropertyString = serde_json::from_value(val)?; + Ok(config) + }) + .transpose(); + + let cfg = match cfg { + Ok(cfg) => cfg.unwrap_or_default(), + Err(err) => { + log::error!( + "could not read system mail notification settings from datacenter.cfg: {err}" + ); + + Default::default() + } + }; + + NotificationPolicy { + target: cfg + .target_system_mail + .unwrap_or_else(|| String::from("mail-to-root")), + policy: cfg.system_mail.unwrap_or_default(), } +} - log::info!("forward mail to <{}>", mail_to.join(",")); +fn forward_common( + mail: &[u8], + config: &mut Config, + target: &NotificationPolicy, +) -> Result<(), Error> { + let real_uid = nix::unistd::getuid(); + // The uid is passed so that `sendmail` can be called as the a correct user. + let notification = + proxmox_notify::Notification::new_forwarded_mail(mail, Some(real_uid.as_raw()))?; - let mut cmd = Command::new("sendmail"); - cmd.args([ - "-bm", "-N", "never", // never send DSN (avoid mail loops) - "-f", &mail_from, "--", - ]); - cmd.args(mail_to); - cmd.env("PATH", "/sbin:/bin:/usr/sbin:/usr/bin"); + // TODO: mail-to-root should *always* be always available, but right now that is only ensured + // in PVE::Notify - maybe we should move that to proxmox_notify? So that we don't have to + // add it here? + proxmox_notify::api::sendmail::add_endpoint( + config, + &SendmailConfig { + name: "mail-to-root".to_string(), + mailto_user: Some(vec!["root at pam".to_string()]), + ..Default::default() + }, + )?; - // with status(), child inherits stdin - cmd.status() - .map_err(|err| format_err!("command {:?} failed - {}", cmd, err))?; + if matches!(target.policy, Policy::Always) { + let target = &target.target; + log::info!("attempting to forward mail via target {target}"); + proxmox_notify::api::common::send(config, target, ¬ification)?; + } else { + log::info!( + "'notify: system-mail=never' set in datacenter.cfg/node.cfg, mail is not forwarded" + ); + } Ok(()) } -/// Wrapper around `proxmox_sys::fs::file_read_optional_string` which also returns `None` upon error -/// after logging it. -fn attempt_file_read>(path: P) -> Option { - match fs::file_read_optional_string(path) { - Ok(contents) => contents, - Err(err) => { - log::error!("{}", err); - None - } - } +/// Forward the mail to root at pam for a PVE installation. If no mail address is configured or +/// if the user.cfg file does not exist (e.g. if PBS is not actually installed), nothing will +/// happen. +fn forward_for_pve(mail: &[u8]) -> Result<(), Error> { + let mut config = notification_config( + PVE_PUB_NOTIFICATION_CFG_FILENAME, + PVE_PRIV_NOTIFICATION_CFG_FILENAME, + )?; + let datacenter_config = attempt_file_read(PVE_DATACENTER_CFG_FILENAME); + let target = system_mail_target_policy(datacenter_config.as_deref()); + + proxmox_notify::context::set_context(&PVE_CONTEXT); + + forward_common(mail, &mut config, &target) +} + +/// Forward the mail to root at pam for a PBS installation. If no mail address is configured or +/// if the user.cfg file does not exist (e.g. if PBS is not actually installed), nothing will +/// happen. +fn forward_for_pbs(mail: &[u8]) -> Result<(), Error> { + let mut config = notification_config( + PBS_PUB_NOTIFICATION_CFG_FILENAME, + PBS_PRIV_NOTIFICATION_CFG_FILENAME, + )?; + let node_config = attempt_file_read(PBS_NODE_CFG_FILENAME); + let target = system_mail_target_policy(node_config.as_deref()); + + proxmox_notify::context::set_context(&PBS_CONTEXT); + + forward_common(mail, &mut config, &target) +} + +/// Parse notification config. +/// +/// If the configuration file does not exist, this will return an empty configuration. +fn notification_config(public_path: &str, private_path: &str) -> Result { + let public_contents = attempt_file_read(public_path).unwrap_or_default(); + let private_contents = attempt_file_read(private_path).unwrap_or_default(); + + Config::new(&public_contents, &private_contents).map_err(Into::into) } fn main() { @@ -135,40 +208,85 @@ fn main() { log::LevelFilter::Info, Some("proxmox-mail-forward"), ) { - eprintln!("unable to inititialize syslog - {}", err); + eprintln!("unable to initialize syslog - {}", err); } - let pbs_user_cfg_content = attempt_file_read(PBS_USER_CFG_FILENAME); - let pve_user_cfg_content = attempt_file_read(PVE_USER_CFG_FILENAME); - let pve_datacenter_cfg_content = attempt_file_read(PVE_DATACENTER_CFG_FILENAME); + // Read the mail that is to be forwarded from stdin + match read_stdin() { + Ok(mail) => { + // Detect if we are on a PVE system. False positives, e.g. from + // left-overs of a co-installation with PBS might lead to a mail + // being forwarded twice or error messages in the system logs (e.g. + // if we fall back to 'mail-to-root' but root at pam has no email address + // configured, then the sendmail target will complain about a missing recipient) + if Path::new(PVE_CFG_PATH).exists() { + if let Err(err) = forward_for_pve(&mail) { + log::error!("could not forward mail for Proxmox VE: {err}"); + } + } - let real_uid = nix::unistd::getuid(); - if let Err(err) = nix::unistd::setresuid(real_uid, real_uid, real_uid) { - log::error!( - "mail forward failed: unable to set effective uid to {}: {}", - real_uid, - err - ); - return; + // Same as above... + if Path::new(PBS_CFG_PATH).exists() { + if let Err(err) = forward_for_pbs(&mail) { + log::error!("could not forward mail for Proxmox Backup Server: {err}"); + } + } + } + Err(err) => { + log::error!("could not read mail from STDIN: {err}") + } } +} - let pbs_mail_to = pbs_user_cfg_content.and_then(|content| get_pbs_mail_to(&content)); - let pve_mail_to = pve_user_cfg_content.and_then(|content| get_pve_mail_to(&content)); - let pve_mail_from = pve_datacenter_cfg_content.and_then(|content| get_pve_mail_from(&content)); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_notify_all_set() { + let config = Some( + " +email_from: root at example.com +notify: target-system-mail=foobar,system-mail=always +keyboard: en-us + ", + ); - let mail_from = pve_mail_from.unwrap_or_else(|| "root".to_string()); + let policy = system_mail_target_policy(config); - let mut mail_to = vec![]; - if let Some(pve_mail_to) = pve_mail_to { - mail_to.push(pve_mail_to); + assert_eq!(policy.target, "foobar"); + assert_eq!(policy.policy, Policy::Always); } - if let Some(pbs_mail_to) = pbs_mail_to { - if !mail_to.contains(&pbs_mail_to) { - mail_to.push(pbs_mail_to); - } + + #[test] + fn test_correct_defaults() { + let config = Some( + " +email_from: root at example.com +keyboard: en-us + ", + ); + + let policy = system_mail_target_policy(config); + + assert_eq!(policy.target, "mail-to-root"); + assert_eq!(policy.policy, Policy::Always); } - if let Err(err) = forward_mail(mail_from, mail_to) { - log::error!("mail forward failed: {}", err); + #[test] + fn test_invalid_policy() { + let config = Some( + " +email_from: root at example.com +notify: system-mail=invalid +keyboard: en-us + ", + ); + + let policy = system_mail_target_policy(config); + + assert_eq!(policy.target, "mail-to-root"); + // Fall back to always if the policy is invalid + assert_eq!(policy.policy, Policy::Always); } } -- 2.39.2 From sascha at sakisoft.de Sun Oct 1 08:59:56 2023 From: sascha at sakisoft.de (Sascha Schmidt) Date: Sun, 01 Oct 2023 08:59:56 +0200 Subject: Watchdog hardware vor VMS Message-ID: Hi, i have the whish to use watchdog inside of VMs. I also want to develop this feature for Proxmox. Can i go with this or is there a pro / con for? best regards sascha From a.lauterer at proxmox.com Mon Oct 2 11:00:26 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Mon, 2 Oct 2023 11:00:26 +0200 Subject: [pve-devel] [PATCH manager v5] ui: ceph: improve discoverability of warning details Message-ID: <20231002090026.725629-1-a.lauterer@proxmox.com> by * replacing the info button with expandable rows that contain the details of the warning * adding two action buttons to copy the summary and details * making the text selectable The row expander works like the one in the mail gateway tracking center -> doubleclick only opens it. The height of the warning grid is limited to not grow too large. A Diffstore is used to avoid expanded rows being collapsed on an update. The rowexpander cannot hide the toggle out of the box. Therefore, if there is no detailed message for a warning, we show a placeholder text. We could consider extending it in the future to only show the toggle if a defined condition is met. Signed-off-by: Aaron Lauterer --- changes since v4: * rebased so the patch applies again v3: * change the whole approach from tooltips and info window to integrating it into the grid itself www/css/ext6-pve.css | 6 +++ www/manager6/ceph/Status.js | 89 +++++++++++++++++++++++++------------ 2 files changed, 67 insertions(+), 28 deletions(-) diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css index edae462b..837b2210 100644 --- a/www/css/ext6-pve.css +++ b/www/css/ext6-pve.css @@ -709,3 +709,9 @@ table.osds td:first-of-type { opacity: 0.0; cursor: default; } + +.pve-ceph-warning-detail { + overflow: auto; + margin: 0; + padding-bottom: 10px; +} diff --git a/www/manager6/ceph/Status.js b/www/manager6/ceph/Status.js index 46338b4a..6bbe33b4 100644 --- a/www/manager6/ceph/Status.js +++ b/www/manager6/ceph/Status.js @@ -1,3 +1,10 @@ +Ext.define('pve-ceph-warnings', { + extend: 'Ext.data.Model', + fields: ['id', 'summary', 'detail', 'severity'], + idProperty: 'id', +}); + + Ext.define('PVE.node.CephStatus', { extend: 'Ext.panel.Panel', alias: 'widget.pveNodeCephStatus', @@ -70,35 +77,51 @@ Ext.define('PVE.node.CephStatus', { xtype: 'grid', itemId: 'warnings', flex: 2, + maxHeight: 430, stateful: true, stateId: 'ceph-status-warnings', + viewConfig: { + enableTextSelection: true, + }, // we load the store manually, to show an emptyText specify an empty intermediate store store: { + type: 'diff', trackRemoved: false, data: [], + rstore: { + storeid: 'pve-ceph-warnings', + type: 'update', + model: 'pve-ceph-warnings', + }, }, updateHealth: function(health) { let checks = health.checks || {}; let checkRecords = Object.keys(checks).sort().map(key => { let check = checks[key]; - return { + let data = { id: key, summary: check.summary.message, - detail: check.detail.reduce((acc, v) => `${acc}\n${v.message}`, ''), + detail: check.detail.reduce((acc, v) => `${acc}\n${v.message}`, '').trimStart(), severity: check.severity, }; + if (data.detail.length === 0) { + data.detail = "no additional data"; + } + return data; }); - this.getStore().loadRawData(checkRecords, false); + let rstore = this.getStore().rstore; + rstore.loadData(checkRecords, false); + rstore.fireEvent('load', rstore, checkRecords, true); }, emptyText: gettext('No Warnings/Errors'), columns: [ { dataIndex: 'severity', - header: gettext('Severity'), + tooltip: gettext('Severity'), align: 'center', - width: 70, + width: 38, renderer: function(value) { let health = PVE.Utils.map_ceph_health[value]; let icon = PVE.Utils.get_health_icon(health); @@ -118,38 +141,48 @@ Ext.define('PVE.node.CephStatus', { }, { xtype: 'actioncolumn', - width: 40, + width: 50, align: 'center', - tooltip: gettext('Detail'), + tooltip: gettext('Actions'), items: [ { - iconCls: 'x-fa fa-info-circle', + iconCls: 'x-fa fa-files-o', + tooltip: gettext('Copy summary'), + handler: function(grid, rowindex, colindex, item, e, record) { + navigator.clipboard.writeText(record.data.summary); + }, + }, + { + iconCls: 'x-fa fa-clipboard', + tooltip: gettext('Copy details'), handler: function(grid, rowindex, colindex, item, e, record) { - var win = Ext.create('Ext.window.Window', { - title: gettext('Detail'), - resizable: true, - modal: true, - width: 650, - height: 400, - layout: { - type: 'fit', - }, - items: [{ - scrollable: true, - padding: 10, - xtype: 'box', - html: [ - '' + Ext.htmlEncode(record.data.summary) + '', - '
' + Ext.htmlEncode(record.data.detail) + '
', - ], - }], - }); - win.show(); + navigator.clipboard.writeText(record.data.detail); }, }, ], }, ], + listeners: { + itemdblclick: function(view, record, row, rowIdx, e) { + // inspired by RowExpander.js + + let rowNode = view.getNode(rowIdx); let + normalRow = Ext.fly(rowNode); + + let collapsedCls = view.rowBodyFeature.rowCollapsedCls; + + if (normalRow.hasCls(collapsedCls)) { + view.rowBodyFeature.rowExpander.toggleRow(rowIdx, record); + } + }, + }, + plugins: [ + { + ptype: 'rowexpander', + expandOnDblClick: false, + rowBodyTpl: '
{detail}
', + }, + ], }, ], }, -- 2.39.2 From t.lamprecht at proxmox.com Mon Oct 2 12:31:23 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Mon, 2 Oct 2023 12:31:23 +0200 Subject: [pve-devel] applied: [PATCH proxmox-perl-rs] notify context: fix 'default_sendmail_from' context method In-Reply-To: <20231002074049.157507-1-l.wagner@proxmox.com> References: <20231002074049.157507-1-l.wagner@proxmox.com> Message-ID: <2b4e4966-1115-4e23-889e-e2142002bb68@proxmox.com> Am 02/10/2023 um 09:40 schrieb Lukas Wagner: > The name of the configuration option in datacenter.cfg is `email_from` > and not `mail_from`. > > Signed-off-by: Lukas Wagner > --- > Reported in our forum: > https://forum.proxmox.com/threads/mail-alerts-not-sent-with-datacenter-default.134305/ > > pve-rs/src/notify_context.rs | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > applied, thanks! From f.weber at proxmox.com Mon Oct 2 17:15:11 2023 From: f.weber at proxmox.com (Friedrich Weber) Date: Mon, 2 Oct 2023 17:15:11 +0200 Subject: [pve-devel] [PATCH installer] install: install correct grub metapackage for the current boot-mode In-Reply-To: <20230928140533.653796-1-s.ivanov@proxmox.com> References: <20230928140533.653796-1-s.ivanov@proxmox.com> Message-ID: <327a34bb-db3a-a1e7-4df2-37d9840b1882@proxmox.com> Tested-by: Friedrich Weber Tested patched ISO provided by Stoiko: * installed in legacy VM ** checked that `grub-pc` is installed ** re-installing it prints "Installing for i386-pc platform" * installed in UEFI VM ** checked that `grub-efi-amd64` is installed ** re-installing it prints "Installing for x86_64-efi platform" and updates the mtime of /boot/efi/EFI/proxmox/grubx64.efi On 28/09/2023 16:05, Stoiko Ivanov wrote: > grub packages in debian split between: > * meta-packages, which handles (among other things) the reinstalling > grub to the actual device/ESP in case of a version upgrade (grub-pc, > grub-efi-amd64) > * bin-packages, which contain the actual boot-loaders > The bin-packages can coexist on a system, but the meta-package > conflict with each other (didn't check why, but I don't see a hard > conflict on a quick glance) > > Currently our ISO installs grub-pc unconditionally (and both bin > packages, since we install the legacy bootloader also on uefi-booted > systems). This results in uefi-systems not getting a new grub > installed automatically upon upgrade. > > Reported in our community-forum from users who upgraded to PVE 8.0, > and still run into an issue fixed in grub for bookworm: > https://forum.proxmox.com/threads/.123512/ > > Reproduced and analyzed by Friedrich. > > This patch changes the installer, to install the meta-package fitting > for the boot-mode. > > We do not set the debconf variable install_devices, because in my > tests a plain debian installed in uefi mode has this set, and a > `grep -ri install_devices /var/lib/dpkg/info` yields only results with > grub-pc. > > Reported-by: Friedrich Weber > Signed-off-by: Stoiko Ivanov > --- > quickly tested by building an ISO (with the necessary modifications to > ship both packages as .deb) and installing in legacy mode and uefi mode > once. > Proxmox/Install.pm | 6 ++++++ > 1 file changed, 6 insertions(+) > > diff --git a/Proxmox/Install.pm b/Proxmox/Install.pm > index 1117fc4..d775ac0 100644 > --- a/Proxmox/Install.pm > +++ b/Proxmox/Install.pm > @@ -1057,6 +1057,12 @@ _EOD > chomp; > my $path = $_; > my ($deb) = $path =~ m/${proxmox_pkgdir}\/(.*\.deb)/; > + > + # the grub-pc/grub-efi-amd64 packages (w/o -bin) are the ones actually updating grub > + # upon upgrade - and conflict with each other - install the fitting one only > + next if ($deb =~ /grub-pc_/ && $run_env->{boot_type} ne 'bios'); > + next if ($deb =~ /grub-efi-amd64_/ && $run_env->{boot_type} ne 'efi'); > + > update_progress($count/$pkg_count, 0.5, 0.75, "extracting $deb"); > print STDERR "extracting: $deb\n"; > syscmd("chroot $targetdir dpkg $dpkg_opts --force-depends --no-triggers --unpack /tmp/pkg/$deb") == 0 From c.heiss at proxmox.com Tue Oct 3 12:21:46 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 3 Oct 2023 12:21:46 +0200 Subject: [pve-devel] [PATCH installer] low level: testmode: take path to disk image instead of using /dev/null Message-ID: <20231003102147.685374-1-c.heiss@proxmox.com> .. in exactly the same way `proxinstall` does, streamlining them both. Up until now, testing the TUI installer often involved hand-editing the `run-env-info.json` to put some proper disk sizes > 0 in place. This makes this process just a lot easier. Signed-off-by: Christoph Heiss --- Makefile | 15 ++++++++++----- proxmox-low-level-installer | 8 ++++---- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index dc180b2..2875d2a 100644 --- a/Makefile +++ b/Makefile @@ -135,26 +135,31 @@ cd-info.test: check-pve: prepare-check-env test.img rm -f cd-info.test; $(MAKE) cd-info.test - ./proxmox-low-level-installer dump-env -t + ./proxmox-low-level-installer dump-env -t test.img G_SLICE=always-malloc perl -I testdir/usr/share/perl5 testdir/usr/bin/proxinstall -t test.img check-pve-multidisks: prepare-check-env test.img test2.img test3.img test4.img test5.big.img rm -f cd-info.test; $(MAKE) cd-info.test - ./proxmox-low-level-installer dump-env -t + ./proxmox-low-level-installer dump-env -t test.img,test2.img,test3.img,test4.img,test5.big.img G_SLICE=always-malloc perl -I testdir/usr/share/perl5 testdir/usr/bin/proxinstall -t test.img,test2.img,test3.img,test4.img,test5.big.img check-pve-tui: prepare-check-env test.img rm -f cd-info.test; $(MAKE) cd-info.test - ./proxmox-low-level-installer dump-env -t + ./proxmox-low-level-installer dump-env -t test.img testdir/usr/bin/proxmox-tui-installer -t test.img +check-pve-tui-multidisks: prepare-check-env test.img test2.img test3.img test4.img test5.big.img + rm -f cd-info.test; $(MAKE) cd-info.test + ./proxmox-low-level-installer dump-env -t test.img,test2.img,test3.img,test4.img,test5.big.img + testdir/usr/bin/proxmox-tui-installer -t test.img,test2.img,test3.img,test4.img,test5.big.img + prepare-check-pmg: prepare-check-env test.img rm -f cd-info.test; $(MAKE) \ PRODUCT=pmg \ PRODUCTLONG="Proxmox Mail Gateway" \ ISONAME='proxmox-mail-gateway' \ cd-info.test - ./proxmox-low-level-installer dump-env -t + ./proxmox-low-level-installer dump-env -t test.img check-pmg: prepare-check-pmg G_SLICE=always-malloc perl -I testdir/usr/share/perl5 testdir/usr/bin/proxinstall -t test.img @@ -168,7 +173,7 @@ prepare-check-pbs: prepare-check-env test.img PRODUCTLONG='Proxmox Backup Server' \ ISONAME='proxmox-backup-server' \ cd-info.test - ./proxmox-low-level-installer dump-env -t + ./proxmox-low-level-installer dump-env -t test.img check-pbs: prepare-check-pbs G_SLICE=always-malloc perl -I testdir/usr/share/perl5 testdir/usr/bin/proxinstall -t test.img diff --git a/proxmox-low-level-installer b/proxmox-low-level-installer index 814961e..0f2bf4f 100755 --- a/proxmox-low-level-installer +++ b/proxmox-low-level-installer @@ -11,14 +11,14 @@ use JSON; use Time::HiRes qw(usleep); { - my $test_mode; + my $test_image; GetOptions( - 'test-mode|t' => \$test_mode + 'test-image|t=s' => \$test_image ) or die "usage error\n"; - # FIXME: use cleaner approach for setting tet mode? - Proxmox::Install::ISOEnv::set_test_image('/dev/null') if $test_mode; + Proxmox::Install::ISOEnv::set_test_image($test_image) if $test_image; } + use Proxmox::Install::ISOEnv; use Proxmox::Install::RunEnv; -- 2.41.0 From t.lamprecht at proxmox.com Tue Oct 3 12:35:56 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 3 Oct 2023 12:35:56 +0200 Subject: [pve-devel] Watchdog hardware vor VMS In-Reply-To: References: Message-ID: <0d5e5970-3241-48dd-82b3-67607f9f54ec@proxmox.com> Hello! Am 01/10/2023 um 08:59 schrieb Sascha Schmidt: > i have the whish to use watchdog inside of VMs. > I also want to develop this feature for Proxmox. > Can i go with this or is there a pro / con for? Proxmox VE already supports setting up a watchdog for virtual machines emulated by QEMU, it's just not exposed on the web UI as it's a bit of a niche feature. But you can use the API or CLI to configure one just fine, for example: Default model triggering a reset if watchdog isn't updated anymore: qm set 1010 --watchdog action=reset A different model doing a power off if guest OS doesn't update the watchdog anymore: qm set 1010 --watchdog model=ib700,action=poweroff cheers, Thomas From a.lauterer at proxmox.com Tue Oct 3 13:36:37 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Tue, 3 Oct 2023 13:36:37 +0200 Subject: [pve-devel] [PATCH manager 1/2] pvereport: dir2text: ignore special . and .. files Message-ID: <20231003113638.2221189-1-a.lauterer@proxmox.com> So far this hasn't been an issue as each user of dir2text wanted files with a specific pattern. But if we want every file in the directory, we need to skip the special files '.' and '..'. Signed-off-by: Aaron Lauterer --- PVE/Report.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/PVE/Report.pm b/PVE/Report.pm index c9109dca..435458b9 100644 --- a/PVE/Report.pm +++ b/PVE/Report.pm @@ -13,6 +13,7 @@ my sub dir2text { my $text = ''; PVE::Tools::dir_glob_foreach($target_dir, $regexp, sub { my ($file) = @_; + return if $file eq '.' || $file eq '..'; $text .= "\n# cat $target_dir$file\n"; $text .= PVE::Tools::file_get_contents($target_dir.$file)."\n"; }); -- 2.39.2 From a.lauterer at proxmox.com Tue Oct 3 13:36:38 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Tue, 3 Oct 2023 13:36:38 +0200 Subject: [pve-devel] [PATCH manager 2/2] pvereport: add interfaces.d directory In-Reply-To: <20231003113638.2221189-1-a.lauterer@proxmox.com> References: <20231003113638.2221189-1-a.lauterer@proxmox.com> Message-ID: <20231003113638.2221189-2-a.lauterer@proxmox.com> With the SDN becoming more prevalent, it is a good idea to include any additional config files in '/etc/network/interfaces.d'. Since no special suffix is enforced, we need to match against any file. Signed-off-by: Aaron Lauterer --- PVE/Report.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/PVE/Report.pm b/PVE/Report.pm index 435458b9..34ddd204 100644 --- a/PVE/Report.pm +++ b/PVE/Report.pm @@ -76,6 +76,7 @@ my $init_report_cmds = sub { 'ip -details -4 route show', 'ip -details -6 route show', 'cat /etc/network/interfaces', + sub { dir2text('/etc/network/interfaces.d/', '.*') }, ], }, firewall => { -- 2.39.2 From f.schauer at proxmox.com Tue Oct 3 14:46:41 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Tue, 3 Oct 2023 14:46:41 +0200 Subject: [pve-devel] [PATCH container] config: Remove duplicate attribute assignment Message-ID: <20231003124641.120833-1-f.schauer@proxmox.com> Signed-off-by: Filip Schauer --- src/PVE/LXC/Config.pm | 1 - 1 file changed, 1 deletion(-) diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm index 2dd57e2..bf6d717 100644 --- a/src/PVE/LXC/Config.pm +++ b/src/PVE/LXC/Config.pm @@ -896,7 +896,6 @@ for (my $i = 0; $i < $MAX_MOUNT_POINTS; $i++) { type => 'string', format => $mp_desc, description => "Use volume as container mount point. Use the special " . "syntax STORAGE_ID:SIZE_IN_GiB to allocate a new volume.", - optional => 1, }; } -- 2.39.2 From t.lamprecht at proxmox.com Tue Oct 3 18:40:27 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 3 Oct 2023 18:40:27 +0200 Subject: [pve-devel] Watchdog hardware vor VMS In-Reply-To: References: <0d5e5970-3241-48dd-82b3-67607f9f54ec@proxmox.com> Message-ID: Am 03/10/2023 um 12:51 schrieb Sascha Schmidt: > Am Dienstag, dem 03.10.2023 um 12:35 +0200 schrieb Thomas Lamprecht: >> Am 01/10/2023 um 08:59 schrieb Sascha Schmidt: >>> i have the whish to use watchdog inside of VMs. >>> I also want to develop this feature for Proxmox. >>> Can i go with this or is there a pro / con for? >> >> Proxmox VE already supports setting up a watchdog for virtual machines >> emulated by QEMU, it's just not exposed on the web UI as it's a bit of >> a niche feature. But you can use the API or CLI to configure one just >> fine, for example: > > Ive succeed by adding it manually to the conf. It shows not up in the > Webui. Thats specially the point i want to resolve :) > So i can go on with my development ? > Why is a web UI integration required? I'm not outright rejecting doing so, but I see rather limited value of having this HW exposed in the UI, as the UI is already rather crowded and that's why I'd like to keep niche features to API/CLI only, so I'd like to manage your expectations ? not that you do lots of work that then won't get accepted. Maybe you could open an enhancement request for this over at our Bugzilla: https://bugzilla.proxmox.com/ I'd then mark it as postponed and comment that others interested in this should show so by commenting too. If we get a few requests with somewhat good use cases I'd be easier for me to argue reevaluating this. Out of interest: What's actually your use case for an emulated QEMU watchdog? Personally I only use the Linux softdog in VMs. Oh, and btw. if you decide to develop something please check out our developer docs, especially the CLA part: https://pve.proxmox.com/wiki/Developer_Documentation#Software_License_and_Copyright Thanks! Thomas From f.ebner at proxmox.com Wed Oct 4 09:35:41 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Wed, 4 Oct 2023 09:35:41 +0200 Subject: [pve-devel] [PATCH storage 2/2] plugin schema: improve description of 'nodes' property In-Reply-To: <20231004073541.104105-1-f.ebner@proxmox.com> References: <20231004073541.104105-1-f.ebner@proxmox.com> Message-ID: <20231004073541.104105-2-f.ebner@proxmox.com> The default description doesn't make it clear what the property is used for in the context of storages. Signed-off-by: Fiona Ebner --- src/PVE/Storage/Plugin.pm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm index dc89d13..953b785 100644 --- a/src/PVE/Storage/Plugin.pm +++ b/src/PVE/Storage/Plugin.pm @@ -138,7 +138,10 @@ my $defaultData = { type => { description => "Storage type." }, storage => get_standard_option('pve-storage-id', { completion => \&PVE::Storage::complete_storage }), - nodes => get_standard_option('pve-node-list', { optional => 1 }), + nodes => get_standard_option('pve-node-list', { + description => "List of nodes for which the storage configuration applies.", + optional => 1, + }), content => { description => "Allowed content types.\n\nNOTE: the value " . "'rootdir' is used for Containers, and value 'images' for VMs.\n", -- 2.39.2 From f.ebner at proxmox.com Wed Oct 4 09:35:40 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Wed, 4 Oct 2023 09:35:40 +0200 Subject: [pve-devel] [PATCH storage 1/2] plugin schema: improve description of 'shared' property Message-ID: <20231004073541.104105-1-f.ebner@proxmox.com> It's not clear to users what this property does otherwise. Latest report from the community forum: https://forum.proxmox.com/threads/134393 Signed-off-by: Fiona Ebner --- src/PVE/Storage/Plugin.pm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PVE/Storage/Plugin.pm b/src/PVE/Storage/Plugin.pm index 815773b..dc89d13 100644 --- a/src/PVE/Storage/Plugin.pm +++ b/src/PVE/Storage/Plugin.pm @@ -167,7 +167,9 @@ my $defaultData = { default => "Unlimited for users with Datastore.Allocate privilege, 5 for other users", }, shared => { - description => "Mark storage as shared.", + description => "Indicate that this is a single storage which is already accessible by " + ."all nodes (or all listed in the 'nodes' option). Will not make a local storage " + ."automatically accessible to other nodes!", type => 'boolean', optional => 1, }, -- 2.39.2 From f.schauer at proxmox.com Wed Oct 4 10:08:19 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Wed, 4 Oct 2023 10:08:19 +0200 Subject: [pve-devel] [PATCH v3 vma-to-pbs] Initial commit Message-ID: <20231004080819.17524-1-f.schauer@proxmox.com> Implement a tool to import VMA files into a Proxmox Backup Server Signed-off-by: Filip Schauer --- Changes since v2: * Use the deb packages from the proxmox-io and proxmox-sys dependencies instead of the proxmox submodule * Remove the proxmox submodule * Update the proxmox-backup-qemu submodule to make it buildable with the newest librust dependencies .cargo/config | 5 + .gitmodules | 3 + Cargo.toml | 19 ++ Makefile | 70 +++++++ src/main.rs | 311 ++++++++++++++++++++++++++++++ src/vma.rs | 340 +++++++++++++++++++++++++++++++++ submodules/proxmox-backup-qemu | 1 + 7 files changed, 749 insertions(+) create mode 100644 .cargo/config create mode 100644 .gitmodules create mode 100644 Cargo.toml create mode 100644 Makefile create mode 100644 src/main.rs create mode 100644 src/vma.rs create mode 160000 submodules/proxmox-backup-qemu diff --git a/.cargo/config b/.cargo/config new file mode 100644 index 0000000..3b5b6e4 --- /dev/null +++ b/.cargo/config @@ -0,0 +1,5 @@ +[source] +[source.debian-packages] +directory = "/usr/share/cargo/registry" +[source.crates-io] +replace-with = "debian-packages" diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..6d21e06 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/proxmox-backup-qemu"] + path = submodules/proxmox-backup-qemu + url = git://git.proxmox.com/git/proxmox-backup-qemu.git diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..9711690 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "vma-to-pbs" +version = "0.0.1" +authors = ["Filip Schauer "] +edition = "2021" + +[dependencies] +anyhow = "1.0" +bincode = "1.3" +clap = { version = "4.0.32", features = ["cargo"] } +md5 = "0.7.0" +scopeguard = "1.1.0" +serde = "1.0" +serde-big-array = "0.4.1" + +proxmox-io = "1.0.1" +proxmox-sys = "0.5.0" + +proxmox-backup-qemu = { path = "submodules/proxmox-backup-qemu" } diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a0c841d --- /dev/null +++ b/Makefile @@ -0,0 +1,70 @@ +include /usr/share/dpkg/default.mk + +PACKAGE = proxmox-vma-to-pbs +BUILDDIR = $(PACKAGE)-$(DEB_VERSION_UPSTREAM) + +ARCH := $(DEB_BUILD_ARCH) + +DSC=$(DEB_SOURCE)_$(DEB_VERSION).dsc +MAIN_DEB=$(PACKAGE)_$(DEB_VERSION)_$(ARCH).deb +OTHER_DEBS = \ + $(PACKAGE)-dev_$(DEB_VERSION)_$(ARCH).deb \ + $(PACKAGE)-dbgsym_$(DEB_VERSION)_$(ARCH).deb +DEBS=$(MAIN_DEB) $(OTHER_DEBS) + +DESTDIR= + +TARGET_DIR := target/debug + +ifeq ($(BUILD_MODE), release) +CARGO_BUILD_ARGS += --release +TARGETDIR := target/release +endif + +.PHONY: all build +all: build + +build: $(TARGETDIR)/vma-to-pbs +$(TARGETDIR)/vma-to-pbs: Cargo.toml src/ + cargo build $(CARGO_BUILD_ARGS) + +.PHONY: install +install: $(TARGETDIR)/vma-to-pbs + install -D -m 0755 $(TARGETDIR)/vma-to-pbs $(DESTDIR)/usr/bin/vma-to-pbs + +$(BUILDDIR): submodule + rm -rf $@ $@.tmp && mkdir $@.tmp + cp -a submodules debian Makefile .cargo Cargo.toml build.rs src $@.tmp/ + mv $@.tmp $@ + +submodule: + [ -e submodules/proxmox-backup-qemu/Cargo.toml ] || [ -e submodules/proxmox/proxmox-sys/Cargo.toml ] || git submodule update --init --recursive + +dsc: + rm -rf $(BUILDDIR) $(DSC) + $(MAKE) $(DSC) + lintian $(DSC) + +$(DSC): $(BUILDDIR) + cd $(BUILDDIR); dpkg-buildpackage -S -us -uc -d + +sbuild: $(DSC) + sbuild $< + +.PHONY: deb dsc +deb: $(OTHER_DEBS) +$(OTHER_DEBS): $(MAIN_DEB) +$(MAIN_DEB): $(BUILDDIR) + cd $(BUILDDIR); dpkg-buildpackage -b -us -uc + lintian $(DEBS) + +distclean: clean +clean: + cargo clean + rm -rf $(PACKAGE)-[0-9]*/ + rm -r *.deb *.dsc $(DEB_SOURCE)*.tar* *.build *.buildinfo *.changes Cargo.lock + +.PHONY: dinstall +dinstall: $(DEBS) + dpkg -i $(DEBS) + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..1aefd29 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,311 @@ +extern crate anyhow; +extern crate clap; +extern crate proxmox_backup_qemu; +extern crate proxmox_io; +extern crate proxmox_sys; +extern crate scopeguard; + +use std::env; +use std::ffi::{c_char, CStr, CString}; +use std::ptr; +use std::time::{SystemTime, UNIX_EPOCH}; + +use anyhow::{anyhow, Context, Result}; +use clap::{command, Arg, ArgAction}; +use proxmox_backup_qemu::*; +use proxmox_sys::linux::tty; +use scopeguard::defer; + +mod vma; +use vma::*; + +fn backup_vma_to_pbs( + vma_file_path: String, + pbs_repository: String, + backup_id: String, + pbs_password: String, + keyfile: Option, + key_password: Option, + master_keyfile: Option, + fingerprint: String, + compress: bool, + encrypt: bool, +) -> Result<()> { + println!("VMA input file: {}", vma_file_path); + println!("PBS repository: {}", pbs_repository); + println!("PBS fingerprint: {}", fingerprint); + println!("compress: {}", compress); + println!("encrypt: {}", encrypt); + + let backup_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); + println!("backup time: {}", backup_time); + + let mut pbs_err: *mut c_char = ptr::null_mut(); + + let pbs_repository_cstr = CString::new(pbs_repository).unwrap(); + let backup_id_cstr = CString::new(backup_id).unwrap(); + let pbs_password_cstr = CString::new(pbs_password).unwrap(); + let fingerprint_cstr = CString::new(fingerprint).unwrap(); + let keyfile_cstr = keyfile.map(|v| CString::new(v).unwrap()); + let keyfile_ptr = keyfile_cstr.map(|v| v.as_ptr()).unwrap_or(ptr::null()); + let key_password_cstr = key_password.map(|v| CString::new(v).unwrap()); + let key_password_ptr = key_password_cstr.map(|v| v.as_ptr()).unwrap_or(ptr::null()); + let master_keyfile_cstr = master_keyfile.map(|v| CString::new(v).unwrap()); + let master_keyfile_ptr = master_keyfile_cstr.map(|v| v.as_ptr()).unwrap_or(ptr::null()); + + let pbs = proxmox_backup_new_ns( + pbs_repository_cstr.as_ptr(), + ptr::null(), + backup_id_cstr.as_ptr(), + backup_time, + PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE, + pbs_password_cstr.as_ptr(), + keyfile_ptr, + key_password_ptr, + master_keyfile_ptr, + true, + false, + fingerprint_cstr.as_ptr(), + &mut pbs_err, + ); + + defer! { + proxmox_backup_disconnect(pbs); + } + + if pbs == ptr::null_mut() { + unsafe { + let pbs_err_cstr = CStr::from_ptr(pbs_err); + return Err(anyhow!("proxmox_backup_new_ns failed: {pbs_err_cstr:?}")); + } + } + + let connect_result = proxmox_backup_connect(pbs, &mut pbs_err); + + if connect_result < 0 { + unsafe { + let pbs_err_cstr = CStr::from_ptr(pbs_err); + return Err(anyhow!("proxmox_backup_connect failed: {pbs_err_cstr:?}")); + } + } + + let mut vma_reader = VmaReader::new(&vma_file_path)?; + + // Handle configs + let configs = vma_reader.get_configs(); + for (config_name, config_data) in configs { + println!("CFG: size: {} name: {}", config_data.len(), config_name); + + let config_name_cstr = CString::new(config_name).unwrap(); + + if proxmox_backup_add_config( + pbs, + config_name_cstr.as_ptr(), + config_data.as_ptr(), + config_data.len() as u64, + &mut pbs_err, + ) < 0 + { + unsafe { + let pbs_err_cstr = CStr::from_ptr(pbs_err); + return Err(anyhow!( + "proxmox_backup_add_config failed: {pbs_err_cstr:?}" + )); + } + } + } + + // Handle block devices + for device_id in 0..255 { + let device_name = match vma_reader.get_device_name(device_id) { + Some(x) => x, + None => { + continue; + } + }; + + let device_size = match vma_reader.get_device_size(device_id) { + Some(x) => x, + None => { + continue; + } + }; + + println!( + "DEV: dev_id={} size: {} devname: {}", + device_id, device_size, device_name + ); + + let device_name_cstr = CString::new(device_name).unwrap(); + let pbs_device_id = proxmox_backup_register_image( + pbs, + device_name_cstr.as_ptr(), + device_size, + false, + &mut pbs_err, + ); + + if pbs_device_id < 0 { + unsafe { + let pbs_err_cstr = CStr::from_ptr(pbs_err); + return Err(anyhow!( + "proxmox_backup_register_image failed: {pbs_err_cstr:?}" + )); + } + } + + let mut image_chunk_buffer = proxmox_io::boxed::zeroed(PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE as usize); + let mut bytes_transferred = 0; + + while bytes_transferred < device_size { + let bytes_left = device_size - bytes_transferred; + let chunk_size = bytes_left.min(PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE); + println!( + "Uploading dev_id: {} offset: {:#0X} - {:#0X}", + device_id, + bytes_transferred, + bytes_transferred + chunk_size + ); + + let is_zero_chunk = vma_reader + .read_device_contents( + device_id, + &mut image_chunk_buffer[0..chunk_size as usize], + bytes_transferred, + ) + .with_context(|| { + format!( + "read {} bytes at offset {} from disk {} from VMA file", + chunk_size, bytes_transferred, device_id + ) + })?; + + let write_data_result = proxmox_backup_write_data( + pbs, + pbs_device_id as u8, + if is_zero_chunk { + ptr::null() + } else { + image_chunk_buffer.as_ptr() + }, + bytes_transferred, + chunk_size, + &mut pbs_err, + ); + + if write_data_result < 0 { + unsafe { + let pbs_err_cstr = CStr::from_ptr(pbs_err); + return Err(anyhow!( + "proxmox_backup_write_data failed: {pbs_err_cstr:?}" + )); + } + } + + bytes_transferred += chunk_size; + } + + if proxmox_backup_close_image(pbs, pbs_device_id as u8, &mut pbs_err) < 0 { + unsafe { + let pbs_err_cstr = CStr::from_ptr(pbs_err); + return Err(anyhow!( + "proxmox_backup_close_image failed: {pbs_err_cstr:?}" + )); + } + } + } + + if proxmox_backup_finish(pbs, &mut pbs_err) < 0 { + unsafe { + let pbs_err_cstr = CStr::from_ptr(pbs_err); + return Err(anyhow!("proxmox_backup_finish failed: {pbs_err_cstr:?}")); + } + } + + Ok(()) +} + +fn main() -> Result<()> { + let matches = command!() + .arg( + Arg::new("repository") + .long("repository") + .value_name("auth_id at host:port:datastore") + .help("Repository URL") + .required(true), + ) + .arg( + Arg::new("vmid") + .long("vmid") + .value_name("VMID") + .help("Backup ID") + .required(true), + ) + .arg( + Arg::new("fingerprint") + .long("fingerprint") + .value_name("FINGERPRINT") + .help("Proxmox Backup Server Fingerprint") + .required(true), + ) + .arg( + Arg::new("keyfile") + .long("keyfile") + .value_name("KEYFILE") + .help("Key file"), + ) + .arg( + Arg::new("master_keyfile") + .long("master_keyfile") + .value_name("MASTER_KEYFILE") + .help("Master key file"), + ) + .arg( + Arg::new("compress") + .long("compress") + .short('c') + .help("Compress the Backup") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("encrypt") + .long("encrypt") + .short('e') + .help("Encrypt the Backup") + .action(ArgAction::SetTrue), + ) + .arg(Arg::new("vma_file")) + .get_matches(); + + let pbs_repository = matches.get_one::("repository").unwrap().to_string(); + let vmid = matches.get_one::("vmid").unwrap().to_string(); + let fingerprint = matches.get_one::("fingerprint").unwrap().to_string(); + + let keyfile = matches.get_one::("keyfile"); + let master_keyfile = matches.get_one::("master_keyfile"); + let compress = matches.get_flag("compress"); + let encrypt = matches.get_flag("encrypt"); + + let vma_file_path = matches.get_one::("vma_file").unwrap().to_string(); + + let pbs_password = String::from_utf8(tty::read_password(&"Password: ").unwrap()).unwrap(); + let key_password = match keyfile { + Some(_) => Some(String::from_utf8(tty::read_password(&"Key Password: ").unwrap()).unwrap()), + None => None, + }; + + backup_vma_to_pbs( + vma_file_path, + pbs_repository, + vmid, + pbs_password, + keyfile.cloned(), + key_password, + master_keyfile.cloned(), + fingerprint, + compress, + encrypt, + )?; + + Ok(()) +} diff --git a/src/vma.rs b/src/vma.rs new file mode 100644 index 0000000..e2c3475 --- /dev/null +++ b/src/vma.rs @@ -0,0 +1,340 @@ +extern crate anyhow; +extern crate md5; + +use std::collections::HashMap; +use std::fs::File; +use std::io::{Read, Seek, SeekFrom}; +use std::mem::size_of; +use std::{cmp, str}; + +use anyhow::{anyhow, Result}; +use bincode::Options; +use serde::{Deserialize, Serialize}; +use serde_big_array::BigArray; + +const VMA_BLOCKS_PER_EXTENT: usize = 59; +const VMA_MAX_CONFIGS: usize = 256; +const VMA_MAX_DEVICES: usize = 256; + +#[repr(C)] +#[derive(Serialize, Deserialize)] +struct VmaDeviceInfoHeader { + pub device_name_offset: u32, + reserved: [u8; 4], + pub device_size: u64, + reserved1: [u8; 16], +} + +#[repr(C)] +#[derive(Serialize, Deserialize)] +struct VmaHeader { + pub magic: [u8; 4], + pub version: u32, + pub uuid: [u8; 16], + pub ctime: u64, + pub md5sum: [u8; 16], + pub blob_buffer_offset: u32, + pub blob_buffer_size: u32, + pub header_size: u32, + #[serde(with = "BigArray")] + reserved: [u8; 1984], + #[serde(with = "BigArray")] + pub config_names: [u32; VMA_MAX_CONFIGS], + #[serde(with = "BigArray")] + pub config_data: [u32; VMA_MAX_CONFIGS], + reserved1: [u8; 4], + #[serde(with = "BigArray")] + pub dev_info: [VmaDeviceInfoHeader; VMA_MAX_DEVICES], +} + +#[repr(C)] +#[derive(Serialize, Deserialize)] +struct VmaBlockInfo { + pub mask: u16, + reserved: u8, + pub dev_id: u8, + pub cluster_num: u32, +} + +#[repr(C)] +#[derive(Serialize, Deserialize)] +struct VmaExtentHeader { + pub magic: [u8; 4], + reserved: [u8; 2], + pub block_count: u16, + pub uuid: [u8; 16], + pub md5sum: [u8; 16], + #[serde(with = "BigArray")] + pub blockinfo: [VmaBlockInfo; VMA_BLOCKS_PER_EXTENT], +} + +#[derive(Clone)] +struct VmaBlockIndexEntry { + pub cluster_file_offset: u64, + pub mask: u16, +} + +pub struct VmaReader { + vma_file: File, + vma_header: VmaHeader, + configs: HashMap, + block_index: Vec>, + blocks_are_indexed: bool, +} + +impl VmaReader { + pub fn new(vma_file_path: &str) -> Result { + let mut vma_file = match File::open(vma_file_path) { + Err(why) => return Err(anyhow!("couldn't open {}: {}", vma_file_path, why)), + Ok(file) => file, + }; + + let vma_header = Self::read_header(&mut vma_file).unwrap(); + let configs = Self::read_blob_buffer(&mut vma_file, &vma_header).unwrap(); + let block_index: Vec> = (0..256).map(|_| Vec::new()).collect(); + + let instance = Self { + vma_file, + vma_header, + configs, + block_index, + blocks_are_indexed: false, + }; + + Ok(instance) + } + + fn read_header(vma_file: &mut File) -> Result { + let mut buffer = Vec::with_capacity(size_of::()); + buffer.resize(size_of::(), 0); + vma_file.read_exact(&mut buffer)?; + + let bincode_options = bincode::DefaultOptions::new() + .with_fixint_encoding() + .with_big_endian(); + + let vma_header: VmaHeader = bincode_options.deserialize(&buffer)?; + + if vma_header.magic != [b'V', b'M', b'A', 0] { + return Err(anyhow!("Invalid magic number")); + } + + if vma_header.version != 1 { + return Err(anyhow!("Invalid VMA version {}", vma_header.version)); + } + + buffer.resize(vma_header.header_size as usize, 0); + vma_file.read_exact(&mut buffer[size_of::()..])?; + + // Fill the MD5 sum field with zeros to compute the MD5 sum + buffer[32..48].fill(0); + let computed_md5sum: [u8; 16] = md5::compute(&buffer).into(); + + if vma_header.md5sum != computed_md5sum { + return Err(anyhow!("Wrong VMA header checksum")); + } + + return Ok(vma_header); + } + + fn read_string_from_file(vma_file: &mut File, file_offset: u64) -> Result { + let mut size_bytes = [0u8; 2]; + vma_file.seek(SeekFrom::Start(file_offset))?; + vma_file.read_exact(&mut size_bytes)?; + let size = u16::from_le_bytes(size_bytes) as usize; + let mut string_bytes = Vec::with_capacity(size - 1); + string_bytes.resize(size - 1, 0); + vma_file.read_exact(&mut string_bytes)?; + let string = str::from_utf8(&string_bytes)?; + + return Ok(string.to_string()); + } + + fn read_blob_buffer( + vma_file: &mut File, + vma_header: &VmaHeader, + ) -> Result> { + let mut configs = HashMap::new(); + + for i in 0..VMA_MAX_CONFIGS { + let config_name_offset = vma_header.config_names[i]; + let config_data_offset = vma_header.config_data[i]; + + if config_name_offset == 0 || config_data_offset == 0 { + continue; + } + + let config_name_file_offset = (vma_header.blob_buffer_offset + config_name_offset) as u64; + let config_data_file_offset = (vma_header.blob_buffer_offset + config_data_offset) as u64; + let config_name = Self::read_string_from_file(vma_file, config_name_file_offset)?; + let config_data = Self::read_string_from_file(vma_file, config_data_file_offset)?; + + configs.insert(String::from(config_name), String::from(config_data)); + } + + return Ok(configs); + } + + pub fn get_configs(&self) -> HashMap { + return self.configs.clone(); + } + + pub fn get_device_name(&mut self, device_id: usize) -> Option { + if device_id >= VMA_MAX_DEVICES { + return None; + } + + let device_name_offset = self.vma_header.dev_info[device_id].device_name_offset; + + if device_name_offset == 0 { + return None; + } + + let device_name_file_offset = (self.vma_header.blob_buffer_offset + device_name_offset) as u64; + let device_name = Self::read_string_from_file(&mut self.vma_file, device_name_file_offset).unwrap(); + + return Some(device_name.to_string()); + } + + pub fn get_device_size(&self, device_id: usize) -> Option { + if device_id >= VMA_MAX_DEVICES { + return None; + } + + let dev_info = &self.vma_header.dev_info[device_id]; + + if dev_info.device_name_offset == 0 { + return None; + } + + return Some(dev_info.device_size); + } + + fn read_extent_header(vma_file: &mut File) -> Result { + let mut buffer = Vec::with_capacity(size_of::()); + buffer.resize(size_of::(), 0); + vma_file.read_exact(&mut buffer)?; + + let bincode_options = bincode::DefaultOptions::new() + .with_fixint_encoding() + .with_big_endian(); + + let vma_extent_header: VmaExtentHeader = bincode_options.deserialize(&buffer)?; + + if vma_extent_header.magic != [b'V', b'M', b'A', b'E'] { + return Err(anyhow!("Invalid magic number")); + } + + // Fill the MD5 sum field with zeros to compute the MD5 sum + buffer[24..40].fill(0); + let computed_md5sum: [u8; 16] = md5::compute(&buffer).into(); + + if vma_extent_header.md5sum != computed_md5sum { + return Err(anyhow!("Wrong VMA extent header checksum")); + } + + return Ok(vma_extent_header); + } + + fn index_device_clusters(&mut self) -> Result<()> { + for device_id in 0..255 { + let device_size = match self.get_device_size(device_id) { + Some(x) => x, + None => { + continue; + } + }; + + let device_cluster_count = (device_size + 4096 * 16 - 1) / (4096 * 16); + + let block_index_entry_placeholder = VmaBlockIndexEntry { + cluster_file_offset: 0, + mask: 0, + }; + + self.block_index[device_id].resize(device_cluster_count as usize, block_index_entry_placeholder); + } + + let mut file_offset = self.vma_header.header_size as u64; + let vma_file_size = self.vma_file.metadata()?.len(); + + while file_offset < vma_file_size { + self.vma_file.seek(SeekFrom::Start(file_offset))?; + let vma_extent_header = Self::read_extent_header(&mut self.vma_file)?; + file_offset += size_of::() as u64; + + for i in 0..VMA_BLOCKS_PER_EXTENT { + let blockinfo = &vma_extent_header.blockinfo[i]; + + if blockinfo.dev_id == 0 { + continue; + } + + let block_index_entry = VmaBlockIndexEntry { + cluster_file_offset: file_offset, + mask: blockinfo.mask, + }; + + self.block_index[blockinfo.dev_id as usize][blockinfo.cluster_num as usize] = block_index_entry; + file_offset += blockinfo.mask.count_ones() as u64 * 4096; + } + } + + self.blocks_are_indexed = true; + + return Ok(()); + } + + pub fn read_device_contents( + &mut self, + device_id: usize, + buffer: &mut [u8], + offset: u64, + ) -> Result { + if device_id >= VMA_MAX_DEVICES { + return Err(anyhow!("invalid device id {}", device_id)); + } + + if offset % (4096 * 16) != 0 { + return Err(anyhow!("offset is not aligned to 65536")); + } + + // Make sure that the device clusters are already indexed + if !self.blocks_are_indexed { + self.index_device_clusters()?; + } + + let this_device_block_index = &self.block_index[device_id]; + let length = cmp::min( + buffer.len(), + this_device_block_index.len() * 4096 * 16 - offset as usize, + ); + let mut buffer_offset = 0; + let mut buffer_is_zero = true; + + while buffer_offset < length { + let block_index_entry = &this_device_block_index[(offset as usize + buffer_offset) / (4096 * 16)]; + self.vma_file.seek(SeekFrom::Start(block_index_entry.cluster_file_offset))?; + + for i in 0..16 { + if buffer_offset >= length { + break; + } + + let block_buffer_end = buffer_offset + cmp::min(length - buffer_offset, 4096); + let block_mask = ((block_index_entry.mask >> i) & 1) == 1; + + if block_mask { + self.vma_file.read_exact(&mut buffer[buffer_offset..block_buffer_end])?; + buffer_is_zero = false; + } else { + buffer[buffer_offset..block_buffer_end].fill(0); + } + + buffer_offset += 4096; + } + } + + return Ok(buffer_is_zero); + } +} diff --git a/submodules/proxmox-backup-qemu b/submodules/proxmox-backup-qemu new file mode 160000 index 0000000..8af623b --- /dev/null +++ b/submodules/proxmox-backup-qemu @@ -0,0 +1 @@ +Subproject commit 8af623b2100bcda171074addbcb27d828bed2e99 -- 2.39.2 From f.schauer at proxmox.com Wed Oct 4 10:28:15 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Wed, 4 Oct 2023 10:28:15 +0200 Subject: [pve-devel] [PATCH v3 vma-to-pbs] Initial commit In-Reply-To: <20231004080819.17524-1-f.schauer@proxmox.com> References: <20231004080819.17524-1-f.schauer@proxmox.com> Message-ID: ignore this, wrong mailing list On 04/10/2023 10:08, Filip Schauer wrote: > Implement a tool to import VMA files into a Proxmox Backup Server > > Signed-off-by: Filip Schauer > --- > Changes since v2: > * Use the deb packages from the proxmox-io and proxmox-sys dependencies > instead of the proxmox submodule > * Remove the proxmox submodule > * Update the proxmox-backup-qemu submodule to make it buildable with > the newest librust dependencies > > .cargo/config | 5 + > .gitmodules | 3 + > Cargo.toml | 19 ++ > Makefile | 70 +++++++ > src/main.rs | 311 ++++++++++++++++++++++++++++++ > src/vma.rs | 340 +++++++++++++++++++++++++++++++++ > submodules/proxmox-backup-qemu | 1 + > 7 files changed, 749 insertions(+) > create mode 100644 .cargo/config > create mode 100644 .gitmodules > create mode 100644 Cargo.toml > create mode 100644 Makefile > create mode 100644 src/main.rs > create mode 100644 src/vma.rs > create mode 160000 submodules/proxmox-backup-qemu > > diff --git a/.cargo/config b/.cargo/config > new file mode 100644 > index 0000000..3b5b6e4 > --- /dev/null > +++ b/.cargo/config > @@ -0,0 +1,5 @@ > +[source] > +[source.debian-packages] > +directory = "/usr/share/cargo/registry" > +[source.crates-io] > +replace-with = "debian-packages" > diff --git a/.gitmodules b/.gitmodules > new file mode 100644 > index 0000000..6d21e06 > --- /dev/null > +++ b/.gitmodules > @@ -0,0 +1,3 @@ > +[submodule "submodules/proxmox-backup-qemu"] > + path = submodules/proxmox-backup-qemu > + url = git://git.proxmox.com/git/proxmox-backup-qemu.git > diff --git a/Cargo.toml b/Cargo.toml > new file mode 100644 > index 0000000..9711690 > --- /dev/null > +++ b/Cargo.toml > @@ -0,0 +1,19 @@ > +[package] > +name = "vma-to-pbs" > +version = "0.0.1" > +authors = ["Filip Schauer "] > +edition = "2021" > + > +[dependencies] > +anyhow = "1.0" > +bincode = "1.3" > +clap = { version = "4.0.32", features = ["cargo"] } > +md5 = "0.7.0" > +scopeguard = "1.1.0" > +serde = "1.0" > +serde-big-array = "0.4.1" > + > +proxmox-io = "1.0.1" > +proxmox-sys = "0.5.0" > + > +proxmox-backup-qemu = { path = "submodules/proxmox-backup-qemu" } > diff --git a/Makefile b/Makefile > new file mode 100644 > index 0000000..a0c841d > --- /dev/null > +++ b/Makefile > @@ -0,0 +1,70 @@ > +include /usr/share/dpkg/default.mk > + > +PACKAGE = proxmox-vma-to-pbs > +BUILDDIR = $(PACKAGE)-$(DEB_VERSION_UPSTREAM) > + > +ARCH := $(DEB_BUILD_ARCH) > + > +DSC=$(DEB_SOURCE)_$(DEB_VERSION).dsc > +MAIN_DEB=$(PACKAGE)_$(DEB_VERSION)_$(ARCH).deb > +OTHER_DEBS = \ > + $(PACKAGE)-dev_$(DEB_VERSION)_$(ARCH).deb \ > + $(PACKAGE)-dbgsym_$(DEB_VERSION)_$(ARCH).deb > +DEBS=$(MAIN_DEB) $(OTHER_DEBS) > + > +DESTDIR= > + > +TARGET_DIR := target/debug > + > +ifeq ($(BUILD_MODE), release) > +CARGO_BUILD_ARGS += --release > +TARGETDIR := target/release > +endif > + > +.PHONY: all build > +all: build > + > +build: $(TARGETDIR)/vma-to-pbs > +$(TARGETDIR)/vma-to-pbs: Cargo.toml src/ > + cargo build $(CARGO_BUILD_ARGS) > + > +.PHONY: install > +install: $(TARGETDIR)/vma-to-pbs > + install -D -m 0755 $(TARGETDIR)/vma-to-pbs $(DESTDIR)/usr/bin/vma-to-pbs > + > +$(BUILDDIR): submodule > + rm -rf $@ $@.tmp && mkdir $@.tmp > + cp -a submodules debian Makefile .cargo Cargo.toml build.rs src $@.tmp/ > + mv $@.tmp $@ > + > +submodule: > + [ -e submodules/proxmox-backup-qemu/Cargo.toml ] || [ -e submodules/proxmox/proxmox-sys/Cargo.toml ] || git submodule update --init --recursive > + > +dsc: > + rm -rf $(BUILDDIR) $(DSC) > + $(MAKE) $(DSC) > + lintian $(DSC) > + > +$(DSC): $(BUILDDIR) > + cd $(BUILDDIR); dpkg-buildpackage -S -us -uc -d > + > +sbuild: $(DSC) > + sbuild $< > + > +.PHONY: deb dsc > +deb: $(OTHER_DEBS) > +$(OTHER_DEBS): $(MAIN_DEB) > +$(MAIN_DEB): $(BUILDDIR) > + cd $(BUILDDIR); dpkg-buildpackage -b -us -uc > + lintian $(DEBS) > + > +distclean: clean > +clean: > + cargo clean > + rm -rf $(PACKAGE)-[0-9]*/ > + rm -r *.deb *.dsc $(DEB_SOURCE)*.tar* *.build *.buildinfo *.changes Cargo.lock > + > +.PHONY: dinstall > +dinstall: $(DEBS) > + dpkg -i $(DEBS) > + > diff --git a/src/main.rs b/src/main.rs > new file mode 100644 > index 0000000..1aefd29 > --- /dev/null > +++ b/src/main.rs > @@ -0,0 +1,311 @@ > +extern crate anyhow; > +extern crate clap; > +extern crate proxmox_backup_qemu; > +extern crate proxmox_io; > +extern crate proxmox_sys; > +extern crate scopeguard; > + > +use std::env; > +use std::ffi::{c_char, CStr, CString}; > +use std::ptr; > +use std::time::{SystemTime, UNIX_EPOCH}; > + > +use anyhow::{anyhow, Context, Result}; > +use clap::{command, Arg, ArgAction}; > +use proxmox_backup_qemu::*; > +use proxmox_sys::linux::tty; > +use scopeguard::defer; > + > +mod vma; > +use vma::*; > + > +fn backup_vma_to_pbs( > + vma_file_path: String, > + pbs_repository: String, > + backup_id: String, > + pbs_password: String, > + keyfile: Option, > + key_password: Option, > + master_keyfile: Option, > + fingerprint: String, > + compress: bool, > + encrypt: bool, > +) -> Result<()> { > + println!("VMA input file: {}", vma_file_path); > + println!("PBS repository: {}", pbs_repository); > + println!("PBS fingerprint: {}", fingerprint); > + println!("compress: {}", compress); > + println!("encrypt: {}", encrypt); > + > + let backup_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(); > + println!("backup time: {}", backup_time); > + > + let mut pbs_err: *mut c_char = ptr::null_mut(); > + > + let pbs_repository_cstr = CString::new(pbs_repository).unwrap(); > + let backup_id_cstr = CString::new(backup_id).unwrap(); > + let pbs_password_cstr = CString::new(pbs_password).unwrap(); > + let fingerprint_cstr = CString::new(fingerprint).unwrap(); > + let keyfile_cstr = keyfile.map(|v| CString::new(v).unwrap()); > + let keyfile_ptr = keyfile_cstr.map(|v| v.as_ptr()).unwrap_or(ptr::null()); > + let key_password_cstr = key_password.map(|v| CString::new(v).unwrap()); > + let key_password_ptr = key_password_cstr.map(|v| v.as_ptr()).unwrap_or(ptr::null()); > + let master_keyfile_cstr = master_keyfile.map(|v| CString::new(v).unwrap()); > + let master_keyfile_ptr = master_keyfile_cstr.map(|v| v.as_ptr()).unwrap_or(ptr::null()); > + > + let pbs = proxmox_backup_new_ns( > + pbs_repository_cstr.as_ptr(), > + ptr::null(), > + backup_id_cstr.as_ptr(), > + backup_time, > + PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE, > + pbs_password_cstr.as_ptr(), > + keyfile_ptr, > + key_password_ptr, > + master_keyfile_ptr, > + true, > + false, > + fingerprint_cstr.as_ptr(), > + &mut pbs_err, > + ); > + > + defer! { > + proxmox_backup_disconnect(pbs); > + } > + > + if pbs == ptr::null_mut() { > + unsafe { > + let pbs_err_cstr = CStr::from_ptr(pbs_err); > + return Err(anyhow!("proxmox_backup_new_ns failed: {pbs_err_cstr:?}")); > + } > + } > + > + let connect_result = proxmox_backup_connect(pbs, &mut pbs_err); > + > + if connect_result < 0 { > + unsafe { > + let pbs_err_cstr = CStr::from_ptr(pbs_err); > + return Err(anyhow!("proxmox_backup_connect failed: {pbs_err_cstr:?}")); > + } > + } > + > + let mut vma_reader = VmaReader::new(&vma_file_path)?; > + > + // Handle configs > + let configs = vma_reader.get_configs(); > + for (config_name, config_data) in configs { > + println!("CFG: size: {} name: {}", config_data.len(), config_name); > + > + let config_name_cstr = CString::new(config_name).unwrap(); > + > + if proxmox_backup_add_config( > + pbs, > + config_name_cstr.as_ptr(), > + config_data.as_ptr(), > + config_data.len() as u64, > + &mut pbs_err, > + ) < 0 > + { > + unsafe { > + let pbs_err_cstr = CStr::from_ptr(pbs_err); > + return Err(anyhow!( > + "proxmox_backup_add_config failed: {pbs_err_cstr:?}" > + )); > + } > + } > + } > + > + // Handle block devices > + for device_id in 0..255 { > + let device_name = match vma_reader.get_device_name(device_id) { > + Some(x) => x, > + None => { > + continue; > + } > + }; > + > + let device_size = match vma_reader.get_device_size(device_id) { > + Some(x) => x, > + None => { > + continue; > + } > + }; > + > + println!( > + "DEV: dev_id={} size: {} devname: {}", > + device_id, device_size, device_name > + ); > + > + let device_name_cstr = CString::new(device_name).unwrap(); > + let pbs_device_id = proxmox_backup_register_image( > + pbs, > + device_name_cstr.as_ptr(), > + device_size, > + false, > + &mut pbs_err, > + ); > + > + if pbs_device_id < 0 { > + unsafe { > + let pbs_err_cstr = CStr::from_ptr(pbs_err); > + return Err(anyhow!( > + "proxmox_backup_register_image failed: {pbs_err_cstr:?}" > + )); > + } > + } > + > + let mut image_chunk_buffer = proxmox_io::boxed::zeroed(PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE as usize); > + let mut bytes_transferred = 0; > + > + while bytes_transferred < device_size { > + let bytes_left = device_size - bytes_transferred; > + let chunk_size = bytes_left.min(PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE); > + println!( > + "Uploading dev_id: {} offset: {:#0X} - {:#0X}", > + device_id, > + bytes_transferred, > + bytes_transferred + chunk_size > + ); > + > + let is_zero_chunk = vma_reader > + .read_device_contents( > + device_id, > + &mut image_chunk_buffer[0..chunk_size as usize], > + bytes_transferred, > + ) > + .with_context(|| { > + format!( > + "read {} bytes at offset {} from disk {} from VMA file", > + chunk_size, bytes_transferred, device_id > + ) > + })?; > + > + let write_data_result = proxmox_backup_write_data( > + pbs, > + pbs_device_id as u8, > + if is_zero_chunk { > + ptr::null() > + } else { > + image_chunk_buffer.as_ptr() > + }, > + bytes_transferred, > + chunk_size, > + &mut pbs_err, > + ); > + > + if write_data_result < 0 { > + unsafe { > + let pbs_err_cstr = CStr::from_ptr(pbs_err); > + return Err(anyhow!( > + "proxmox_backup_write_data failed: {pbs_err_cstr:?}" > + )); > + } > + } > + > + bytes_transferred += chunk_size; > + } > + > + if proxmox_backup_close_image(pbs, pbs_device_id as u8, &mut pbs_err) < 0 { > + unsafe { > + let pbs_err_cstr = CStr::from_ptr(pbs_err); > + return Err(anyhow!( > + "proxmox_backup_close_image failed: {pbs_err_cstr:?}" > + )); > + } > + } > + } > + > + if proxmox_backup_finish(pbs, &mut pbs_err) < 0 { > + unsafe { > + let pbs_err_cstr = CStr::from_ptr(pbs_err); > + return Err(anyhow!("proxmox_backup_finish failed: {pbs_err_cstr:?}")); > + } > + } > + > + Ok(()) > +} > + > +fn main() -> Result<()> { > + let matches = command!() > + .arg( > + Arg::new("repository") > + .long("repository") > + .value_name("auth_id at host:port:datastore") > + .help("Repository URL") > + .required(true), > + ) > + .arg( > + Arg::new("vmid") > + .long("vmid") > + .value_name("VMID") > + .help("Backup ID") > + .required(true), > + ) > + .arg( > + Arg::new("fingerprint") > + .long("fingerprint") > + .value_name("FINGERPRINT") > + .help("Proxmox Backup Server Fingerprint") > + .required(true), > + ) > + .arg( > + Arg::new("keyfile") > + .long("keyfile") > + .value_name("KEYFILE") > + .help("Key file"), > + ) > + .arg( > + Arg::new("master_keyfile") > + .long("master_keyfile") > + .value_name("MASTER_KEYFILE") > + .help("Master key file"), > + ) > + .arg( > + Arg::new("compress") > + .long("compress") > + .short('c') > + .help("Compress the Backup") > + .action(ArgAction::SetTrue), > + ) > + .arg( > + Arg::new("encrypt") > + .long("encrypt") > + .short('e') > + .help("Encrypt the Backup") > + .action(ArgAction::SetTrue), > + ) > + .arg(Arg::new("vma_file")) > + .get_matches(); > + > + let pbs_repository = matches.get_one::("repository").unwrap().to_string(); > + let vmid = matches.get_one::("vmid").unwrap().to_string(); > + let fingerprint = matches.get_one::("fingerprint").unwrap().to_string(); > + > + let keyfile = matches.get_one::("keyfile"); > + let master_keyfile = matches.get_one::("master_keyfile"); > + let compress = matches.get_flag("compress"); > + let encrypt = matches.get_flag("encrypt"); > + > + let vma_file_path = matches.get_one::("vma_file").unwrap().to_string(); > + > + let pbs_password = String::from_utf8(tty::read_password(&"Password: ").unwrap()).unwrap(); > + let key_password = match keyfile { > + Some(_) => Some(String::from_utf8(tty::read_password(&"Key Password: ").unwrap()).unwrap()), > + None => None, > + }; > + > + backup_vma_to_pbs( > + vma_file_path, > + pbs_repository, > + vmid, > + pbs_password, > + keyfile.cloned(), > + key_password, > + master_keyfile.cloned(), > + fingerprint, > + compress, > + encrypt, > + )?; > + > + Ok(()) > +} > diff --git a/src/vma.rs b/src/vma.rs > new file mode 100644 > index 0000000..e2c3475 > --- /dev/null > +++ b/src/vma.rs > @@ -0,0 +1,340 @@ > +extern crate anyhow; > +extern crate md5; > + > +use std::collections::HashMap; > +use std::fs::File; > +use std::io::{Read, Seek, SeekFrom}; > +use std::mem::size_of; > +use std::{cmp, str}; > + > +use anyhow::{anyhow, Result}; > +use bincode::Options; > +use serde::{Deserialize, Serialize}; > +use serde_big_array::BigArray; > + > +const VMA_BLOCKS_PER_EXTENT: usize = 59; > +const VMA_MAX_CONFIGS: usize = 256; > +const VMA_MAX_DEVICES: usize = 256; > + > +#[repr(C)] > +#[derive(Serialize, Deserialize)] > +struct VmaDeviceInfoHeader { > + pub device_name_offset: u32, > + reserved: [u8; 4], > + pub device_size: u64, > + reserved1: [u8; 16], > +} > + > +#[repr(C)] > +#[derive(Serialize, Deserialize)] > +struct VmaHeader { > + pub magic: [u8; 4], > + pub version: u32, > + pub uuid: [u8; 16], > + pub ctime: u64, > + pub md5sum: [u8; 16], > + pub blob_buffer_offset: u32, > + pub blob_buffer_size: u32, > + pub header_size: u32, > + #[serde(with = "BigArray")] > + reserved: [u8; 1984], > + #[serde(with = "BigArray")] > + pub config_names: [u32; VMA_MAX_CONFIGS], > + #[serde(with = "BigArray")] > + pub config_data: [u32; VMA_MAX_CONFIGS], > + reserved1: [u8; 4], > + #[serde(with = "BigArray")] > + pub dev_info: [VmaDeviceInfoHeader; VMA_MAX_DEVICES], > +} > + > +#[repr(C)] > +#[derive(Serialize, Deserialize)] > +struct VmaBlockInfo { > + pub mask: u16, > + reserved: u8, > + pub dev_id: u8, > + pub cluster_num: u32, > +} > + > +#[repr(C)] > +#[derive(Serialize, Deserialize)] > +struct VmaExtentHeader { > + pub magic: [u8; 4], > + reserved: [u8; 2], > + pub block_count: u16, > + pub uuid: [u8; 16], > + pub md5sum: [u8; 16], > + #[serde(with = "BigArray")] > + pub blockinfo: [VmaBlockInfo; VMA_BLOCKS_PER_EXTENT], > +} > + > +#[derive(Clone)] > +struct VmaBlockIndexEntry { > + pub cluster_file_offset: u64, > + pub mask: u16, > +} > + > +pub struct VmaReader { > + vma_file: File, > + vma_header: VmaHeader, > + configs: HashMap, > + block_index: Vec>, > + blocks_are_indexed: bool, > +} > + > +impl VmaReader { > + pub fn new(vma_file_path: &str) -> Result { > + let mut vma_file = match File::open(vma_file_path) { > + Err(why) => return Err(anyhow!("couldn't open {}: {}", vma_file_path, why)), > + Ok(file) => file, > + }; > + > + let vma_header = Self::read_header(&mut vma_file).unwrap(); > + let configs = Self::read_blob_buffer(&mut vma_file, &vma_header).unwrap(); > + let block_index: Vec> = (0..256).map(|_| Vec::new()).collect(); > + > + let instance = Self { > + vma_file, > + vma_header, > + configs, > + block_index, > + blocks_are_indexed: false, > + }; > + > + Ok(instance) > + } > + > + fn read_header(vma_file: &mut File) -> Result { > + let mut buffer = Vec::with_capacity(size_of::()); > + buffer.resize(size_of::(), 0); > + vma_file.read_exact(&mut buffer)?; > + > + let bincode_options = bincode::DefaultOptions::new() > + .with_fixint_encoding() > + .with_big_endian(); > + > + let vma_header: VmaHeader = bincode_options.deserialize(&buffer)?; > + > + if vma_header.magic != [b'V', b'M', b'A', 0] { > + return Err(anyhow!("Invalid magic number")); > + } > + > + if vma_header.version != 1 { > + return Err(anyhow!("Invalid VMA version {}", vma_header.version)); > + } > + > + buffer.resize(vma_header.header_size as usize, 0); > + vma_file.read_exact(&mut buffer[size_of::()..])?; > + > + // Fill the MD5 sum field with zeros to compute the MD5 sum > + buffer[32..48].fill(0); > + let computed_md5sum: [u8; 16] = md5::compute(&buffer).into(); > + > + if vma_header.md5sum != computed_md5sum { > + return Err(anyhow!("Wrong VMA header checksum")); > + } > + > + return Ok(vma_header); > + } > + > + fn read_string_from_file(vma_file: &mut File, file_offset: u64) -> Result { > + let mut size_bytes = [0u8; 2]; > + vma_file.seek(SeekFrom::Start(file_offset))?; > + vma_file.read_exact(&mut size_bytes)?; > + let size = u16::from_le_bytes(size_bytes) as usize; > + let mut string_bytes = Vec::with_capacity(size - 1); > + string_bytes.resize(size - 1, 0); > + vma_file.read_exact(&mut string_bytes)?; > + let string = str::from_utf8(&string_bytes)?; > + > + return Ok(string.to_string()); > + } > + > + fn read_blob_buffer( > + vma_file: &mut File, > + vma_header: &VmaHeader, > + ) -> Result> { > + let mut configs = HashMap::new(); > + > + for i in 0..VMA_MAX_CONFIGS { > + let config_name_offset = vma_header.config_names[i]; > + let config_data_offset = vma_header.config_data[i]; > + > + if config_name_offset == 0 || config_data_offset == 0 { > + continue; > + } > + > + let config_name_file_offset = (vma_header.blob_buffer_offset + config_name_offset) as u64; > + let config_data_file_offset = (vma_header.blob_buffer_offset + config_data_offset) as u64; > + let config_name = Self::read_string_from_file(vma_file, config_name_file_offset)?; > + let config_data = Self::read_string_from_file(vma_file, config_data_file_offset)?; > + > + configs.insert(String::from(config_name), String::from(config_data)); > + } > + > + return Ok(configs); > + } > + > + pub fn get_configs(&self) -> HashMap { > + return self.configs.clone(); > + } > + > + pub fn get_device_name(&mut self, device_id: usize) -> Option { > + if device_id >= VMA_MAX_DEVICES { > + return None; > + } > + > + let device_name_offset = self.vma_header.dev_info[device_id].device_name_offset; > + > + if device_name_offset == 0 { > + return None; > + } > + > + let device_name_file_offset = (self.vma_header.blob_buffer_offset + device_name_offset) as u64; > + let device_name = Self::read_string_from_file(&mut self.vma_file, device_name_file_offset).unwrap(); > + > + return Some(device_name.to_string()); > + } > + > + pub fn get_device_size(&self, device_id: usize) -> Option { > + if device_id >= VMA_MAX_DEVICES { > + return None; > + } > + > + let dev_info = &self.vma_header.dev_info[device_id]; > + > + if dev_info.device_name_offset == 0 { > + return None; > + } > + > + return Some(dev_info.device_size); > + } > + > + fn read_extent_header(vma_file: &mut File) -> Result { > + let mut buffer = Vec::with_capacity(size_of::()); > + buffer.resize(size_of::(), 0); > + vma_file.read_exact(&mut buffer)?; > + > + let bincode_options = bincode::DefaultOptions::new() > + .with_fixint_encoding() > + .with_big_endian(); > + > + let vma_extent_header: VmaExtentHeader = bincode_options.deserialize(&buffer)?; > + > + if vma_extent_header.magic != [b'V', b'M', b'A', b'E'] { > + return Err(anyhow!("Invalid magic number")); > + } > + > + // Fill the MD5 sum field with zeros to compute the MD5 sum > + buffer[24..40].fill(0); > + let computed_md5sum: [u8; 16] = md5::compute(&buffer).into(); > + > + if vma_extent_header.md5sum != computed_md5sum { > + return Err(anyhow!("Wrong VMA extent header checksum")); > + } > + > + return Ok(vma_extent_header); > + } > + > + fn index_device_clusters(&mut self) -> Result<()> { > + for device_id in 0..255 { > + let device_size = match self.get_device_size(device_id) { > + Some(x) => x, > + None => { > + continue; > + } > + }; > + > + let device_cluster_count = (device_size + 4096 * 16 - 1) / (4096 * 16); > + > + let block_index_entry_placeholder = VmaBlockIndexEntry { > + cluster_file_offset: 0, > + mask: 0, > + }; > + > + self.block_index[device_id].resize(device_cluster_count as usize, block_index_entry_placeholder); > + } > + > + let mut file_offset = self.vma_header.header_size as u64; > + let vma_file_size = self.vma_file.metadata()?.len(); > + > + while file_offset < vma_file_size { > + self.vma_file.seek(SeekFrom::Start(file_offset))?; > + let vma_extent_header = Self::read_extent_header(&mut self.vma_file)?; > + file_offset += size_of::() as u64; > + > + for i in 0..VMA_BLOCKS_PER_EXTENT { > + let blockinfo = &vma_extent_header.blockinfo[i]; > + > + if blockinfo.dev_id == 0 { > + continue; > + } > + > + let block_index_entry = VmaBlockIndexEntry { > + cluster_file_offset: file_offset, > + mask: blockinfo.mask, > + }; > + > + self.block_index[blockinfo.dev_id as usize][blockinfo.cluster_num as usize] = block_index_entry; > + file_offset += blockinfo.mask.count_ones() as u64 * 4096; > + } > + } > + > + self.blocks_are_indexed = true; > + > + return Ok(()); > + } > + > + pub fn read_device_contents( > + &mut self, > + device_id: usize, > + buffer: &mut [u8], > + offset: u64, > + ) -> Result { > + if device_id >= VMA_MAX_DEVICES { > + return Err(anyhow!("invalid device id {}", device_id)); > + } > + > + if offset % (4096 * 16) != 0 { > + return Err(anyhow!("offset is not aligned to 65536")); > + } > + > + // Make sure that the device clusters are already indexed > + if !self.blocks_are_indexed { > + self.index_device_clusters()?; > + } > + > + let this_device_block_index = &self.block_index[device_id]; > + let length = cmp::min( > + buffer.len(), > + this_device_block_index.len() * 4096 * 16 - offset as usize, > + ); > + let mut buffer_offset = 0; > + let mut buffer_is_zero = true; > + > + while buffer_offset < length { > + let block_index_entry = &this_device_block_index[(offset as usize + buffer_offset) / (4096 * 16)]; > + self.vma_file.seek(SeekFrom::Start(block_index_entry.cluster_file_offset))?; > + > + for i in 0..16 { > + if buffer_offset >= length { > + break; > + } > + > + let block_buffer_end = buffer_offset + cmp::min(length - buffer_offset, 4096); > + let block_mask = ((block_index_entry.mask >> i) & 1) == 1; > + > + if block_mask { > + self.vma_file.read_exact(&mut buffer[buffer_offset..block_buffer_end])?; > + buffer_is_zero = false; > + } else { > + buffer[buffer_offset..block_buffer_end].fill(0); > + } > + > + buffer_offset += 4096; > + } > + } > + > + return Ok(buffer_is_zero); > + } > +} > diff --git a/submodules/proxmox-backup-qemu b/submodules/proxmox-backup-qemu > new file mode 160000 > index 0000000..8af623b > --- /dev/null > +++ b/submodules/proxmox-backup-qemu > @@ -0,0 +1 @@ > +Subproject commit 8af623b2100bcda171074addbcb27d828bed2e99 From s.ivanov at proxmox.com Wed Oct 4 14:05:55 2023 From: s.ivanov at proxmox.com (Stoiko Ivanov) Date: Wed, 4 Oct 2023 14:05:55 +0200 Subject: [pve-devel] [PATCH kernel-helper/manager] check for fitting grub-meta package on uefi systems Message-ID: <20231004120558.382180-1-s.ivanov@proxmox.com> The following patchset is a followup to the one for the installer: https://lists.proxmox.com/pipermail/pve-devel/2023-September/059270.html As suggested by Thomas - adding the check to proxmox-kernel-helper seems like a good idea. While adding it to d/postinst I thought that this might not be the best place - and that getting the warning upon every kernel-upgrade would be better vs. upon every upgrade of proxmox-kernel-helper (which are far less often). (Can gladly send the version with d/postinst as well) If the pve-manager patch gets applied - I'd push the equivalent change to pmg and provide one for pbs. Tested on legacy and uefi VMs installed with pve-8.0 iso and grub-efi-amd64 (and systemd-boot) removed vs. installed. proxmox-kernel-helper: Stoiko Ivanov (2): proxmox-boot-tool: do not exit early in kernel-hook proxmox-boot-tool: check if correct grub metapackage is installed src/proxmox-boot/zz-proxmox-boot | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) pve-manager Stoiko Ivanov (1): pve7to8: check for proper grub meta-package for bootmode PVE/CLI/pve7to8.pm | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) -- 2.39.2 From s.ivanov at proxmox.com Wed Oct 4 14:05:56 2023 From: s.ivanov at proxmox.com (Stoiko Ivanov) Date: Wed, 4 Oct 2023 14:05:56 +0200 Subject: [pve-devel] [PATCH 1/2] proxmox-boot-tool: do not exit early in kernel-hook In-Reply-To: <20231004120558.382180-1-s.ivanov@proxmox.com> References: <20231004120558.382180-1-s.ivanov@proxmox.com> Message-ID: <20231004120558.382180-2-s.ivanov@proxmox.com> update_esps is called first in the actual execution below - exiting early does not work for systems that don't use proxmox-boot-tool if a check added later needs to work there too. Signed-off-by: Stoiko Ivanov --- src/proxmox-boot/zz-proxmox-boot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proxmox-boot/zz-proxmox-boot b/src/proxmox-boot/zz-proxmox-boot index 793882b..1adc1b1 100755 --- a/src/proxmox-boot/zz-proxmox-boot +++ b/src/proxmox-boot/zz-proxmox-boot @@ -44,7 +44,7 @@ fi update_esps() { if [ ! -f "${ESP_LIST}" ]; then warn "No ${ESP_LIST} found, skipping ESP sync." - exit 0 + return fi if [ -f /etc/kernel/cmdline ]; then # we can have cmdline files with multiple or no new line at all, handle both! -- 2.39.2 From s.ivanov at proxmox.com Wed Oct 4 14:05:57 2023 From: s.ivanov at proxmox.com (Stoiko Ivanov) Date: Wed, 4 Oct 2023 14:05:57 +0200 Subject: [pve-devel] [PATCH 2/2] proxmox-boot-tool: check if correct grub metapackage is installed In-Reply-To: <20231004120558.382180-1-s.ivanov@proxmox.com> References: <20231004120558.382180-1-s.ivanov@proxmox.com> Message-ID: <20231004120558.382180-3-s.ivanov@proxmox.com> this part of the hook applies only to systems not using pbt for bootmangement. Currently our ISO installs grub-pc unconditionally - and never the conflicting grub-efi-amd64. Both packages are responsible for running grub-install (for the appropriate disks) upon an upgrade of grub. This results in grub currently not getting updated on uefi-booted systems (which do not use proxmox-boot-tool). The patch causes a warning to be printed to notify the user. Also considered putting the check+warning in d/postinst - but this way it will get triggered more often (upon every kernel-upgrade/update-initramfs, instead of only on proxmox-kernel-helper updates, which are less often), increasing the chances of being noticed. checking for the changelog-presence was chosen, over `dpkg-query` for the status, for consistency with the similar patch for pve7to8 (and potentially a small speed-gain). Suggested-by: Thomas Lamprecht Signed-off-by: Stoiko Ivanov --- src/proxmox-boot/zz-proxmox-boot | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/proxmox-boot/zz-proxmox-boot b/src/proxmox-boot/zz-proxmox-boot index 1adc1b1..0d08dbf 100755 --- a/src/proxmox-boot/zz-proxmox-boot +++ b/src/proxmox-boot/zz-proxmox-boot @@ -215,6 +215,23 @@ disable_systemd_boot_hook() { } +check_grub_efi_package() { + + if [ -f "${ESP_LIST}" ]; then + return + fi + + if [ ! -d /sys/firmware/efi ]; then + return + fi + + if [ ! -f /usr/share/doc/grub-efi-amd64/changelog.Debian.gz ]; then + return + fi + warn "uefi-booted system, without grub-efi-amd64 package - /boot/efi will not be updated" + +} + set -- $DEB_MAINT_PARAMS mode="${1#\'}" mode="${mode%\'}" @@ -228,6 +245,7 @@ case $0:$mode in BOOT_KVERS="$(boot_kernel_list "$@")" update_esps disable_systemd_boot_hook + check_grub_efi_package ;; */postrm.d/*:|*/postrm.d/*:remove) reexec_in_mountns "$@" @@ -235,6 +253,7 @@ case $0:$mode in BOOT_KVERS="$(boot_kernel_list)" update_esps disable_systemd_boot_hook + check_grub_efi_package ;; esac -- 2.39.2 From s.ivanov at proxmox.com Wed Oct 4 14:05:58 2023 From: s.ivanov at proxmox.com (Stoiko Ivanov) Date: Wed, 4 Oct 2023 14:05:58 +0200 Subject: [pve-devel] [PATCH 1/1] pve7to8: check for proper grub meta-package for bootmode In-Reply-To: <20231004120558.382180-1-s.ivanov@proxmox.com> References: <20231004120558.382180-1-s.ivanov@proxmox.com> Message-ID: <20231004120558.382180-4-s.ivanov@proxmox.com> This should catch installations from our ISO on non-ZFS in uefi mode, which won't get the updated grub efi binary installed upon upgrade, because grub-pc is installed instead of grub-efi-amd64. Adding this to pve7to8 should make this even more visible, than the corresponding patch for promxox-kernel-helper (warnings printed during regular package upgrades might be overlooked more easily than a yellow line in the major upgrade checkscript) The if/else order was chosen to limit the nesting level of the long messages. Signed-off-by: Stoiko Ivanov --- PVE/CLI/pve7to8.pm | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/PVE/CLI/pve7to8.pm b/PVE/CLI/pve7to8.pm index d1a71eff..2e2304ea 100644 --- a/PVE/CLI/pve7to8.pm +++ b/PVE/CLI/pve7to8.pm @@ -1302,29 +1302,36 @@ sub check_time_sync { sub check_bootloader { log_info("Checking bootloader configuration..."); - if (!$upgraded) { - log_skip("not yet upgraded, no need to check the presence of systemd-boot"); - return; - } - if (! -f "/etc/kernel/proxmox-boot-uuids") { - log_skip("proxmox-boot-tool not used for bootloader configuration"); + if (! -d '/sys/firmware/efi') { + log_skip("System booted in legacy-mode - no need for additional packages"); return; } - if (! -d "/sys/firmware/efi") { - log_skip("System booted in legacy-mode - no need for systemd-boot"); - return; - } - - if ( -f "/usr/share/doc/systemd-boot/changelog.Debian.gz") { - log_pass("systemd-boot is installed"); - } else { + if ( -f "/etc/kernel/proxmox-boot-uuids") { + if (!$upgraded) { + log_skip("not yet upgraded, no need to check the presence of systemd-boot"); + return; + } + if ( -f "/usr/share/doc/systemd-boot/changelog.Debian.gz") { + log_pass("bootloader packages installed correctly"); + return; + } log_warn( "proxmox-boot-tool is used for bootloader configuration in uefi mode" - . "but the separate systemd-boot package, existing in Debian Bookworm is not installed" - . "initializing new ESPs will not work until the package is installed" + . " but the separate systemd-boot package, existing in Debian Bookworm is not installed" + . " initializing new ESPs will not work until the package is installed" + ); + return; + } elsif ( ! -f "/usr/share/doc/grub-efi-amd64/changelog.Debian.gz" ) { + log_warn( + "System booted in uefi mode but grub-efi-amd64 meta-package not installed" + . " new grub versions will not be installed to /boot/efi -" + . " install grub-efi-amd64" ); + return; + } else { + log_pass("bootloader packages installed correctly"); } } -- 2.39.2 From c.heiss at proxmox.com Wed Oct 4 16:42:14 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 4 Oct 2023 16:42:14 +0200 Subject: [pve-devel] [PATCH installer 3/7] tui: add optional min-value constraint to `NumericEditView` and `DiskSizeEditView` In-Reply-To: <20231004144232.327071-1-c.heiss@proxmox.com> References: <20231004144232.327071-1-c.heiss@proxmox.com> Message-ID: <20231004144232.327071-4-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/views/mod.rs | 46 ++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/proxmox-tui-installer/src/views/mod.rs b/proxmox-tui-installer/src/views/mod.rs index 7efd487..0fe715e 100644 --- a/proxmox-tui-installer/src/views/mod.rs +++ b/proxmox-tui-installer/src/views/mod.rs @@ -60,6 +60,7 @@ where pub struct NumericEditView { view: EditView, + min_value: Option, max_value: Option, max_content_width: Option, allow_empty: bool, @@ -72,6 +73,7 @@ where pub fn new() -> Self { Self { view: EditView::new().content("0"), + min_value: None, max_value: None, max_content_width: None, allow_empty: false, @@ -81,12 +83,20 @@ where pub fn new_empty() -> Self { Self { view: EditView::new(), + min_value: None, max_value: None, max_content_width: None, allow_empty: true, } } + pub fn with_range(min: T, max: T) -> Self { + let mut view = Self::new(); + view.min_value = Some(min); + view.max_value = Some(max); + view + } + pub fn max_value(mut self, max: T) -> Self { self.max_value = Some(max); self @@ -113,12 +123,19 @@ where } } + pub fn set_min_value(&mut self, min: T) { + self.min_value = Some(min); + } + pub fn set_max_value(&mut self, max: T) { self.max_value = Some(max); } fn in_range(&self, value: T) -> bool { - !self.max_value.map_or(false, |max| value >= max) + let too_small = self.min_value.map_or(false, |min| value < min); + let too_large = self.max_value.map_or(false, |max| value > max); + + !too_small && !too_large } fn check_bounds(&mut self, original: Rc, result: EventResult) -> EventResult { @@ -226,6 +243,10 @@ impl DiskSizeEditView { Self { view } } + pub fn with_range(min: f64, max: f64) -> Self { + Self::new().min_value(min).max_value(max) + } + pub fn content(mut self, content: f64) -> Self { if let Some(view) = self .view @@ -255,13 +276,17 @@ impl DiskSizeEditView { } } + pub fn min_value(mut self, min: f64) -> Self { + if let Some(view) = self.get_inner_mut() { + view.set_min_value(min); + } + + self + } + pub fn max_value(mut self, max: f64) -> Self { - if let Some(view) = self - .view - .get_child_mut(0) - .and_then(|v| v.downcast_mut::>()) - { - view.get_inner_mut().set_max_value(max); + if let Some(view) = self.get_inner_mut() { + view.set_max_value(max); } self @@ -281,6 +306,13 @@ impl DiskSizeEditView { None => Err(NumericEditViewError::InvalidView), } } + + fn get_inner_mut(&mut self) -> Option<&mut FloatEditView> { + self.view + .get_child_mut(0) + .and_then(|v| v.downcast_mut::>()) + .map(|v| v.get_inner_mut()) + } } impl ViewWrapper for DiskSizeEditView { -- 2.42.0 From c.heiss at proxmox.com Wed Oct 4 16:42:15 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 4 Oct 2023 16:42:15 +0200 Subject: [pve-devel] [PATCH installer 4/7] tui: add min/max constraints for ZFS bootdisk parameters In-Reply-To: <20231004144232.327071-1-c.heiss@proxmox.com> References: <20231004144232.327071-1-c.heiss@proxmox.com> Message-ID: <20231004144232.327071-5-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/views/bootdisk.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index 46bdd9f..8b5b5d2 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -20,6 +20,9 @@ use crate::{ }; use crate::{setup::ProxmoxProduct, InstallerState}; +// See Proxmox::Sys::Block::partition_bootable_disk() +const MINIMUM_DISK_SIZE: f64 = 2.; + pub struct BootdiskOptionsView { view: LinearLayout, advanced_options: Rc>, @@ -501,7 +504,10 @@ impl ZfsBootdiskOptionsView { // TODO: Re-apply previous disk selection from `options` correctly fn new(disks: &[Disk], options: &ZfsBootdiskOptions) -> Self { let inner = FormView::new() - .child("ashift", IntegerEditView::new().content(options.ashift)) + .child( + "ashift", + IntegerEditView::with_range(9, 13).content(options.ashift), + ) .child( "compress", SelectView::new() @@ -526,8 +532,15 @@ impl ZfsBootdiskOptionsView { .unwrap_or_default(), ), ) - .child("copies", IntegerEditView::new().content(options.copies)) - .child("hdsize", DiskSizeEditView::new().content(options.disk_size)); + .child( + "copies", + IntegerEditView::with_range(1, 3).content(options.copies), + ) + .child( + "hdsize", + DiskSizeEditView::with_range(MINIMUM_DISK_SIZE, options.disk_size) + .content(options.disk_size), + ); let view = MultiDiskOptionsView::new(disks, &options.selected_disks, inner) .top_panel(TextView::new( -- 2.42.0 From c.heiss at proxmox.com Wed Oct 4 16:42:16 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 4 Oct 2023 16:42:16 +0200 Subject: [pve-devel] [PATCH installer 5/7] tui: add min/max contraints for LVM bootdisk parameters In-Reply-To: <20231004144232.327071-1-c.heiss@proxmox.com> References: <20231004144232.327071-1-c.heiss@proxmox.com> Message-ID: <20231004144232.327071-6-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/views/bootdisk.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index 8b5b5d2..bb421a1 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -248,27 +248,34 @@ impl LvmBootdiskOptionsView { let view = FormView::new() .child( "Total size", - DiskSizeEditView::new() - .content(options.total_size) - .max_value(options.total_size), + DiskSizeEditView::with_range(MINIMUM_DISK_SIZE, options.total_size) + .content(options.total_size), ) .child( "Swap size", - DiskSizeEditView::new_emptyable().content_maybe(options.swap_size), + DiskSizeEditView::new_emptyable() + .content_maybe(options.swap_size) + .max_value(options.total_size), ) .child_conditional( is_pve, "Maximum root volume size", - DiskSizeEditView::new_emptyable().content_maybe(options.max_root_size), + DiskSizeEditView::new_emptyable() + .content_maybe(options.max_root_size) + .max_value(options.total_size), ) .child_conditional( is_pve, "Maximum data volume size", - DiskSizeEditView::new_emptyable().content_maybe(options.max_data_size), + DiskSizeEditView::new_emptyable() + .content_maybe(options.max_data_size) + .max_value(options.total_size), ) .child( "Minimum free LVM space", - DiskSizeEditView::new_emptyable().content_maybe(options.min_lvm_free), + DiskSizeEditView::new_emptyable() + .content_maybe(options.min_lvm_free) + .max_value(options.total_size), ); Self { view } -- 2.42.0 From c.heiss at proxmox.com Wed Oct 4 16:42:12 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 4 Oct 2023 16:42:12 +0200 Subject: [pve-devel] [PATCH installer 1/7] tui: fix setting content when using the `DiskSizeEditView` builder In-Reply-To: <20231004144232.327071-1-c.heiss@proxmox.com> References: <20231004144232.327071-1-c.heiss@proxmox.com> Message-ID: <20231004144232.327071-2-c.heiss@proxmox.com> Previously, it would throw away all other settings (like `max_value`), if a construct like DiskSizeEditView::new().max_value(8.0).content(8.0) was used, due to simply replacing the inner view. Instead, modify the inner view. Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/views/mod.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/proxmox-tui-installer/src/views/mod.rs b/proxmox-tui-installer/src/views/mod.rs index aa24fa4..76f96a1 100644 --- a/proxmox-tui-installer/src/views/mod.rs +++ b/proxmox-tui-installer/src/views/mod.rs @@ -1,4 +1,4 @@ -use std::{net::IpAddr, rc::Rc, str::FromStr}; +use std::{mem, net::IpAddr, rc::Rc, str::FromStr}; use cursive::{ event::{Event, EventResult}, @@ -190,8 +190,21 @@ impl DiskSizeEditView { } pub fn content(mut self, content: f64) -> Self { - if let Some(view) = self.view.get_child_mut(0).and_then(|v| v.downcast_mut()) { - *view = FloatEditView::new().content(content).full_width(); + if let Some(view) = self + .view + .get_child_mut(0) + .and_then(|v| v.downcast_mut::>()) + { + // We need actual ownership here of the inner `FloatEditView` to call `.content()` on + // it. Thus first swap it out with a dummy, modify it and swap it back in. + // This procedure ensures other settings (like `max_value`) is preserved on the inner + // view. + + let mut inner = FloatEditView::new(); + mem::swap(view.get_inner_mut(), &mut inner); + + inner = inner.content(content); + mem::swap(view.get_inner_mut(), &mut inner); } self -- 2.42.0 From c.heiss at proxmox.com Wed Oct 4 16:42:17 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 4 Oct 2023 16:42:17 +0200 Subject: [pve-devel] [PATCH installer 6/7] tui: add min/max contraints for Btrfs bootdisk parameters In-Reply-To: <20231004144232.327071-1-c.heiss@proxmox.com> References: <20231004144232.327071-1-c.heiss@proxmox.com> Message-ID: <20231004144232.327071-7-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/views/bootdisk.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index bb421a1..66d909c 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -470,7 +470,11 @@ impl BtrfsBootdiskOptionsView { let view = MultiDiskOptionsView::new( disks, &options.selected_disks, - FormView::new().child("hdsize", DiskSizeEditView::new().content(options.disk_size)), + FormView::new().child( + "hdsize", + DiskSizeEditView::with_range(MINIMUM_DISK_SIZE, options.disk_size) + .content(options.disk_size), + ), ) .top_panel(TextView::new("Btrfs integration is a technology preview!").center()); -- 2.42.0 From c.heiss at proxmox.com Wed Oct 4 16:42:11 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 4 Oct 2023 16:42:11 +0200 Subject: [pve-devel] [PATCH installer 0/7] tui: add min/max constraints for bootdisk parameters Message-ID: <20231004144232.327071-1-c.heiss@proxmox.com> This adds minimum and maximum value constraints to the LVM, Btrfs and ZFS parameters in the bootdisk dialog. The current behaviour is to to either silently discard such values, which might yield unexpected results to users, or to throw some error later on during installation (due to e.g. failed ZFS pool creation), which does not indicate the exact cause. Both cases are really not desirable from a UX standpoint, especially with e.g. ZFS, where users might not know about the various constraints. The maximum value is clamped in such a way that the user cannot even enter a value bigger than allowed (which is the current behavior), but the same cannot be done for the minimum value. If the allowed range is 9 to 13, one could not enter e.g. `1` first, even if `12` was the desired value. Thus, the other approach is to simply validate on the dialog confirmation, passing down errors. The latter needed some (prep-)work to convert `Option` -> `Result`. Two new error enums were also introduced, to better represent errors that occured, rather than fiddeling around with strings. The TUI tests introduced in #7 show some techniques how components and their interactions can be properly tested. The same can be done for the GUI installer of course, but I didn't want to include in this series to avoid it being larger-than-necessary. series. Christoph Heiss (7): tui: fix setting content when using the `DiskSizeEditView` builder tui: improve `FormView` error handling tui: add optional min-value constraint to `NumericEditView` and `DiskSizeEditView` tui: add min/max constraints for ZFS bootdisk parameters tui: add min/max contraints for LVM bootdisk parameters tui: add min/max contraints for Btrfs bootdisk parameters tui: views: add some TUI component tests proxmox-tui-installer/src/main.rs | 51 ++- proxmox-tui-installer/src/options.rs | 2 +- proxmox-tui-installer/src/views/bootdisk.rs | 151 ++++--- proxmox-tui-installer/src/views/mod.rs | 421 ++++++++++++++++---- proxmox-tui-installer/src/views/timezone.rs | 115 +++++- 5 files changed, 608 insertions(+), 132 deletions(-) -- 2.41.0 From c.heiss at proxmox.com Wed Oct 4 16:42:18 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 4 Oct 2023 16:42:18 +0200 Subject: [pve-devel] [PATCH installer 7/7] tui: views: add some TUI component tests In-Reply-To: <20231004144232.327071-1-c.heiss@proxmox.com> References: <20231004144232.327071-1-c.heiss@proxmox.com> Message-ID: <20231004144232.327071-8-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/main.rs | 35 ++++++ proxmox-tui-installer/src/options.rs | 2 +- proxmox-tui-installer/src/views/mod.rs | 127 +++++++++++++++++++- proxmox-tui-installer/src/views/timezone.rs | 109 +++++++++++++++++ 4 files changed, 267 insertions(+), 6 deletions(-) diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index ab990a8..4971071 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -937,3 +937,38 @@ impl FromStr for UiMessage { } } } + +#[cfg(test)] +mod tests { + pub mod utils { + use cursive::{ + event::{Event, Key}, + reexports::crossbeam_channel::Sender, + view::Nameable, + Cursive, CursiveRunner, Vec2, View, + }; + + pub fn puppet_siv(view: impl View) -> (CursiveRunner, Sender>) { + let backend = cursive::backends::puppet::Backend::init(Some(Vec2::new(80, 24))); + let input = backend.input(); + let mut siv = Cursive::new().into_runner(backend); + + siv.add_layer(view.with_name("root")); + input.send(Some(Event::Refresh)).unwrap(); + siv.step(); + + (siv, input) + } + + pub fn send_keys( + siv: &mut CursiveRunner, + input: &Sender>, + keys: Vec, + ) { + for key in keys { + input.send(Some(Event::Key(key))).unwrap() + } + siv.step(); + } + } +} diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs index a0a81c9..d3f213d 100644 --- a/proxmox-tui-installer/src/options.rs +++ b/proxmox-tui-installer/src/options.rs @@ -275,7 +275,7 @@ impl BootdiskOptions { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct TimezoneOptions { pub country: String, pub timezone: String, diff --git a/proxmox-tui-installer/src/views/mod.rs b/proxmox-tui-installer/src/views/mod.rs index 0fe715e..b95bc20 100644 --- a/proxmox-tui-installer/src/views/mod.rs +++ b/proxmox-tui-installer/src/views/mod.rs @@ -18,9 +18,11 @@ pub use table_view::*; mod timezone; pub use timezone::*; +#[derive(Debug, PartialEq)] pub enum NumericEditViewError where - T: FromStr, + T: FromStr + fmt::Debug + PartialEq, + ::Err: fmt::Debug + PartialEq, { ParseError(::Err), OutOfRange { @@ -34,8 +36,8 @@ where impl fmt::Display for NumericEditViewError where - T: fmt::Display + FromStr, - ::Err: fmt::Display, + T: fmt::Debug + fmt::Display + FromStr + PartialEq, + ::Err: fmt::Debug + fmt::Display + PartialEq, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use NumericEditViewError::*; @@ -68,7 +70,8 @@ pub struct NumericEditView { impl NumericEditView where - T: Copy + ToString + FromStr + PartialOrd, + T: fmt::Debug + Copy + ToString + FromStr + PartialOrd + PartialEq, + ::Err: fmt::Debug + PartialEq, { pub fn new() -> Self { Self { @@ -347,7 +350,8 @@ impl FormViewGetValue for SelectView { impl FormViewGetValue for NumericEditView where - T: Copy + ToString + FromStr + PartialOrd, + T: fmt::Debug + Copy + ToString + FromStr + PartialOrd + PartialEq, + ::Err: fmt::Debug + PartialEq, NumericEditView: ViewWrapper, { type Error = NumericEditViewError; @@ -405,6 +409,7 @@ impl FormViewGetValue for DiskSizeEditView { } } +#[derive(PartialEq, Eq)] pub enum FormViewError { ChildNotFound(usize), ValueError(T), @@ -635,3 +640,115 @@ impl CidrAddressEditView { impl ViewWrapper for CidrAddressEditView { cursive::wrap_impl!(self.view: LinearLayout); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn numeric_edit_view_setting_content_preserves_settings() { + let mut view = NumericEditView::with_range(2., 4.).content(2.); + assert_eq!(view.min_value, Some(2.)); + assert_eq!(view.max_value, Some(4.)); + assert_eq!(view.get_content(), Ok(2.)); + + view = view.content(4.); + assert_eq!(view.get_content(), Ok(4.)); + + view = view.content(3.); + assert_eq!(view.get_content(), Ok(3.)); + + view = view.content(0.); + let err = view.get_content(); + match err { + Err(NumericEditViewError::OutOfRange { value, min, max }) => { + assert_eq!(value, 0.); + assert_eq!(min, Some(2.)); + assert_eq!(max, Some(4.)); + } + _ => panic!(), + } + assert_eq!( + err.unwrap_err().to_string(), + "out of range: 0, must be at least 2 and at most 4".to_owned(), + ); + } + + #[test] + fn disk_size_edit_view_setting_content_preserves_settings() { + let mut view = DiskSizeEditView::with_range(2., 4.).content(2.); + assert_eq!(view.get_inner_mut().unwrap().min_value, Some(2.)); + assert_eq!(view.get_inner_mut().unwrap().max_value, Some(4.)); + assert_eq!(view.get_content(), Ok(2.)); + + view = view.content(4.); + assert_eq!(view.get_content(), Ok(4.)); + + view = view.content(3.); + assert_eq!(view.get_content(), Ok(3.)); + + view = view.content(0.); + let err = view.get_content(); + match err { + Err(NumericEditViewError::OutOfRange { value, min, max }) => { + assert_eq!(value, 0.); + assert_eq!(min, Some(2.)); + assert_eq!(max, Some(4.)); + } + _ => panic!(), + } + assert_eq!( + err.unwrap_err().to_string(), + "out of range: 0, must be at least 2 and at most 4".to_owned() + ); + } + + #[test] + fn numeric_edit_view_get_content() { + let mut view = NumericEditView::::new(); + + assert_eq!(view.get_content(), Ok(0)); + view.set_min_value(2); + assert!(view.get_content().is_err()); + view.set_max_value(4); + view = view.content(3); + assert_eq!(view.get_content(), Ok(3)); + view = view.content(5); + assert!(view.get_content().is_err()); + + view.view.set_content(""); + view.allow_empty = true; + assert_eq!(view.get_content(), Err(NumericEditViewError::Empty)); + } + + #[test] + fn form_view_get_value() { + let mut view = FormView::new() + .child("foo", EditView::new().content("foo")) + .child_conditional(false, "fake-bar", EditView::new().content("fake-bar")) + .child_conditional(true, "bar", EditView::new().content("bar")) + .child("numeric", NumericEditView::with_range(2, 4).content(2)) + .child("disksize", DiskSizeEditView::with_range(2., 4.).content(2.)) + .child( + "disksize-opt", + DiskSizeEditView::new_emptyable() + .max_value(4.) + .content_maybe(Some(2.)), + ); + + assert_eq!(view.len(), 5); + assert_eq!(view.get_value::(0), Ok("foo".to_string())); + assert_eq!(view.get_value::(1), Ok("bar".to_string())); + assert_eq!(view.get_value::, _>(2), Ok(2)); + assert_eq!(view.get_value::(3), Ok(2.)); + assert_eq!(view.get_opt_value::(4), Ok(Some(2.))); + + view.replace_child( + 4, + DiskSizeEditView::new_emptyable() + .max_value(4.) + .content_maybe(None), + ); + assert_eq!(view.get_opt_value::(4), Ok(None)); + } +} diff --git a/proxmox-tui-installer/src/views/timezone.rs b/proxmox-tui-installer/src/views/timezone.rs index bd38a92..88055d0 100644 --- a/proxmox-tui-installer/src/views/timezone.rs +++ b/proxmox-tui-installer/src/views/timezone.rs @@ -132,3 +132,112 @@ impl TimezoneOptionsView { impl ViewWrapper for TimezoneOptionsView { cursive::wrap_impl!(self.view: FormView); } + +#[cfg(test)] +mod tests { + use super::*; + use crate::{setup::CountryInfo, tests::utils::*}; + use cursive::event::Key; + use std::collections::HashMap; + + fn locale_info() -> LocaleInfo { + let mut cczones = HashMap::new(); + let mut countries = HashMap::new(); + let mut kmap = HashMap::new(); + + cczones.insert("at".to_owned(), vec!["Europe/Vienna".to_owned()]); + cczones.insert("jp".to_owned(), vec!["Asia/Tokyo".to_owned()]); + + countries.insert( + "at".to_owned(), + CountryInfo { + name: "Austria".to_owned(), + zone: "Europe/Vienna".to_owned(), + kmap: "de".to_owned(), + }, + ); + countries.insert( + "jp".to_owned(), + CountryInfo { + name: "Japan".to_owned(), + zone: "Asia/Tokyo".to_owned(), + kmap: "jp".to_owned(), + }, + ); + + kmap.insert( + "de".to_owned(), + KeyboardMapping { + name: "German".to_owned(), + id: "de".to_owned(), + xkb_layout: "de".to_owned(), + xkb_variant: "nodeadkeys".to_owned(), + }, + ); + + LocaleInfo { + cczones, + countries, + kmap, + } + } + + #[test] + fn timezone_view_sets_zones_correctly() { + let options = TimezoneOptions { + country: "at".to_owned(), + timezone: "Europe/Vienna".to_owned(), + kb_layout: "de".to_owned(), + }; + + let view = TimezoneOptionsView::new(&locale_info(), &options); + let (mut siv, input) = puppet_siv(view); + + assert_eq!( + siv.find_name::("root") + .unwrap() + .get_values(), + Ok(TimezoneOptions { + country: "at".to_owned(), + timezone: "Europe/Vienna".to_owned(), + kb_layout: "de".to_owned(), + }) + ); + + // Navigate to timezone and select the second element, aka. UTC + send_keys( + &mut siv, + &input, + vec![Key::Down, Key::Enter, Key::Down, Key::Enter], + ); + + assert_eq!( + siv.find_name::("root") + .unwrap() + .get_values(), + Ok(TimezoneOptions { + country: "at".to_owned(), + timezone: "UTC".to_owned(), + kb_layout: "de".to_owned(), + }) + ); + + // Navigate up again to country and select the second one + send_keys( + &mut siv, + &input, + vec![Key::Up, Key::Enter, Key::Down, Key::Enter], + ); + + assert_eq!( + siv.find_name::("root") + .unwrap() + .get_values(), + Ok(TimezoneOptions { + country: "jp".to_owned(), + timezone: "Asia/Tokyo".to_owned(), + kb_layout: "de".to_owned(), + }) + ); + } +} -- 2.42.0 From c.heiss at proxmox.com Wed Oct 4 16:42:13 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 4 Oct 2023 16:42:13 +0200 Subject: [pve-devel] [PATCH installer 2/7] tui: improve `FormView` error handling In-Reply-To: <20231004144232.327071-1-c.heiss@proxmox.com> References: <20231004144232.327071-1-c.heiss@proxmox.com> Message-ID: <20231004144232.327071-3-c.heiss@proxmox.com> Mostly internal changes without any user-visible changes; replaces all optional return values in form with result that can hold more specific error causes. Signed-off-by: Christoph Heiss --- RFC; just a thought: It could make sense to introduce the `anyhow` crate here in the installer as well. We already use it in PBS, so nothing completely new and it would simplify error handling quite a bit as well, I think. But we can also do without it as well, as this series shows. proxmox-tui-installer/src/main.rs | 16 +- proxmox-tui-installer/src/views/bootdisk.rs | 105 +++++---- proxmox-tui-installer/src/views/mod.rs | 245 +++++++++++++++----- proxmox-tui-installer/src/views/timezone.rs | 6 +- 4 files changed, 259 insertions(+), 113 deletions(-) diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index 3f01713..ab990a8 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -499,15 +499,15 @@ fn password_dialog(siv: &mut Cursive) -> InstallerView { let options = siv.call_on_name("password-options", |view: &mut FormView| { let root_password = view .get_value::(0) - .ok_or("failed to retrieve password")?; + .map_err(|_| "failed to retrieve password")?; let confirm_password = view .get_value::(1) - .ok_or("failed to retrieve password confirmation")?; + .map_err(|_| "failed to retrieve password confirmation")?; let email = view .get_value::(2) - .ok_or("failed to retrieve email")?; + .map_err(|_| "failed to retrieve email")?; let email_regex = Regex::new(r"^[\w\+\-\~]+(\.[\w\+\-\~]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*$") @@ -588,27 +588,27 @@ fn network_dialog(siv: &mut Cursive) -> InstallerView { let options = siv.call_on_name("network-options", |view: &mut FormView| { let ifname = view .get_value::(0) - .ok_or("failed to retrieve management interface name")?; + .map_err(|_| "failed to retrieve management interface name")?; let fqdn = view .get_value::(1) - .ok_or("failed to retrieve host FQDN")? + .map_err(|_| "failed to retrieve host FQDN")? .parse::() .map_err(|err| format!("hostname does not look valid:\n\n{err}"))?; let address = view .get_value::(2) - .ok_or("failed to retrieve host address")?; + .map_err(|_| "failed to retrieve host address")?; let gateway = view .get_value::(3) - .ok_or("failed to retrieve gateway address")? + .map_err(|_| "failed to retrieve gateway address")? .parse::() .map_err(|err| err.to_string())?; let dns_server = view .get_value::(4) - .ok_or("failed to retrieve DNS server address")? + .map_err(|_| "failed to retrieve DNS server address")? .parse::() .map_err(|err| err.to_string())?; diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index dbd13ea..46bdd9f 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -75,8 +75,11 @@ impl BootdiskOptionsView { .get_child_mut(0) .and_then(|v| v.downcast_mut::>()) .map(NamedView::::get_mut) - .and_then(|v| v.get_value::, _>(0)) - .ok_or("failed to retrieve bootdisk")?; + .ok_or("failed go retrieve disk selection view") + .and_then(|v| { + v.get_value::, _>(0) + .map_err(|_| "failed to retrieve bootdisk") + })?; options.disks = vec![disk]; } @@ -181,8 +184,9 @@ impl AdvancedBootdiskOptionsView { .view .get_child(1) .and_then(|v| v.downcast_ref::()) - .and_then(|v| v.get_value::, _>(0)) - .ok_or("Failed to retrieve filesystem type".to_owned())?; + .ok_or("Failed to retrieve advanced bootdisk options view")? + .get_value::, _>(0) + .map_err(|_| "Failed to retrieve filesystem type".to_owned())?; let advanced = self .view @@ -190,21 +194,15 @@ impl AdvancedBootdiskOptionsView { .ok_or("Failed to retrieve advanced bootdisk options view".to_owned())?; if let Some(view) = advanced.downcast_mut::() { - let advanced = view - .get_values() - .map(AdvancedBootdiskOptions::Lvm) - .ok_or("Failed to retrieve advanced bootdisk options")?; + let advanced = view.get_values()?; Ok(BootdiskOptions { disks: vec![], fstype, - advanced, + advanced: AdvancedBootdiskOptions::Lvm(advanced), }) } else if let Some(view) = advanced.downcast_mut::() { - let (disks, advanced) = view - .get_values() - .ok_or("Failed to retrieve advanced bootdisk options")?; - + let (disks, advanced) = view.get_values()?; if let FsType::Zfs(level) = fstype { check_zfs_raid_config(level, &disks).map_err(|err| format!("{fstype}: {err}"))?; } @@ -215,9 +213,7 @@ impl AdvancedBootdiskOptionsView { advanced: AdvancedBootdiskOptions::Zfs(advanced), }) } else if let Some(view) = advanced.downcast_mut::() { - let (disks, advanced) = view - .get_values() - .ok_or("Failed to retrieve advanced bootdisk options")?; + let (disks, advanced) = view.get_values()?; if let FsType::Btrfs(level) = fstype { check_btrfs_raid_config(level, &disks).map_err(|err| format!("{fstype}: {err}"))?; @@ -275,25 +271,34 @@ impl LvmBootdiskOptionsView { Self { view } } - fn get_values(&mut self) -> Option { + fn get_values(&mut self) -> Result { let is_pve = crate::setup_info().config.product == ProxmoxProduct::PVE; let min_lvm_free_id = if is_pve { 4 } else { 2 }; + + let total_size = self.view.get_value::(0)?; + let swap_size = self.view.get_opt_value::(1)?; + let max_root_size = if is_pve { - self.view.get_value::(2) + self.view.get_opt_value::(2)? } else { None }; let max_data_size = if is_pve { - self.view.get_value::(3) + self.view.get_opt_value::(3)? } else { None }; - Some(LvmBootdiskOptions { - total_size: self.view.get_value::(0)?, - swap_size: self.view.get_value::(1), + + let min_lvm_free = self + .view + .get_opt_value::(min_lvm_free_id)?; + + Ok(LvmBootdiskOptions { + total_size, + swap_size, max_root_size, max_data_size, - min_lvm_free: self.view.get_value::(min_lvm_free_id), + min_lvm_free, }) } } @@ -405,7 +410,7 @@ impl MultiDiskOptionsView { let mut selected_disks = Vec::new(); for i in 0..disk_form.len() { - let disk = disk_form.get_value::>, _>(i)?; + let disk = disk_form.get_value::>, _>(i).ok()?; // `None` means no disk was selected for this slot if let Some(disk) = disk { @@ -462,11 +467,19 @@ impl BtrfsBootdiskOptionsView { Self { view } } - fn get_values(&mut self) -> Option<(Vec, BtrfsBootdiskOptions)> { - let (disks, selected_disks) = self.view.get_disks_and_selection()?; - let disk_size = self.view.inner_mut()?.get_value::(0)?; + fn get_values(&mut self) -> Result<(Vec, BtrfsBootdiskOptions), String> { + let (disks, selected_disks) = self + .view + .get_disks_and_selection() + .ok_or("failed to retrieve disk selection".to_owned())?; + + let disk_size = self + .view + .inner_mut() + .ok_or("failed to retrieve Btrfs disk size")? + .get_value::(0)?; - Some(( + Ok(( disks, BtrfsBootdiskOptions { disk_size, @@ -524,17 +537,31 @@ impl ZfsBootdiskOptionsView { Self { view } } - fn get_values(&mut self) -> Option<(Vec, ZfsBootdiskOptions)> { - let (disks, selected_disks) = self.view.get_disks_and_selection()?; - let view = self.view.inner_mut()?; - - let ashift = view.get_value::(0)?; - let compress = view.get_value::, _>(1)?; - let checksum = view.get_value::, _>(2)?; - let copies = view.get_value::(3)?; - let disk_size = view.get_value::(4)?; - - Some(( + fn get_values(&mut self) -> Result<(Vec, ZfsBootdiskOptions), String> { + let (disks, selected_disks) = self + .view + .get_disks_and_selection() + .ok_or("failed to retrieve disk selection".to_owned())?; + + let view = self.view.inner_mut().ok_or("failed to retrieve view")?; + + let ashift = view + .get_value::(0) + .map_err(|err| format!("invalid ashift value: {err}"))?; + let compress = view + .get_value::, _>(1) + .map_err(|_| "failed to get compress option")?; + let checksum = view + .get_value::, _>(2) + .map_err(|_| "failed to get checksum option")?; + let copies = view + .get_value::(3) + .map_err(|err| format!("invalid copies value: {err}"))?; + let disk_size = view + .get_value::(4) + .map_err(|err| format!("invalid disk size: {err}"))?; + + Ok(( disks, ZfsBootdiskOptions { ashift, diff --git a/proxmox-tui-installer/src/views/mod.rs b/proxmox-tui-installer/src/views/mod.rs index 76f96a1..7efd487 100644 --- a/proxmox-tui-installer/src/views/mod.rs +++ b/proxmox-tui-installer/src/views/mod.rs @@ -1,4 +1,4 @@ -use std::{mem, net::IpAddr, rc::Rc, str::FromStr}; +use std::{fmt, mem, net::IpAddr, rc::Rc, str::FromStr}; use cursive::{ event::{Event, EventResult}, @@ -18,6 +18,46 @@ pub use table_view::*; mod timezone; pub use timezone::*; +pub enum NumericEditViewError +where + T: FromStr, +{ + ParseError(::Err), + OutOfRange { + value: T, + min: Option, + max: Option, + }, + Empty, + InvalidView, +} + +impl fmt::Display for NumericEditViewError +where + T: fmt::Display + FromStr, + ::Err: fmt::Display, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use NumericEditViewError::*; + match self { + ParseError(err) => write!(f, "failed to parse value: {err}"), + OutOfRange { value, min, max } => { + write!(f, "out of range: {value}")?; + match (min, max) { + (Some(min), Some(max)) => { + write!(f, ", must be at least {min} and at most {max}") + } + (Some(min), None) => write!(f, ", must be at least {min}"), + (None, Some(max)) => write!(f, ", must be at most {max}"), + _ => Ok(()), + } + } + Empty => write!(f, "value required"), + InvalidView => write!(f, "invalid view state"), + } + } +} + pub struct NumericEditView { view: EditView, max_value: Option, @@ -25,7 +65,10 @@ pub struct NumericEditView { allow_empty: bool, } -impl NumericEditView { +impl NumericEditView +where + T: Copy + ToString + FromStr + PartialOrd, +{ pub fn new() -> Self { Self { view: EditView::new().content("0"), @@ -35,6 +78,15 @@ impl NumericEditView { } } + pub fn new_empty() -> Self { + Self { + view: EditView::new(), + max_value: None, + max_content_width: None, + allow_empty: true, + } + } + pub fn max_value(mut self, max: T) -> Self { self.max_value = Some(max); self @@ -46,30 +98,18 @@ impl NumericEditView { self } - pub fn allow_empty(mut self, value: bool) -> Self { - self.allow_empty = value; - - if value { - self.view = EditView::new(); - } else { - self.view = EditView::new().content("0"); - } - - self.view.set_max_content_width(self.max_content_width); - self - } - - pub fn get_content(&self) -> Result::Err> { - assert!(!self.allow_empty); - self.view.get_content().parse() - } - - pub fn get_content_maybe(&self) -> Option::Err>> { + pub fn get_content(&self) -> Result> { let content = self.view.get_content(); - if !content.is_empty() { - Some(self.view.get_content().parse()) - } else { - None + + match content.parse() { + Err(_) if content.is_empty() && self.allow_empty => Err(NumericEditViewError::Empty), + Err(err) => Err(NumericEditViewError::ParseError(err)), + Ok(value) if !self.in_range(value) => Err(NumericEditViewError::OutOfRange { + value, + min: self.min_value, + max: self.max_value, + }), + Ok(value) => Ok(value), } } @@ -77,6 +117,10 @@ impl NumericEditView { self.max_value = Some(max); } + fn in_range(&self, value: T) -> bool { + !self.max_value.map_or(false, |max| value >= max) + } + fn check_bounds(&mut self, original: Rc, result: EventResult) -> EventResult { // Check if the new value is actually valid according to the max value, if set if let Some(max) = self.max_value { @@ -163,7 +207,6 @@ impl IntegerEditView { pub struct DiskSizeEditView { view: LinearLayout, - allow_empty: bool, } impl DiskSizeEditView { @@ -172,21 +215,15 @@ impl DiskSizeEditView { .child(FloatEditView::new().full_width()) .child(TextView::new(" GB")); - Self { - view, - allow_empty: false, - } + Self { view } } pub fn new_emptyable() -> Self { let view = LinearLayout::horizontal() - .child(FloatEditView::new().allow_empty(true).full_width()) + .child(FloatEditView::new_empty().full_width()) .child(TextView::new(" GB")); - Self { - view, - allow_empty: true, - } + Self { view } } pub fn content(mut self, content: f64) -> Self { @@ -230,20 +267,19 @@ impl DiskSizeEditView { self } - pub fn get_content(&self) -> Option { - self.with_view(|v| { - v.get_child(0)? - .downcast_ref::>()? - .with_view(|v| { - if self.allow_empty { - v.get_content_maybe().and_then(Result::ok) - } else { - v.get_content().ok() - } - }) - .flatten() - }) - .flatten() + pub fn get_content(&self) -> Result> { + let content = self + .with_view(|v| { + v.get_child(0)? + .downcast_ref::>()? + .with_view(|v| v.get_content()) + }) + .flatten(); + + match content { + Some(res) => res, + None => Err(NumericEditViewError::InvalidView), + } } } @@ -252,18 +288,28 @@ impl ViewWrapper for DiskSizeEditView { } pub trait FormViewGetValue { - fn get_value(&self) -> Option; + type Error; + + fn get_value(&self) -> Result; + + fn get_opt_value(&self) -> Result, Self::Error> { + self.get_value().map(|val| Some(val)) + } } impl FormViewGetValue for EditView { - fn get_value(&self) -> Option { - Some((*self.get_content()).clone()) + type Error = (); + + fn get_value(&self) -> Result { + Ok((*self.get_content()).clone()) } } impl FormViewGetValue for SelectView { - fn get_value(&self) -> Option { - self.selection().map(|v| (*v).clone()) + type Error = (); + + fn get_value(&self) -> Result { + self.selection().map(|v| (*v).clone()).ok_or(()) } } @@ -272,14 +318,26 @@ where T: Copy + ToString + FromStr + PartialOrd, NumericEditView: ViewWrapper, { - fn get_value(&self) -> Option { - self.get_content().ok() + type Error = NumericEditViewError; + + fn get_value(&self) -> Result { + self.get_content() + } + + fn get_opt_value(&self) -> Result, Self::Error> { + match self.get_content() { + Ok(val) => Ok(Some(val)), + Err(NumericEditViewError::Empty) => Ok(None), + Err(err) => Err(err), + } } } impl FormViewGetValue for CidrAddressEditView { - fn get_value(&self) -> Option { - self.get_values() + type Error = (); + + fn get_value(&self) -> Result { + self.get_values().ok_or(()) } } @@ -289,15 +347,59 @@ where NamedView: ViewWrapper, as ViewWrapper>::V: FormViewGetValue, { - fn get_value(&self) -> Option { - self.with_view(|v| v.get_value()).flatten() + type Error = (); + + fn get_value(&self) -> Result { + match self.with_view(|v| v.get_value()) { + Some(Ok(res)) => Ok(res), + _ => Err(()), + } } } impl FormViewGetValue for DiskSizeEditView { - fn get_value(&self) -> Option { + type Error = NumericEditViewError; + + fn get_value(&self) -> Result { self.get_content() } + + fn get_opt_value(&self) -> Result, Self::Error> { + match self.get_content() { + Ok(val) => Ok(Some(val)), + Err(NumericEditViewError::Empty) => Ok(None), + Err(err) => Err(err), + } + } +} + +pub enum FormViewError { + ChildNotFound(usize), + ValueError(T), +} + +impl fmt::Display for FormViewError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::ChildNotFound(i) => write!(f, "child with index {i} does not exist"), + Self::ValueError(err) => write!(f, "{err}"), + } + } +} + +impl fmt::Debug for FormViewError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::ChildNotFound(index) => write!(f, "ChildNotFound({index})"), + Self::ValueError(err) => write!(f, "ValueError({err:?})"), + } + } +} + +impl From> for String { + fn from(value: FormViewError) -> Self { + value.to_string() + } } pub struct FormView { @@ -348,11 +450,28 @@ impl FormView { .downcast_mut::() } - pub fn get_value(&self, index: usize) -> Option + pub fn get_value( + &self, + index: usize, + ) -> Result>::Error>> + where + T: View + FormViewGetValue, + { + self.get_child::(index) + .ok_or(FormViewError::ChildNotFound(index)) + .and_then(|v| v.get_value().map_err(FormViewError::ValueError)) + } + + pub fn get_opt_value( + &self, + index: usize, + ) -> Result, FormViewError<>::Error>> where T: View + FormViewGetValue, { - self.get_child::(index)?.get_value() + self.get_child::(index) + .ok_or(FormViewError::ChildNotFound(index)) + .and_then(|v| v.get_opt_value().map_err(FormViewError::ValueError)) } pub fn replace_child(&mut self, index: usize, view: impl View) { diff --git a/proxmox-tui-installer/src/views/timezone.rs b/proxmox-tui-installer/src/views/timezone.rs index 6732286..bd38a92 100644 --- a/proxmox-tui-installer/src/views/timezone.rs +++ b/proxmox-tui-installer/src/views/timezone.rs @@ -100,17 +100,17 @@ impl TimezoneOptionsView { let country = self .view .get_value::(0) - .ok_or("failed to retrieve timezone")?; + .map_err(|_| "failed to retrieve country")?; let timezone = self .view .get_value::, _>(1) - .ok_or("failed to retrieve timezone")?; + .map_err(|_| "failed to retrieve timezone")?; let kmap = self .view .get_value::, _>(2) - .ok_or("failed to retrieve keyboard layout")?; + .map_err(|_| "failed to retrieve keyboard layout")?; Ok(TimezoneOptions { country, -- 2.42.0 From f.weber at proxmox.com Wed Oct 4 16:53:46 2023 From: f.weber at proxmox.com (Friedrich Weber) Date: Wed, 4 Oct 2023 16:53:46 +0200 Subject: [pve-devel] [PATCH pve-installer v9] update the PCI(e) docs In-Reply-To: <20230720093248.44712-1-n.ullreich@proxmox.com> References: <20230720093248.44712-1-n.ullreich@proxmox.com> Message-ID: Ping due to user interest [1]. The patch still applies (though the repo in the subject should be `pve-docs`), and AFAICT the feedback for v8 has been addressed. [1] https://forum.proxmox.com/threads/134202/#post-592682 On 20/07/2023 11:32, Noel Ullreich wrote: > A little update to the PCI(e) docs. The PCI wiki article has been > reworked as well, in line with changes from this patch. > > Along some minor grammar fixes added: > * how to check if kernelmodules are being loaded > * how to check which drivers to blacklist > * how to add softdeps for module loading > * where to find kernel params > > Signed-off-by: Noel Ullreich > --- > changes from v1: > * fixed spelling mistakes > * reduced code snippets of how to check iommu groupings to one > * moved where to find kernel params to kernel cmdline section > * removed wrong info on display output. will add correct info to > Examples-Wiki > * changed module names to variable-names, so that people can't > blindly copy-paste. > * restructured commit message ;) > > changes from v2: > * while moving where to find the kernel params to the kernel > cmdline section, I forgot to remove it from the pci(e) section > * fixed typo in the link to the kernel param section > > changes from v3: > * Some restructuring of the layout as well as moving parts of the > PCI examples wiki to the docs here. This should lead to well- > structured, concise docs that are independent from the PCI wiki. > * found some more minor grammar errors > * found a spelling mistake in qm.adoc > > changes from v4: > * formatted the git message wrong again :/ > > changes from v5: > * fixed links to wiki > * moved where to find kernel params to end of its chapter > * the `vfio_virqfd` does not need to be loaded anymore with kernel 6.2 > > changes from v6: the public wiki was updated -> fixed the links > > changes from v7: > Forum user Leesteken noted that for Intel cpus IOMMU is not automatically > activated anymore. > https://forum.proxmox.com/threads/fix-pci-passthrough-documentation.122521/post-566926 > Thanks Leesteken :) > > changes from v8: > Amended Dominiks notes: > * added a note to remove `vfio_virqfd` with pve 7 eol > * fixed formatting of a note-block > > qm-pci-passthrough.adoc | 165 +++++++++++++++++++++++++++++++--------- > qm.adoc | 2 +- > system-booting.adoc | 8 ++ > 3 files changed, 137 insertions(+), 38 deletions(-) > > diff --git a/qm-pci-passthrough.adoc b/qm-pci-passthrough.adoc > index b90a0b9..693deb7 100644 > --- a/qm-pci-passthrough.adoc > +++ b/qm-pci-passthrough.adoc > @@ -13,19 +13,27 @@ features (e.g., offloading). > But, if you pass through a device to a virtual machine, you cannot use that > device anymore on the host or in any other VM. > > +Note that, while PCI passthrough is available for i440fx and q35 machines, PCIe > +passthrough is only available on q35 machines. This does not mean that > +PCIe capable devices that are passed through as PCI devices will only run at > +PCI speeds. Passing through devices as PCIe just sets a flag for the guest to > +tell it that the device is a PCIe device instead of a "really fast legacy PCI > +device". Some guest applications benefit from this. > + > General Requirements > ~~~~~~~~~~~~~~~~~~~~ > > -Since passthrough is a feature which also needs hardware support, there are > -some requirements to check and preparations to be done to make it work. > - > +Since passthrough is performed on real hardware, it needs to fulfill some > +requirements. A brief overview of these requirements is given below, for more > +information on specific devices, see > +https://pve.proxmox.com/wiki/PCI_Passthrough[PCI Passthrough Examples]. > > Hardware > ^^^^^^^^ > Your hardware needs to support `IOMMU` (*I*/*O* **M**emory **M**anagement > **U**nit) interrupt remapping, this includes the CPU and the mainboard. > > -Generally, Intel systems with VT-d, and AMD systems with AMD-Vi support this. > +Generally, Intel systems with VT-d and AMD systems with AMD-Vi support this. > But it is not guaranteed that everything will work out of the box, due > to bad hardware implementation and missing or low quality drivers. > > @@ -35,6 +43,17 @@ hardware, but even then, many modern system can support this. > Please refer to your hardware vendor to check if they support this feature > under Linux for your specific setup. > > +Determining PCI Card Address > +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > + > +The easiest way is to use the GUI to add a device of type "Host PCI" in the VM's > +hardware tab. Alternatively, you can use the command line. > + > +You can locate your card using > + > +---- > + lspci > +---- > > Configuration > ^^^^^^^^^^^^^ > @@ -44,13 +63,12 @@ some configuration to enable PCI(e) passthrough. > > .IOMMU > > -First, you have to enable IOMMU support in your BIOS/UEFI. Usually the > -corresponding setting is called `IOMMU` or `VT-d`,but you should find the exact > +First, you will have to enable IOMMU support in your BIOS/UEFI. Usually the > +corresponding setting is called `IOMMU` or `VT-d`, but you should find the exact > option name in the manual of your motherboard. > > -For Intel CPUs, you may also need to enable the IOMMU on the > -xref:sysboot_edit_kernel_cmdline[kernel command line] for older (pre-5.15) > -kernels by adding: > +For Intel CPUs, you also need to enable the IOMMU on the > +xref:sysboot_edit_kernel_cmdline[kernel command line] kernels by adding: > > ---- > intel_iommu=on > @@ -74,14 +92,17 @@ to the xref:sysboot_edit_kernel_cmdline[kernel commandline]. > > .Kernel Modules > > +//TODO: remove `vfio_virqfd` stuff with eol of pve 7 > You have to make sure the following modules are loaded. This can be achieved by > -adding them to `'/etc/modules'' > +adding them to `'/etc/modules''. In kernels newer than 6.2 ({pve} 8 and onward) > +the 'vfio_virqfd' module is part of the 'vfio' module, therefore loading > +'vfio_virqfd' in {pve} 8 and newer is not necessary. > > ---- > vfio > vfio_iommu_type1 > vfio_pci > - vfio_virqfd > + vfio_virqfd #not needed if on kernel 6.2 or newer > ---- > > [[qm_pci_passthrough_update_initramfs]] > @@ -92,6 +113,14 @@ After changing anything modules related, you need to refresh your > # update-initramfs -u -k all > ---- > > +To check if the modules are being loaded, the output of > + > +---- > +# lsmod | grep vfio > +---- > + > +should include the four modules from above. > + > .Finish Configuration > > Finally reboot to bring the changes into effect and check that it is indeed > @@ -104,11 +133,16 @@ enabled. > should display that `IOMMU`, `Directed I/O` or `Interrupt Remapping` is > enabled, depending on hardware and kernel the exact message can vary. > > +For notes on how to troubleshoot or verify if IOMMU is working as intended, please > +see the https://pve.proxmox.com/wiki/PCI_Passthrough#Verifying_IOMMU_parameters[Verifying IOMMU Parameters] > +section in our wiki. > + > It is also important that the device(s) you want to pass through > -are in a *separate* `IOMMU` group. This can be checked with: > +are in a *separate* `IOMMU` group. This can be checked with a call to the {pve} > +API: > > ---- > -# find /sys/kernel/iommu_groups/ -type l > +# pvesh get /nodes/{nodename}/hardware/pci --pci-class-blacklist "" > ---- > > It is okay if the device is in an `IOMMU` group together with its functions > @@ -159,8 +193,8 @@ PCI(e) card, for example a GPU or a network card. > Host Configuration > ^^^^^^^^^^^^^^^^^^ > > -In this case, the host must not use the card. There are two methods to achieve > -this: > +{pve} tries to automatically make the PCI(e) device unavailable for the host. > +However, if this doesn't work, there are two things that can be done: > > * pass the device IDs to the options of the 'vfio-pci' modules by adding > + > @@ -175,7 +209,7 @@ the vendor and device IDs obtained by: > # lspci -nn > ---- > > -* blacklist the driver completely on the host, ensuring that it is free to bind > +* blacklist the driver on the host completely, ensuring that it is free to bind > for passthrough, with > + > ---- > @@ -183,11 +217,49 @@ for passthrough, with > ---- > + > in a .conf file in */etc/modprobe.d/*. > ++ > +To find the drivername, execute > ++ > +---- > +# lspci -k > +---- > ++ > +for example: > ++ > +---- > +# lspci -k | grep -A 3 "VGA" > +---- > ++ > +will output something similar to > ++ > +---- > +01:00.0 VGA compatible controller: NVIDIA Corporation GP108 [GeForce GT 1030] (rev a1) > + Subsystem: Micro-Star International Co., Ltd. [MSI] GP108 [GeForce GT 1030] > + Kernel driver in use: > + Kernel modules: > +---- > ++ > +Now we can blacklist the drivers by writing them into a .conf file: > ++ > +---- > +echo "blacklist " >> /etc/modprobe.d/blacklist.conf > +---- > > For both methods you need to > xref:qm_pci_passthrough_update_initramfs[update the `initramfs`] again and > reboot after that. > > +Should this not work, you might need to set a soft dependency to load the gpu > +modules before loading 'vfio-pci'. This can be done with the 'softdep' flag, see > +also the manpages on 'modprobe.d' for more information. > + > +For example, if you are using drivers named : > + > +---- > +# echo "softdep pre: vfio-pci" >> /etc/modprobe.d/.conf > +---- > + > + > .Verify Configuration > > To check if your changes were successful, you can use > @@ -208,13 +280,42 @@ passthrough. > [[qm_pci_passthrough_vm_config]] > VM Configuration > ^^^^^^^^^^^^^^^^ > -To pass through the device you need to set the *hostpciX* option in the VM > +When passing through a GPU, the best compatibility is reached when using > +'q35' as machine type, 'OVMF' ('UEFI' for VMs) instead of SeaBIOS and PCIe > +instead of PCI. Note that if you want to use 'OVMF' for GPU passthrough, the > +GPU needs to have an UEFI capable ROM, otherwise use SeaBIOS instead. To check if > +the ROM is UEFI capable, see the > +https://pve.proxmox.com/wiki/PCI_Passthrough#How_to_know_if_a_graphics_card_is_UEFI_.28OVMF.29_compatible[PCI Passthrough Examples] > +wiki. > + > +Furthermore, using OVMF, disabling vga arbitration may be possible, reducing the > +amount of legacy code needed to be run during boot. To disable vga arbitration: > + > +---- > + echo "options vfio-pci ids=, disable_vga=1" > /etc/modprobe.d/vfio.conf > +---- > + > +replacing the and with the ones obtained from > + > +---- > +# lspci -nn > +---- > + > +PCI devices can be added in the web interface in the hardware section of the VM. > +Alternatively, you can use the command line; set the *hostpciX* option in the VM > configuration, for example by executing: > > ---- > # qm set VMID -hostpci0 00:02.0 > ---- > > +or by adding a line to the VM configuration file: > + > +---- > + hostpci0: 00:02.0 > +---- > + > + > If your device has multiple functions (e.g., ``00:02.0`' and ``00:02.1`' ), > you can pass them through all together with the shortened syntax ``00:02`'. > This is equivalent with checking the ``All Functions`' checkbox in the > @@ -262,21 +363,21 @@ For example: > # qm set VMID -hostpci0 02:00,device-id=0x10f6,sub-vendor-id=0x0000 > ---- > > - > -Other considerations > -^^^^^^^^^^^^^^^^^^^^ > - > -When passing through a GPU, the best compatibility is reached when using > -'q35' as machine type, 'OVMF' ('EFI' for VMs) instead of SeaBIOS and PCIe > -instead of PCI. Note that if you want to use 'OVMF' for GPU passthrough, the > -GPU needs to have an EFI capable ROM, otherwise use SeaBIOS instead. > - > SR-IOV > ~~~~~~ > > -Another variant for passing through PCI(e) devices, is to use the hardware > +Another variant for passing through PCI(e) devices is to use the hardware > virtualization features of your devices, if available. > > +.Enabling SR-IOV > +[NOTE] > +==== > +To use SR-IOV, platform support is especially important. It may be necessary > +to enable this feature in the BIOS/UEFI first, or to use a specific PCI(e) port > +for it to work. In doubt, consult the manual of the platform or contact its > +vendor. > +==== > + > 'SR-IOV' (**S**ingle-**R**oot **I**nput/**O**utput **V**irtualization) enables > a single device to provide multiple 'VF' (**V**irtual **F**unctions) to the > system. Each of those 'VF' can be used in a different VM, with full hardware > @@ -288,7 +389,6 @@ Currently, the most common use case for this are NICs (**N**etwork > physical port. This allows using features such as checksum offloading, etc. to > be used inside a VM, reducing the (host) CPU overhead. > > - > Host Configuration > ^^^^^^^^^^^^^^^^^^ > > @@ -326,14 +426,6 @@ After creating VFs, you should see them as separate PCI(e) devices when > outputting them with `lspci`. Get their ID and pass them through like a > xref:qm_pci_passthrough_vm_config[normal PCI(e) device]. > > -Other considerations > -^^^^^^^^^^^^^^^^^^^^ > - > -For this feature, platform support is especially important. It may be necessary > -to enable this feature in the BIOS/EFI first, or to use a specific PCI(e) port > -for it to work. In doubt, consult the manual of the platform or contact its > -vendor. > - > Mediated Devices (vGPU, GVT-g) > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > > @@ -346,7 +438,6 @@ With this, a physical Card is able to create virtual cards, similar to SR-IOV. > The difference is that mediated devices do not appear as PCI(e) devices in the > host, and are such only suited for using in virtual machines. > > - > Host Configuration > ^^^^^^^^^^^^^^^^^^ > > diff --git a/qm.adoc b/qm.adoc > index b3c3034..ed804e2 100644 > --- a/qm.adoc > +++ b/qm.adoc > @@ -139,7 +139,7 @@ snapshots) more intelligently. > {pve} allows to boot VMs with different firmware and machine types, namely > xref:qm_bios_and_uefi[SeaBIOS and OVMF]. In most cases you want to switch from > the default SeaBIOS to OVMF only if you plan to use > -xref:qm_pci_passthrough[PCIe pass through]. A VMs 'Machine Type' defines the > +xref:qm_pci_passthrough[PCIe passthrough]. A VMs 'Machine Type' defines the > hardware layout of the VM's virtual motherboard. You can choose between the > default https://en.wikipedia.org/wiki/Intel_440FX[Intel 440FX] or the > https://ark.intel.com/content/www/us/en/ark/products/31918/intel-82q35-graphics-and-memory-controller.html[Q35] > diff --git a/system-booting.adoc b/system-booting.adoc > index 0e61e3d..71603b0 100644 > --- a/system-booting.adoc > +++ b/system-booting.adoc > @@ -288,6 +288,14 @@ The kernel commandline needs to be placed as one line in `/etc/kernel/cmdline`. > To apply your changes, run `proxmox-boot-tool refresh`, which sets it as the > `option` line for all config files in `loader/entries/proxmox-*.conf`. > > +A complete list of kernel parameters can be found at > +'https://www.kernel.org/doc/html/v/admin-guide/kernel-parameters.html'. > +replace with the major.minor version (e.g. 5.15). You can > +find your kernel version by running > + > +---- > +# uname -r > +---- > > [[sysboot_kernel_pin]] > Override the Kernel-Version for next Boot From c.heiss at proxmox.com Wed Oct 4 18:00:56 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 4 Oct 2023 18:00:56 +0200 Subject: [pve-devel] [PATCH installer 2/3] tui: bootdisk: pass down product info to advanced dialog In-Reply-To: <20231004160325.704249-1-c.heiss@proxmox.com> References: <20231004160325.704249-1-c.heiss@proxmox.com> Message-ID: <20231004160325.704249-3-c.heiss@proxmox.com> Enables the advanced and LVM dialog to determine what options (max root/data size and Btrfs RAIDs) by itself, without needing to resort to the global `setup_info()` function. No functional changes. Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/views/bootdisk.rs | 74 ++++++++++++++------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index dbd13ea..ba08c8b 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -16,7 +16,7 @@ use crate::{ FsType, LvmBootdiskOptions, ZfsBootdiskOptions, ZfsRaidLevel, FS_TYPES, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS, }, - setup::BootType, + setup::{BootType, ProductConfig}, }; use crate::{setup::ProxmoxProduct, InstallerState}; @@ -37,6 +37,11 @@ impl BootdiskOptionsView { ) .with_name("bootdisk-options-target-disk"); + let product_conf = siv + .user_data::() + .map(|state| state.setup_info.config.clone()) + .unwrap(); // Safety: InstallerState must always be set + let advanced_options = Rc::new(RefCell::new(options.clone())); let advanced_button = LinearLayout::horizontal() @@ -45,7 +50,11 @@ impl BootdiskOptionsView { let disks = disks.to_owned(); let options = advanced_options.clone(); move |siv| { - siv.add_layer(advanced_options_view(&disks, options.clone())); + siv.add_layer(advanced_options_view( + &disks, + options.clone(), + product_conf.clone(), + )); } })); @@ -95,10 +104,9 @@ struct AdvancedBootdiskOptionsView { } impl AdvancedBootdiskOptionsView { - fn new(disks: &[Disk], options: &BootdiskOptions) -> Self { - let enable_btrfs = crate::setup_info().config.enable_btrfs; - - let filter_btrfs = |fstype: &&FsType| -> bool { enable_btrfs || !fstype.is_btrfs() }; + fn new(disks: &[Disk], options: &BootdiskOptions, product_conf: ProductConfig) -> Self { + let filter_btrfs = + |fstype: &&FsType| -> bool { product_conf.enable_btrfs || !fstype.is_btrfs() }; let fstype_select = SelectView::new() .popup() @@ -126,7 +134,10 @@ impl AdvancedBootdiskOptionsView { .child(DummyView.full_width()); match &options.advanced { - AdvancedBootdiskOptions::Lvm(lvm) => view.add_child(LvmBootdiskOptionsView::new(lvm)), + AdvancedBootdiskOptions::Lvm(lvm) => view.add_child(LvmBootdiskOptionsView::new( + lvm, + product_conf.product == ProxmoxProduct::PVE, + )), AdvancedBootdiskOptions::Zfs(zfs) => { view.add_child(ZfsBootdiskOptionsView::new(disks, zfs)) } @@ -139,6 +150,11 @@ impl AdvancedBootdiskOptionsView { } fn fstype_on_submit(siv: &mut Cursive, disks: &[Disk], fstype: &FsType) { + let is_pve = siv + .user_data::() + .map(|state| state.setup_info.config.product == ProxmoxProduct::PVE) + .unwrap_or_default(); + siv.call_on_name("advanced-bootdisk-options-dialog", |view: &mut Dialog| { if let Some(AdvancedBootdiskOptionsView { view }) = view.get_content_mut().downcast_mut() @@ -147,6 +163,7 @@ impl AdvancedBootdiskOptionsView { match fstype { FsType::Ext4 | FsType::Xfs => view.add_child(LvmBootdiskOptionsView::new( &LvmBootdiskOptions::defaults_from(&disks[0]), + is_pve, )), FsType::Zfs(_) => view.add_child(ZfsBootdiskOptionsView::new( disks, @@ -240,11 +257,11 @@ impl ViewWrapper for AdvancedBootdiskOptionsView { struct LvmBootdiskOptionsView { view: FormView, + has_extra_fields: bool, } impl LvmBootdiskOptionsView { - fn new(options: &LvmBootdiskOptions) -> Self { - let is_pve = crate::setup_info().config.product == ProxmoxProduct::PVE; + fn new(options: &LvmBootdiskOptions, show_extra_fields: bool) -> Self { // TODO: Set maximum accordingly to disk size let view = FormView::new() .child( @@ -258,12 +275,12 @@ impl LvmBootdiskOptionsView { DiskSizeEditView::new_emptyable().content_maybe(options.swap_size), ) .child_conditional( - is_pve, + show_extra_fields, "Maximum root volume size", DiskSizeEditView::new_emptyable().content_maybe(options.max_root_size), ) .child_conditional( - is_pve, + show_extra_fields, "Maximum data volume size", DiskSizeEditView::new_emptyable().content_maybe(options.max_data_size), ) @@ -272,22 +289,24 @@ impl LvmBootdiskOptionsView { DiskSizeEditView::new_emptyable().content_maybe(options.min_lvm_free), ); - Self { view } + Self { + view, + has_extra_fields: show_extra_fields, + } } fn get_values(&mut self) -> Option { - let is_pve = crate::setup_info().config.product == ProxmoxProduct::PVE; - let min_lvm_free_id = if is_pve { 4 } else { 2 }; - let max_root_size = if is_pve { - self.view.get_value::(2) - } else { - None - }; - let max_data_size = if is_pve { - self.view.get_value::(3) - } else { - None - }; + let min_lvm_free_id = if self.has_extra_fields { 4 } else { 2 }; + + let max_root_size = self + .has_extra_fields + .then(|| self.view.get_value::(2)) + .flatten(); + let max_data_size = self + .has_extra_fields + .then(|| self.view.get_value::(3)) + .flatten(); + Some(LvmBootdiskOptions { total_size: self.view.get_value::(0)?, swap_size: self.view.get_value::(1), @@ -552,10 +571,15 @@ impl ViewWrapper for ZfsBootdiskOptionsView { cursive::wrap_impl!(self.view: MultiDiskOptionsView); } -fn advanced_options_view(disks: &[Disk], options: Rc>) -> impl View { +fn advanced_options_view( + disks: &[Disk], + options: Rc>, + product_conf: ProductConfig, +) -> impl View { Dialog::around(AdvancedBootdiskOptionsView::new( disks, &(*options).borrow(), + product_conf, )) .title("Advanced bootdisk options") .button("Ok", { -- 2.42.0 From c.heiss at proxmox.com Wed Oct 4 18:00:54 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 4 Oct 2023 18:00:54 +0200 Subject: [pve-devel] [PATCH installer 0/3] tui: remove global, unsafe setup info Message-ID: <20231004160325.704249-1-c.heiss@proxmox.com> Removes the `static mut` for holding a `SetupInfo` instance. This is done by either passing the needed info as parameter, or in some cases, the needed information is already available through other means. Not only does it get rid of some ugly, unsafe code, it is needed anyway as a prerequisite by Aaron for pulling out non-TUI-related code into a separate, shared crate. No functional changes overall. Christoph Heiss (3): tui: refactor `NetworkOptions` to have a `defaults_from()` function tui: bootdisk: pass down product info to advanced dialog tui: remove obsolete, global `SetupInfo` state proxmox-tui-installer/src/main.rs | 34 +++------- proxmox-tui-installer/src/options.rs | 36 ++++------ proxmox-tui-installer/src/setup.rs | 10 ++- proxmox-tui-installer/src/views/bootdisk.rs | 74 ++++++++++++++------- 4 files changed, 74 insertions(+), 80 deletions(-) -- 2.41.0 From c.heiss at proxmox.com Wed Oct 4 18:00:57 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 4 Oct 2023 18:00:57 +0200 Subject: [pve-devel] [PATCH installer 3/3] tui: remove obsolete, global `SetupInfo` state In-Reply-To: <20231004160325.704249-1-c.heiss@proxmox.com> References: <20231004160325.704249-1-c.heiss@proxmox.com> Message-ID: <20231004160325.704249-4-c.heiss@proxmox.com> No functional changes. Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/main.rs | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index ea8c8d9..49f378b 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -1,3 +1,5 @@ +#![forbid(unsafe_code)] + use std::{ collections::HashMap, env, @@ -50,24 +52,6 @@ const PROXMOX_LOGO: &str = r#" | __/| | | (_) > <| | | | | | (_) > < |_| |_| \___/_/\_\_| |_| |_|\___/_/\_\ "#; -/// ISO information is available globally. -static mut SETUP_INFO: Option = None; - -pub fn setup_info() -> &'static SetupInfo { - unsafe { SETUP_INFO.as_ref().unwrap() } -} - -fn init_setup_info(info: SetupInfo) { - unsafe { - SETUP_INFO = Some(info); - } -} - -#[inline] -pub fn current_product() -> setup::ProxmoxProduct { - setup_info().config.product -} - struct InstallerView { view: ResizedView, } @@ -223,7 +207,6 @@ fn installer_setup(in_test_mode: bool) -> Result<(SetupInfo, LocaleInfo, Runtime setup::read_json(&path).map_err(|err| format!("Failed to retrieve setup info: {err}"))? }; - init_setup_info(installer_info.clone()); let locale_info = { let mut path = path.clone(); -- 2.42.0 From c.heiss at proxmox.com Wed Oct 4 18:00:55 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 4 Oct 2023 18:00:55 +0200 Subject: [pve-devel] [PATCH installer 1/3] tui: refactor `NetworkOptions` to have a `defaults_from()` function In-Reply-To: <20231004160325.704249-1-c.heiss@proxmox.com> References: <20231004160325.704249-1-c.heiss@proxmox.com> Message-ID: <20231004160325.704249-2-c.heiss@proxmox.com> This aligns it with the other constructors for options struct by introducing a `::defaults_from()` function. No functional changes. Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/main.rs | 15 ++++++------ proxmox-tui-installer/src/options.rs | 36 ++++++++++------------------ proxmox-tui-installer/src/setup.rs | 10 ++++---- 3 files changed, 24 insertions(+), 37 deletions(-) diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index 3f01713..ea8c8d9 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -166,7 +166,6 @@ enum InstallerStep { #[derive(Clone)] struct InstallerState { options: InstallerOptions, - /// FIXME: Remove: setup_info: SetupInfo, runtime_info: RuntimeInfo, locales: LocaleInfo, @@ -184,7 +183,7 @@ fn main() { _ => cfg!(debug_assertions), }; - let (locales, runtime_info) = match installer_setup(in_test_mode) { + let (setup_info, locales, runtime_info) = match installer_setup(in_test_mode) { Ok(result) => result, Err(err) => initial_setup_error(&mut siv, &err), }; @@ -197,10 +196,10 @@ fn main() { bootdisk: BootdiskOptions::defaults_from(&runtime_info.disks[0]), timezone: TimezoneOptions::defaults_from(&runtime_info, &locales), password: Default::default(), - network: NetworkOptions::from(&runtime_info.network), + network: NetworkOptions::defaults_from(&setup_info, &runtime_info.network), autoreboot: false, }, - setup_info: setup_info().clone(), // FIXME: REMOVE + setup_info, runtime_info, locales, steps: HashMap::new(), @@ -211,20 +210,20 @@ fn main() { siv.run(); } -fn installer_setup(in_test_mode: bool) -> Result<(LocaleInfo, RuntimeInfo), String> { +fn installer_setup(in_test_mode: bool) -> Result<(SetupInfo, LocaleInfo, RuntimeInfo), String> { let base_path = if in_test_mode { "./testdir" } else { "/" }; let mut path = PathBuf::from(base_path); path.push("run"); path.push("proxmox-installer"); - let installer_info = { + let installer_info: SetupInfo = { let mut path = path.clone(); path.push("iso-info.json"); setup::read_json(&path).map_err(|err| format!("Failed to retrieve setup info: {err}"))? }; - init_setup_info(installer_info); + init_setup_info(installer_info.clone()); let locale_info = { let mut path = path.clone(); @@ -245,7 +244,7 @@ fn installer_setup(in_test_mode: bool) -> Result<(LocaleInfo, RuntimeInfo), Stri if runtime_info.disks.is_empty() { Err("The installer could not find any supported hard disks.".to_owned()) } else { - Ok((locale_info, runtime_info)) + Ok((installer_info, locale_info, runtime_info)) } } diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs index a0a81c9..56ad7f2 100644 --- a/proxmox-tui-installer/src/options.rs +++ b/proxmox-tui-installer/src/options.rs @@ -1,7 +1,7 @@ use std::net::{IpAddr, Ipv4Addr}; use std::{cmp, fmt}; -use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo}; +use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo, SetupInfo}; use crate::utils::{CidrAddress, Fqdn}; use crate::SummaryOption; @@ -337,43 +337,33 @@ pub struct NetworkOptions { pub dns_server: IpAddr, } -impl Default for NetworkOptions { - fn default() -> Self { - let fqdn = format!( - "{}.example.invalid", - crate::current_product().default_hostname() - ); - // TODO: Retrieve automatically - Self { +impl NetworkOptions { + pub fn defaults_from(setup: &SetupInfo, network: &NetworkInfo) -> Self { + let hostname = setup.config.product.default_hostname(); + + let mut this = Self { ifname: String::new(), - fqdn: fqdn.parse().unwrap(), + fqdn: Fqdn::from(&format!("{hostname}.example.invalid")).unwrap(), // Safety: The provided mask will always be valid. address: CidrAddress::new(Ipv4Addr::UNSPECIFIED, 0).unwrap(), gateway: Ipv4Addr::UNSPECIFIED.into(), dns_server: Ipv4Addr::UNSPECIFIED.into(), - } - } -} - -impl From<&NetworkInfo> for NetworkOptions { - fn from(info: &NetworkInfo) -> Self { - let mut this = Self::default(); + }; - if let Some(ip) = info.dns.dns.first() { + if let Some(ip) = network.dns.dns.first() { this.dns_server = *ip; } - if let Some(domain) = &info.dns.domain { - let hostname = crate::current_product().default_hostname(); + if let Some(domain) = &network.dns.domain { if let Ok(fqdn) = Fqdn::from(&format!("{hostname}.{domain}")) { this.fqdn = fqdn; } } - if let Some(routes) = &info.routes { + if let Some(routes) = &network.routes { let mut filled = false; if let Some(gw) = &routes.gateway4 { - if let Some(iface) = info.interfaces.get(&gw.dev) { + if let Some(iface) = network.interfaces.get(&gw.dev) { this.ifname = iface.name.clone(); if let Some(addresses) = &iface.addresses { if let Some(addr) = addresses.iter().find(|addr| addr.is_ipv4()) { @@ -386,7 +376,7 @@ impl From<&NetworkInfo> for NetworkOptions { } if !filled { if let Some(gw) = &routes.gateway6 { - if let Some(iface) = info.interfaces.get(&gw.dev) { + if let Some(iface) = network.interfaces.get(&gw.dev) { if let Some(addresses) = &iface.addresses { if let Some(addr) = addresses.iter().find(|addr| addr.is_ipv6()) { this.ifname = iface.name.clone(); diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs index 942e319..493bfaf 100644 --- a/proxmox-tui-installer/src/setup.rs +++ b/proxmox-tui-installer/src/setup.rs @@ -196,12 +196,10 @@ impl From for InstallConfig { mngmt_nic: options.network.ifname, - hostname: options - .network - .fqdn - .host() - .unwrap_or_else(|| crate::current_product().default_hostname()) - .to_owned(), + // Safety: At this point, it is know that we have a valid FQDN, as + // this is set by the TUI network panel, which only lets the user + // continue if a valid FQDN is provided. + hostname: options.network.fqdn.host().expect("valid FQDN").to_owned(), domain: options.network.fqdn.domain(), cidr: options.network.address, gateway: options.network.gateway, -- 2.42.0 From f.gruenbichler at proxmox.com Thu Oct 5 10:56:45 2023 From: f.gruenbichler at proxmox.com (Fabian =?iso-8859-1?q?Gr=FCnbichler?=) Date: Thu, 05 Oct 2023 10:56:45 +0200 Subject: [pve-devel] [PATCH qemu-server v7 4/11] feature #1027: virtio-fs support In-Reply-To: <20230809083739.100024-5-m.frank@proxmox.com> References: <20230809083739.100024-1-m.frank@proxmox.com> <20230809083739.100024-5-m.frank@proxmox.com> Message-ID: <1696495156.74igartj2g.astroid@yuna.none> On August 9, 2023 10:37 am, Markus Frank wrote: > add support for sharing directories with a guest vm > > virtio-fs needs virtiofsd to be started. > > In order to start virtiofsd as a process (despite being a daemon it is does not run > in the background), a double-fork is used. > > virtiofsd should close itself together with qemu. > > There are the parameters dirid > and the optional parameters direct-io & cache. > Additionally the xattr & acl parameter overwrite the > directory mapping settings for xattr & acl. > > The dirid gets mapped to the path on the current node > and is also used as a mount-tag (name used to mount the > device on the guest). > > example config: > ``` > virtiofs0: foo,direct-io=1,cache=always,acl=1 > virtiofs1: dirid=bar,cache=never,xattr=1 > ``` > > For information on the optional parameters see there: > https://gitlab.com/virtio-fs/virtiofsd/-/blob/main/README.md > > Signed-off-by: Markus Frank > --- > I did not get virtiofsd to run with run_command without creating zombie > processes after stutdown. > So I replaced run_command with exec for now. > Maybe someone can find out why this happens. > > PVE/QemuServer.pm | 174 ++++++++++++++++++++++++++++++++++++++- > PVE/QemuServer/Memory.pm | 25 ++++-- > debian/control | 1 + > 3 files changed, 193 insertions(+), 7 deletions(-) > > diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm > index 484bc7f..d547dd6 100644 > --- a/PVE/QemuServer.pm > +++ b/PVE/QemuServer.pm > @@ -43,6 +43,7 @@ use PVE::PBSClient; > use PVE::RESTEnvironment qw(log_warn); > use PVE::RPCEnvironment; > use PVE::Storage; > +use PVE::Mapping::Dir; > use PVE::SysFSTools; > use PVE::Systemd; > use PVE::Tools qw(run_command file_read_firstline file_get_contents dir_glob_foreach get_host_arch $IPV6RE); > @@ -276,6 +277,42 @@ my $rng_fmt = { > }, > }; > > +my $virtiofs_fmt = { > + 'dirid' => { > + type => 'string', > + default_key => 1, > + description => "Mapping identifier of the directory mapping to be" > + ." shared with the guest. Also used as a mount tag inside the VM.", > + format_description => 'mapping-id', > + format => 'pve-configid', > + }, > + 'cache' => { > + type => 'string', > + description => "The caching policy the file system should use" > + ." (auto, always, never).", > + format_description => "virtiofs-cache", > + enum => [qw(auto always never)], > + optional => 1, > + }, > + 'direct-io' => { > + type => 'boolean', > + description => "Honor the O_DIRECT flag passed down by guest applications", > + format_description => "virtiofs-directio", > + optional => 1, > + }, > + xattr => { > + type => 'boolean', > + description => "Enable support for extended attributes.", > + optional => 1, > + }, > + acl => { > + type => 'boolean', > + description => "Enable support for posix ACLs (implies --xattr).", > + optional => 1, > + }, > +}; > +PVE::JSONSchema::register_format('pve-qm-virtiofs', $virtiofs_fmt); > + > my $meta_info_fmt = { > 'ctime' => { > type => 'integer', > @@ -840,6 +877,7 @@ while (my ($k, $v) = each %$confdesc) { > } > > my $MAX_NETS = 32; > +my $MAX_VIRTIOFS = 10; > my $MAX_SERIAL_PORTS = 4; > my $MAX_PARALLEL_PORTS = 3; > my $MAX_NUMA = 8; > @@ -984,6 +1022,21 @@ my $netdesc = { > > PVE::JSONSchema::register_standard_option("pve-qm-net", $netdesc); > > +my $virtiofsdesc = { > + optional => 1, > + type => 'string', format => $virtiofs_fmt, > + description => "share files between host and guest", > +}; > +PVE::JSONSchema::register_standard_option("pve-qm-virtiofs", $virtiofsdesc); > + > +sub max_virtiofs { > + return $MAX_VIRTIOFS; > +} > + > +for (my $i = 0; $i < $MAX_VIRTIOFS; $i++) { > + $confdesc->{"virtiofs$i"} = $virtiofsdesc; > +} > + > my $ipconfig_fmt = { > ip => { > type => 'string', > @@ -4113,6 +4166,21 @@ sub config_to_command { > push @$devices, '-device', $netdevicefull; > } > > + my $virtiofs_enabled = 0; > + for (my $i = 0; $i < $MAX_VIRTIOFS; $i++) { > + my $opt = "virtiofs$i"; > + > + next if !$conf->{$opt}; > + my $virtiofs = parse_property_string('pve-qm-virtiofs', $conf->{$opt}); > + next if !$virtiofs; > + > + push @$devices, '-chardev', "socket,id=virtfs$i,path=/var/run/virtiofsd/vm$vmid-fs$i"; > + push @$devices, '-device', 'vhost-user-fs-pci,queue-size=1024' > + .",chardev=virtfs$i,tag=$virtiofs->{dirid}"; > + > + $virtiofs_enabled = 1; > + } > + > if ($conf->{ivshmem}) { > my $ivshmem = parse_property_string($ivshmem_fmt, $conf->{ivshmem}); > > @@ -4172,6 +4240,14 @@ sub config_to_command { > } > push @$machineFlags, "type=${machine_type_min}"; > > + if ($virtiofs_enabled && !$conf->{numa}) { > + # kvm: '-machine memory-backend' and '-numa memdev' properties are > + # mutually exclusive > + push @$devices, '-object', 'memory-backend-file,id=virtiofs-mem' > + .",size=$conf->{memory}M,mem-path=/dev/shm,share=on"; as discussed off-list, this might be switched to memfd to avoid /dev/shm (same further below) > + push @$machineFlags, 'memory-backend=virtiofs-mem'; > + } > + > push @$cmd, @$devices; > push @$cmd, '-rtc', join(',', @$rtcFlags) if scalar(@$rtcFlags); > push @$cmd, '-machine', join(',', @$machineFlags) if scalar(@$machineFlags); > @@ -4198,6 +4274,85 @@ sub config_to_command { > return wantarray ? ($cmd, $vollist, $spice_port, $pci_devices) : $cmd; > } > > +sub start_virtiofs { > + my ($vmid, $fsid, $virtiofs) = @_; > + > + my $dir_cfg = PVE::Mapping::Dir::config()->{ids}->{$virtiofs->{dirid}}; > + my $node_list = PVE::Mapping::Dir::find_on_current_node($virtiofs->{dirid}); > + > + if (!$node_list || scalar($node_list->@*) != 1) { > + die "virtiofs needs exactly one mapping for this node\n"; > + } > + > + eval { > + PVE::Mapping::Dir::assert_valid($node_list->[0]); > + }; > + if (my $err = $@) { > + die "Directory Mapping invalid: $err\n"; > + } > + > + my $node_cfg = $node_list->[0]; > + my $path = $node_cfg->{path}; > + my $socket_path_root = "/var/run/virtiofsd"; > + mkdir $socket_path_root; > + my $socket_path = "$socket_path_root/vm$vmid-fs$fsid"; > + unlink($socket_path); > + my $socket = IO::Socket::UNIX->new( > + Type => SOCK_STREAM, > + Local => $socket_path, > + Listen => 1, > + ) or die "cannot create socket - $!\n"; > + > + my $flags = fcntl($socket, F_GETFD, 0) > + or die "failed to get file descriptor flags: $!\n"; > + fcntl($socket, F_SETFD, $flags & ~FD_CLOEXEC) > + or die "failed to remove FD_CLOEXEC from file descriptor\n"; > + > + my $fd = $socket->fileno(); > + > + my $virtiofsd_bin = '/usr/libexec/virtiofsd'; > + > + my $pid = fork(); > + if ($pid == 0) { > + setsid(); > + $0 = "task pve-vm$vmid-virtiofs$fsid"; > + for my $fd_loop (3 .. POSIX::sysconf( &POSIX::_SC_OPEN_MAX )) { > + POSIX::close($fd_loop) if ($fd_loop != $fd); > + } > + > + my $pid2 = fork(); > + if ($pid2 == 0) { > + my $cmd = [$virtiofsd_bin, "--fd=$fd", "--shared-dir=$path"]; > + push @$cmd, '--xattr' if ($virtiofs->{xattr}); > + push @$cmd, '--posix-acl' if ($virtiofs->{acl}); > + > + # Default to dir config xattr & acl settings > + push @$cmd, '--xattr' > + if !defined $virtiofs->{'xattr'} && $dir_cfg->{'xattr'}; > + push @$cmd, '--posix-acl' > + if !defined $virtiofs->{'acl'} && $dir_cfg->{'acl'}; nit: this could be a lot simpler: my $xattr = $virtiofs->{xattr} // $dir_cfg->{xattr}; push @$cmd, '--xattr' if $xattr; or even as a one-liner ;) same for ACL > + > + push @$cmd, '--announce-submounts' if ($node_cfg->{submounts}); > + push @$cmd, '--allow-direct-io' if ($virtiofs->{'direct-io'}); > + push @$cmd, "--cache=$virtiofs->{'cache'}" if ($virtiofs->{'cache'}); > + > + exec(@$cmd); > + } elsif (!defined($pid2)) { > + die "could not fork to start virtiofsd\n"; > + } else { > + POSIX::_exit(0); > + } > + } elsif (!defined($pid)) { > + die "could not fork to start virtiofsd\n"; > + } else { > + waitpid($pid, 0); > + } > + > + # return socket to keep it alive, > + # so that qemu will wait for virtiofsd to start > + return $socket; > +} From f.gruenbichler at proxmox.com Thu Oct 5 10:56:19 2023 From: f.gruenbichler at proxmox.com (Fabian =?iso-8859-1?q?Gr=FCnbichler?=) Date: Thu, 05 Oct 2023 10:56:19 +0200 Subject: [pve-devel] [PATCH qemu-server v7 5/11] Permission check for virtiofs directory access In-Reply-To: <20230809083739.100024-6-m.frank@proxmox.com> References: <20230809083739.100024-1-m.frank@proxmox.com> <20230809083739.100024-6-m.frank@proxmox.com> Message-ID: <1696495486.8njpavz612.astroid@yuna.none> this should likely also be added to PVE::QemuServer::check_mappings so that cloning and restoring are covered (properly).. checking permission for deletion is also not handled AFAICT, or for reverting pending changes - basically, everywhere bridge or PCI/USB mapping access is checked, Dir mapping access should be as well ;) On August 9, 2023 10:37 am, Markus Frank wrote: > Signed-off-by: Markus Frank > --- > PVE/API2/Qemu.pm | 18 ++++++++++++++++++ > 1 file changed, 18 insertions(+) > > diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm > index 9606e72..65830f9 100644 > --- a/PVE/API2/Qemu.pm > +++ b/PVE/API2/Qemu.pm > @@ -586,6 +586,19 @@ my $check_vm_create_serial_perm = sub { > return 1; > }; > > +my sub check_vm_dir_perm { > + my ($rpcenv, $authuser, $param) = @_; > + > + return 1 if $authuser eq 'root at pam'; > + > + foreach my $opt (keys %{$param}) { > + next if $opt !~ m/^virtiofs\d+$/; > + my $virtiofs = PVE::JSONSchema::parse_property_string('pve-qm-virtiofs', $param->{$opt}); > + $rpcenv->check_full($authuser, "/mapping/dir/$virtiofs->{dirid}", ['Mapping.Use']); > + } > + return 1; > +}; > + > my sub check_usb_perm { > my ($rpcenv, $authuser, $vmid, $pool, $opt, $value) = @_; > > @@ -687,6 +700,8 @@ my $check_vm_modify_config_perm = sub { > # the user needs Disk and PowerMgmt privileges to change the vmstate > # also needs privileges on the storage, that will be checked later > $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk', 'VM.PowerMgmt' ]); > + } elsif ($opt =~ m/^virtiofs\d$/) { > + $rpcenv->check_vm_perm($authuser, $vmid, $pool, ['VM.Config.Disk']); > } else { > # catches args, lock, etc. > # new options will be checked here > @@ -925,6 +940,7 @@ __PACKAGE__->register_method({ > > &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, $pool, [ keys %$param]); > > + check_vm_dir_perm($rpcenv, $authuser, $param); > &$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); > @@ -1660,6 +1676,8 @@ my $update_vm_api = sub { > > &$check_vm_modify_config_perm($rpcenv, $authuser, $vmid, undef, [keys %$param]); > > + check_vm_dir_perm($rpcenv, $authuser, $param); > + > &$check_storage_access($rpcenv, $authuser, $storecfg, $vmid, $param); > > PVE::QemuServer::check_bridge_access($rpcenv, $authuser, $param); > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > From f.gruenbichler at proxmox.com Thu Oct 5 10:56:58 2023 From: f.gruenbichler at proxmox.com (Fabian =?iso-8859-1?q?Gr=FCnbichler?=) Date: Thu, 05 Oct 2023 10:56:58 +0200 Subject: [pve-devel] [PATCH docs v7 3/11] added shared filesystem doc for virtio-fs In-Reply-To: <20230809083739.100024-4-m.frank@proxmox.com> References: <20230809083739.100024-1-m.frank@proxmox.com> <20230809083739.100024-4-m.frank@proxmox.com> Message-ID: <1696494947.lj3xmc46wa.astroid@yuna.none> On August 9, 2023 10:37 am, Markus Frank wrote: > Signed-off-by: Markus Frank > --- > qm.adoc | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- > 1 file changed, 68 insertions(+), 2 deletions(-) > > diff --git a/qm.adoc b/qm.adoc > index e35dbf0..8f4020d 100644 > --- a/qm.adoc > +++ b/qm.adoc > @@ -997,6 +997,71 @@ recommended to always use a limiter to avoid guests using too many host > resources. If desired, a value of '0' for `max_bytes` can be used to disable > all limits. > > +[[qm_virtiofs]] > +Virtio-fs > +~~~~~~~~~ > + > +Virtio-fs is a shared file system, that enables sharing a directory between > +host and guest VM while taking advantage of the locality of virtual machines > +and the hypervisor to get a higher throughput than 9p. > + > +Linux VMs with kernel >=5.4 support this feature by default. > + > +There is a guide available on how to utilize virtiofs in Windows VMs. > +https://github.com/virtio-win/kvm-guest-drivers-windows/wiki/Virtiofs:-Shared-file-system > + > +Add mapping for Shared Directories > +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > + > +To add a mapping, go to the Resource Mapping tab in Datacenter in the WebUI, > +use the API directly with pvesh as described in the > +xref:resource_mapping[Resource Mapping] section, > +or add the mapping to the configuration file `/etc/pve/mapping/dir.cfg`: > + > +---- > +some-dir-id > + map node=node1,path=/mnt/share/,submounts=1 > + map node=node2,path=/mnt/share/, > + xattr 1 > + acl 1 > +---- > + > +Set `submounts` to `1` when multiple file systems are mounted in a > +shared directory. > + > +Add virtiofs to VM > +^^^^^^^^^^^^^^^^^^ > + > +To share a directory using virtio-fs, you need to specify the directory ID > +(dirid) that has been configured in the Resource Mapping. Additionally, you > +can set the `cache` option to either `always`, `never`, or `auto`, depending on > +your requirements. If you want virtio-fs to honor the `O_DIRECT` flag, you can > +set the `direct-io` parameter to `1`. > +Additionally it possible to overwrite the default mapping settings > +for xattr & acl by setting then to either `1` or `0`. > + > +The `acl` parameter automatically implies `xattr`, that is, it makes no > +difference whether you set xattr to `0` if acl is set to `1`. > + > +---- > +qm set -virtiofs0 dirid=,tag=,cache=always,direct-io=1 > +qm set -virtiofs1 ,tag=,cache=never,xattr=1 > +qm set -virtiofs2 ,tag=,acl=1 nit: the mount tag is not settable anymore in this version some more caveats and limitations would be nice in the docs IMHO! > +---- > + > +To mount virtio-fs in a guest VM with the Linux kernel virtiofs driver, run the > +following command: > + > +The dirid associated with the path on the current node is also used as the > +mount tag (name used to mount the device on the guest). > + > +---- > +mount -t virtiofs > +---- > + > +For more information on available virtiofsd parameters, see the > +https://gitlab.com/virtio-fs/virtiofsd[GitLab virtiofsd project page]. > + > [[qm_bootorder]] > Device Boot Order > ~~~~~~~~~~~~~~~~~ > @@ -1600,8 +1665,9 @@ in the relevant tab in the `Resource Mappings` category, or on the cli with > # pvesh create /cluster/mapping/ > ---- > > -Where `` is the hardware type (currently either `pci` or `usb`) and > -`` are the device mappings and other configuration parameters. > +Where `` is the hardware type (currently either `pci`, `usb` or > +xref:qm_virtiofs[dir]) and `` are the device mappings and other > +configuration parameters. > > Note that the options must include a map property with all identifying > properties of that hardware, so that it's possible to verify the hardware did > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > From f.gruenbichler at proxmox.com Thu Oct 5 10:57:11 2023 From: f.gruenbichler at proxmox.com (Fabian =?iso-8859-1?q?Gr=FCnbichler?=) Date: Thu, 05 Oct 2023 10:57:11 +0200 Subject: [pve-devel] [PATCH cluster/guest-common/docs/qemu-server/manager v6 0/11] virtiofs In-Reply-To: <20230809083739.100024-1-m.frank@proxmox.com> References: <20230809083739.100024-1-m.frank@proxmox.com> Message-ID: <1696494070.i92rf99hre.astroid@yuna.none> On August 9, 2023 10:37 am, Markus Frank wrote: > qemu-server patches require pve-guest-common and pve-cluster patches > pve-manager patches require the pve-doc patch > > I did not get virtiofsd to run with run_command without creating zombie > processes after stutdown. > So I replaced run_command with exec for now. > Maybe someone can find out why this happens. some high-level remarks: - in general, seems to work as expected within the limitations of the current virtiofsd - log messages by virtiofsd after the initial startup are lost, adding `--syslog` or otherwise improving the process startup to capture them would be good - I am not sure whether we want to expose this on the GUI just yet - checking earlier when doing a snapshot with RAM might be sensible (since virtiofsd state is not migrateable, it's also not snapshot-saveable and aborts pretty early on, but a nicer error message up front would be even better)[1] - maybe default to ACLs off, or improve detection of support, since having them on but no support means no mounting possible (haven't tested whether the same applies to XATTRs as well) - currently virtiofsd crashing means no recovery until VM is fully stopped and restarted [2] - virtiofsd not responding for whatever reason means NFS-like hanging access in the VM (this should be noted somewhere) - virtiofs shares don't seem to work on older Linux VMs with memory hotplug enabled (it might be good to have some sort of supported/tested-with matrix somewhere so that users don't have to try known-to-not-work combinations..) - bwlimit support once upstream has it would be nice[3] - reboots seem broken (accessing the mount after the reboot hangs), but that might be fixed with a newer upstream version[4] that I'll prepare in the meantime :) noting the build-order/interdependencies would be nice ;) some more smaller nits noted in individual patches 1: https://gitlab.com/virtio-fs/virtiofsd/-/issues/81 2: https://gitlab.com/virtio-fs/virtiofsd/-/issues/62 3: https://gitlab.com/virtio-fs/virtiofsd/-/merge_requests/147 4: https://gitlab.com/virtio-fs/virtiofsd/-/commit/ee50078626536b8e25389f01e7e4be43897418c9 From f.ebner at proxmox.com Thu Oct 5 11:50:15 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Thu, 5 Oct 2023 11:50:15 +0200 Subject: [pve-devel] applied: [PATCH proxmox-resource-scheduling] pve static: add one to avoid boosting tiny relative differences In-Reply-To: References: <20230321164452.268358-1-f.ebner@proxmox.com> Message-ID: Am 22.03.23 um 09:32 schrieb Thomas Lamprecht: > Am 21/03/2023 um 17:44 schrieb Fiona Ebner: >> Only the relative difference for values between different alternatives >> is relevant, meaning 0.002 vs 0.004 and 0.2 vs 0.4 will influence the >> scoring in the same way. This is not really desirable, because values >> closer to 1.0 indicate higher load and thus should influence the >> scoring more than differences that are actually tiny, but big when >> viewed as a relative difference. >> >> To avoid the issue, simply add 1.0 to all values. Like that, 1.002 vs >> 1.004 will also be small when viewed as a relative difference. >> >> Signed-off-by: Fiona Ebner >> --- >> src/pve_static.rs | 10 ++++++---- >> 1 file changed, 6 insertions(+), 4 deletions(-) >> >> > > applied series (yesterday already), thanks! Seems like only this single patch was applied, I don't see proxmox-perl-rs ones there (still apply cleanly). From f.ebner at proxmox.com Thu Oct 5 16:05:46 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Thu, 5 Oct 2023 16:05:46 +0200 Subject: [pve-devel] [PATCH ha-manager 2/2] usage: basic: avoid node autovivification when adding service usage In-Reply-To: <20231005140546.88771-1-f.ebner@proxmox.com> References: <20231005140546.88771-1-f.ebner@proxmox.com> Message-ID: <20231005140546.88771-2-f.ebner@proxmox.com> Part of what caused bug #4984. Make the code future-proof and warn when the node was never registered in the plugin, similar to what the 'static' usage plugin already does. Signed-off-by: Fiona Ebner --- src/PVE/HA/Usage/Basic.pm | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/PVE/HA/Usage/Basic.pm b/src/PVE/HA/Usage/Basic.pm index f066350..d6b3d6c 100644 --- a/src/PVE/HA/Usage/Basic.pm +++ b/src/PVE/HA/Usage/Basic.pm @@ -10,6 +10,7 @@ sub new { return bless { nodes => {}, + haenv => $haenv, }, $class; } @@ -40,7 +41,14 @@ sub contains_node { sub add_service_usage_to_node { my ($self, $nodename, $sid, $service_node, $migration_target) = @_; - $self->{nodes}->{$nodename}++; + if ($self->contains_node($nodename)) { + $self->{nodes}->{$nodename}++; + } else { + $self->{haenv}->log( + 'warning', + "unable to add service '$sid' usage to node '$nodename' - node not in usage hash", + ); + } } sub score_nodes_to_start_service { -- 2.39.2 From f.ebner at proxmox.com Thu Oct 5 16:05:45 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Thu, 5 Oct 2023 16:05:45 +0200 Subject: [pve-devel] [PATCH ha-manager 1/2] fix #4984: recompute online usage: add service usage to migration target only if online Message-ID: <20231005140546.88771-1-f.ebner@proxmox.com> Otherwise, when using the 'basic' plugin, this would lead to autovivification of the $target node in the Perl hash tracking the usage and it would wrongly be considered online when selecting the recovery node. The 'static' plugin was not affected, because it would check and warn before adding usage to a node that was not registered with add_node() first. Doing the same in the 'basic' plugin will be done by another patch. Signed-off-by: Fiona Ebner --- src/PVE/HA/Manager.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/PVE/HA/Manager.pm b/src/PVE/HA/Manager.pm index 6a13360..d983672 100644 --- a/src/PVE/HA/Manager.pm +++ b/src/PVE/HA/Manager.pm @@ -275,7 +275,8 @@ sub recompute_online_node_usage { # count it for both, source and target as load is put on both $online_node_usage->add_service_usage_to_node($source, $sid, $source, $target) if $state ne 'request_start_balance'; - $online_node_usage->add_service_usage_to_node($target, $sid, $source, $target); + $online_node_usage->add_service_usage_to_node($target, $sid, $source, $target) + if $online_node_usage->contains_node($target); } elsif ($state eq 'stopped' || $state eq 'request_start') { # do nothing } else { -- 2.39.2 From f.ebner at proxmox.com Thu Oct 5 16:07:04 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Thu, 5 Oct 2023 16:07:04 +0200 Subject: [pve-devel] [PATCH ha-manager] sim: test hardware: fix typo in error message Message-ID: <20231005140704.89248-1-f.ebner@proxmox.com> Signed-off-by: Fiona Ebner --- src/PVE/HA/Sim/TestHardware.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PVE/HA/Sim/TestHardware.pm b/src/PVE/HA/Sim/TestHardware.pm index 1966596..919a460 100644 --- a/src/PVE/HA/Sim/TestHardware.pm +++ b/src/PVE/HA/Sim/TestHardware.pm @@ -123,7 +123,7 @@ sub run { my $looptime = scalar(@nodes) * 2; # twice the node count $looptime = 20 if $looptime < 20; - die "unable to simulate so many nodes. You need to increate watchdog/lock timeouts.\n" + die "unable to simulate so many nodes. You need to increase watchdog/lock timeouts.\n" if $looptime >= 60; my $first_loop = 1; -- 2.39.2 From glena at bah.net Thu Oct 5 16:12:09 2023 From: glena at bah.net (Glen Aidukas) Date: Thu, 5 Oct 2023 10:12:09 -0400 Subject: [pve-devel] [PATCH ha-manager] sim: test hardware: fix typo in error message In-Reply-To: <20231005140704.89248-1-f.ebner@proxmox.com> References: <20231005140704.89248-1-f.ebner@proxmox.com> Message-ID: unsubscribe On Thu, Oct 5, 2023 at 10:07?AM Fiona Ebner wrote: > Signed-off-by: Fiona Ebner > --- > src/PVE/HA/Sim/TestHardware.pm | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/src/PVE/HA/Sim/TestHardware.pm > b/src/PVE/HA/Sim/TestHardware.pm > index 1966596..919a460 100644 > --- a/src/PVE/HA/Sim/TestHardware.pm > +++ b/src/PVE/HA/Sim/TestHardware.pm > @@ -123,7 +123,7 @@ sub run { > my $looptime = scalar(@nodes) * 2; # twice the node count > $looptime = 20 if $looptime < 20; > > - die "unable to simulate so many nodes. You need to increate > watchdog/lock timeouts.\n" > + die "unable to simulate so many nodes. You need to increase > watchdog/lock timeouts.\n" > if $looptime >= 60; > > my $first_loop = 1; > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > From f.ebner at proxmox.com Thu Oct 5 16:13:17 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Thu, 5 Oct 2023 16:13:17 +0200 Subject: [pve-devel] [PATCH ha-manager 1/2] fix #4984: recompute online usage: add service usage to migration target only if online In-Reply-To: <20231005140546.88771-1-f.ebner@proxmox.com> References: <20231005140546.88771-1-f.ebner@proxmox.com> Message-ID: <9fa3b783-44d2-0b46-3ba3-411915d3ba12@proxmox.com> Forgot to mention that this also applies to stable-7 (bug report was with a Proxmox VE 7 system). From f.ebner at proxmox.com Fri Oct 6 12:23:14 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Fri, 6 Oct 2023 12:23:14 +0200 Subject: [pve-devel] [PATCH qemu 5/7] add patch to disable graph locking In-Reply-To: <20230928125926.202540-6-f.ebner@proxmox.com> References: <20230928125926.202540-1-f.ebner@proxmox.com> <20230928125926.202540-6-f.ebner@proxmox.com> Message-ID: <56d17c5d-f94f-b514-45c7-5fba40e393c2@proxmox.com> Am 28.09.23 um 14:59 schrieb Fiona Ebner: > + void bdrv_graph_wrlock(BlockDriverState *bs) > + { > + AioContext *ctx = NULL; > + > + GLOBAL_STATE_CODE(); > ++ /* > ++ * TODO Some callers hold an AioContext lock when this is called, which > ++ * causes deadlocks. Reenable once the AioContext locking is cleaned up (or > ++ * AioContext locks are gone). > ++ */ > ++#if 0 > + assert(!qatomic_read(&has_writer)); > ++#endif > + > + /* > + * Release only non-mainloop AioContext. The mainloop often relies on the > +@@ -126,6 +137,7 @@ void bdrv_graph_wrlock(BlockDriverState *bs) > + } > + } > + > ++#if 0 > + /* Make sure that constantly arriving new I/O doesn't cause starvation */ > + bdrv_drain_all_begin_nopoll(); > + > +@@ -154,6 +166,7 @@ void bdrv_graph_wrlock(BlockDriverState *bs) > + } while (reader_count() >= 1); > + > + bdrv_drain_all_end(); > ++#endif > + > + if (ctx) { > + aio_context_acquire(bdrv_get_aio_context(bs)); I missed that there was another commit in between, so the #ifdeffery doesn't get rid of the part releasing+acquiring the AioContext lock. Should not be a real issue, but who knows. I'll send a v2 to make it proper (and also want to pick another stable fix). From f.ebner at proxmox.com Fri Oct 6 13:01:39 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Fri, 6 Oct 2023 13:01:39 +0200 Subject: [pve-devel] [PATCH-SERIES v2 qemu] update to QEMU 8.1.1 Message-ID: <20231006110148.154914-1-f.ebner@proxmox.com> Patch changes: For backup, opening the backup dump block driver needed to be adapted, because of coroutine context changes. Block graph locking was disabled, because of deadlocks. Snapshot code has a huge performance regression which required a workaround. Meta-changes: Use --disable-download options to avoid automatic downloads during build, but require the user to do so once themselves. Also done when initializing the submodule in the Makefile Switch back to using QEMU's keycodemapdb instead of splitting it out in our build-dir. Wasn't updated since QEMU 6.0 anymore and the reason for the split is not known. If anything pops up, we can re-do it and document the reason this time. Versioned Breaks for qemu-server required, because an upstream change would prevent VM boot with most configurations (including default one). Changes in v2: * Add stable fix for guest-triggerable SCSI crash. * Make reverting graph locking complete. Fiona Ebner (9): d/rules: use disable-download option instead of git-submodules=ignore buildsys: fixup submodule target buildsys: use QEMU's keycodemapdb again update submodule and patches to QEMU 8.1.1 add patch to disable graph locking add patch to avoid huge snapshot performance regression d/control: add versioned Breaks for qemu-server <= 8.0.6 cherry-pick stable fixes to avoid crash in IO error scenarios add stable fix to avoid crash in SCSI when guest uses too small blocksize Makefile | 20 +- debian/control | 1 + ...d-support-for-sync-bitmap-mode-never.patch | 124 +- ...-support-for-conditional-and-always-.patch | 10 +- ...check-for-bitmap-mode-without-bitmap.patch | 4 +- ...-to-bdrv_dirty_bitmap_merge_internal.patch | 6 +- .../0006-mirror-move-some-checks-to-qmp.patch | 8 +- ...race-with-clients-disconnecting-earl.patch | 22 +- ...as-Internal-cdbs-have-16-byte-length.patch | 10 +- ...ial-deadlock-when-draining-during-tr.patch | 10 +- ...irty-bitmap-fix-loading-bitmap-when.patch} | 2 +- ...hen-getting-cursor-without-a-console.patch | 36 - ...el-async-DMA-operation-before-reset.patch} | 4 +- ...-memory-prevent-dma-reentracy-issues.patch | 130 - ...t-graph-lock-Disable-locking-for-now.patch | 140 + ...le-reentrancy-detection-for-script-R.patch | 39 - ...-disable-reentrancy-detection-for-io.patch | 37 - ...-workaround-snapshot-performance-reg.patch | 57 + ...ile-posix-Clear-bs-bl.zoned-on-error.patch | 87 + ...sable-reentrancy-detection-for-iomem.patch | 35 - ...le-reentrancy-detection-for-apic-msi.patch | 36 - ...osix-Check-bs-bl.zoned-for-zone-info.patch | 59 + ...ix-Fix-zone-update-in-I-O-error-path.patch | 33 + ...-Simplify-raw_co_prw-s-out-zone-code.patch | 58 + .../extra/0011-vhost-fix-the-fd-leak.patch | 29 - ...k-Disallow-block-sizes-smaller-than-.patch | 43 + ...k-file-change-locking-default-to-off.patch | 6 +- ...he-CPU-model-to-kvm64-32-instead-of-.patch | 4 +- ...erfs-no-default-logfile-if-daemonize.patch | 4 +- ...PVE-Up-glusterfs-allow-partial-reads.patch | 2 +- ...return-success-on-info-without-snaps.patch | 4 +- ...dd-add-osize-and-read-from-to-stdin-.patch | 12 +- ...E-Up-qemu-img-dd-add-isize-parameter.patch | 14 +- ...PVE-Up-qemu-img-dd-add-n-skip_create.patch | 10 +- ...-add-l-option-for-loading-a-snapshot.patch | 14 +- ...virtio-balloon-improve-query-balloon.patch | 12 +- .../0014-PVE-qapi-modify-query-machines.patch | 12 +- .../0015-PVE-qapi-modify-spice-query.patch | 4 +- ...nnel-implementation-for-savevm-async.patch | 8 +- ...async-for-background-state-snapshots.patch | 58 +- ...add-optional-buffer-size-to-QEMUFile.patch | 44 +- ...add-the-zeroinit-block-driver-filter.patch | 10 +- ...-Add-dummy-id-command-line-parameter.patch | 10 +- ...le-posix-make-locking-optiono-on-cre.patch | 18 +- ...3-PVE-monitor-disable-oob-capability.patch | 4 +- ...sed-balloon-qemu-4-0-config-size-fal.patch | 4 +- ...E-Allow-version-code-in-machine-type.patch | 22 +- ...VE-Backup-add-vma-backup-format-code.patch | 22 +- ...-Backup-add-backup-dump-block-driver.patch | 4 +- ...ckup-Proxmox-backup-patches-for-QEMU.patch | 127 +- ...estore-new-command-to-restore-from-p.patch | 4 +- ...k-driver-to-map-backup-archives-into.patch | 54 +- ...ct-stderr-to-journal-when-daemonized.patch | 12 +- ...igrate-dirty-bitmap-state-via-savevm.patch | 23 +- ...dirty-bitmap-migrate-other-bitmaps-e.patch | 4 +- ...all-back-to-open-iscsi-initiatorname.patch | 4 +- ...PVE-block-stream-increase-chunk-size.patch | 2 +- ...accept-NULL-qiov-in-bdrv_pad_request.patch | 14 +- .../0039-block-add-alloc-track-driver.patch | 2 +- ...apshots-hold-the-BQL-during-setup-ca.patch | 24 +- ...vm-async-don-t-hold-BQL-during-setup.patch | 4 +- debian/patches/series | 18 +- debian/rules | 2 +- keycodemapdb/LICENSE.BSD | 27 - keycodemapdb/LICENSE.GPL2 | 339 --- keycodemapdb/README | 114 - keycodemapdb/data/README | 89 - keycodemapdb/data/keymaps.csv | 539 ---- keycodemapdb/meson.build | 1 - keycodemapdb/tests/.gitignore | 11 - keycodemapdb/tests/Makefile | 150 -- keycodemapdb/tests/javascript | 53 - keycodemapdb/tests/python2 | 3 - keycodemapdb/tests/python3 | 3 - keycodemapdb/tests/stdc++.cc | 40 - keycodemapdb/tests/stdc.c | 64 - keycodemapdb/tests/test.py | 30 - keycodemapdb/thirdparty/LICENSE-argparse.txt | 20 - keycodemapdb/thirdparty/__init__.py | 0 keycodemapdb/thirdparty/argparse.py | 2392 ----------------- keycodemapdb/tools/keymap-gen | 1147 -------- qemu | 2 +- 82 files changed, 902 insertions(+), 5758 deletions(-) rename debian/patches/extra/{0010-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch => 0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch} (98%) delete mode 100644 debian/patches/extra/0004-ui-return-NULL-when-getting-cursor-without-a-console.patch rename debian/patches/extra/{0012-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch => 0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch} (97%) delete mode 100644 debian/patches/extra/0005-memory-prevent-dma-reentracy-issues.patch create mode 100644 debian/patches/extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch delete mode 100644 debian/patches/extra/0006-lsi53c895a-disable-reentrancy-detection-for-script-R.patch delete mode 100644 debian/patches/extra/0007-bcm2835_property-disable-reentrancy-detection-for-io.patch create mode 100644 debian/patches/extra/0007-migration-states-workaround-snapshot-performance-reg.patch create mode 100644 debian/patches/extra/0008-file-posix-Clear-bs-bl.zoned-on-error.patch delete mode 100644 debian/patches/extra/0008-raven-disable-reentrancy-detection-for-iomem.patch delete mode 100644 debian/patches/extra/0009-apic-disable-reentrancy-detection-for-apic-msi.patch create mode 100644 debian/patches/extra/0009-file-posix-Check-bs-bl.zoned-for-zone-info.patch create mode 100644 debian/patches/extra/0010-file-posix-Fix-zone-update-in-I-O-error-path.patch create mode 100644 debian/patches/extra/0011-file-posix-Simplify-raw_co_prw-s-out-zone-code.patch delete mode 100644 debian/patches/extra/0011-vhost-fix-the-fd-leak.patch create mode 100644 debian/patches/extra/0012-hw-scsi-scsi-disk-Disallow-block-sizes-smaller-than-.patch delete mode 100644 keycodemapdb/LICENSE.BSD delete mode 100644 keycodemapdb/LICENSE.GPL2 delete mode 100644 keycodemapdb/README delete mode 100644 keycodemapdb/data/README delete mode 100644 keycodemapdb/data/keymaps.csv delete mode 100644 keycodemapdb/meson.build delete mode 100644 keycodemapdb/tests/.gitignore delete mode 100644 keycodemapdb/tests/Makefile delete mode 100755 keycodemapdb/tests/javascript delete mode 100755 keycodemapdb/tests/python2 delete mode 100755 keycodemapdb/tests/python3 delete mode 100644 keycodemapdb/tests/stdc++.cc delete mode 100644 keycodemapdb/tests/stdc.c delete mode 100644 keycodemapdb/tests/test.py delete mode 100644 keycodemapdb/thirdparty/LICENSE-argparse.txt delete mode 100644 keycodemapdb/thirdparty/__init__.py delete mode 100644 keycodemapdb/thirdparty/argparse.py delete mode 100755 keycodemapdb/tools/keymap-gen -- 2.39.2 From f.ebner at proxmox.com Fri Oct 6 13:01:44 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Fri, 6 Oct 2023 13:01:44 +0200 Subject: [pve-devel] [PATCH v2 qemu 5/9] add patch to disable graph locking In-Reply-To: <20231006110148.154914-1-f.ebner@proxmox.com> References: <20231006110148.154914-1-f.ebner@proxmox.com> Message-ID: <20231006110148.154914-6-f.ebner@proxmox.com> There are still some issues with graph locking, e.g. deadlocks during backup canceling [0] and initial attempts to fix it didn't work [1]. Because the AioContext locks still exist, it should still be safe to disable graph locking. [0]: https://lists.nongnu.org/archive/html/qemu-devel/2023-09/msg00729.html [1]: https://lists.nongnu.org/archive/html/qemu-devel/2023-09/msg06905.html Signed-off-by: Fiona Ebner --- Changes in v2: * also define away AioContext release/acquire in graph_wrlock() function. ...t-graph-lock-Disable-locking-for-now.patch | 140 ++++++++++++++++++ debian/patches/series | 1 + 2 files changed, 141 insertions(+) create mode 100644 debian/patches/extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch diff --git a/debian/patches/extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch b/debian/patches/extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch new file mode 100644 index 0000000..f0648d2 --- /dev/null +++ b/debian/patches/extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch @@ -0,0 +1,140 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Fiona Ebner +Date: Thu, 28 Sep 2023 10:07:03 +0200 +Subject: [PATCH] Revert "Revert "graph-lock: Disable locking for now"" + +This reverts commit 3cce22defb4b0e47cf135444e30cc673cff5ebad. + +There are still some issues with graph locking, e.g. deadlocks during +backup canceling [0]. Because the AioContext locks still exist, it +should be safe to disable locking again. + +From the original 80fc5d2600 ("graph-lock: Disable locking for now"): + +> We don't currently rely on graph locking yet. It is supposed to replace +> the AioContext lock eventually to enable multiqueue support, but as long +> as we still have the AioContext lock, it is sufficient without the graph +> lock. Once the AioContext lock goes away, the deadlock doesn't exist any +> more either and this commit can be reverted. (Of course, it can also be +> reverted while the AioContext lock still exists if the callers have been +> fixed.) + +[0]: https://lists.nongnu.org/archive/html/qemu-devel/2023-09/msg00729.html + +Signed-off-by: Fiona Ebner +--- + block/graph-lock.c | 24 ++++++++++++++++++++++++ + 1 file changed, 24 insertions(+) + +diff --git a/block/graph-lock.c b/block/graph-lock.c +index 5e66f01ae8..5c2873262a 100644 +--- a/block/graph-lock.c ++++ b/block/graph-lock.c +@@ -30,8 +30,10 @@ BdrvGraphLock graph_lock; + /* Protects the list of aiocontext and orphaned_reader_count */ + static QemuMutex aio_context_list_lock; + ++#if 0 + /* Written and read with atomic operations. */ + static int has_writer; ++#endif + + /* + * A reader coroutine could move from an AioContext to another. +@@ -88,6 +90,7 @@ void unregister_aiocontext(AioContext *ctx) + g_free(ctx->bdrv_graph); + } + ++#if 0 + static uint32_t reader_count(void) + { + BdrvGraphRWlock *brdv_graph; +@@ -105,12 +108,19 @@ static uint32_t reader_count(void) + assert((int32_t)rd >= 0); + return rd; + } ++#endif + + void bdrv_graph_wrlock(BlockDriverState *bs) + { ++#if 0 + AioContext *ctx = NULL; + + GLOBAL_STATE_CODE(); ++ /* ++ * TODO Some callers hold an AioContext lock when this is called, which ++ * causes deadlocks. Reenable once the AioContext locking is cleaned up (or ++ * AioContext locks are gone). ++ */ + assert(!qatomic_read(&has_writer)); + + /* +@@ -158,11 +168,13 @@ void bdrv_graph_wrlock(BlockDriverState *bs) + if (ctx) { + aio_context_acquire(bdrv_get_aio_context(bs)); + } ++#endif + } + + void bdrv_graph_wrunlock(void) + { + GLOBAL_STATE_CODE(); ++#if 0 + QEMU_LOCK_GUARD(&aio_context_list_lock); + assert(qatomic_read(&has_writer)); + +@@ -174,10 +186,13 @@ void bdrv_graph_wrunlock(void) + + /* Wake up all coroutine that are waiting to read the graph */ + qemu_co_enter_all(&reader_queue, &aio_context_list_lock); ++#endif + } + + void coroutine_fn bdrv_graph_co_rdlock(void) + { ++ /* TODO Reenable when wrlock is reenabled */ ++#if 0 + BdrvGraphRWlock *bdrv_graph; + bdrv_graph = qemu_get_current_aio_context()->bdrv_graph; + +@@ -237,10 +252,12 @@ void coroutine_fn bdrv_graph_co_rdlock(void) + qemu_co_queue_wait(&reader_queue, &aio_context_list_lock); + } + } ++#endif + } + + void coroutine_fn bdrv_graph_co_rdunlock(void) + { ++#if 0 + BdrvGraphRWlock *bdrv_graph; + bdrv_graph = qemu_get_current_aio_context()->bdrv_graph; + +@@ -258,6 +275,7 @@ void coroutine_fn bdrv_graph_co_rdunlock(void) + if (qatomic_read(&has_writer)) { + aio_wait_kick(); + } ++#endif + } + + void bdrv_graph_rdlock_main_loop(void) +@@ -275,13 +293,19 @@ void bdrv_graph_rdunlock_main_loop(void) + void assert_bdrv_graph_readable(void) + { + /* reader_count() is slow due to aio_context_list_lock lock contention */ ++ /* TODO Reenable when wrlock is reenabled */ ++#if 0 + #ifdef CONFIG_DEBUG_GRAPH_LOCK + assert(qemu_in_main_thread() || reader_count()); + #endif ++#endif + } + + void assert_bdrv_graph_writable(void) + { + assert(qemu_in_main_thread()); ++ /* TODO Reenable when wrlock is reenabled */ ++#if 0 + assert(qatomic_read(&has_writer)); ++#endif + } diff --git a/debian/patches/series b/debian/patches/series index 01d4d3c..6d681da 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -3,6 +3,7 @@ extra/0002-scsi-megasas-Internal-cdbs-have-16-byte-length.patch extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch +extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch -- 2.39.2 From f.ebner at proxmox.com Fri Oct 6 13:01:40 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Fri, 6 Oct 2023 13:01:40 +0200 Subject: [pve-devel] [PATCH v2 qemu 1/9] d/rules: use disable-download option instead of git-submodules=ignore In-Reply-To: <20231006110148.154914-1-f.ebner@proxmox.com> References: <20231006110148.154914-1-f.ebner@proxmox.com> Message-ID: <20231006110148.154914-2-f.ebner@proxmox.com> See the following QEMU commits for reference: 0c5f3dcbb2 ("configure: add --enable-pypi and --disable-pypi") ac4ccac740 ("configure: rename --enable-pypi to --enable-download, control subprojects too") 6f3ae23b29 ("configure: remove --with-git-submodules=") removed The last one removed the option and the closest thing to git-submodule=ignore is using disable-download. Which will then just verify that the submodules are present. Building now will require running either * Running 'meson subprojects download' in the qemu submodule first. * Using --enable-download, but then the submodules would be downloaded for each build (if not already downloaded in the submodule first) and it's just a bit too surprising if downloads happen during build. The disable-download option will also disable automatic downloading of missing Python modules from PyPI. Hopefully, it's enough to add them as Debian build dependencies when required. Signed-off-by: Fiona Ebner --- No changes in v2. debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index 4d06697..51f56c5 100755 --- a/debian/rules +++ b/debian/rules @@ -38,7 +38,7 @@ endif # guest-agent is only required for guest systems ./configure \ - --with-git-submodules=ignore \ + --disable-download \ --docdir=/usr/share/doc/pve-qemu-kvm \ --localstatedir=/var \ --prefix=/usr \ -- 2.39.2 From f.ebner at proxmox.com Fri Oct 6 13:01:41 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Fri, 6 Oct 2023 13:01:41 +0200 Subject: [pve-devel] [PATCH v2 qemu 2/9] buildsys: fixup submodule target In-Reply-To: <20231006110148.154914-1-f.ebner@proxmox.com> References: <20231006110148.154914-1-f.ebner@proxmox.com> Message-ID: <20231006110148.154914-3-f.ebner@proxmox.com> It's not enough to initialize the submodules anymore, as some got replaced by wrap files, see QEMU commit 2019cabfee ("meson: subprojects: replace submodules with wrap files"). Download the subprojects during initialization of the QEMU submodule, so building (without the automagical --enable-download) can succeeed afterwards. Signed-off-by: Fiona Ebner --- No changes in v2. Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6c62c78..e389a9c 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,10 @@ all: $(DEBS) .PHONY: submodule submodule: - test -f "$(SRCDIR)/configure" || git submodule update --init --recursive +ifeq ($(shell test -f "$(SRCDIR)/configure" && echo 1 || echo 0), 0) + git submodule update --init --recursive + cd $(SRCDIR); meson subprojects download +endif PC_BIOS_FW_PURGE_LIST_IN = \ hppa-firmware.img \ -- 2.39.2 From f.ebner at proxmox.com Fri Oct 6 13:01:46 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Fri, 6 Oct 2023 13:01:46 +0200 Subject: [pve-devel] [PATCH v2 qemu 7/9] d/control: add versioned Breaks for qemu-server <= 8.0.6 In-Reply-To: <20231006110148.154914-1-f.ebner@proxmox.com> References: <20231006110148.154914-1-f.ebner@proxmox.com> Message-ID: <20231006110148.154914-8-f.ebner@proxmox.com> Upstream QEMU commit 4271f40383 ("virtio-net: correctly report maximum tx_queue_size value") made setting an invalid tx_queue_size for a non-vDPA/vhost-user net device a hard error. Now, qemu-server before commit 089aed81 ("cfg2cmd: netdev: fix value for tx_queue_size") did just that, so the newer QEMU version would break start-up for most VMs (a default vNIC configuration would be affected). Signed-off-by: Fiona Ebner --- No changes in v2. debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 8328cd4..7e6cd6b 100644 --- a/debian/control +++ b/debian/control @@ -79,6 +79,7 @@ Replaces: pve-kvm, qemu-system-arm, qemu-system-x86, qemu-utils, +Breaks: qemu-server (<= 8.0.6) Description: Full virtualization on x86 hardware Using KVM, one can run multiple virtual PCs, each running unmodified Linux or Windows images. Each virtual machine has private virtualized hardware: a -- 2.39.2 From f.ebner at proxmox.com Fri Oct 6 13:01:45 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Fri, 6 Oct 2023 13:01:45 +0200 Subject: [pve-devel] [PATCH v2 qemu 6/9] add patch to avoid huge snapshot performance regression In-Reply-To: <20231006110148.154914-1-f.ebner@proxmox.com> References: <20231006110148.154914-1-f.ebner@proxmox.com> Message-ID: <20231006110148.154914-7-f.ebner@proxmox.com> Taking a snapshot became prohibitively slow because of the migration_transferred_bytes() call in migration_rate_exceeded() [0]. This also applied to the async snapshot taking in Proxmox VE, so work around the issue until it is fixed upstream. [0]: https://gitlab.com/qemu-project/qemu/-/issues/1821 Signed-off-by: Fiona Ebner --- No changes in v2. ...-workaround-snapshot-performance-reg.patch | 57 +++++++++++++++++++ debian/patches/series | 1 + 2 files changed, 58 insertions(+) create mode 100644 debian/patches/extra/0007-migration-states-workaround-snapshot-performance-reg.patch diff --git a/debian/patches/extra/0007-migration-states-workaround-snapshot-performance-reg.patch b/debian/patches/extra/0007-migration-states-workaround-snapshot-performance-reg.patch new file mode 100644 index 0000000..8031837 --- /dev/null +++ b/debian/patches/extra/0007-migration-states-workaround-snapshot-performance-reg.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Fiona Ebner +Date: Thu, 28 Sep 2023 11:19:14 +0200 +Subject: [PATCH] migration states: workaround snapshot performance regression + +Commit 813cd616 ("migration: Use migration_transferred_bytes() to +calculate rate_limit") introduced a prohibitive performance regression +when taking a snapshot [0]. The reason turns out to be the flushing +done by migration_transferred_bytes() + +Just use a _noflush version of the relevant function as a workaround +until upstream fixes the issue. This is inspired by a not-applied +upstream series [1], but doing the very minimum to avoid the +regression. + +[0]: https://gitlab.com/qemu-project/qemu/-/issues/1821 +[1]: https://lists.nongnu.org/archive/html/qemu-devel/2023-05/msg07708.html + +Signed-off-by: Fiona Ebner +--- + migration/migration-stats.c | 16 +++++++++++++++- + 1 file changed, 15 insertions(+), 1 deletion(-) + +diff --git a/migration/migration-stats.c b/migration/migration-stats.c +index 095d6d75bb..8073c8ebaa 100644 +--- a/migration/migration-stats.c ++++ b/migration/migration-stats.c +@@ -18,6 +18,20 @@ + + MigrationAtomicStats mig_stats; + ++/* ++ * Same as migration_transferred_bytes below, but using the _noflush ++ * variant of qemu_file_transferred() to avoid a performance ++ * regression in migration_rate_exceeded(). ++ */ ++static uint64_t migration_transferred_bytes_noflush(QEMUFile *f) ++{ ++ uint64_t multifd = stat64_get(&mig_stats.multifd_bytes); ++ uint64_t qemu_file = qemu_file_transferred_noflush(f); ++ ++ trace_migration_transferred_bytes(qemu_file, multifd); ++ return qemu_file + multifd; ++} ++ + bool migration_rate_exceeded(QEMUFile *f) + { + if (qemu_file_get_error(f)) { +@@ -25,7 +39,7 @@ bool migration_rate_exceeded(QEMUFile *f) + } + + uint64_t rate_limit_start = stat64_get(&mig_stats.rate_limit_start); +- uint64_t rate_limit_current = migration_transferred_bytes(f); ++ uint64_t rate_limit_current = migration_transferred_bytes_noflush(f); + uint64_t rate_limit_used = rate_limit_current - rate_limit_start; + uint64_t rate_limit_max = stat64_get(&mig_stats.rate_limit_max); + diff --git a/debian/patches/series b/debian/patches/series index 6d681da..c27c245 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -4,6 +4,7 @@ extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch +extra/0007-migration-states-workaround-snapshot-performance-reg.patch bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch -- 2.39.2 From f.ebner at proxmox.com Fri Oct 6 13:01:48 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Fri, 6 Oct 2023 13:01:48 +0200 Subject: [pve-devel] [PATCH v2 qemu 9/9] add stable fix to avoid crash in SCSI when guest uses too small blocksize In-Reply-To: <20231006110148.154914-1-f.ebner@proxmox.com> References: <20231006110148.154914-1-f.ebner@proxmox.com> Message-ID: <20231006110148.154914-10-f.ebner@proxmox.com> Signed-off-by: Fiona Ebner --- New in v2. ...k-Disallow-block-sizes-smaller-than-.patch | 43 +++++++++++++++++++ debian/patches/series | 1 + 2 files changed, 44 insertions(+) create mode 100644 debian/patches/extra/0012-hw-scsi-scsi-disk-Disallow-block-sizes-smaller-than-.patch diff --git a/debian/patches/extra/0012-hw-scsi-scsi-disk-Disallow-block-sizes-smaller-than-.patch b/debian/patches/extra/0012-hw-scsi-scsi-disk-Disallow-block-sizes-smaller-than-.patch new file mode 100644 index 0000000..9d05c66 --- /dev/null +++ b/debian/patches/extra/0012-hw-scsi-scsi-disk-Disallow-block-sizes-smaller-than-.patch @@ -0,0 +1,43 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Thomas Huth +Date: Mon, 25 Sep 2023 11:18:54 +0200 +Subject: [PATCH] hw/scsi/scsi-disk: Disallow block sizes smaller than 512 + [CVE-2023-42467] + +We are doing things like + + nb_sectors /= (s->qdev.blocksize / BDRV_SECTOR_SIZE); + +in the code here (e.g. in scsi_disk_emulate_mode_sense()), so if +the blocksize is smaller than BDRV_SECTOR_SIZE (=512), this crashes +with a division by 0 exception. Thus disallow block sizes of 256 +bytes to avoid this situation. + +Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1813 +CVE: 2023-42467 +Signed-off-by: Thomas Huth +Message-ID: <20230925091854.49198-1-thuth at redhat.com> +Signed-off-by: Paolo Bonzini +(cherry-picked from commit 7cfcc79b0ab800959716738aff9419f53fc68c9c) +Signed-off-by: Fiona Ebner +--- + hw/scsi/scsi-disk.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/hw/scsi/scsi-disk.c b/hw/scsi/scsi-disk.c +index e0d79c7966..477ee2bcd4 100644 +--- a/hw/scsi/scsi-disk.c ++++ b/hw/scsi/scsi-disk.c +@@ -1628,9 +1628,10 @@ static void scsi_disk_emulate_mode_select(SCSIDiskReq *r, uint8_t *inbuf) + * Since the existing code only checks/updates bits 8-15 of the block + * size, restrict ourselves to the same requirement for now to ensure + * that a block size set by a block descriptor and then read back by +- * a subsequent SCSI command will be the same ++ * a subsequent SCSI command will be the same. Also disallow a block ++ * size of 256 since we cannot handle anything below BDRV_SECTOR_SIZE. + */ +- if (bs && !(bs & ~0xff00) && bs != s->qdev.blocksize) { ++ if (bs && !(bs & ~0xfe00) && bs != s->qdev.blocksize) { + s->qdev.blocksize = bs; + trace_scsi_disk_mode_select_set_blocksize(s->qdev.blocksize); + } diff --git a/debian/patches/series b/debian/patches/series index 71f7e01..a661a9e 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -9,6 +9,7 @@ extra/0008-file-posix-Clear-bs-bl.zoned-on-error.patch extra/0009-file-posix-Check-bs-bl.zoned-for-zone-info.patch extra/0010-file-posix-Fix-zone-update-in-I-O-error-path.patch extra/0011-file-posix-Simplify-raw_co_prw-s-out-zone-code.patch +extra/0012-hw-scsi-scsi-disk-Disallow-block-sizes-smaller-than-.patch bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch -- 2.39.2 From f.ebner at proxmox.com Fri Oct 6 13:01:47 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Fri, 6 Oct 2023 13:01:47 +0200 Subject: [pve-devel] [PATCH v2 qemu 8/9] cherry-pick stable fixes to avoid crash in IO error scenarios In-Reply-To: <20231006110148.154914-1-f.ebner@proxmox.com> References: <20231006110148.154914-1-f.ebner@proxmox.com> Message-ID: <20231006110148.154914-9-f.ebner@proxmox.com> Upstream report of the issue [0]. I ran into it too by chance by filling up my NFS storage with the VM's qcow2 disk. [0]: https://bugzilla.redhat.com/show_bug.cgi?id=2234374 Signed-off-by: Fiona Ebner --- No changes in v2. ...ile-posix-Clear-bs-bl.zoned-on-error.patch | 87 +++++++++++++++++++ ...osix-Check-bs-bl.zoned-for-zone-info.patch | 59 +++++++++++++ ...ix-Fix-zone-update-in-I-O-error-path.patch | 33 +++++++ ...-Simplify-raw_co_prw-s-out-zone-code.patch | 58 +++++++++++++ ...k-file-change-locking-default-to-off.patch | 2 +- ...le-posix-make-locking-optiono-on-cre.patch | 14 +-- debian/patches/series | 4 + 7 files changed, 249 insertions(+), 8 deletions(-) create mode 100644 debian/patches/extra/0008-file-posix-Clear-bs-bl.zoned-on-error.patch create mode 100644 debian/patches/extra/0009-file-posix-Check-bs-bl.zoned-for-zone-info.patch create mode 100644 debian/patches/extra/0010-file-posix-Fix-zone-update-in-I-O-error-path.patch create mode 100644 debian/patches/extra/0011-file-posix-Simplify-raw_co_prw-s-out-zone-code.patch diff --git a/debian/patches/extra/0008-file-posix-Clear-bs-bl.zoned-on-error.patch b/debian/patches/extra/0008-file-posix-Clear-bs-bl.zoned-on-error.patch new file mode 100644 index 0000000..0e3dc3e --- /dev/null +++ b/debian/patches/extra/0008-file-posix-Clear-bs-bl.zoned-on-error.patch @@ -0,0 +1,87 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hanna Czenczek +Date: Thu, 24 Aug 2023 17:53:40 +0200 +Subject: [PATCH] file-posix: Clear bs->bl.zoned on error + +bs->bl.zoned is what indicates whether the zone information is present +and valid; it is the only thing that raw_refresh_zoned_limits() sets if +CONFIG_BLKZONED is not defined, and it is also the only thing that it +sets if CONFIG_BLKZONED is defined, but there are no zones. + +Make sure that it is always set to BLK_Z_NONE if there is an error +anywhere in raw_refresh_zoned_limits() so that we do not accidentally +announce zones while our information is incomplete or invalid. + +This also fixes a memory leak in the last error path in +raw_refresh_zoned_limits(). + +Signed-off-by: Hanna Czenczek +Message-Id: <20230824155345.109765-2-hreitz at redhat.com> +Reviewed-by: Sam Li +(cherry-picked from commit 56d1a022a77ea2125564913665eeadf3e303a671) +Signed-off-by: Fiona Ebner +--- + block/file-posix.c | 21 ++++++++++++--------- + 1 file changed, 12 insertions(+), 9 deletions(-) + +diff --git a/block/file-posix.c b/block/file-posix.c +index b16e9c21a1..2b88b9eefa 100644 +--- a/block/file-posix.c ++++ b/block/file-posix.c +@@ -1412,11 +1412,9 @@ static void raw_refresh_zoned_limits(BlockDriverState *bs, struct stat *st, + BlockZoneModel zoned; + int ret; + +- bs->bl.zoned = BLK_Z_NONE; +- + ret = get_sysfs_zoned_model(st, &zoned); + if (ret < 0 || zoned == BLK_Z_NONE) { +- return; ++ goto no_zoned; + } + bs->bl.zoned = zoned; + +@@ -1437,10 +1435,10 @@ static void raw_refresh_zoned_limits(BlockDriverState *bs, struct stat *st, + if (ret < 0) { + error_setg_errno(errp, -ret, "Unable to read chunk_sectors " + "sysfs attribute"); +- return; ++ goto no_zoned; + } else if (!ret) { + error_setg(errp, "Read 0 from chunk_sectors sysfs attribute"); +- return; ++ goto no_zoned; + } + bs->bl.zone_size = ret << BDRV_SECTOR_BITS; + +@@ -1448,10 +1446,10 @@ static void raw_refresh_zoned_limits(BlockDriverState *bs, struct stat *st, + if (ret < 0) { + error_setg_errno(errp, -ret, "Unable to read nr_zones " + "sysfs attribute"); +- return; ++ goto no_zoned; + } else if (!ret) { + error_setg(errp, "Read 0 from nr_zones sysfs attribute"); +- return; ++ goto no_zoned; + } + bs->bl.nr_zones = ret; + +@@ -1472,10 +1470,15 @@ static void raw_refresh_zoned_limits(BlockDriverState *bs, struct stat *st, + ret = get_zones_wp(bs, s->fd, 0, bs->bl.nr_zones, 0); + if (ret < 0) { + error_setg_errno(errp, -ret, "report wps failed"); +- bs->wps = NULL; +- return; ++ goto no_zoned; + } + qemu_co_mutex_init(&bs->wps->colock); ++ return; ++ ++no_zoned: ++ bs->bl.zoned = BLK_Z_NONE; ++ g_free(bs->wps); ++ bs->wps = NULL; + } + #else /* !defined(CONFIG_BLKZONED) */ + static void raw_refresh_zoned_limits(BlockDriverState *bs, struct stat *st, diff --git a/debian/patches/extra/0009-file-posix-Check-bs-bl.zoned-for-zone-info.patch b/debian/patches/extra/0009-file-posix-Check-bs-bl.zoned-for-zone-info.patch new file mode 100644 index 0000000..a505816 --- /dev/null +++ b/debian/patches/extra/0009-file-posix-Check-bs-bl.zoned-for-zone-info.patch @@ -0,0 +1,59 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hanna Czenczek +Date: Thu, 24 Aug 2023 17:53:41 +0200 +Subject: [PATCH] file-posix: Check bs->bl.zoned for zone info + +Instead of checking bs->wps or bs->bl.zone_size for whether zone +information is present, check bs->bl.zoned. That is the flag that +raw_refresh_zoned_limits() reliably sets to indicate zone support. If +it is set to something other than BLK_Z_NONE, other values and objects +like bs->wps and bs->bl.zone_size must be non-null/zero and valid; if it +is not, we cannot rely on their validity. + +Signed-off-by: Hanna Czenczek +Message-Id: <20230824155345.109765-3-hreitz at redhat.com> +Reviewed-by: Sam Li +(cherry-picked from commit 4b5d80f3d02096a9bb1f651f6b3401ba40877159) +Signed-off-by: Fiona Ebner +--- + block/file-posix.c | 12 +++++++----- + 1 file changed, 7 insertions(+), 5 deletions(-) + +diff --git a/block/file-posix.c b/block/file-posix.c +index 2b88b9eefa..46e22403fe 100644 +--- a/block/file-posix.c ++++ b/block/file-posix.c +@@ -2455,9 +2455,10 @@ static int coroutine_fn raw_co_prw(BlockDriverState *bs, uint64_t offset, + if (fd_open(bs) < 0) + return -EIO; + #if defined(CONFIG_BLKZONED) +- if ((type & (QEMU_AIO_WRITE | QEMU_AIO_ZONE_APPEND)) && bs->wps) { ++ if ((type & (QEMU_AIO_WRITE | QEMU_AIO_ZONE_APPEND)) && ++ bs->bl.zoned != BLK_Z_NONE) { + qemu_co_mutex_lock(&bs->wps->colock); +- if (type & QEMU_AIO_ZONE_APPEND && bs->bl.zone_size) { ++ if (type & QEMU_AIO_ZONE_APPEND) { + int index = offset / bs->bl.zone_size; + offset = bs->wps->wp[index]; + } +@@ -2508,8 +2509,8 @@ out: + { + BlockZoneWps *wps = bs->wps; + if (ret == 0) { +- if ((type & (QEMU_AIO_WRITE | QEMU_AIO_ZONE_APPEND)) +- && wps && bs->bl.zone_size) { ++ if ((type & (QEMU_AIO_WRITE | QEMU_AIO_ZONE_APPEND)) && ++ bs->bl.zoned != BLK_Z_NONE) { + uint64_t *wp = &wps->wp[offset / bs->bl.zone_size]; + if (!BDRV_ZT_IS_CONV(*wp)) { + if (type & QEMU_AIO_ZONE_APPEND) { +@@ -2529,7 +2530,8 @@ out: + } + } + +- if ((type & (QEMU_AIO_WRITE | QEMU_AIO_ZONE_APPEND)) && wps) { ++ if ((type & (QEMU_AIO_WRITE | QEMU_AIO_ZONE_APPEND)) && ++ bs->blk.zoned != BLK_Z_NONE) { + qemu_co_mutex_unlock(&wps->colock); + } + } diff --git a/debian/patches/extra/0010-file-posix-Fix-zone-update-in-I-O-error-path.patch b/debian/patches/extra/0010-file-posix-Fix-zone-update-in-I-O-error-path.patch new file mode 100644 index 0000000..fb32c62 --- /dev/null +++ b/debian/patches/extra/0010-file-posix-Fix-zone-update-in-I-O-error-path.patch @@ -0,0 +1,33 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hanna Czenczek +Date: Thu, 24 Aug 2023 17:53:42 +0200 +Subject: [PATCH] file-posix: Fix zone update in I/O error path + +We must check that zone information is present before running +update_zones_wp(). + +Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=2234374 +Fixes: Coverity CID 1512459 +Signed-off-by: Hanna Czenczek +Message-Id: <20230824155345.109765-4-hreitz at redhat.com> +Reviewed-by: Sam Li +(cherry-picked from commit deab5c9a4ed74f76a713008a42527762b30a7e84) +Signed-off-by: Fiona Ebner +--- + block/file-posix.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/block/file-posix.c b/block/file-posix.c +index 46e22403fe..a050682e97 100644 +--- a/block/file-posix.c ++++ b/block/file-posix.c +@@ -2525,7 +2525,8 @@ out: + } + } + } else { +- if (type & (QEMU_AIO_WRITE | QEMU_AIO_ZONE_APPEND)) { ++ if ((type & (QEMU_AIO_WRITE | QEMU_AIO_ZONE_APPEND)) && ++ bs->bl.zoned != BLK_Z_NONE) { + update_zones_wp(bs, s->fd, 0, 1); + } + } diff --git a/debian/patches/extra/0011-file-posix-Simplify-raw_co_prw-s-out-zone-code.patch b/debian/patches/extra/0011-file-posix-Simplify-raw_co_prw-s-out-zone-code.patch new file mode 100644 index 0000000..6164160 --- /dev/null +++ b/debian/patches/extra/0011-file-posix-Simplify-raw_co_prw-s-out-zone-code.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hanna Czenczek +Date: Thu, 24 Aug 2023 17:53:43 +0200 +Subject: [PATCH] file-posix: Simplify raw_co_prw's 'out' zone code + +We duplicate the same condition three times here, pull it out to the top +level. + +Signed-off-by: Hanna Czenczek +Message-Id: <20230824155345.109765-5-hreitz at redhat.com> +Reviewed-by: Sam Li +(cherry-picked from commit d31b50a15dd25a560749b25fc40b6484fd1a57b7) +Signed-off-by: Fiona Ebner +--- + block/file-posix.c | 18 +++++------------- + 1 file changed, 5 insertions(+), 13 deletions(-) + +diff --git a/block/file-posix.c b/block/file-posix.c +index a050682e97..aa89789737 100644 +--- a/block/file-posix.c ++++ b/block/file-posix.c +@@ -2506,11 +2506,10 @@ static int coroutine_fn raw_co_prw(BlockDriverState *bs, uint64_t offset, + + out: + #if defined(CONFIG_BLKZONED) +-{ +- BlockZoneWps *wps = bs->wps; +- if (ret == 0) { +- if ((type & (QEMU_AIO_WRITE | QEMU_AIO_ZONE_APPEND)) && +- bs->bl.zoned != BLK_Z_NONE) { ++ if ((type & (QEMU_AIO_WRITE | QEMU_AIO_ZONE_APPEND)) && ++ bs->bl.zoned != BLK_Z_NONE) { ++ BlockZoneWps *wps = bs->wps; ++ if (ret == 0) { + uint64_t *wp = &wps->wp[offset / bs->bl.zone_size]; + if (!BDRV_ZT_IS_CONV(*wp)) { + if (type & QEMU_AIO_ZONE_APPEND) { +@@ -2523,19 +2522,12 @@ out: + *wp = offset + bytes; + } + } +- } +- } else { +- if ((type & (QEMU_AIO_WRITE | QEMU_AIO_ZONE_APPEND)) && +- bs->bl.zoned != BLK_Z_NONE) { ++ } else { + update_zones_wp(bs, s->fd, 0, 1); + } +- } + +- if ((type & (QEMU_AIO_WRITE | QEMU_AIO_ZONE_APPEND)) && +- bs->blk.zoned != BLK_Z_NONE) { + qemu_co_mutex_unlock(&wps->colock); + } +-} + #endif + return ret; + } diff --git a/debian/patches/pve/0001-PVE-Config-block-file-change-locking-default-to-off.patch b/debian/patches/pve/0001-PVE-Config-block-file-change-locking-default-to-off.patch index 1c3d08c..3d8785c 100644 --- a/debian/patches/pve/0001-PVE-Config-block-file-change-locking-default-to-off.patch +++ b/debian/patches/pve/0001-PVE-Config-block-file-change-locking-default-to-off.patch @@ -14,7 +14,7 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/block/file-posix.c b/block/file-posix.c -index b16e9c21a1..26aa0172b4 100644 +index aa89789737..0db366a851 100644 --- a/block/file-posix.c +++ b/block/file-posix.c @@ -564,7 +564,7 @@ static QemuOptsList raw_runtime_opts = { diff --git a/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch b/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch index 904f76e..766c4f9 100644 --- a/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch +++ b/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch @@ -13,10 +13,10 @@ Signed-off-by: Thomas Lamprecht 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/block/file-posix.c b/block/file-posix.c -index 26aa0172b4..504c6ed316 100644 +index 0db366a851..46f1ee38ae 100644 --- a/block/file-posix.c +++ b/block/file-posix.c -@@ -2872,6 +2872,7 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) +@@ -2870,6 +2870,7 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) int fd; uint64_t perm, shared; int result = 0; @@ -24,7 +24,7 @@ index 26aa0172b4..504c6ed316 100644 /* Validate options and set default values */ assert(options->driver == BLOCKDEV_DRIVER_FILE); -@@ -2912,19 +2913,22 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) +@@ -2910,19 +2911,22 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) perm = BLK_PERM_WRITE | BLK_PERM_RESIZE; shared = BLK_PERM_ALL & ~BLK_PERM_RESIZE; @@ -59,7 +59,7 @@ index 26aa0172b4..504c6ed316 100644 } /* Clear the file by truncating it to 0 */ -@@ -2978,13 +2982,15 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) +@@ -2976,13 +2980,15 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) } out_unlock: @@ -82,7 +82,7 @@ index 26aa0172b4..504c6ed316 100644 } out_close: -@@ -3008,6 +3014,7 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, +@@ -3006,6 +3012,7 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, PreallocMode prealloc; char *buf = NULL; Error *local_err = NULL; @@ -90,7 +90,7 @@ index 26aa0172b4..504c6ed316 100644 /* Skip file: protocol prefix */ strstart(filename, "file:", &filename); -@@ -3030,6 +3037,18 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, +@@ -3028,6 +3035,18 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, return -EINVAL; } @@ -109,7 +109,7 @@ index 26aa0172b4..504c6ed316 100644 options = (BlockdevCreateOptions) { .driver = BLOCKDEV_DRIVER_FILE, .u.file = { -@@ -3041,6 +3060,8 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, +@@ -3039,6 +3058,8 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, .nocow = nocow, .has_extent_size_hint = has_extent_size_hint, .extent_size_hint = extent_size_hint, diff --git a/debian/patches/series b/debian/patches/series index c27c245..71f7e01 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -5,6 +5,10 @@ extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch extra/0007-migration-states-workaround-snapshot-performance-reg.patch +extra/0008-file-posix-Clear-bs-bl.zoned-on-error.patch +extra/0009-file-posix-Check-bs-bl.zoned-for-zone-info.patch +extra/0010-file-posix-Fix-zone-update-in-I-O-error-path.patch +extra/0011-file-posix-Simplify-raw_co_prw-s-out-zone-code.patch bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch -- 2.39.2 From f.ebner at proxmox.com Fri Oct 6 13:01:43 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Fri, 6 Oct 2023 13:01:43 +0200 Subject: [pve-devel] [PATCH v2 qemu 4/9] update submodule and patches to QEMU 8.1.1 In-Reply-To: <20231006110148.154914-1-f.ebner@proxmox.com> References: <20231006110148.154914-1-f.ebner@proxmox.com> Message-ID: <20231006110148.154914-5-f.ebner@proxmox.com> Bigger notable changes: * Commit 1a30b0f5d7 ("block: .bdrv_open is non-coroutine and unlocked") broke the PVE backup patches, in particular setting up the backup dump block driver, because bdrv_new_open_driver() cannot be called from a coroutine. To fix it, bdrv_co_open() is used instead, and while it's a much more involved function, the result should be essentially the same. The only difference I noticed is that the BDRV_O_ALLOW_RDWR flag is also set in the resulting bds (block driver state), but that shouldn't hurt. Smaller notable changes: * aio_set_fd_handler() dropped its 'is_external' parameter stating that all callers now pass false in 60f782b6b7 ("aio: remove aio_disable_external() API"). The calls in the PVE patches also passed false, so just drop the parameter too. * global_state_store() does not have a return value anymore, so the user in the PVE savevm-async patch was adapted. For context, see c33f1829f8 ("migration: never fail in global_state_store()"). * Renames affecting the PVE savevm-async patch: migrate_use_block() -> migrate_block() and ram_counters -> mig_stats 9d4b1e5f22 ("migration: Move migrate_use_block() to options.c") aff3f6606d ("migration: Rename ram_counters to mig_stats") Signed-off-by: Fiona Ebner --- No changes in v2. ...d-support-for-sync-bitmap-mode-never.patch | 124 ++++++++--------- ...-support-for-conditional-and-always-.patch | 10 +- ...check-for-bitmap-mode-without-bitmap.patch | 4 +- ...-to-bdrv_dirty_bitmap_merge_internal.patch | 6 +- .../0006-mirror-move-some-checks-to-qmp.patch | 8 +- ...race-with-clients-disconnecting-earl.patch | 22 +-- ...as-Internal-cdbs-have-16-byte-length.patch | 10 +- ...ial-deadlock-when-draining-during-tr.patch | 10 +- ...irty-bitmap-fix-loading-bitmap-when.patch} | 2 +- ...hen-getting-cursor-without-a-console.patch | 36 ----- ...el-async-DMA-operation-before-reset.patch} | 4 +- ...-memory-prevent-dma-reentracy-issues.patch | 130 ------------------ ...le-reentrancy-detection-for-script-R.patch | 39 ------ ...-disable-reentrancy-detection-for-io.patch | 37 ----- ...sable-reentrancy-detection-for-iomem.patch | 35 ----- ...le-reentrancy-detection-for-apic-msi.patch | 36 ----- .../extra/0011-vhost-fix-the-fd-leak.patch | 29 ---- ...k-file-change-locking-default-to-off.patch | 6 +- ...he-CPU-model-to-kvm64-32-instead-of-.patch | 4 +- ...erfs-no-default-logfile-if-daemonize.patch | 4 +- ...PVE-Up-glusterfs-allow-partial-reads.patch | 2 +- ...return-success-on-info-without-snaps.patch | 4 +- ...dd-add-osize-and-read-from-to-stdin-.patch | 12 +- ...E-Up-qemu-img-dd-add-isize-parameter.patch | 14 +- ...PVE-Up-qemu-img-dd-add-n-skip_create.patch | 10 +- ...-add-l-option-for-loading-a-snapshot.patch | 14 +- ...virtio-balloon-improve-query-balloon.patch | 12 +- .../0014-PVE-qapi-modify-query-machines.patch | 12 +- .../0015-PVE-qapi-modify-spice-query.patch | 4 +- ...nnel-implementation-for-savevm-async.patch | 8 +- ...async-for-background-state-snapshots.patch | 58 ++++---- ...add-optional-buffer-size-to-QEMUFile.patch | 44 +++--- ...add-the-zeroinit-block-driver-filter.patch | 10 +- ...-Add-dummy-id-command-line-parameter.patch | 10 +- ...le-posix-make-locking-optiono-on-cre.patch | 18 +-- ...3-PVE-monitor-disable-oob-capability.patch | 4 +- ...sed-balloon-qemu-4-0-config-size-fal.patch | 4 +- ...E-Allow-version-code-in-machine-type.patch | 22 +-- ...VE-Backup-add-vma-backup-format-code.patch | 22 +-- ...-Backup-add-backup-dump-block-driver.patch | 4 +- ...ckup-Proxmox-backup-patches-for-QEMU.patch | 127 ++++++++++++----- ...estore-new-command-to-restore-from-p.patch | 4 +- ...k-driver-to-map-backup-archives-into.patch | 54 ++++---- ...ct-stderr-to-journal-when-daemonized.patch | 12 +- ...igrate-dirty-bitmap-state-via-savevm.patch | 23 ++-- ...dirty-bitmap-migrate-other-bitmaps-e.patch | 4 +- ...all-back-to-open-iscsi-initiatorname.patch | 4 +- ...PVE-block-stream-increase-chunk-size.patch | 2 +- ...accept-NULL-qiov-in-bdrv_pad_request.patch | 14 +- .../0039-block-add-alloc-track-driver.patch | 2 +- ...apshots-hold-the-BQL-during-setup-ca.patch | 24 ++-- ...vm-async-don-t-hold-BQL-during-setup.patch | 4 +- debian/patches/series | 11 +- qemu | 2 +- 54 files changed, 411 insertions(+), 720 deletions(-) rename debian/patches/extra/{0010-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch => 0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch} (98%) delete mode 100644 debian/patches/extra/0004-ui-return-NULL-when-getting-cursor-without-a-console.patch rename debian/patches/extra/{0012-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch => 0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch} (97%) delete mode 100644 debian/patches/extra/0005-memory-prevent-dma-reentracy-issues.patch delete mode 100644 debian/patches/extra/0006-lsi53c895a-disable-reentrancy-detection-for-script-R.patch delete mode 100644 debian/patches/extra/0007-bcm2835_property-disable-reentrancy-detection-for-io.patch delete mode 100644 debian/patches/extra/0008-raven-disable-reentrancy-detection-for-iomem.patch delete mode 100644 debian/patches/extra/0009-apic-disable-reentrancy-detection-for-apic-msi.patch delete mode 100644 debian/patches/extra/0011-vhost-fix-the-fd-leak.patch diff --git a/debian/patches/bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch b/debian/patches/bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch index 4d9b8b9..c0cb23f 100644 --- a/debian/patches/bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch +++ b/debian/patches/bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch @@ -27,18 +27,18 @@ Signed-off-by: Ma Haocong Signed-off-by: John Snow Signed-off-by: Fabian Gr?nbichler Signed-off-by: Thomas Lamprecht -[FE: rebased for 8.0] +[FE: rebased for 8.1.1] Signed-off-by: Fiona Ebner --- block/mirror.c | 98 +++++++++++++++++++++----- blockdev.c | 38 +++++++++- include/block/block_int-global-state.h | 4 +- - qapi/block-core.json | 29 ++++++-- + qapi/block-core.json | 25 ++++++- tests/unit/test-block-iothread.c | 4 +- - 5 files changed, 144 insertions(+), 29 deletions(-) + 5 files changed, 142 insertions(+), 27 deletions(-) diff --git a/block/mirror.c b/block/mirror.c -index 663e2b7002..9099c75992 100644 +index d3cacd1708..1ff42c8af1 100644 --- a/block/mirror.c +++ b/block/mirror.c @@ -51,7 +51,7 @@ typedef struct MirrorBlockJob { @@ -59,7 +59,7 @@ index 663e2b7002..9099c75992 100644 BdrvDirtyBitmap *dirty_bitmap; BdrvDirtyBitmapIter *dbi; uint8_t *buf; -@@ -703,7 +705,8 @@ static int mirror_exit_common(Job *job) +@@ -705,7 +707,8 @@ static int mirror_exit_common(Job *job) bdrv_child_refresh_perms(mirror_top_bs, mirror_top_bs->backing, &error_abort); if (!abort && s->backing_mode == MIRROR_SOURCE_BACKING_CHAIN) { @@ -69,7 +69,7 @@ index 663e2b7002..9099c75992 100644 BlockDriverState *unfiltered_target = bdrv_skip_filters(target_bs); if (bdrv_cow_bs(unfiltered_target) != backing) { -@@ -801,6 +804,16 @@ static void mirror_abort(Job *job) +@@ -809,6 +812,16 @@ static void mirror_abort(Job *job) assert(ret == 0); } @@ -86,7 +86,7 @@ index 663e2b7002..9099c75992 100644 static void coroutine_fn mirror_throttle(MirrorBlockJob *s) { int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); -@@ -987,7 +1000,8 @@ static int coroutine_fn mirror_run(Job *job, Error **errp) +@@ -997,7 +1010,8 @@ static int coroutine_fn mirror_run(Job *job, Error **errp) mirror_free_init(s); s->last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); @@ -96,7 +96,7 @@ index 663e2b7002..9099c75992 100644 ret = mirror_dirty_init(s); if (ret < 0 || job_is_cancelled(&s->common.job)) { goto immediate_exit; -@@ -1240,6 +1254,7 @@ static const BlockJobDriver mirror_job_driver = { +@@ -1251,6 +1265,7 @@ static const BlockJobDriver mirror_job_driver = { .run = mirror_run, .prepare = mirror_prepare, .abort = mirror_abort, @@ -104,7 +104,7 @@ index 663e2b7002..9099c75992 100644 .pause = mirror_pause, .complete = mirror_complete, .cancel = mirror_cancel, -@@ -1256,6 +1271,7 @@ static const BlockJobDriver commit_active_job_driver = { +@@ -1267,6 +1282,7 @@ static const BlockJobDriver commit_active_job_driver = { .run = mirror_run, .prepare = mirror_prepare, .abort = mirror_abort, @@ -112,7 +112,7 @@ index 663e2b7002..9099c75992 100644 .pause = mirror_pause, .complete = mirror_complete, .cancel = commit_active_cancel, -@@ -1647,7 +1663,10 @@ static BlockJob *mirror_start_job( +@@ -1658,7 +1674,10 @@ static BlockJob *mirror_start_job( BlockCompletionFunc *cb, void *opaque, const BlockJobDriver *driver, @@ -124,7 +124,7 @@ index 663e2b7002..9099c75992 100644 bool auto_complete, const char *filter_node_name, bool is_mirror, MirrorCopyMode copy_mode, Error **errp) -@@ -1659,10 +1678,39 @@ static BlockJob *mirror_start_job( +@@ -1670,10 +1689,39 @@ static BlockJob *mirror_start_job( uint64_t target_perms, target_shared_perms; int ret; @@ -166,7 +166,7 @@ index 663e2b7002..9099c75992 100644 assert(is_power_of_2(granularity)); if (buf_size < 0) { -@@ -1793,7 +1841,9 @@ static BlockJob *mirror_start_job( +@@ -1804,7 +1852,9 @@ static BlockJob *mirror_start_job( s->replaces = g_strdup(replaces); s->on_source_error = on_source_error; s->on_target_error = on_target_error; @@ -177,7 +177,7 @@ index 663e2b7002..9099c75992 100644 s->backing_mode = backing_mode; s->zero_target = zero_target; s->copy_mode = copy_mode; -@@ -1814,6 +1864,18 @@ static BlockJob *mirror_start_job( +@@ -1825,6 +1875,18 @@ static BlockJob *mirror_start_job( bdrv_disable_dirty_bitmap(s->dirty_bitmap); } @@ -196,7 +196,7 @@ index 663e2b7002..9099c75992 100644 ret = block_job_add_bdrv(&s->common, "source", bs, 0, BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE | BLK_PERM_CONSISTENT_READ, -@@ -1891,6 +1953,9 @@ fail: +@@ -1902,6 +1964,9 @@ fail: if (s->dirty_bitmap) { bdrv_release_dirty_bitmap(s->dirty_bitmap); } @@ -206,7 +206,7 @@ index 663e2b7002..9099c75992 100644 job_early_fail(&s->common.job); } -@@ -1908,31 +1973,25 @@ void mirror_start(const char *job_id, BlockDriverState *bs, +@@ -1919,31 +1984,25 @@ void mirror_start(const char *job_id, BlockDriverState *bs, BlockDriverState *target, const char *replaces, int creation_flags, int64_t speed, uint32_t granularity, int64_t buf_size, @@ -243,7 +243,7 @@ index 663e2b7002..9099c75992 100644 } BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, -@@ -1959,7 +2018,8 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, +@@ -1970,7 +2029,8 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, job_id, bs, creation_flags, base, NULL, speed, 0, 0, MIRROR_LEAVE_BACKING_CHAIN, false, on_error, on_error, true, cb, opaque, @@ -254,10 +254,10 @@ index 663e2b7002..9099c75992 100644 errp); if (!job) { diff --git a/blockdev.c b/blockdev.c -index e464daea58..50e4a9c682 100644 +index e6eba61484..a8b1fd2a73 100644 --- a/blockdev.c +++ b/blockdev.c -@@ -2942,6 +2942,9 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, +@@ -2848,6 +2848,9 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, BlockDriverState *target, const char *replaces, enum MirrorSyncMode sync, @@ -267,15 +267,15 @@ index e464daea58..50e4a9c682 100644 BlockMirrorBackingMode backing_mode, bool zero_target, bool has_speed, int64_t speed, -@@ -2960,6 +2963,7 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, +@@ -2866,6 +2869,7 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, { BlockDriverState *unfiltered_bs; int job_flags = JOB_DEFAULT; + BdrvDirtyBitmap *bitmap = NULL; - if (!has_speed) { - speed = 0; -@@ -3011,6 +3015,29 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, + GLOBAL_STATE_CODE(); + GRAPH_RDLOCK_GUARD_MAINLOOP(); +@@ -2920,6 +2924,29 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, sync = MIRROR_SYNC_MODE_FULL; } @@ -305,7 +305,7 @@ index e464daea58..50e4a9c682 100644 if (!replaces) { /* We want to mirror from @bs, but keep implicit filters on top */ unfiltered_bs = bdrv_skip_implicit_filters(bs); -@@ -3056,8 +3083,8 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, +@@ -2965,8 +2992,8 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, * and will allow to check whether the node still exist at mirror completion */ mirror_start(job_id, bs, target, @@ -316,7 +316,7 @@ index e464daea58..50e4a9c682 100644 on_source_error, on_target_error, unmap, filter_node_name, copy_mode, errp); } -@@ -3202,6 +3229,8 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp) +@@ -3114,6 +3141,8 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp) blockdev_mirror_common(arg->job_id, bs, target_bs, arg->replaces, arg->sync, @@ -325,7 +325,7 @@ index e464daea58..50e4a9c682 100644 backing_mode, zero_target, arg->has_speed, arg->speed, arg->has_granularity, arg->granularity, -@@ -3223,6 +3252,8 @@ void qmp_blockdev_mirror(const char *job_id, +@@ -3135,6 +3164,8 @@ void qmp_blockdev_mirror(const char *job_id, const char *device, const char *target, const char *replaces, MirrorSyncMode sync, @@ -334,7 +334,7 @@ index e464daea58..50e4a9c682 100644 bool has_speed, int64_t speed, bool has_granularity, uint32_t granularity, bool has_buf_size, int64_t buf_size, -@@ -3271,7 +3302,8 @@ void qmp_blockdev_mirror(const char *job_id, +@@ -3183,7 +3214,8 @@ void qmp_blockdev_mirror(const char *job_id, } blockdev_mirror_common(job_id, bs, target_bs, @@ -345,7 +345,7 @@ index e464daea58..50e4a9c682 100644 has_granularity, granularity, has_buf_size, buf_size, diff --git a/include/block/block_int-global-state.h b/include/block/block_int-global-state.h -index 902406eb99..d559be928c 100644 +index da5fb31089..32f0f9858a 100644 --- a/include/block/block_int-global-state.h +++ b/include/block/block_int-global-state.h @@ -152,7 +152,9 @@ void mirror_start(const char *job_id, BlockDriverState *bs, @@ -360,31 +360,26 @@ index 902406eb99..d559be928c 100644 BlockdevOnError on_source_error, BlockdevOnError on_target_error, diff --git a/qapi/block-core.json b/qapi/block-core.json -index c05ad0c07e..3c945c1f93 100644 +index 2b1d493d6e..903392cb8f 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json -@@ -2095,10 +2095,19 @@ - # (all the disk, only the sectors allocated in the topmost image, or - # only new I/O). +@@ -2145,6 +2145,15 @@ + # destination (all the disk, only the sectors allocated in the + # topmost image, or only new I/O). # -+# @bitmap: The name of a bitmap to use for sync=bitmap mode. This argument must -+# be present for bitmap mode and absent otherwise. The bitmap's -+# granularity is used instead of @granularity (since 4.1). ++# @bitmap: The name of a bitmap to use for sync=bitmap mode. This ++# argument must be present for bitmap mode and absent otherwise. ++# The bitmap's granularity is used instead of @granularity (Since ++# 4.1). +# -+# @bitmap-mode: Specifies the type of data the bitmap should contain after -+# the operation concludes. Must be present if sync is "bitmap". -+# Must NOT be present otherwise. (Since 4.1) ++# @bitmap-mode: Specifies the type of data the bitmap should contain ++# after the operation concludes. Must be present if sync is ++# "bitmap". Must NOT be present otherwise. (Since 4.1) +# - # @granularity: granularity of the dirty bitmap, default is 64K - # if the image format doesn't have clusters, 4K if the clusters - # are smaller than that, else the cluster size. Must be a --# power of 2 between 512 and 64M (since 1.4). -+# power of 2 between 512 and 64M. Must not be specified if -+# @bitmap is present (since 1.4). - # - # @buf-size: maximum amount of data in flight from source to - # target (since 1.4). -@@ -2138,7 +2147,9 @@ + # @granularity: granularity of the dirty bitmap, default is 64K if the + # image format doesn't have clusters, 4K if the clusters are + # smaller than that, else the cluster size. Must be a power of 2 +@@ -2187,7 +2196,9 @@ { 'struct': 'DriveMirror', 'data': { '*job-id': 'str', 'device': 'str', 'target': 'str', '*format': 'str', '*node-name': 'str', '*replaces': 'str', @@ -395,28 +390,23 @@ index c05ad0c07e..3c945c1f93 100644 '*speed': 'int', '*granularity': 'uint32', '*buf-size': 'int', '*on-source-error': 'BlockdevOnError', '*on-target-error': 'BlockdevOnError', -@@ -2417,10 +2428,19 @@ - # (all the disk, only the sectors allocated in the topmost image, or - # only new I/O). +@@ -2471,6 +2482,15 @@ + # destination (all the disk, only the sectors allocated in the + # topmost image, or only new I/O). # -+# @bitmap: The name of a bitmap to use for sync=bitmap mode. This argument must -+# be present for bitmap mode and absent otherwise. The bitmap's -+# granularity is used instead of @granularity (since 4.1). ++# @bitmap: The name of a bitmap to use for sync=bitmap mode. This ++# argument must be present for bitmap mode and absent otherwise. ++# The bitmap's granularity is used instead of @granularity (since ++# 4.1). +# -+# @bitmap-mode: Specifies the type of data the bitmap should contain after -+# the operation concludes. Must be present if sync is "bitmap". -+# Must NOT be present otherwise. (Since 4.1) ++# @bitmap-mode: Specifies the type of data the bitmap should contain ++# after the operation concludes. Must be present if sync is ++# "bitmap". Must NOT be present otherwise. (Since 4.1) +# - # @granularity: granularity of the dirty bitmap, default is 64K - # if the image format doesn't have clusters, 4K if the clusters - # are smaller than that, else the cluster size. Must be a --# power of 2 between 512 and 64M -+# power of 2 between 512 and 64M . Must not be specified if -+# @bitmap is present. - # - # @buf-size: maximum amount of data in flight from source to - # target -@@ -2470,7 +2490,8 @@ + # @granularity: granularity of the dirty bitmap, default is 64K if the + # image format doesn't have clusters, 4K if the clusters are + # smaller than that, else the cluster size. Must be a power of 2 +@@ -2521,7 +2541,8 @@ { 'command': 'blockdev-mirror', 'data': { '*job-id': 'str', 'device': 'str', 'target': 'str', '*replaces': 'str', @@ -427,7 +417,7 @@ index c05ad0c07e..3c945c1f93 100644 '*buf-size': 'int', '*on-source-error': 'BlockdevOnError', '*on-target-error': 'BlockdevOnError', diff --git a/tests/unit/test-block-iothread.c b/tests/unit/test-block-iothread.c -index 3a5e1eb2c4..c1ecc49073 100644 +index d727a5fee8..8a34aa2328 100644 --- a/tests/unit/test-block-iothread.c +++ b/tests/unit/test-block-iothread.c @@ -757,8 +757,8 @@ static void test_propagate_mirror(void) diff --git a/debian/patches/bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch b/debian/patches/bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch index b0ade68..a4a5a0b 100644 --- a/debian/patches/bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch +++ b/debian/patches/bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch @@ -24,10 +24,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/block/mirror.c b/block/mirror.c -index 9099c75992..e2ff42067b 100644 +index 1ff42c8af1..11b8a8e959 100644 --- a/block/mirror.c +++ b/block/mirror.c -@@ -680,8 +680,6 @@ static int mirror_exit_common(Job *job) +@@ -682,8 +682,6 @@ static int mirror_exit_common(Job *job) bdrv_unfreeze_backing_chain(mirror_top_bs, target_bs); } @@ -36,7 +36,7 @@ index 9099c75992..e2ff42067b 100644 /* Make sure that the source BDS doesn't go away during bdrv_replace_node, * before we can call bdrv_drained_end */ bdrv_ref(src); -@@ -782,6 +780,18 @@ static int mirror_exit_common(Job *job) +@@ -788,6 +786,18 @@ static int mirror_exit_common(Job *job) block_job_remove_all_bdrv(bjob); bdrv_replace_node(mirror_top_bs, mirror_top_bs->backing->bs, &error_abort); @@ -55,7 +55,7 @@ index 9099c75992..e2ff42067b 100644 bs_opaque->job = NULL; bdrv_drained_end(src); -@@ -1688,10 +1698,6 @@ static BlockJob *mirror_start_job( +@@ -1699,10 +1709,6 @@ static BlockJob *mirror_start_job( " sync mode", MirrorSyncMode_str(sync_mode)); return NULL; @@ -66,7 +66,7 @@ index 9099c75992..e2ff42067b 100644 } } else if (bitmap) { error_setg(errp, -@@ -1708,6 +1714,12 @@ static BlockJob *mirror_start_job( +@@ -1719,6 +1725,12 @@ static BlockJob *mirror_start_job( return NULL; } granularity = bdrv_dirty_bitmap_granularity(bitmap); diff --git a/debian/patches/bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch b/debian/patches/bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch index 302bbc5..4546b78 100644 --- a/debian/patches/bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch +++ b/debian/patches/bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch @@ -16,10 +16,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 3 insertions(+) diff --git a/blockdev.c b/blockdev.c -index 50e4a9c682..e6b2b1e338 100644 +index a8b1fd2a73..83d5cc1e49 100644 --- a/blockdev.c +++ b/blockdev.c -@@ -3036,6 +3036,9 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, +@@ -2945,6 +2945,9 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, if (bdrv_dirty_bitmap_check(bitmap, BDRV_BITMAP_ALLOW_RO, errp)) { return; } diff --git a/debian/patches/bitmap-mirror/0004-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch b/debian/patches/bitmap-mirror/0004-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch index b954682..93a1524 100644 --- a/debian/patches/bitmap-mirror/0004-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch +++ b/debian/patches/bitmap-mirror/0004-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch @@ -16,10 +16,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/block/mirror.c b/block/mirror.c -index e2ff42067b..f42953837b 100644 +index 11b8a8e959..00f2665ca4 100644 --- a/block/mirror.c +++ b/block/mirror.c -@@ -786,8 +786,8 @@ static int mirror_exit_common(Job *job) +@@ -792,8 +792,8 @@ static int mirror_exit_common(Job *job) job->ret == 0 && ret == 0)) { /* Success; synchronize copy back to sync. */ bdrv_clear_dirty_bitmap(s->sync_bitmap, NULL); @@ -30,7 +30,7 @@ index e2ff42067b..f42953837b 100644 } } bdrv_release_dirty_bitmap(s->dirty_bitmap); -@@ -1881,11 +1881,8 @@ static BlockJob *mirror_start_job( +@@ -1892,11 +1892,8 @@ static BlockJob *mirror_start_job( } if (s->sync_mode == MIRROR_SYNC_MODE_BITMAP) { diff --git a/debian/patches/bitmap-mirror/0006-mirror-move-some-checks-to-qmp.patch b/debian/patches/bitmap-mirror/0006-mirror-move-some-checks-to-qmp.patch index 5298342..9a3108f 100644 --- a/debian/patches/bitmap-mirror/0006-mirror-move-some-checks-to-qmp.patch +++ b/debian/patches/bitmap-mirror/0006-mirror-move-some-checks-to-qmp.patch @@ -21,10 +21,10 @@ Signed-off-by: Fiona Ebner 3 files changed, 70 insertions(+), 59 deletions(-) diff --git a/block/mirror.c b/block/mirror.c -index f42953837b..8f79efaa87 100644 +index 00f2665ca4..60cf574de5 100644 --- a/block/mirror.c +++ b/block/mirror.c -@@ -1688,31 +1688,13 @@ static BlockJob *mirror_start_job( +@@ -1699,31 +1699,13 @@ static BlockJob *mirror_start_job( uint64_t target_perms, target_shared_perms; int ret; @@ -62,10 +62,10 @@ index f42953837b..8f79efaa87 100644 if (bitmap_mode != BITMAP_SYNC_MODE_NEVER) { diff --git a/blockdev.c b/blockdev.c -index e6b2b1e338..bdae211a54 100644 +index 83d5cc1e49..060d86a65f 100644 --- a/blockdev.c +++ b/blockdev.c -@@ -3015,7 +3015,36 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, +@@ -2924,7 +2924,36 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, sync = MIRROR_SYNC_MODE_FULL; } diff --git a/debian/patches/extra/0001-monitor-qmp-fix-race-with-clients-disconnecting-earl.patch b/debian/patches/extra/0001-monitor-qmp-fix-race-with-clients-disconnecting-earl.patch index a7fe592..5ed0d76 100644 --- a/debian/patches/extra/0001-monitor-qmp-fix-race-with-clients-disconnecting-earl.patch +++ b/debian/patches/extra/0001-monitor-qmp-fix-race-with-clients-disconnecting-earl.patch @@ -48,7 +48,7 @@ Signed-off-by: Thomas Lamprecht 6 files changed, 59 insertions(+), 5 deletions(-) diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h -index 033390f699..ad35d4fea8 100644 +index 965f5d5450..e04bd059b6 100644 --- a/include/monitor/monitor.h +++ b/include/monitor/monitor.h @@ -16,6 +16,7 @@ extern QemuOptsList qemu_mon_opts; @@ -60,10 +60,10 @@ index 033390f699..ad35d4fea8 100644 void monitor_init_globals(void); void monitor_init_globals_core(void); diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h -index 53e3808054..a19f8cbc2b 100644 +index 252de85681..8db28f9272 100644 --- a/monitor/monitor-internal.h +++ b/monitor/monitor-internal.h -@@ -152,6 +152,13 @@ typedef struct { +@@ -151,6 +151,13 @@ typedef struct { QemuMutex qmp_queue_lock; /* Input queue that holds all the parsed QMP requests */ GQueue *qmp_requests; @@ -78,10 +78,10 @@ index 53e3808054..a19f8cbc2b 100644 /** diff --git a/monitor/monitor.c b/monitor/monitor.c -index 8dc96f6af9..f3c38cb714 100644 +index dc352f9e9d..56e1307014 100644 --- a/monitor/monitor.c +++ b/monitor/monitor.c -@@ -135,6 +135,21 @@ bool monitor_cur_is_qmp(void) +@@ -117,6 +117,21 @@ bool monitor_cur_is_qmp(void) return cur_mon && monitor_is_qmp(cur_mon); } @@ -104,10 +104,10 @@ index 8dc96f6af9..f3c38cb714 100644 * Is @mon is using readline? * Note: not all HMP monitors use readline, e.g., gdbserver has a diff --git a/monitor/qmp.c b/monitor/qmp.c -index 092c527b6f..6b8cfcf6d8 100644 +index 6eee450fe4..c15bf1e1fc 100644 --- a/monitor/qmp.c +++ b/monitor/qmp.c -@@ -141,6 +141,8 @@ static void monitor_qmp_dispatch(MonitorQMP *mon, QObject *req) +@@ -165,6 +165,8 @@ static void monitor_qmp_dispatch(MonitorQMP *mon, QObject *req) QDict *rsp; QDict *error; @@ -116,7 +116,7 @@ index 092c527b6f..6b8cfcf6d8 100644 rsp = qmp_dispatch(mon->commands, req, qmp_oob_enabled(mon), &mon->common); -@@ -156,7 +158,17 @@ static void monitor_qmp_dispatch(MonitorQMP *mon, QObject *req) +@@ -180,7 +182,17 @@ static void monitor_qmp_dispatch(MonitorQMP *mon, QObject *req) } } @@ -135,7 +135,7 @@ index 092c527b6f..6b8cfcf6d8 100644 qobject_unref(rsp); } -@@ -444,6 +456,7 @@ static void monitor_qmp_event(void *opaque, QEMUChrEvent event) +@@ -478,6 +490,7 @@ static void monitor_qmp_event(void *opaque, QEMUChrEvent event) switch (event) { case CHR_EVENT_OPENED: @@ -144,7 +144,7 @@ index 092c527b6f..6b8cfcf6d8 100644 monitor_qmp_caps_reset(mon); data = qmp_greeting(mon); diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c -index 0990873ec8..e605003771 100644 +index 555528b6bb..3baa508b4b 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -117,16 +117,28 @@ typedef struct QmpDispatchBH { @@ -180,7 +180,7 @@ index 0990873ec8..e605003771 100644 aio_co_wake(data->co); } -@@ -231,6 +243,7 @@ QDict *qmp_dispatch(const QmpCommandList *cmds, QObject *request, +@@ -231,6 +243,7 @@ QDict *coroutine_mixed_fn qmp_dispatch(const QmpCommandList *cmds, QObject *requ .ret = &ret, .errp = &err, .co = qemu_coroutine_self(), diff --git a/debian/patches/extra/0002-scsi-megasas-Internal-cdbs-have-16-byte-length.patch b/debian/patches/extra/0002-scsi-megasas-Internal-cdbs-have-16-byte-length.patch index 20ec053..328f6fb 100644 --- a/debian/patches/extra/0002-scsi-megasas-Internal-cdbs-have-16-byte-length.patch +++ b/debian/patches/extra/0002-scsi-megasas-Internal-cdbs-have-16-byte-length.patch @@ -22,10 +22,10 @@ Signed-off-by: Fiona Ebner 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c -index 9cbbb16121..d624866bb6 100644 +index 32c70c9e99..984b6a3145 100644 --- a/hw/scsi/megasas.c +++ b/hw/scsi/megasas.c -@@ -1780,7 +1780,7 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) +@@ -1781,7 +1781,7 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) uint8_t cdb[16]; int len; struct SCSIDevice *sdev = NULL; @@ -34,7 +34,7 @@ index 9cbbb16121..d624866bb6 100644 lba_count = le32_to_cpu(cmd->frame->io.header.data_len); lba_start_lo = le32_to_cpu(cmd->frame->io.lba_lo); -@@ -1789,7 +1789,6 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) +@@ -1790,7 +1790,6 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) target_id = cmd->frame->header.target_id; lun_id = cmd->frame->header.lun_id; @@ -42,7 +42,7 @@ index 9cbbb16121..d624866bb6 100644 if (target_id < MFI_MAX_LD && lun_id == 0) { sdev = scsi_device_find(&s->bus, 0, target_id, lun_id); -@@ -1804,15 +1803,6 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) +@@ -1805,15 +1804,6 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) return MFI_STAT_DEVICE_NOT_FOUND; } @@ -58,7 +58,7 @@ index 9cbbb16121..d624866bb6 100644 cmd->iov_size = lba_count * sdev->blocksize; if (megasas_map_sgl(s, cmd, &cmd->frame->io.sgl)) { megasas_write_sense(cmd, SENSE_CODE(TARGET_FAILURE)); -@@ -1823,7 +1813,7 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) +@@ -1824,7 +1814,7 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) megasas_encode_lba(cdb, lba_start, lba_count, is_write); cmd->req = scsi_req_new(sdev, cmd->index, diff --git a/debian/patches/extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch b/debian/patches/extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch index 89491a5..018f0c9 100644 --- a/debian/patches/extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch +++ b/debian/patches/extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch @@ -55,7 +55,7 @@ Signed-off-by: Fiona Ebner 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hw/ide/core.c b/hw/ide/core.c -index 45d14a25e9..08e1f0c3d7 100644 +index 07971c0218..6a74afe564 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -444,7 +444,7 @@ static void ide_trim_bh_cb(void *opaque) @@ -76,8 +76,8 @@ index 45d14a25e9..08e1f0c3d7 100644 replay_bh_schedule_event(iocb->bh); } } -@@ -515,9 +517,6 @@ BlockAIOCB *ide_issue_trim( - IDEState *s = opaque; +@@ -516,9 +518,6 @@ BlockAIOCB *ide_issue_trim( + IDEDevice *dev = s->unit ? s->bus->slave : s->bus->master; TrimAIOCB *iocb; - /* Paired with a decrement in ide_trim_bh_cb() */ @@ -85,8 +85,8 @@ index 45d14a25e9..08e1f0c3d7 100644 - iocb = blk_aio_get(&trim_aiocb_info, s->blk, cb, cb_opaque); iocb->s = s; - iocb->bh = qemu_bh_new(ide_trim_bh_cb, iocb); -@@ -740,8 +739,9 @@ void ide_cancel_dma_sync(IDEState *s) + iocb->bh = qemu_bh_new_guarded(ide_trim_bh_cb, iocb, +@@ -742,8 +741,9 @@ void ide_cancel_dma_sync(IDEState *s) */ if (s->bus->dma->aiocb) { trace_ide_cancel_dma_sync_remaining(); diff --git a/debian/patches/extra/0010-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch b/debian/patches/extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch similarity index 98% rename from debian/patches/extra/0010-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch rename to debian/patches/extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch index bb01ced..4dae6ca 100644 --- a/debian/patches/extra/0010-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch +++ b/debian/patches/extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch @@ -20,7 +20,7 @@ Signed-off-by: Fiona Ebner 1 file changed, 6 insertions(+) diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c -index fe73aa94b1..7eaf498439 100644 +index 032fc5f405..e1ae3b7316 100644 --- a/migration/block-dirty-bitmap.c +++ b/migration/block-dirty-bitmap.c @@ -805,8 +805,11 @@ static int dirty_bitmap_load_start(QEMUFile *f, DBMLoadState *s) diff --git a/debian/patches/extra/0004-ui-return-NULL-when-getting-cursor-without-a-console.patch b/debian/patches/extra/0004-ui-return-NULL-when-getting-cursor-without-a-console.patch deleted file mode 100644 index 0b8d2c0..0000000 --- a/debian/patches/extra/0004-ui-return-NULL-when-getting-cursor-without-a-console.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= -Date: Fri, 28 Apr 2023 19:48:06 +0400 -Subject: [PATCH] ui: return NULL when getting cursor without a console -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -VNC may try to get the current cursor even when there are no consoles -and crashes. Simple reproducer is qemu with -nodefaults. - -Fixes: (again) -https://gitlab.com/qemu-project/qemu/-/issues/1548 - -Fixes: commit 385ac97f8 ("ui: keep current cursor with QemuConsole") -Signed-off-by: Marc-Andr? Lureau -Reviewed-by: Philippe Mathieu-Daud? -(picked up from https://lists.nongnu.org/archive/html/qemu-devel/2023-04/msg05598.html) -Signed-off-by: Fiona Ebner ---- - ui/console.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/ui/console.c b/ui/console.c -index e173731e20..7461446e71 100644 ---- a/ui/console.c -+++ b/ui/console.c -@@ -2306,7 +2306,7 @@ QEMUCursor *qemu_console_get_cursor(QemuConsole *con) - if (con == NULL) { - con = active_console; - } -- return con->cursor; -+ return con ? con->cursor : NULL; - } - - bool qemu_console_is_visible(QemuConsole *con) diff --git a/debian/patches/extra/0012-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch b/debian/patches/extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch similarity index 97% rename from debian/patches/extra/0012-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch rename to debian/patches/extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch index d11ae00..ef1a649 100644 --- a/debian/patches/extra/0012-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch +++ b/debian/patches/extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch @@ -68,10 +68,10 @@ Signed-off-by: Fiona Ebner 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hw/ide/core.c b/hw/ide/core.c -index 08e1f0c3d7..148fccdef2 100644 +index 6a74afe564..289347af58 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c -@@ -2513,19 +2513,19 @@ static void ide_dummy_transfer_stop(IDEState *s) +@@ -2515,19 +2515,19 @@ static void ide_dummy_transfer_stop(IDEState *s) void ide_bus_reset(IDEBus *bus) { diff --git a/debian/patches/extra/0005-memory-prevent-dma-reentracy-issues.patch b/debian/patches/extra/0005-memory-prevent-dma-reentracy-issues.patch deleted file mode 100644 index c9d0cd5..0000000 --- a/debian/patches/extra/0005-memory-prevent-dma-reentracy-issues.patch +++ /dev/null @@ -1,130 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alexander Bulekov -Date: Thu, 27 Apr 2023 17:10:06 -0400 -Subject: [PATCH] memory: prevent dma-reentracy issues - -Add a flag to the DeviceState, when a device is engaged in PIO/MMIO/DMA. -This flag is set/checked prior to calling a device's MemoryRegion -handlers, and set when device code initiates DMA. The purpose of this -flag is to prevent two types of DMA-based reentrancy issues: - -1.) mmio -> dma -> mmio case -2.) bh -> dma write -> mmio case - -These issues have led to problems such as stack-exhaustion and -use-after-frees. - -Summary of the problem from Peter Maydell: -https://lore.kernel.org/qemu-devel/CAFEAcA_23vc7hE3iaM-JVA6W38LK4hJoWae5KcknhPRD5fPBZA at mail.gmail.com - -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/62 -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/540 -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/541 -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/556 -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/557 -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/827 -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1282 -Resolves: CVE-2023-0330 - -Signed-off-by: Alexander Bulekov -Reviewed-by: Thomas Huth -Message-Id: <20230427211013.2994127-2-alxndr at bu.edu> -[thuth: Replace warn_report() with warn_report_once()] -Signed-off-by: Thomas Huth -(cherry-picked from commit a2e1753b8054344f32cf94f31c6399a58794a380) -Signed-off-by: Fiona Ebner ---- - include/exec/memory.h | 5 +++++ - include/hw/qdev-core.h | 7 +++++++ - softmmu/memory.c | 16 ++++++++++++++++ - 3 files changed, 28 insertions(+) - -diff --git a/include/exec/memory.h b/include/exec/memory.h -index 15ade918ba..e45ce6061f 100644 ---- a/include/exec/memory.h -+++ b/include/exec/memory.h -@@ -767,6 +767,8 @@ struct MemoryRegion { - bool is_iommu; - RAMBlock *ram_block; - Object *owner; -+ /* owner as TYPE_DEVICE. Used for re-entrancy checks in MR access hotpath */ -+ DeviceState *dev; - - const MemoryRegionOps *ops; - void *opaque; -@@ -791,6 +793,9 @@ struct MemoryRegion { - unsigned ioeventfd_nb; - MemoryRegionIoeventfd *ioeventfds; - RamDiscardManager *rdm; /* Only for RAM */ -+ -+ /* For devices designed to perform re-entrant IO into their own IO MRs */ -+ bool disable_reentrancy_guard; - }; - - struct IOMMUMemoryRegion { -diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h -index bd50ad5ee1..7623703943 100644 ---- a/include/hw/qdev-core.h -+++ b/include/hw/qdev-core.h -@@ -162,6 +162,10 @@ struct NamedClockList { - QLIST_ENTRY(NamedClockList) node; - }; - -+typedef struct { -+ bool engaged_in_io; -+} MemReentrancyGuard; -+ - /** - * DeviceState: - * @realized: Indicates whether the device has been fully constructed. -@@ -194,6 +198,9 @@ struct DeviceState { - int alias_required_for_version; - ResettableState reset; - GSList *unplug_blockers; -+ -+ /* Is the device currently in mmio/pio/dma? Used to prevent re-entrancy */ -+ MemReentrancyGuard mem_reentrancy_guard; - }; - - struct DeviceListener { -diff --git a/softmmu/memory.c b/softmmu/memory.c -index b1a6cae6f5..b7b3386e9d 100644 ---- a/softmmu/memory.c -+++ b/softmmu/memory.c -@@ -542,6 +542,18 @@ static MemTxResult access_with_adjusted_size(hwaddr addr, - access_size_max = 4; - } - -+ /* Do not allow more than one simultaneous access to a device's IO Regions */ -+ if (mr->dev && !mr->disable_reentrancy_guard && -+ !mr->ram_device && !mr->ram && !mr->rom_device && !mr->readonly) { -+ if (mr->dev->mem_reentrancy_guard.engaged_in_io) { -+ warn_report_once("Blocked re-entrant IO on MemoryRegion: " -+ "%s at addr: 0x%" HWADDR_PRIX, -+ memory_region_name(mr), addr); -+ return MEMTX_ACCESS_ERROR; -+ } -+ mr->dev->mem_reentrancy_guard.engaged_in_io = true; -+ } -+ - /* FIXME: support unaligned access? */ - access_size = MAX(MIN(size, access_size_max), access_size_min); - access_mask = MAKE_64BIT_MASK(0, access_size * 8); -@@ -556,6 +568,9 @@ static MemTxResult access_with_adjusted_size(hwaddr addr, - access_mask, attrs); - } - } -+ if (mr->dev) { -+ mr->dev->mem_reentrancy_guard.engaged_in_io = false; -+ } - return r; - } - -@@ -1170,6 +1185,7 @@ static void memory_region_do_init(MemoryRegion *mr, - } - mr->name = g_strdup(name); - mr->owner = owner; -+ mr->dev = (DeviceState *) object_dynamic_cast(mr->owner, TYPE_DEVICE); - mr->ram_block = NULL; - - if (name) { diff --git a/debian/patches/extra/0006-lsi53c895a-disable-reentrancy-detection-for-script-R.patch b/debian/patches/extra/0006-lsi53c895a-disable-reentrancy-detection-for-script-R.patch deleted file mode 100644 index 96d254c..0000000 --- a/debian/patches/extra/0006-lsi53c895a-disable-reentrancy-detection-for-script-R.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alexander Bulekov -Date: Thu, 27 Apr 2023 17:10:10 -0400 -Subject: [PATCH] lsi53c895a: disable reentrancy detection for script RAM - -As the code is designed to use the memory APIs to access the script ram, -disable reentrancy checks for the pseudo-RAM ram_io MemoryRegion. - -In the future, ram_io may be converted from an IO to a proper RAM MemoryRegion. - -Reported-by: Fiona Ebner -Signed-off-by: Alexander Bulekov -Reviewed-by: Thomas Huth -Reviewed-by: Darren Kenny -Message-Id: <20230427211013.2994127-6-alxndr at bu.edu> -Signed-off-by: Thomas Huth -(cherry-picked from commit bfd6e7ae6a72b84e2eb9574f56e6ec037f05182c) -Signed-off-by: Fiona Ebner ---- - hw/scsi/lsi53c895a.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/hw/scsi/lsi53c895a.c b/hw/scsi/lsi53c895a.c -index bbf32d3f73..17af67935f 100644 ---- a/hw/scsi/lsi53c895a.c -+++ b/hw/scsi/lsi53c895a.c -@@ -2313,6 +2313,12 @@ static void lsi_scsi_realize(PCIDevice *dev, Error **errp) - memory_region_init_io(&s->io_io, OBJECT(s), &lsi_io_ops, s, - "lsi-io", 256); - -+ /* -+ * Since we use the address-space API to interact with ram_io, disable the -+ * re-entrancy guard. -+ */ -+ s->ram_io.disable_reentrancy_guard = true; -+ - address_space_init(&s->pci_io_as, pci_address_space_io(dev), "lsi-pci-io"); - qdev_init_gpio_out(d, &s->ext_irq, 1); - diff --git a/debian/patches/extra/0007-bcm2835_property-disable-reentrancy-detection-for-io.patch b/debian/patches/extra/0007-bcm2835_property-disable-reentrancy-detection-for-io.patch deleted file mode 100644 index 6ec9d03..0000000 --- a/debian/patches/extra/0007-bcm2835_property-disable-reentrancy-detection-for-io.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alexander Bulekov -Date: Thu, 27 Apr 2023 17:10:11 -0400 -Subject: [PATCH] bcm2835_property: disable reentrancy detection for iomem - -As the code is designed for re-entrant calls from bcm2835_property to -bcm2835_mbox and back into bcm2835_property, mark iomem as -reentrancy-safe. - -Signed-off-by: Alexander Bulekov -Reviewed-by: Thomas Huth -Message-Id: <20230427211013.2994127-7-alxndr at bu.edu> -Signed-off-by: Thomas Huth -(cherry-picked from commit 985c4a4e547afb9573b6bd6843d20eb2c3d1d1cd) -Signed-off-by: Fiona Ebner ---- - hw/misc/bcm2835_property.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/hw/misc/bcm2835_property.c b/hw/misc/bcm2835_property.c -index 890ae7bae5..de056ea2df 100644 ---- a/hw/misc/bcm2835_property.c -+++ b/hw/misc/bcm2835_property.c -@@ -382,6 +382,13 @@ static void bcm2835_property_init(Object *obj) - - memory_region_init_io(&s->iomem, OBJECT(s), &bcm2835_property_ops, s, - TYPE_BCM2835_PROPERTY, 0x10); -+ -+ /* -+ * bcm2835_property_ops call into bcm2835_mbox, which in-turn reads from -+ * iomem. As such, mark iomem as re-entracy safe. -+ */ -+ s->iomem.disable_reentrancy_guard = true; -+ - sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); - sysbus_init_irq(SYS_BUS_DEVICE(s), &s->mbox_irq); - } diff --git a/debian/patches/extra/0008-raven-disable-reentrancy-detection-for-iomem.patch b/debian/patches/extra/0008-raven-disable-reentrancy-detection-for-iomem.patch deleted file mode 100644 index bea68d4..0000000 --- a/debian/patches/extra/0008-raven-disable-reentrancy-detection-for-iomem.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alexander Bulekov -Date: Thu, 27 Apr 2023 17:10:12 -0400 -Subject: [PATCH] raven: disable reentrancy detection for iomem - -As the code is designed for re-entrant calls from raven_io_ops to -pci-conf, mark raven_io_ops as reentrancy-safe. - -Signed-off-by: Alexander Bulekov -Message-Id: <20230427211013.2994127-8-alxndr at bu.edu> -Signed-off-by: Thomas Huth -(cherry-picked from commit 6dad5a6810d9c60ca320d01276f6133bbcfa1fc7) -Signed-off-by: Fiona Ebner ---- - hw/pci-host/raven.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/hw/pci-host/raven.c b/hw/pci-host/raven.c -index 072ffe3c5e..9a11ac4b2b 100644 ---- a/hw/pci-host/raven.c -+++ b/hw/pci-host/raven.c -@@ -294,6 +294,13 @@ static void raven_pcihost_initfn(Object *obj) - memory_region_init(&s->pci_memory, obj, "pci-memory", 0x3f000000); - address_space_init(&s->pci_io_as, &s->pci_io, "raven-io"); - -+ /* -+ * Raven's raven_io_ops use the address-space API to access pci-conf-idx -+ * (which is also owned by the raven device). As such, mark the -+ * pci_io_non_contiguous as re-entrancy safe. -+ */ -+ s->pci_io_non_contiguous.disable_reentrancy_guard = true; -+ - /* CPU address space */ - memory_region_add_subregion(address_space_mem, PCI_IO_BASE_ADDR, - &s->pci_io); diff --git a/debian/patches/extra/0009-apic-disable-reentrancy-detection-for-apic-msi.patch b/debian/patches/extra/0009-apic-disable-reentrancy-detection-for-apic-msi.patch deleted file mode 100644 index 154cc36..0000000 --- a/debian/patches/extra/0009-apic-disable-reentrancy-detection-for-apic-msi.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alexander Bulekov -Date: Thu, 27 Apr 2023 17:10:13 -0400 -Subject: [PATCH] apic: disable reentrancy detection for apic-msi - -As the code is designed for re-entrant calls to apic-msi, mark apic-msi -as reentrancy-safe. - -Signed-off-by: Alexander Bulekov -Reviewed-by: Darren Kenny -Message-Id: <20230427211013.2994127-9-alxndr at bu.edu> -Signed-off-by: Thomas Huth -(cherry-picked from commit 50795ee051a342c681a9b45671c552fbd6274db8) -Signed-off-by: Fiona Ebner ---- - hw/intc/apic.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/hw/intc/apic.c b/hw/intc/apic.c -index 20b5a94073..ac3d47d231 100644 ---- a/hw/intc/apic.c -+++ b/hw/intc/apic.c -@@ -885,6 +885,13 @@ static void apic_realize(DeviceState *dev, Error **errp) - memory_region_init_io(&s->io_memory, OBJECT(s), &apic_io_ops, s, "apic-msi", - APIC_SPACE_SIZE); - -+ /* -+ * apic-msi's apic_mem_write can call into ioapic_eoi_broadcast, which can -+ * write back to apic-msi. As such mark the apic-msi region re-entrancy -+ * safe. -+ */ -+ s->io_memory.disable_reentrancy_guard = true; -+ - s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, apic_timer, s); - local_apics[s->id] = s; - diff --git a/debian/patches/extra/0011-vhost-fix-the-fd-leak.patch b/debian/patches/extra/0011-vhost-fix-the-fd-leak.patch deleted file mode 100644 index 31392fb..0000000 --- a/debian/patches/extra/0011-vhost-fix-the-fd-leak.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Li Feng -Date: Mon, 31 Jul 2023 20:10:06 +0800 -Subject: [PATCH] vhost: fix the fd leak - -When the vhost-user reconnect to the backend, the notifer should be -cleanup. Otherwise, the fd resource will be exhausted. - -Fixes: f9a09ca3ea ("vhost: add support for configure interrupt") - -Signed-off-by: Li Feng -Reviewed-by: Raphael Norwitz ---- - hw/virtio/vhost.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c -index a266396576..8e3311781f 100644 ---- a/hw/virtio/vhost.c -+++ b/hw/virtio/vhost.c -@@ -2034,6 +2034,8 @@ void vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings) - event_notifier_test_and_clear( - &hdev->vqs[VHOST_QUEUE_NUM_CONFIG_INR].masked_config_notifier); - event_notifier_test_and_clear(&vdev->config_notifier); -+ event_notifier_cleanup( -+ &hdev->vqs[VHOST_QUEUE_NUM_CONFIG_INR].masked_config_notifier); - - trace_vhost_dev_stop(hdev, vdev->name, vrings); - diff --git a/debian/patches/pve/0001-PVE-Config-block-file-change-locking-default-to-off.patch b/debian/patches/pve/0001-PVE-Config-block-file-change-locking-default-to-off.patch index 4926a64..1c3d08c 100644 --- a/debian/patches/pve/0001-PVE-Config-block-file-change-locking-default-to-off.patch +++ b/debian/patches/pve/0001-PVE-Config-block-file-change-locking-default-to-off.patch @@ -14,10 +14,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/block/file-posix.c b/block/file-posix.c -index c2dee3f056..9681bd0434 100644 +index b16e9c21a1..26aa0172b4 100644 --- a/block/file-posix.c +++ b/block/file-posix.c -@@ -553,7 +553,7 @@ static QemuOptsList raw_runtime_opts = { +@@ -564,7 +564,7 @@ static QemuOptsList raw_runtime_opts = { { .name = "locking", .type = QEMU_OPT_STRING, @@ -26,7 +26,7 @@ index c2dee3f056..9681bd0434 100644 }, { .name = "pr-manager", -@@ -653,7 +653,7 @@ static int raw_open_common(BlockDriverState *bs, QDict *options, +@@ -664,7 +664,7 @@ static int raw_open_common(BlockDriverState *bs, QDict *options, s->use_lock = false; break; case ON_OFF_AUTO_AUTO: diff --git a/debian/patches/pve/0003-PVE-Config-set-the-CPU-model-to-kvm64-32-instead-of-.patch b/debian/patches/pve/0003-PVE-Config-set-the-CPU-model-to-kvm64-32-instead-of-.patch index 2827fa4..297e250 100644 --- a/debian/patches/pve/0003-PVE-Config-set-the-CPU-model-to-kvm64-32-instead-of-.patch +++ b/debian/patches/pve/0003-PVE-Config-set-the-CPU-model-to-kvm64-32-instead-of-.patch @@ -10,10 +10,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/i386/cpu.h b/target/i386/cpu.h -index d243e290d3..3489b05ec4 100644 +index e0771a1043..1018ccc0b8 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h -@@ -2203,9 +2203,9 @@ uint64_t cpu_get_tsc(CPUX86State *env); +@@ -2243,9 +2243,9 @@ uint64_t cpu_get_tsc(CPUX86State *env); #define CPU_RESOLVING_TYPE TYPE_X86_CPU #ifdef TARGET_X86_64 diff --git a/debian/patches/pve/0005-PVE-Config-glusterfs-no-default-logfile-if-daemonize.patch b/debian/patches/pve/0005-PVE-Config-glusterfs-no-default-logfile-if-daemonize.patch index 6f3afdb..947fc90 100644 --- a/debian/patches/pve/0005-PVE-Config-glusterfs-no-default-logfile-if-daemonize.patch +++ b/debian/patches/pve/0005-PVE-Config-glusterfs-no-default-logfile-if-daemonize.patch @@ -9,7 +9,7 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/block/gluster.c b/block/gluster.c -index 185a83e5e5..f11a40aa9e 100644 +index ad5fadbe79..d0011085c4 100644 --- a/block/gluster.c +++ b/block/gluster.c @@ -43,7 +43,7 @@ @@ -24,7 +24,7 @@ index 185a83e5e5..f11a40aa9e 100644 @@ -425,6 +425,7 @@ static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf, int old_errno; SocketAddressList *server; - unsigned long long port; + uint64_t port; + const char *logfile; glfs = glfs_find_preopened(gconf->volume); diff --git a/debian/patches/pve/0007-PVE-Up-glusterfs-allow-partial-reads.patch b/debian/patches/pve/0007-PVE-Up-glusterfs-allow-partial-reads.patch index 122a07d..c4e6729 100644 --- a/debian/patches/pve/0007-PVE-Up-glusterfs-allow-partial-reads.patch +++ b/debian/patches/pve/0007-PVE-Up-glusterfs-allow-partial-reads.patch @@ -16,7 +16,7 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/block/gluster.c b/block/gluster.c -index f11a40aa9e..6756e6b886 100644 +index d0011085c4..2df3d6e35d 100644 --- a/block/gluster.c +++ b/block/gluster.c @@ -58,6 +58,7 @@ typedef struct GlusterAIOCB { diff --git a/debian/patches/pve/0008-PVE-Up-qemu-img-return-success-on-info-without-snaps.patch b/debian/patches/pve/0008-PVE-Up-qemu-img-return-success-on-info-without-snaps.patch index feb1ef3..fb505e5 100644 --- a/debian/patches/pve/0008-PVE-Up-qemu-img-return-success-on-info-without-snaps.patch +++ b/debian/patches/pve/0008-PVE-Up-qemu-img-return-success-on-info-without-snaps.patch @@ -9,10 +9,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qemu-img.c b/qemu-img.c -index 9aeac69fa6..0919fac1f1 100644 +index 27f48051b0..bb287d8538 100644 --- a/qemu-img.c +++ b/qemu-img.c -@@ -3059,7 +3059,8 @@ static int img_info(int argc, char **argv) +@@ -3062,7 +3062,8 @@ static int img_info(int argc, char **argv) list = collect_image_info_list(image_opts, filename, fmt, chain, force_share); if (!list) { diff --git a/debian/patches/pve/0009-PVE-Up-qemu-img-dd-add-osize-and-read-from-to-stdin-.patch b/debian/patches/pve/0009-PVE-Up-qemu-img-dd-add-osize-and-read-from-to-stdin-.patch index ffc30d0..5b88664 100644 --- a/debian/patches/pve/0009-PVE-Up-qemu-img-dd-add-osize-and-read-from-to-stdin-.patch +++ b/debian/patches/pve/0009-PVE-Up-qemu-img-dd-add-osize-and-read-from-to-stdin-.patch @@ -54,10 +54,10 @@ index 1b1dab5b17..d1616c045a 100644 DEF("info", img_info, diff --git a/qemu-img.c b/qemu-img.c -index 0919fac1f1..c584de648c 100644 +index bb287d8538..09c0340d16 100644 --- a/qemu-img.c +++ b/qemu-img.c -@@ -4885,10 +4885,12 @@ static int img_bitmap(int argc, char **argv) +@@ -4888,10 +4888,12 @@ static int img_bitmap(int argc, char **argv) #define C_IF 04 #define C_OF 010 #define C_SKIP 020 @@ -70,7 +70,7 @@ index 0919fac1f1..c584de648c 100644 }; struct DdIo { -@@ -4964,6 +4966,19 @@ static int img_dd_skip(const char *arg, +@@ -4967,6 +4969,19 @@ static int img_dd_skip(const char *arg, return 0; } @@ -90,7 +90,7 @@ index 0919fac1f1..c584de648c 100644 static int img_dd(int argc, char **argv) { int ret = 0; -@@ -5004,6 +5019,7 @@ static int img_dd(int argc, char **argv) +@@ -5007,6 +5022,7 @@ static int img_dd(int argc, char **argv) { "if", img_dd_if, C_IF }, { "of", img_dd_of, C_OF }, { "skip", img_dd_skip, C_SKIP }, @@ -98,7 +98,7 @@ index 0919fac1f1..c584de648c 100644 { NULL, NULL, 0 } }; const struct option long_options[] = { -@@ -5079,91 +5095,112 @@ static int img_dd(int argc, char **argv) +@@ -5082,91 +5098,112 @@ static int img_dd(int argc, char **argv) arg = NULL; } @@ -275,7 +275,7 @@ index 0919fac1f1..c584de648c 100644 } if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz || -@@ -5180,20 +5217,43 @@ static int img_dd(int argc, char **argv) +@@ -5183,20 +5220,43 @@ static int img_dd(int argc, char **argv) in.buf = g_new(uint8_t, in.bsz); for (out_pos = 0; in_pos < size; ) { diff --git a/debian/patches/pve/0010-PVE-Up-qemu-img-dd-add-isize-parameter.patch b/debian/patches/pve/0010-PVE-Up-qemu-img-dd-add-isize-parameter.patch index af21381..0325fe9 100644 --- a/debian/patches/pve/0010-PVE-Up-qemu-img-dd-add-isize-parameter.patch +++ b/debian/patches/pve/0010-PVE-Up-qemu-img-dd-add-isize-parameter.patch @@ -16,10 +16,10 @@ Signed-off-by: Fiona Ebner 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/qemu-img.c b/qemu-img.c -index c584de648c..a57ceeddfe 100644 +index 09c0340d16..556535d9d5 100644 --- a/qemu-img.c +++ b/qemu-img.c -@@ -4886,11 +4886,13 @@ static int img_bitmap(int argc, char **argv) +@@ -4889,11 +4889,13 @@ static int img_bitmap(int argc, char **argv) #define C_OF 010 #define C_SKIP 020 #define C_OSIZE 040 @@ -33,7 +33,7 @@ index c584de648c..a57ceeddfe 100644 }; struct DdIo { -@@ -4979,6 +4981,19 @@ static int img_dd_osize(const char *arg, +@@ -4982,6 +4984,19 @@ static int img_dd_osize(const char *arg, return 0; } @@ -53,7 +53,7 @@ index c584de648c..a57ceeddfe 100644 static int img_dd(int argc, char **argv) { int ret = 0; -@@ -4993,12 +5008,14 @@ static int img_dd(int argc, char **argv) +@@ -4996,12 +5011,14 @@ static int img_dd(int argc, char **argv) int c, i; const char *out_fmt = "raw"; const char *fmt = NULL; @@ -69,7 +69,7 @@ index c584de648c..a57ceeddfe 100644 }; struct DdIo in = { .bsz = 512, /* Block size is by default 512 bytes */ -@@ -5020,6 +5037,7 @@ static int img_dd(int argc, char **argv) +@@ -5023,6 +5040,7 @@ static int img_dd(int argc, char **argv) { "of", img_dd_of, C_OF }, { "skip", img_dd_skip, C_SKIP }, { "osize", img_dd_osize, C_OSIZE }, @@ -77,7 +77,7 @@ index c584de648c..a57ceeddfe 100644 { NULL, NULL, 0 } }; const struct option long_options[] = { -@@ -5216,9 +5234,10 @@ static int img_dd(int argc, char **argv) +@@ -5219,9 +5237,10 @@ static int img_dd(int argc, char **argv) in.buf = g_new(uint8_t, in.bsz); @@ -90,7 +90,7 @@ index c584de648c..a57ceeddfe 100644 if (blk1) { in_ret = blk_pread(blk1, in_pos, bytes, in.buf, 0); if (in_ret == 0) { -@@ -5227,6 +5246,9 @@ static int img_dd(int argc, char **argv) +@@ -5230,6 +5249,9 @@ static int img_dd(int argc, char **argv) } else { in_ret = read(STDIN_FILENO, in.buf, bytes); if (in_ret == 0) { diff --git a/debian/patches/pve/0011-PVE-Up-qemu-img-dd-add-n-skip_create.patch b/debian/patches/pve/0011-PVE-Up-qemu-img-dd-add-n-skip_create.patch index 376aa67..5cca59a 100644 --- a/debian/patches/pve/0011-PVE-Up-qemu-img-dd-add-n-skip_create.patch +++ b/debian/patches/pve/0011-PVE-Up-qemu-img-dd-add-n-skip_create.patch @@ -65,10 +65,10 @@ index d1616c045a..b5b0bb4467 100644 DEF("info", img_info, diff --git a/qemu-img.c b/qemu-img.c -index a57ceeddfe..06d814e39c 100644 +index 556535d9d5..289c78febb 100644 --- a/qemu-img.c +++ b/qemu-img.c -@@ -5010,7 +5010,7 @@ static int img_dd(int argc, char **argv) +@@ -5013,7 +5013,7 @@ static int img_dd(int argc, char **argv) const char *fmt = NULL; int64_t size = 0, readsize = 0; int64_t out_pos, in_pos; @@ -77,7 +77,7 @@ index a57ceeddfe..06d814e39c 100644 struct DdInfo dd = { .flags = 0, .count = 0, -@@ -5048,7 +5048,7 @@ static int img_dd(int argc, char **argv) +@@ -5051,7 +5051,7 @@ static int img_dd(int argc, char **argv) { 0, 0, 0, 0 } }; @@ -86,7 +86,7 @@ index a57ceeddfe..06d814e39c 100644 if (c == EOF) { break; } -@@ -5068,6 +5068,9 @@ static int img_dd(int argc, char **argv) +@@ -5071,6 +5071,9 @@ static int img_dd(int argc, char **argv) case 'h': help(); break; @@ -96,7 +96,7 @@ index a57ceeddfe..06d814e39c 100644 case 'U': force_share = true; break; -@@ -5198,13 +5201,15 @@ static int img_dd(int argc, char **argv) +@@ -5201,13 +5204,15 @@ static int img_dd(int argc, char **argv) size - in.bsz * in.offset, &error_abort); } diff --git a/debian/patches/pve/0012-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch b/debian/patches/pve/0012-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch index debfc54..d649d24 100644 --- a/debian/patches/pve/0012-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch +++ b/debian/patches/pve/0012-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch @@ -46,10 +46,10 @@ index b5b0bb4467..36f97e1f19 100644 DEF("info", img_info, diff --git a/qemu-img.c b/qemu-img.c -index 06d814e39c..e2c06c496d 100644 +index 289c78febb..da543d05cb 100644 --- a/qemu-img.c +++ b/qemu-img.c -@@ -5002,6 +5002,7 @@ static int img_dd(int argc, char **argv) +@@ -5005,6 +5005,7 @@ static int img_dd(int argc, char **argv) BlockDriver *drv = NULL, *proto_drv = NULL; BlockBackend *blk1 = NULL, *blk2 = NULL; QemuOpts *opts = NULL; @@ -57,7 +57,7 @@ index 06d814e39c..e2c06c496d 100644 QemuOptsList *create_opts = NULL; Error *local_err = NULL; bool image_opts = false; -@@ -5011,6 +5012,7 @@ static int img_dd(int argc, char **argv) +@@ -5014,6 +5015,7 @@ static int img_dd(int argc, char **argv) int64_t size = 0, readsize = 0; int64_t out_pos, in_pos; bool force_share = false, skip_create = false; @@ -65,7 +65,7 @@ index 06d814e39c..e2c06c496d 100644 struct DdInfo dd = { .flags = 0, .count = 0, -@@ -5048,7 +5050,7 @@ static int img_dd(int argc, char **argv) +@@ -5051,7 +5053,7 @@ static int img_dd(int argc, char **argv) { 0, 0, 0, 0 } }; @@ -74,7 +74,7 @@ index 06d814e39c..e2c06c496d 100644 if (c == EOF) { break; } -@@ -5071,6 +5073,19 @@ static int img_dd(int argc, char **argv) +@@ -5074,6 +5076,19 @@ static int img_dd(int argc, char **argv) case 'n': skip_create = true; break; @@ -94,7 +94,7 @@ index 06d814e39c..e2c06c496d 100644 case 'U': force_share = true; break; -@@ -5130,11 +5145,24 @@ static int img_dd(int argc, char **argv) +@@ -5133,11 +5148,24 @@ static int img_dd(int argc, char **argv) if (dd.flags & C_IF) { blk1 = img_open(image_opts, in.filename, fmt, 0, false, false, force_share); @@ -120,7 +120,7 @@ index 06d814e39c..e2c06c496d 100644 } if (dd.flags & C_OSIZE) { -@@ -5289,6 +5317,7 @@ static int img_dd(int argc, char **argv) +@@ -5292,6 +5320,7 @@ static int img_dd(int argc, char **argv) out: g_free(arg); qemu_opts_del(opts); diff --git a/debian/patches/pve/0013-PVE-virtio-balloon-improve-query-balloon.patch b/debian/patches/pve/0013-PVE-virtio-balloon-improve-query-balloon.patch index b465314..2c1524f 100644 --- a/debian/patches/pve/0013-PVE-virtio-balloon-improve-query-balloon.patch +++ b/debian/patches/pve/0013-PVE-virtio-balloon-improve-query-balloon.patch @@ -59,10 +59,10 @@ index c3e55ef9e9..0e32e6201f 100644 qapi_free_BalloonInfo(info); } diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c -index 746f07c4d2..a41854b902 100644 +index d004cf29d2..2660ed520b 100644 --- a/hw/virtio/virtio-balloon.c +++ b/hw/virtio/virtio-balloon.c -@@ -804,8 +804,37 @@ static uint64_t virtio_balloon_get_features(VirtIODevice *vdev, uint64_t f, +@@ -782,8 +782,37 @@ static uint64_t virtio_balloon_get_features(VirtIODevice *vdev, uint64_t f, static void virtio_balloon_stat(void *opaque, BalloonInfo *info) { VirtIOBalloon *dev = opaque; @@ -103,12 +103,12 @@ index 746f07c4d2..a41854b902 100644 static void virtio_balloon_to_target(void *opaque, ram_addr_t target) diff --git a/qapi/machine.json b/qapi/machine.json -index 604b686e59..15f5f86683 100644 +index a08b6576ca..5c9a4d55f4 100644 --- a/qapi/machine.json +++ b/qapi/machine.json -@@ -1056,9 +1056,29 @@ - # @actual: the logical size of the VM in bytes - # Formula used: logical_vm_size = vm_ram_size - balloon_size +@@ -1063,9 +1063,29 @@ + # @actual: the logical size of the VM in bytes Formula used: + # logical_vm_size = vm_ram_size - balloon_size # +# @last_update: time when stats got updated from guest +# diff --git a/debian/patches/pve/0014-PVE-qapi-modify-query-machines.patch b/debian/patches/pve/0014-PVE-qapi-modify-query-machines.patch index e40d67f..ab331f3 100644 --- a/debian/patches/pve/0014-PVE-qapi-modify-query-machines.patch +++ b/debian/patches/pve/0014-PVE-qapi-modify-query-machines.patch @@ -13,10 +13,10 @@ Signed-off-by: Dietmar Maurer 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/hw/core/machine-qmp-cmds.c b/hw/core/machine-qmp-cmds.c -index b98ff15089..24595f618c 100644 +index 3860a50c3b..40821e2317 100644 --- a/hw/core/machine-qmp-cmds.c +++ b/hw/core/machine-qmp-cmds.c -@@ -103,6 +103,12 @@ MachineInfoList *qmp_query_machines(Error **errp) +@@ -91,6 +91,12 @@ MachineInfoList *qmp_query_machines(Error **errp) info->numa_mem_supported = mc->numa_mem_supported; info->deprecated = !!mc->deprecation_reason; info->acpi = !!object_class_property_find(OBJECT_CLASS(mc), "acpi"); @@ -30,19 +30,19 @@ index b98ff15089..24595f618c 100644 info->default_cpu_type = g_strdup(mc->default_cpu_type); } diff --git a/qapi/machine.json b/qapi/machine.json -index 15f5f86683..c904280085 100644 +index 5c9a4d55f4..fbb61f18e4 100644 --- a/qapi/machine.json +++ b/qapi/machine.json -@@ -138,6 +138,8 @@ +@@ -139,6 +139,8 @@ # # @is-default: whether the machine is default # +# @is-current: whether this machine is currently used +# # @cpu-max: maximum number of CPUs supported by the machine type - # (since 1.5) + # (since 1.5) # -@@ -161,7 +163,7 @@ +@@ -163,7 +165,7 @@ ## { 'struct': 'MachineInfo', 'data': { 'name': 'str', '*alias': 'str', diff --git a/debian/patches/pve/0015-PVE-qapi-modify-spice-query.patch b/debian/patches/pve/0015-PVE-qapi-modify-spice-query.patch index df551da..26c6840 100644 --- a/debian/patches/pve/0015-PVE-qapi-modify-spice-query.patch +++ b/debian/patches/pve/0015-PVE-qapi-modify-spice-query.patch @@ -14,10 +14,10 @@ Signed-off-by: Fiona Ebner 2 files changed, 7 insertions(+) diff --git a/qapi/ui.json b/qapi/ui.json -index 98322342f7..316d4dc933 100644 +index 006616aa77..dfd1d3e36b 100644 --- a/qapi/ui.json +++ b/qapi/ui.json -@@ -310,11 +310,14 @@ +@@ -317,11 +317,14 @@ # # @channels: a list of @SpiceChannel for each active spice channel # diff --git a/debian/patches/pve/0016-PVE-add-IOChannel-implementation-for-savevm-async.patch b/debian/patches/pve/0016-PVE-add-IOChannel-implementation-for-savevm-async.patch index ce12543..40c9b32 100644 --- a/debian/patches/pve/0016-PVE-add-IOChannel-implementation-for-savevm-async.patch +++ b/debian/patches/pve/0016-PVE-add-IOChannel-implementation-for-savevm-async.patch @@ -269,14 +269,14 @@ index 0000000000..17ae2cb261 + +#endif /* QIO_CHANNEL_SAVEVM_ASYNC_H */ diff --git a/migration/meson.build b/migration/meson.build -index 0d1bb9f96e..8a142fc7a9 100644 +index 1ae28523a1..37ddcb5d60 100644 --- a/migration/meson.build +++ b/migration/meson.build -@@ -13,6 +13,7 @@ softmmu_ss.add(files( +@@ -13,6 +13,7 @@ system_ss.add(files( 'block-dirty-bitmap.c', 'channel.c', 'channel-block.c', + 'channel-savevm-async.c', - 'colo-failover.c', - 'colo.c', + 'dirtyrate.c', 'exec.c', + 'fd.c', diff --git a/debian/patches/pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch b/debian/patches/pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch index 0c98142..976f73f 100644 --- a/debian/patches/pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch +++ b/debian/patches/pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch @@ -35,20 +35,20 @@ Signed-off-by: Fiona Ebner include/migration/snapshot.h | 2 + include/monitor/hmp.h | 3 + migration/meson.build | 1 + - migration/savevm-async.c | 533 +++++++++++++++++++++++++++++++++++ + migration/savevm-async.c | 531 +++++++++++++++++++++++++++++++++++ monitor/hmp-cmds.c | 38 +++ qapi/migration.json | 34 +++ qapi/misc.json | 16 ++ qemu-options.hx | 12 + softmmu/vl.c | 10 + - 11 files changed, 679 insertions(+) + 11 files changed, 677 insertions(+) create mode 100644 migration/savevm-async.c diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx -index 47d63d26db..a166bff3d5 100644 +index f5b37eb74a..10fdd822e0 100644 --- a/hmp-commands-info.hx +++ b/hmp-commands-info.hx -@@ -540,6 +540,19 @@ SRST +@@ -525,6 +525,19 @@ SRST Show current migration parameters. ERST @@ -69,10 +69,10 @@ index 47d63d26db..a166bff3d5 100644 .name = "balloon", .args_type = "", diff --git a/hmp-commands.hx b/hmp-commands.hx -index bb85ee1d26..d9f9f42d11 100644 +index 2cbd0f77a0..e352f86872 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx -@@ -1846,3 +1846,20 @@ SRST +@@ -1865,3 +1865,20 @@ SRST List event channels in the guest ERST #endif @@ -105,7 +105,7 @@ index e72083b117..c846d37806 100644 + #endif diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h -index fdb69b7f9c..fdf6b45fb8 100644 +index 13f9a2dedb..7a7def7530 100644 --- a/include/monitor/hmp.h +++ b/include/monitor/hmp.h @@ -28,6 +28,7 @@ void hmp_info_status(Monitor *mon, const QDict *qdict); @@ -126,11 +126,11 @@ index fdb69b7f9c..fdf6b45fb8 100644 void coroutine_fn hmp_screendump(Monitor *mon, const QDict *qdict); void hmp_chardev_add(Monitor *mon, const QDict *qdict); diff --git a/migration/meson.build b/migration/meson.build -index 8a142fc7a9..a7824b5266 100644 +index 37ddcb5d60..07f6057acc 100644 --- a/migration/meson.build +++ b/migration/meson.build -@@ -25,6 +25,7 @@ softmmu_ss.add(files( - 'multifd-zlib.c', +@@ -26,6 +26,7 @@ system_ss.add(files( + 'options.c', 'postcopy-ram.c', 'savevm.c', + 'savevm-async.c', @@ -139,13 +139,15 @@ index 8a142fc7a9..a7824b5266 100644 'threadinfo.c', diff --git a/migration/savevm-async.c b/migration/savevm-async.c new file mode 100644 -index 0000000000..aa2017d496 +index 0000000000..e9fc18fb10 --- /dev/null +++ b/migration/savevm-async.c -@@ -0,0 +1,533 @@ +@@ -0,0 +1,531 @@ +#include "qemu/osdep.h" +#include "migration/channel-savevm-async.h" +#include "migration/migration.h" ++#include "migration/migration-stats.h" ++#include "migration/options.h" +#include "migration/savevm.h" +#include "migration/snapshot.h" +#include "migration/global_state.h" @@ -420,11 +422,7 @@ index 0000000000..aa2017d496 + DPRINTF("savevm iterate pending size %lu ret %d\n", pending_size, ret); + } else { + qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL); -+ ret = global_state_store(); -+ if (ret) { -+ save_snapshot_error("global_state_store error %d", ret); -+ break; -+ } ++ global_state_store(); + + DPRINTF("savevm iterate complete\n"); + break; @@ -485,7 +483,7 @@ index 0000000000..aa2017d496 + return; + } + -+ if (migrate_use_block()) { ++ if (migrate_block()) { + error_set(errp, ERROR_CLASS_GENERIC_ERROR, + "Block migration and snapshots are incompatible"); + return; @@ -538,7 +536,7 @@ index 0000000000..aa2017d496 + * here (blocking main thread, from QMP) to avoid race conditions. + */ + migrate_init(ms); -+ memset(&ram_counters, 0, sizeof(ram_counters)); ++ memset(&mig_stats, 0, sizeof(mig_stats)); + memset(&compression_counters, 0, sizeof(compression_counters)); + ms->to_dst_file = snap_state.file; + @@ -730,12 +728,12 @@ index 6c559b48c8..91be698308 100644 + } +} diff --git a/qapi/migration.json b/qapi/migration.json -index c84fa10e86..1702b92553 100644 +index 8843e74b59..aca0ca1ac1 100644 --- a/qapi/migration.json +++ b/qapi/migration.json -@@ -261,6 +261,40 @@ - '*compression': 'CompressionStats', - '*socket-address': ['SocketAddress'] } } +@@ -291,6 +291,40 @@ + '*dirty-limit-throttle-time-per-round': 'uint64', + '*dirty-limit-ring-full-time': 'uint64'} } +## +# @SaveVMInfo: @@ -775,10 +773,10 @@ index c84fa10e86..1702b92553 100644 # @query-migrate: # diff --git a/qapi/misc.json b/qapi/misc.json -index 6ddd16ea28..e5681ae8a2 100644 +index cda2effa81..94a58bb0bf 100644 --- a/qapi/misc.json +++ b/qapi/misc.json -@@ -469,6 +469,22 @@ +@@ -456,6 +456,22 @@ ## { 'command': 'query-fdsets', 'returns': ['FdsetInfo'] } @@ -802,10 +800,10 @@ index 6ddd16ea28..e5681ae8a2 100644 # @CommandLineParameterType: # diff --git a/qemu-options.hx b/qemu-options.hx -index fdddfab6ff..fdd551c2bb 100644 +index b56f6b2fb2..c8c78c92d4 100644 --- a/qemu-options.hx +++ b/qemu-options.hx -@@ -4398,6 +4398,18 @@ SRST +@@ -4479,6 +4479,18 @@ SRST Start right away with a saved state (``loadvm`` in monitor) ERST @@ -825,7 +823,7 @@ index fdddfab6ff..fdd551c2bb 100644 DEF("daemonize", 0, QEMU_OPTION_daemonize, \ "-daemonize daemonize QEMU after initializing\n", QEMU_ARCH_ALL) diff --git a/softmmu/vl.c b/softmmu/vl.c -index ea20b23e4c..0eabc71b68 100644 +index b0b96f67fa..f3251de3e7 100644 --- a/softmmu/vl.c +++ b/softmmu/vl.c @@ -164,6 +164,7 @@ static const char *accelerators; @@ -836,7 +834,7 @@ index ea20b23e4c..0eabc71b68 100644 static QTAILQ_HEAD(, ObjectOption) object_opts = QTAILQ_HEAD_INITIALIZER(object_opts); static QTAILQ_HEAD(, DeviceOption) device_opts = QTAILQ_HEAD_INITIALIZER(device_opts); static int display_remote; -@@ -2612,6 +2613,12 @@ void qmp_x_exit_preconfig(Error **errp) +@@ -2643,6 +2644,12 @@ void qmp_x_exit_preconfig(Error **errp) if (loadvm) { load_snapshot(loadvm, NULL, false, NULL, &error_fatal); @@ -849,7 +847,7 @@ index ea20b23e4c..0eabc71b68 100644 } if (replay_mode != REPLAY_MODE_NONE) { replay_vmstate_init(); -@@ -3159,6 +3166,9 @@ void qemu_init(int argc, char **argv) +@@ -3190,6 +3197,9 @@ void qemu_init(int argc, char **argv) case QEMU_OPTION_loadvm: loadvm = optarg; break; diff --git a/debian/patches/pve/0018-PVE-add-optional-buffer-size-to-QEMUFile.patch b/debian/patches/pve/0018-PVE-add-optional-buffer-size-to-QEMUFile.patch index f5f6f7b..c946137 100644 --- a/debian/patches/pve/0018-PVE-add-optional-buffer-size-to-QEMUFile.patch +++ b/debian/patches/pve/0018-PVE-add-optional-buffer-size-to-QEMUFile.patch @@ -19,11 +19,11 @@ Signed-off-by: Fiona Ebner 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/migration/qemu-file.c b/migration/qemu-file.c -index 102ab3b439..5ced17aba4 100644 +index 19c33c9985..e9ffff0f0a 100644 --- a/migration/qemu-file.c +++ b/migration/qemu-file.c -@@ -31,8 +31,8 @@ - #include "trace.h" +@@ -33,8 +33,8 @@ + #include "options.h" #include "qapi/error.h" -#define IO_BUF_SIZE 32768 @@ -33,7 +33,7 @@ index 102ab3b439..5ced17aba4 100644 struct QEMUFile { const QEMUFileHooks *hooks; -@@ -55,7 +55,8 @@ struct QEMUFile { +@@ -46,7 +46,8 @@ struct QEMUFile { int buf_index; int buf_size; /* 0 when writing */ @@ -43,8 +43,8 @@ index 102ab3b439..5ced17aba4 100644 DECLARE_BITMAP(may_free, MAX_IOV_SIZE); struct iovec iov[MAX_IOV_SIZE]; -@@ -127,7 +128,9 @@ bool qemu_file_mode_is_not_valid(const char *mode) - return false; +@@ -100,7 +101,9 @@ int qemu_file_shutdown(QEMUFile *f) + return 0; } -static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable) @@ -54,7 +54,7 @@ index 102ab3b439..5ced17aba4 100644 { QEMUFile *f; -@@ -136,6 +139,8 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable) +@@ -109,6 +112,8 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable) object_ref(ioc); f->ioc = ioc; f->is_writable = is_writable; @@ -63,7 +63,7 @@ index 102ab3b439..5ced17aba4 100644 return f; } -@@ -146,17 +151,27 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable) +@@ -119,17 +124,27 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable) */ QEMUFile *qemu_file_get_return_path(QEMUFile *f) { @@ -94,7 +94,7 @@ index 102ab3b439..5ced17aba4 100644 } void qemu_file_set_hooks(QEMUFile *f, const QEMUFileHooks *hooks) -@@ -414,7 +429,7 @@ static ssize_t qemu_fill_buffer(QEMUFile *f) +@@ -375,7 +390,7 @@ static ssize_t coroutine_mixed_fn qemu_fill_buffer(QEMUFile *f) do { len = qio_channel_read(f->ioc, (char *)f->buf + pending, @@ -103,7 +103,7 @@ index 102ab3b439..5ced17aba4 100644 &local_error); if (len == QIO_CHANNEL_ERR_BLOCK) { if (qemu_in_coroutine()) { -@@ -464,6 +479,8 @@ int qemu_fclose(QEMUFile *f) +@@ -425,6 +440,8 @@ int qemu_fclose(QEMUFile *f) } g_clear_pointer(&f->ioc, object_unref); @@ -112,7 +112,7 @@ index 102ab3b439..5ced17aba4 100644 /* If any error was spotted before closing, we should report it * instead of the close() return value. */ -@@ -518,7 +535,7 @@ static void add_buf_to_iovec(QEMUFile *f, size_t len) +@@ -479,7 +496,7 @@ static void add_buf_to_iovec(QEMUFile *f, size_t len) { if (!add_to_iovec(f, f->buf + f->buf_index, len, false)) { f->buf_index += len; @@ -121,7 +121,7 @@ index 102ab3b439..5ced17aba4 100644 qemu_fflush(f); } } -@@ -544,7 +561,7 @@ void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, size_t size) +@@ -504,7 +521,7 @@ void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, size_t size) } while (size > 0) { @@ -130,7 +130,7 @@ index 102ab3b439..5ced17aba4 100644 if (l > size) { l = size; } -@@ -591,8 +608,8 @@ size_t qemu_peek_buffer(QEMUFile *f, uint8_t **buf, size_t size, size_t offset) +@@ -549,8 +566,8 @@ size_t coroutine_mixed_fn qemu_peek_buffer(QEMUFile *f, uint8_t **buf, size_t si size_t index; assert(!qemu_file_is_writable(f)); @@ -141,7 +141,7 @@ index 102ab3b439..5ced17aba4 100644 /* The 1st byte to read from */ index = f->buf_index + offset; -@@ -642,7 +659,7 @@ size_t qemu_get_buffer(QEMUFile *f, uint8_t *buf, size_t size) +@@ -600,7 +617,7 @@ size_t coroutine_mixed_fn qemu_get_buffer(QEMUFile *f, uint8_t *buf, size_t size size_t res; uint8_t *src; @@ -150,16 +150,16 @@ index 102ab3b439..5ced17aba4 100644 if (res == 0) { return done; } -@@ -676,7 +693,7 @@ size_t qemu_get_buffer(QEMUFile *f, uint8_t *buf, size_t size) +@@ -634,7 +651,7 @@ size_t coroutine_mixed_fn qemu_get_buffer(QEMUFile *f, uint8_t *buf, size_t size */ - size_t qemu_get_buffer_in_place(QEMUFile *f, uint8_t **buf, size_t size) + size_t coroutine_mixed_fn qemu_get_buffer_in_place(QEMUFile *f, uint8_t **buf, size_t size) { - if (size < IO_BUF_SIZE) { + if (size < f->buf_allocated_size) { size_t res; uint8_t *src = NULL; -@@ -701,7 +718,7 @@ int qemu_peek_byte(QEMUFile *f, int offset) +@@ -659,7 +676,7 @@ int coroutine_mixed_fn qemu_peek_byte(QEMUFile *f, int offset) int index = f->buf_index + offset; assert(!qemu_file_is_writable(f)); @@ -168,7 +168,7 @@ index 102ab3b439..5ced17aba4 100644 if (index >= f->buf_size) { qemu_fill_buffer(f); -@@ -853,7 +870,7 @@ static int qemu_compress_data(z_stream *stream, uint8_t *dest, size_t dest_len, +@@ -777,7 +794,7 @@ static int qemu_compress_data(z_stream *stream, uint8_t *dest, size_t dest_len, ssize_t qemu_put_compression_data(QEMUFile *f, z_stream *stream, const uint8_t *p, size_t size) { @@ -178,7 +178,7 @@ index 102ab3b439..5ced17aba4 100644 if (blen < compressBound(size)) { return -1; diff --git a/migration/qemu-file.h b/migration/qemu-file.h -index 9d0155a2a1..cc06240e8d 100644 +index 47015f5201..1312b7c903 100644 --- a/migration/qemu-file.h +++ b/migration/qemu-file.h @@ -63,7 +63,9 @@ typedef struct QEMUFileHooks { @@ -192,10 +192,10 @@ index 9d0155a2a1..cc06240e8d 100644 int qemu_fclose(QEMUFile *f); diff --git a/migration/savevm-async.c b/migration/savevm-async.c -index aa2017d496..b97f2c4f14 100644 +index e9fc18fb10..80624fada8 100644 --- a/migration/savevm-async.c +++ b/migration/savevm-async.c -@@ -380,7 +380,7 @@ void qmp_savevm_start(const char *statefile, Error **errp) +@@ -378,7 +378,7 @@ void qmp_savevm_start(const char *statefile, Error **errp) QIOChannel *ioc = QIO_CHANNEL(qio_channel_savevm_async_new(snap_state.target, &snap_state.bs_pos)); @@ -204,7 +204,7 @@ index aa2017d496..b97f2c4f14 100644 if (!snap_state.file) { error_set(errp, ERROR_CLASS_GENERIC_ERROR, "failed to open '%s'", statefile); -@@ -498,7 +498,8 @@ int load_snapshot_from_blockdev(const char *filename, Error **errp) +@@ -496,7 +496,8 @@ int load_snapshot_from_blockdev(const char *filename, Error **errp) blk_op_block_all(be, blocker); /* restore the VM state */ diff --git a/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch b/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch index 39264ee..1cb8166 100644 --- a/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch +++ b/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch @@ -13,17 +13,17 @@ Signed-off-by: Fiona Ebner create mode 100644 block/zeroinit.c diff --git a/block/meson.build b/block/meson.build -index 382bec0e7d..253fe49fa2 100644 +index 529fc172c6..1833c71ce9 100644 --- a/block/meson.build +++ b/block/meson.build -@@ -44,6 +44,7 @@ block_ss.add(files( - 'vmdk.c', - 'vpc.c', +@@ -40,6 +40,7 @@ block_ss.add(files( + 'throttle-groups.c', + 'throttle.c', 'write-threshold.c', + 'zeroinit.c', ), zstd, zlib, gnutls) - softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) + system_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) diff --git a/block/zeroinit.c b/block/zeroinit.c new file mode 100644 index 0000000000..1257342724 diff --git a/debian/patches/pve/0020-PVE-Add-dummy-id-command-line-parameter.patch b/debian/patches/pve/0020-PVE-Add-dummy-id-command-line-parameter.patch index 2c44e34..5327c11 100644 --- a/debian/patches/pve/0020-PVE-Add-dummy-id-command-line-parameter.patch +++ b/debian/patches/pve/0020-PVE-Add-dummy-id-command-line-parameter.patch @@ -14,10 +14,10 @@ Signed-off-by: Thomas Lamprecht 2 files changed, 11 insertions(+) diff --git a/qemu-options.hx b/qemu-options.hx -index fdd551c2bb..4eb43b7bc5 100644 +index c8c78c92d4..20ca2cdba7 100644 --- a/qemu-options.hx +++ b/qemu-options.hx -@@ -1162,6 +1162,9 @@ legacy PC, they are not recommended for modern configurations. +@@ -1197,6 +1197,9 @@ legacy PC, they are not recommended for modern configurations. ERST @@ -28,10 +28,10 @@ index fdd551c2bb..4eb43b7bc5 100644 "-fda/-fdb file use 'file' as floppy disk 0/1 image\n", QEMU_ARCH_ALL) DEF("fdb", HAS_ARG, QEMU_OPTION_fdb, "", QEMU_ARCH_ALL) diff --git a/softmmu/vl.c b/softmmu/vl.c -index 0eabc71b68..323f6a23d4 100644 +index f3251de3e7..1b63ffd33d 100644 --- a/softmmu/vl.c +++ b/softmmu/vl.c -@@ -2648,6 +2648,7 @@ void qemu_init(int argc, char **argv) +@@ -2679,6 +2679,7 @@ void qemu_init(int argc, char **argv) MachineClass *machine_class; bool userconfig = true; FILE *vmstate_dump_file = NULL; @@ -39,7 +39,7 @@ index 0eabc71b68..323f6a23d4 100644 qemu_add_opts(&qemu_drive_opts); qemu_add_drive_opts(&qemu_legacy_drive_opts); -@@ -3271,6 +3272,13 @@ void qemu_init(int argc, char **argv) +@@ -3302,6 +3303,13 @@ void qemu_init(int argc, char **argv) machine_parse_property_opt(qemu_find_opts("smp-opts"), "smp", optarg); break; diff --git a/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch b/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch index e9e9f12..904f76e 100644 --- a/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch +++ b/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch @@ -13,10 +13,10 @@ Signed-off-by: Thomas Lamprecht 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/block/file-posix.c b/block/file-posix.c -index 9681bd0434..044890822d 100644 +index 26aa0172b4..504c6ed316 100644 --- a/block/file-posix.c +++ b/block/file-posix.c -@@ -2483,6 +2483,7 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) +@@ -2872,6 +2872,7 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) int fd; uint64_t perm, shared; int result = 0; @@ -24,7 +24,7 @@ index 9681bd0434..044890822d 100644 /* Validate options and set default values */ assert(options->driver == BLOCKDEV_DRIVER_FILE); -@@ -2523,19 +2524,22 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) +@@ -2912,19 +2913,22 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) perm = BLK_PERM_WRITE | BLK_PERM_RESIZE; shared = BLK_PERM_ALL & ~BLK_PERM_RESIZE; @@ -59,7 +59,7 @@ index 9681bd0434..044890822d 100644 } /* Clear the file by truncating it to 0 */ -@@ -2589,13 +2593,15 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) +@@ -2978,13 +2982,15 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) } out_unlock: @@ -82,7 +82,7 @@ index 9681bd0434..044890822d 100644 } out_close: -@@ -2619,6 +2625,7 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, +@@ -3008,6 +3014,7 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, PreallocMode prealloc; char *buf = NULL; Error *local_err = NULL; @@ -90,7 +90,7 @@ index 9681bd0434..044890822d 100644 /* Skip file: protocol prefix */ strstart(filename, "file:", &filename); -@@ -2641,6 +2648,18 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, +@@ -3030,6 +3037,18 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, return -EINVAL; } @@ -109,7 +109,7 @@ index 9681bd0434..044890822d 100644 options = (BlockdevCreateOptions) { .driver = BLOCKDEV_DRIVER_FILE, .u.file = { -@@ -2652,6 +2671,8 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, +@@ -3041,6 +3060,8 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, .nocow = nocow, .has_extent_size_hint = has_extent_size_hint, .extent_size_hint = extent_size_hint, @@ -119,10 +119,10 @@ index 9681bd0434..044890822d 100644 }; return raw_co_create(&options, errp); diff --git a/qapi/block-core.json b/qapi/block-core.json -index 3c945c1f93..542add004b 100644 +index 903392cb8f..125aa89858 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json -@@ -4740,7 +4740,8 @@ +@@ -4876,7 +4876,8 @@ 'size': 'size', '*preallocation': 'PreallocMode', '*nocow': 'bool', diff --git a/debian/patches/pve/0023-PVE-monitor-disable-oob-capability.patch b/debian/patches/pve/0023-PVE-monitor-disable-oob-capability.patch index 9abde33..e2f16af 100644 --- a/debian/patches/pve/0023-PVE-monitor-disable-oob-capability.patch +++ b/debian/patches/pve/0023-PVE-monitor-disable-oob-capability.patch @@ -18,10 +18,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monitor/qmp.c b/monitor/qmp.c -index 6b8cfcf6d8..3ec67e32d3 100644 +index c15bf1e1fc..04fe25c62c 100644 --- a/monitor/qmp.c +++ b/monitor/qmp.c -@@ -519,8 +519,7 @@ void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp) +@@ -553,8 +553,7 @@ void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp) qemu_chr_fe_set_echo(&mon->common.chr, true); /* Note: we run QMP monitor in I/O thread when @chr supports that */ diff --git a/debian/patches/pve/0024-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch b/debian/patches/pve/0024-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch index 79008ee..277fa3f 100644 --- a/debian/patches/pve/0024-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch +++ b/debian/patches/pve/0024-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch @@ -26,10 +26,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hw/core/machine.c b/hw/core/machine.c -index 2f6ccf5623..a5927e92f1 100644 +index f0d35c6401..1427983543 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c -@@ -142,7 +142,8 @@ GlobalProperty hw_compat_4_0[] = { +@@ -148,7 +148,8 @@ GlobalProperty hw_compat_4_0[] = { { "virtio-vga", "edid", "false" }, { "virtio-gpu-device", "edid", "false" }, { "virtio-device", "use-started", "false" }, diff --git a/debian/patches/pve/0025-PVE-Allow-version-code-in-machine-type.patch b/debian/patches/pve/0025-PVE-Allow-version-code-in-machine-type.patch index d88d1d0..5ec00c1 100644 --- a/debian/patches/pve/0025-PVE-Allow-version-code-in-machine-type.patch +++ b/debian/patches/pve/0025-PVE-Allow-version-code-in-machine-type.patch @@ -21,10 +21,10 @@ Signed-off-by: Fiona Ebner 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/hw/core/machine-qmp-cmds.c b/hw/core/machine-qmp-cmds.c -index 24595f618c..ee9cb0cd04 100644 +index 40821e2317..ee93ddd69a 100644 --- a/hw/core/machine-qmp-cmds.c +++ b/hw/core/machine-qmp-cmds.c -@@ -107,6 +107,11 @@ MachineInfoList *qmp_query_machines(Error **errp) +@@ -95,6 +95,11 @@ MachineInfoList *qmp_query_machines(Error **errp) if (strcmp(mc->name, MACHINE_GET_CLASS(current_machine)->name) == 0) { info->has_is_current = true; info->is_current = true; @@ -37,10 +37,10 @@ index 24595f618c..ee9cb0cd04 100644 if (mc->default_cpu_type) { diff --git a/include/hw/boards.h b/include/hw/boards.h -index 6fbbfd56c8..61a526e97d 100644 +index ed83360198..f8b88cd86a 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h -@@ -232,6 +232,8 @@ struct MachineClass { +@@ -235,6 +235,8 @@ struct MachineClass { const char *desc; const char *deprecation_reason; @@ -50,10 +50,10 @@ index 6fbbfd56c8..61a526e97d 100644 void (*reset)(MachineState *state, ShutdownCause reason); void (*wakeup)(MachineState *state); diff --git a/qapi/machine.json b/qapi/machine.json -index c904280085..47f3facdb2 100644 +index fbb61f18e4..7da3c519ba 100644 --- a/qapi/machine.json +++ b/qapi/machine.json -@@ -159,6 +159,8 @@ +@@ -161,6 +161,8 @@ # # @acpi: machine type supports ACPI (since 8.0) # @@ -62,7 +62,7 @@ index c904280085..47f3facdb2 100644 # Since: 1.2 ## { 'struct': 'MachineInfo', -@@ -166,7 +168,7 @@ +@@ -168,7 +170,7 @@ '*is-default': 'bool', '*is-current': 'bool', 'cpu-max': 'int', 'hotpluggable-cpus': 'bool', 'numa-mem-supported': 'bool', 'deprecated': 'bool', '*default-cpu-type': 'str', @@ -72,10 +72,10 @@ index c904280085..47f3facdb2 100644 ## # @query-machines: diff --git a/softmmu/vl.c b/softmmu/vl.c -index 323f6a23d4..25abdc9da7 100644 +index 1b63ffd33d..20ba2c5c87 100644 --- a/softmmu/vl.c +++ b/softmmu/vl.c -@@ -1578,6 +1578,7 @@ static const QEMUOption *lookup_opt(int argc, char **argv, +@@ -1597,6 +1597,7 @@ static const QEMUOption *lookup_opt(int argc, char **argv, static MachineClass *select_machine(QDict *qdict, Error **errp) { const char *optarg = qdict_get_try_str(qdict, "type"); @@ -83,7 +83,7 @@ index 323f6a23d4..25abdc9da7 100644 GSList *machines = object_class_get_list(TYPE_MACHINE, false); MachineClass *machine_class; Error *local_err = NULL; -@@ -1595,6 +1596,11 @@ static MachineClass *select_machine(QDict *qdict, Error **errp) +@@ -1614,6 +1615,11 @@ static MachineClass *select_machine(QDict *qdict, Error **errp) } } @@ -95,7 +95,7 @@ index 323f6a23d4..25abdc9da7 100644 g_slist_free(machines); if (local_err) { error_append_hint(&local_err, "Use -machine help to list supported machines\n"); -@@ -3213,12 +3219,31 @@ void qemu_init(int argc, char **argv) +@@ -3244,12 +3250,31 @@ void qemu_init(int argc, char **argv) case QEMU_OPTION_machine: { bool help; diff --git a/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch b/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch index 857d22d..3c3a150 100644 --- a/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch +++ b/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch @@ -26,23 +26,23 @@ Signed-off-by: Fiona Ebner create mode 100644 vma.h diff --git a/block/meson.build b/block/meson.build -index 253fe49fa2..744b698a82 100644 +index 1833c71ce9..59b71ba9f3 100644 --- a/block/meson.build +++ b/block/meson.build -@@ -47,6 +47,8 @@ block_ss.add(files( +@@ -43,6 +43,8 @@ block_ss.add(files( 'zeroinit.c', ), zstd, zlib, gnutls) +block_ss.add(files('../vma-writer.c'), libuuid) + - softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) - softmmu_ss.add(files('block-ram-registrar.c')) + system_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) + system_ss.add(files('block-ram-registrar.c')) diff --git a/meson.build b/meson.build -index 30447cfaef..38a4e2bcef 100644 +index 912c7e7847..95cd15f698 100644 --- a/meson.build +++ b/meson.build -@@ -1527,6 +1527,8 @@ keyutils = dependency('libkeyutils', required: false, +@@ -1777,6 +1777,8 @@ endif has_gettid = cc.has_function('gettid') @@ -51,7 +51,7 @@ index 30447cfaef..38a4e2bcef 100644 # libselinux selinux = dependency('libselinux', required: get_option('selinux'), -@@ -3650,6 +3652,9 @@ if have_tools +@@ -3907,6 +3909,9 @@ if have_tools dependencies: [blockdev, qemuutil, gnutls, selinux], install: true) @@ -936,7 +936,7 @@ index 0000000000..81a891c6b1 + diff --git a/vma-writer.c b/vma-writer.c new file mode 100644 -index 0000000000..ac7da237d0 +index 0000000000..6b7af81cae --- /dev/null +++ b/vma-writer.c @@ -0,0 +1,793 @@ @@ -1146,10 +1146,10 @@ index 0000000000..ac7da237d0 +{ + assert(qemu_in_coroutine()); + AioContext *ctx = qemu_get_current_aio_context(); -+ aio_set_fd_handler(ctx, fd, false, NULL, (IOHandler *)qemu_coroutine_enter, -+ NULL, NULL, qemu_coroutine_self()); ++ aio_set_fd_handler(ctx, fd, NULL, (IOHandler *)qemu_coroutine_enter, NULL, ++ NULL, qemu_coroutine_self()); + qemu_coroutine_yield(); -+ aio_set_fd_handler(ctx, fd, false, NULL, NULL, NULL, NULL, NULL); ++ aio_set_fd_handler(ctx, fd, NULL, NULL, NULL, NULL, NULL); +} + +static ssize_t coroutine_fn diff --git a/debian/patches/pve/0028-PVE-Backup-add-backup-dump-block-driver.patch b/debian/patches/pve/0028-PVE-Backup-add-backup-dump-block-driver.patch index cc4a679..0d7fd96 100644 --- a/debian/patches/pve/0028-PVE-Backup-add-backup-dump-block-driver.patch +++ b/debian/patches/pve/0028-PVE-Backup-add-backup-dump-block-driver.patch @@ -243,7 +243,7 @@ index 39410dcf8d..af87fa6aa9 100644 if (perf->max_chunk && perf->max_chunk < cluster_size) { error_setg(errp, "Required max-chunk (%" PRIi64 ") is less than backup " diff --git a/block/meson.build b/block/meson.build -index 744b698a82..f580f95395 100644 +index 59b71ba9f3..6fde9f7dcd 100644 --- a/block/meson.build +++ b/block/meson.build @@ -4,6 +4,7 @@ block_ss.add(files( @@ -255,7 +255,7 @@ index 744b698a82..f580f95395 100644 'blkdebug.c', 'blklogwrites.c', diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h -index f01bb8b617..d7ffd1826e 100644 +index 74195c3004..0f2e1817ad 100644 --- a/include/block/block_int-common.h +++ b/include/block/block_int-common.h @@ -26,6 +26,7 @@ diff --git a/debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch b/debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch index 7a4881b..4c26aba 100644 --- a/debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch +++ b/debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch @@ -84,30 +84,68 @@ Signed-off-by: Wolfgang Bumiller create jobs in a drained section] Signed-off-by: Fiona Ebner --- - block/meson.build | 5 + - block/monitor/block-hmp-cmds.c | 39 ++ - blockdev.c | 1 + - hmp-commands-info.hx | 14 + - hmp-commands.hx | 29 + - include/monitor/hmp.h | 3 + - meson.build | 1 + - monitor/hmp-cmds.c | 72 +++ - proxmox-backup-client.c | 146 +++++ - proxmox-backup-client.h | 60 ++ - pve-backup.c | 1067 ++++++++++++++++++++++++++++++++ - qapi/block-core.json | 229 +++++++ - qapi/common.json | 13 + - qapi/machine.json | 15 +- - 14 files changed, 1681 insertions(+), 13 deletions(-) + block/backup-dump.c | 10 +- + block/meson.build | 5 + + block/monitor/block-hmp-cmds.c | 39 ++ + blockdev.c | 1 + + hmp-commands-info.hx | 14 + + hmp-commands.hx | 29 + + include/block/block_int-common.h | 2 +- + include/monitor/hmp.h | 3 + + meson.build | 1 + + monitor/hmp-cmds.c | 72 ++ + proxmox-backup-client.c | 146 ++++ + proxmox-backup-client.h | 60 ++ + pve-backup.c | 1067 ++++++++++++++++++++++++++++++ + qapi/block-core.json | 229 +++++++ + qapi/common.json | 14 + + qapi/machine.json | 16 +- + 16 files changed, 1690 insertions(+), 18 deletions(-) create mode 100644 proxmox-backup-client.c create mode 100644 proxmox-backup-client.h create mode 100644 pve-backup.c +diff --git a/block/backup-dump.c b/block/backup-dump.c +index 232a094426..e46abf1070 100644 +--- a/block/backup-dump.c ++++ b/block/backup-dump.c +@@ -9,6 +9,8 @@ + */ + + #include "qemu/osdep.h" ++ ++#include "qapi/qmp/qdict.h" + #include "qom/object_interfaces.h" + #include "block/block_int.h" + +@@ -141,7 +143,7 @@ static void bdrv_backup_dump_init(void) + block_init(bdrv_backup_dump_init); + + +-BlockDriverState *bdrv_backup_dump_create( ++BlockDriverState *coroutine_fn bdrv_co_backup_dump_create( + int dump_cb_block_size, + uint64_t byte_size, + BackupDumpFunc *dump_cb, +@@ -149,9 +151,11 @@ BlockDriverState *bdrv_backup_dump_create( + Error **errp) + { + BDRVBackupDumpState *state; +- BlockDriverState *bs = bdrv_new_open_driver( +- &bdrv_backup_dump_drive, NULL, BDRV_O_RDWR, errp); + ++ QDict *options = qdict_new(); ++ qdict_put_str(options, "driver", "backup-dump-drive"); ++ ++ BlockDriverState *bs = bdrv_co_open(NULL, NULL, options, BDRV_O_RDWR, errp); + if (!bs) { + return NULL; + } diff --git a/block/meson.build b/block/meson.build -index f580f95395..5bcebb934b 100644 +index 6fde9f7dcd..6d468f89e5 100644 --- a/block/meson.build +++ b/block/meson.build -@@ -49,6 +49,11 @@ block_ss.add(files( +@@ -45,6 +45,11 @@ block_ss.add(files( ), zstd, zlib, gnutls) block_ss.add(files('../vma-writer.c'), libuuid) @@ -117,8 +155,8 @@ index f580f95395..5bcebb934b 100644 +), libproxmox_backup_qemu) + - softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) - softmmu_ss.add(files('block-ram-registrar.c')) + system_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) + system_ss.add(files('block-ram-registrar.c')) diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c index ca2599de44..6efe28cef5 100644 --- a/block/monitor/block-hmp-cmds.c @@ -167,7 +205,7 @@ index ca2599de44..6efe28cef5 100644 + hmp_handle_error(mon, error); +} diff --git a/blockdev.c b/blockdev.c -index bdae211a54..315a27fc09 100644 +index 060d86a65f..79c3575612 100644 --- a/blockdev.c +++ b/blockdev.c @@ -37,6 +37,7 @@ @@ -179,10 +217,10 @@ index bdae211a54..315a27fc09 100644 #include "monitor/monitor.h" #include "qemu/error-report.h" diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx -index a166bff3d5..4b75966c2e 100644 +index 10fdd822e0..15937793c1 100644 --- a/hmp-commands-info.hx +++ b/hmp-commands-info.hx -@@ -486,6 +486,20 @@ SRST +@@ -471,6 +471,20 @@ SRST Show the current VM UUID. ERST @@ -204,7 +242,7 @@ index a166bff3d5..4b75966c2e 100644 { .name = "usernet", diff --git a/hmp-commands.hx b/hmp-commands.hx -index d9f9f42d11..ddb9678dc3 100644 +index e352f86872..0c8b6725fb 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -101,6 +101,35 @@ ERST @@ -243,8 +281,21 @@ index d9f9f42d11..ddb9678dc3 100644 ERST { +diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h +index 0f2e1817ad..0a0339eee4 100644 +--- a/include/block/block_int-common.h ++++ b/include/block/block_int-common.h +@@ -63,7 +63,7 @@ + + typedef int BackupDumpFunc(void *opaque, uint64_t offset, uint64_t bytes, const void *buf); + +-BlockDriverState *bdrv_backup_dump_create( ++BlockDriverState *coroutine_fn bdrv_co_backup_dump_create( + int dump_cb_block_size, + uint64_t byte_size, + BackupDumpFunc *dump_cb, diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h -index fdf6b45fb8..e01b2201d8 100644 +index 7a7def7530..cba7afe70c 100644 --- a/include/monitor/hmp.h +++ b/include/monitor/hmp.h @@ -32,6 +32,7 @@ void hmp_info_savevm(Monitor *mon, const QDict *qdict); @@ -265,10 +316,10 @@ index fdf6b45fb8..e01b2201d8 100644 void hmp_device_add(Monitor *mon, const QDict *qdict); void hmp_device_del(Monitor *mon, const QDict *qdict); diff --git a/meson.build b/meson.build -index 38a4e2bcef..443b3238f9 100644 +index 95cd15f698..fa79372745 100644 --- a/meson.build +++ b/meson.build -@@ -1528,6 +1528,7 @@ keyutils = dependency('libkeyutils', required: false, +@@ -1778,6 +1778,7 @@ endif has_gettid = cc.has_function('gettid') libuuid = cc.find_library('uuid', required: true) @@ -586,7 +637,7 @@ index 0000000000..8cbf645b2c +#endif /* PROXMOX_BACKUP_CLIENT_H */ diff --git a/pve-backup.c b/pve-backup.c new file mode 100644 -index 0000000000..21441b2f97 +index 0000000000..d84d807654 --- /dev/null +++ b/pve-backup.c @@ -0,0 +1,1067 @@ @@ -1394,7 +1445,7 @@ index 0000000000..21441b2f97 + goto err_mutex; + } + -+ if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, errp))) { ++ if (!(di->target = bdrv_co_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, errp))) { + goto err_mutex; + } + @@ -1422,7 +1473,7 @@ index 0000000000..21441b2f97 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; + l = g_list_next(l); + -+ if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, errp))) { ++ if (!(di->target = bdrv_co_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, errp))) { + goto err_mutex; + } + @@ -1658,10 +1709,10 @@ index 0000000000..21441b2f97 + return ret; +} diff --git a/qapi/block-core.json b/qapi/block-core.json -index 542add004b..985859ddee 100644 +index 125aa89858..331c8336d1 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json -@@ -835,6 +835,235 @@ +@@ -839,6 +839,235 @@ { 'command': 'query-block', 'returns': ['BlockInfo'], 'allow-preconfig': true } @@ -1898,10 +1949,10 @@ index 542add004b..985859ddee 100644 # @BlockDeviceTimedStats: # diff --git a/qapi/common.json b/qapi/common.json -index 356db3f670..aae8a3b682 100644 +index 6fed9cde1a..630a2a8f9a 100644 --- a/qapi/common.json +++ b/qapi/common.json -@@ -206,3 +206,16 @@ +@@ -207,3 +207,17 @@ ## { 'struct': 'HumanReadableText', 'data': { 'human-readable-text': 'str' } } @@ -1915,11 +1966,12 @@ index 356db3f670..aae8a3b682 100644 +# +# Since: 0.14.0 +# -+# Notes: If no UUID was specified for the guest, a null UUID is returned. ++# Notes: If no UUID was specified for the guest, a null UUID is ++# returned. +## +{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} } diff --git a/qapi/machine.json b/qapi/machine.json -index 47f3facdb2..46760978ae 100644 +index 7da3c519ba..888457f810 100644 --- a/qapi/machine.json +++ b/qapi/machine.json @@ -4,6 +4,8 @@ @@ -1931,7 +1983,7 @@ index 47f3facdb2..46760978ae 100644 ## # = Machines ## -@@ -228,19 +230,6 @@ +@@ -230,20 +232,6 @@ ## { 'command': 'query-target', 'returns': 'TargetInfo' } @@ -1944,7 +1996,8 @@ index 47f3facdb2..46760978ae 100644 -# -# Since: 0.14 -# --# Notes: If no UUID was specified for the guest, a null UUID is returned. +-# Notes: If no UUID was specified for the guest, a null UUID is +-# returned. -## -{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} } - diff --git a/debian/patches/pve/0031-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch b/debian/patches/pve/0031-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch index da798b9..4cabccf 100644 --- a/debian/patches/pve/0031-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch +++ b/debian/patches/pve/0031-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch @@ -14,10 +14,10 @@ Signed-off-by: Wolfgang Bumiller create mode 100644 pbs-restore.c diff --git a/meson.build b/meson.build -index 443b3238f9..32ab849ce6 100644 +index fa79372745..b12428a6a0 100644 --- a/meson.build +++ b/meson.build -@@ -3656,6 +3656,10 @@ if have_tools +@@ -3913,6 +3913,10 @@ if have_tools vma = executable('vma', files('vma.c', 'vma-reader.c') + genh, dependencies: [authz, block, crypto, io, qom], install: true) diff --git a/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch b/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch index 102cfcf..a07e81e 100644 --- a/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch +++ b/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch @@ -24,10 +24,10 @@ Signed-off-by: Fiona Ebner create mode 100644 block/pbs.c diff --git a/block/meson.build b/block/meson.build -index 5bcebb934b..eece0d5743 100644 +index 6d468f89e5..becc99ac4e 100644 --- a/block/meson.build +++ b/block/meson.build -@@ -54,6 +54,9 @@ block_ss.add(files( +@@ -50,6 +50,9 @@ block_ss.add(files( '../pve-backup.c', ), libproxmox_backup_qemu) @@ -35,8 +35,8 @@ index 5bcebb934b..eece0d5743 100644 +block_ss.add(when: 'CONFIG_PBS_BDRV', if_true: libproxmox_backup_qemu) + - softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) - softmmu_ss.add(files('block-ram-registrar.c')) + system_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) + system_ss.add(files('block-ram-registrar.c')) diff --git a/block/pbs.c b/block/pbs.c new file mode 100644 index 0000000000..a2211e0f3b @@ -349,40 +349,40 @@ index 0000000000..a2211e0f3b + +block_init(bdrv_pbs_init); diff --git a/configure b/configure -index a62a3e6be9..1ac0feb46b 100755 +index 133f4e3235..f5a830c1f3 100755 --- a/configure +++ b/configure -@@ -288,6 +288,7 @@ linux_user="" +@@ -256,6 +256,7 @@ qemu_suffix="qemu" + softmmu="yes" + linux_user="" bsd_user="" - pie="" - coroutine="" +pbs_bdrv="yes" plugins="$default_feature" - meson="" ninja="" -@@ -873,6 +874,10 @@ for opt do + python= +@@ -809,6 +810,10 @@ for opt do ;; - --with-coroutine=*) coroutine="$optarg" + --enable-download) download="enabled"; git_submodules_action=update; ;; + --disable-pbs-bdrv) pbs_bdrv="no" + ;; + --enable-pbs-bdrv) pbs_bdrv="yes" + ;; - --with-git=*) git="$optarg" - ;; - --with-git-submodules=*) -@@ -1049,6 +1054,7 @@ cat << EOF - debug-info debugging information - safe-stack SafeStack Stack Smash Protection. Depends on - clang/llvm and requires coroutine backend ucontext. + --enable-plugins) if test "$mingw32" = "yes"; then + error_exit "TCG plugins not currently supported on Windows platforms" + else +@@ -959,6 +964,7 @@ cat << EOF + bsd-user all BSD usermode emulation targets + pie Position Independent Executables + debug-tcg TCG debugging (default is disabled) + pbs-bdrv Proxmox backup server read-only block driver support NOTE: The object files are built at the place where configure is launched EOF -@@ -2386,6 +2392,9 @@ echo "TARGET_DIRS=$target_list" >> $config_host_mak - if test "$modules" = "yes"; then - echo "CONFIG_MODULES=y" >> $config_host_mak +@@ -1744,6 +1750,9 @@ if test "$solaris" = "yes" ; then fi + echo "SRC_PATH=$source_path" >> $config_host_mak + echo "TARGET_DIRS=$target_list" >> $config_host_mak +if test "$pbs_bdrv" = "yes" ; then + echo "CONFIG_PBS_BDRV=y" >> $config_host_mak +fi @@ -390,10 +390,10 @@ index a62a3e6be9..1ac0feb46b 100755 # XXX: suppress that if [ "$bsd" = "yes" ] ; then diff --git a/meson.build b/meson.build -index 32ab849ce6..69afe3441b 100644 +index b12428a6a0..6e74b8f88d 100644 --- a/meson.build +++ b/meson.build -@@ -4041,7 +4041,7 @@ summary_info += {'bzip2 support': libbzip2} +@@ -4317,7 +4317,7 @@ summary_info += {'bzip2 support': libbzip2} summary_info += {'lzfse support': liblzfse} summary_info += {'zstd support': zstd} summary_info += {'NUMA host support': numa} @@ -403,10 +403,10 @@ index 32ab849ce6..69afe3441b 100644 summary_info += {'libdaxctl support': libdaxctl} summary_info += {'libudev': libudev} diff --git a/qapi/block-core.json b/qapi/block-core.json -index 985859ddee..d601fb4ab2 100644 +index 331c8336d1..a818d5f90f 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json -@@ -3304,6 +3304,7 @@ +@@ -3396,6 +3396,7 @@ 'parallels', 'preallocate', 'qcow', 'qcow2', 'qed', 'quorum', 'raw', 'rbd', { 'name': 'replication', 'if': 'CONFIG_REPLICATION' }, @@ -414,7 +414,7 @@ index 985859ddee..d601fb4ab2 100644 'ssh', 'throttle', 'vdi', 'vhdx', { 'name': 'virtio-blk-vfio-pci', 'if': 'CONFIG_BLKIO' }, { 'name': 'virtio-blk-vhost-user', 'if': 'CONFIG_BLKIO' }, -@@ -3380,6 +3381,17 @@ +@@ -3482,6 +3483,17 @@ { 'struct': 'BlockdevOptionsNull', 'data': { '*size': 'int', '*latency-ns': 'uint64', '*read-zeroes': 'bool' } } @@ -432,7 +432,7 @@ index 985859ddee..d601fb4ab2 100644 ## # @BlockdevOptionsNVMe: # -@@ -4753,6 +4765,7 @@ +@@ -4886,6 +4898,7 @@ 'nfs': 'BlockdevOptionsNfs', 'null-aio': 'BlockdevOptionsNull', 'null-co': 'BlockdevOptionsNull', diff --git a/debian/patches/pve/0033-PVE-redirect-stderr-to-journal-when-daemonized.patch b/debian/patches/pve/0033-PVE-redirect-stderr-to-journal-when-daemonized.patch index 147951c..fbbe3e5 100644 --- a/debian/patches/pve/0033-PVE-redirect-stderr-to-journal-when-daemonized.patch +++ b/debian/patches/pve/0033-PVE-redirect-stderr-to-journal-when-daemonized.patch @@ -14,10 +14,10 @@ Signed-off-by: Thomas Lamprecht 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build -index 69afe3441b..b2e9b2aec7 100644 +index 6e74b8f88d..e6c4a89195 100644 --- a/meson.build +++ b/meson.build -@@ -1528,6 +1528,7 @@ keyutils = dependency('libkeyutils', required: false, +@@ -1778,6 +1778,7 @@ endif has_gettid = cc.has_function('gettid') libuuid = cc.find_library('uuid', required: true) @@ -25,16 +25,16 @@ index 69afe3441b..b2e9b2aec7 100644 libproxmox_backup_qemu = cc.find_library('proxmox_backup_qemu', required: true) # libselinux -@@ -3144,6 +3145,7 @@ if have_block +@@ -3405,6 +3406,7 @@ if have_block # os-posix.c contains POSIX-specific functions used by qemu-storage-daemon, # os-win32.c does not blockdev_ss.add(when: 'CONFIG_POSIX', if_true: files('os-posix.c')) + blockdev_ss.add(when: 'CONFIG_POSIX', if_true: libsystemd) - softmmu_ss.add(when: 'CONFIG_WIN32', if_true: [files('os-win32.c')]) + system_ss.add(when: 'CONFIG_WIN32', if_true: [files('os-win32.c')]) endif diff --git a/os-posix.c b/os-posix.c -index 90ea71725f..33745a8c22 100644 +index cfcb96533c..fb2ad87009 100644 --- a/os-posix.c +++ b/os-posix.c @@ -28,6 +28,8 @@ @@ -46,7 +46,7 @@ index 90ea71725f..33745a8c22 100644 /* Needed early for CONFIG_BSD etc. */ #include "net/slirp.h" -@@ -301,9 +303,10 @@ void os_setup_post(void) +@@ -310,9 +312,10 @@ void os_setup_post(void) dup2(fd, 0); dup2(fd, 1); diff --git a/debian/patches/pve/0034-PVE-Migrate-dirty-bitmap-state-via-savevm.patch b/debian/patches/pve/0034-PVE-Migrate-dirty-bitmap-state-via-savevm.patch index 6da71b7..7009b8f 100644 --- a/debian/patches/pve/0034-PVE-Migrate-dirty-bitmap-state-via-savevm.patch +++ b/debian/patches/pve/0034-PVE-Migrate-dirty-bitmap-state-via-savevm.patch @@ -26,7 +26,7 @@ Signed-off-by: Fiona Ebner create mode 100644 migration/pbs-state.c diff --git a/include/migration/misc.h b/include/migration/misc.h -index 8b49841016..78f63ca400 100644 +index 7dcc0b5c2c..4c940b2475 100644 --- a/include/migration/misc.h +++ b/include/migration/misc.h @@ -77,4 +77,7 @@ bool migration_in_bg_snapshot(void); @@ -38,25 +38,24 @@ index 8b49841016..78f63ca400 100644 + #endif diff --git a/migration/meson.build b/migration/meson.build -index a7824b5266..de6a271b58 100644 +index 07f6057acc..343994d891 100644 --- a/migration/meson.build +++ b/migration/meson.build -@@ -6,8 +6,10 @@ migration_files = files( +@@ -7,7 +7,9 @@ migration_files = files( 'vmstate.c', 'qemu-file.c', 'yank_functions.c', + 'pbs-state.c', ) - softmmu_ss.add(migration_files) -+softmmu_ss.add(libproxmox_backup_qemu) ++system_ss.add(libproxmox_backup_qemu) - softmmu_ss.add(files( + system_ss.add(files( 'block-dirty-bitmap.c', diff --git a/migration/migration.c b/migration/migration.c -index 99f86bd6c2..db229e72c9 100644 +index 5528acb65e..1186bdb984 100644 --- a/migration/migration.c +++ b/migration/migration.c -@@ -245,6 +245,7 @@ void migration_object_init(void) +@@ -160,6 +160,7 @@ void migration_object_init(void) blk_mig_init(); ram_mig_init(); dirty_bitmap_mig_init(); @@ -175,7 +174,7 @@ index 0000000000..887e998b9e + NULL); +} diff --git a/pve-backup.c b/pve-backup.c -index 21441b2f97..5e5c37e06d 100644 +index d84d807654..9c8b88d075 100644 --- a/pve-backup.c +++ b/pve-backup.c @@ -1060,6 +1060,7 @@ ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp) @@ -187,10 +186,10 @@ index 21441b2f97..5e5c37e06d 100644 ret->pbs_masterkey = true; ret->backup_max_workers = true; diff --git a/qapi/block-core.json b/qapi/block-core.json -index d601fb4ab2..16be1e02ec 100644 +index a818d5f90f..48eb47c6ea 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json -@@ -987,6 +987,11 @@ +@@ -991,6 +991,11 @@ # @pbs-dirty-bitmap-savevm: True if 'dirty-bitmaps' migration capability can # safely be set for savevm-async. # @@ -202,7 +201,7 @@ index d601fb4ab2..16be1e02ec 100644 # @pbs-masterkey: True if the QMP backup call supports the 'master_keyfile' # parameter. # -@@ -997,6 +1002,7 @@ +@@ -1001,6 +1006,7 @@ 'data': { 'pbs-dirty-bitmap': 'bool', 'query-bitmap-info': 'bool', 'pbs-dirty-bitmap-savevm': 'bool', diff --git a/debian/patches/pve/0035-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch b/debian/patches/pve/0035-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch index bd721fc..75f5c28 100644 --- a/debian/patches/pve/0035-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch +++ b/debian/patches/pve/0035-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch @@ -19,10 +19,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c -index 7eaf498439..509f3df0a6 100644 +index e1ae3b7316..285dd1d148 100644 --- a/migration/block-dirty-bitmap.c +++ b/migration/block-dirty-bitmap.c -@@ -539,7 +539,7 @@ static int add_bitmaps_to_list(DBMSaveState *s, BlockDriverState *bs, +@@ -540,7 +540,7 @@ static int add_bitmaps_to_list(DBMSaveState *s, BlockDriverState *bs, if (bdrv_dirty_bitmap_check(bitmap, BDRV_BITMAP_DEFAULT, &local_err)) { error_report_err(local_err); diff --git a/debian/patches/pve/0036-PVE-fall-back-to-open-iscsi-initiatorname.patch b/debian/patches/pve/0036-PVE-fall-back-to-open-iscsi-initiatorname.patch index 0e50c93..f9fee14 100644 --- a/debian/patches/pve/0036-PVE-fall-back-to-open-iscsi-initiatorname.patch +++ b/debian/patches/pve/0036-PVE-fall-back-to-open-iscsi-initiatorname.patch @@ -21,10 +21,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 30 insertions(+) diff --git a/block/iscsi.c b/block/iscsi.c -index 9fc0bed90b..1d40933165 100644 +index 34f97ab646..398782963d 100644 --- a/block/iscsi.c +++ b/block/iscsi.c -@@ -1392,12 +1392,42 @@ static char *get_initiator_name(QemuOpts *opts) +@@ -1391,12 +1391,42 @@ static char *get_initiator_name(QemuOpts *opts) const char *name; char *iscsi_name; UuidInfo *uuid_info; diff --git a/debian/patches/pve/0037-PVE-block-stream-increase-chunk-size.patch b/debian/patches/pve/0037-PVE-block-stream-increase-chunk-size.patch index 5707bee..28dd8d1 100644 --- a/debian/patches/pve/0037-PVE-block-stream-increase-chunk-size.patch +++ b/debian/patches/pve/0037-PVE-block-stream-increase-chunk-size.patch @@ -11,7 +11,7 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/stream.c b/block/stream.c -index 7f9e1ecdbb..6a29d80398 100644 +index e522bbdec5..afed72db55 100644 --- a/block/stream.c +++ b/block/stream.c @@ -27,7 +27,7 @@ enum { diff --git a/debian/patches/pve/0038-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch b/debian/patches/pve/0038-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch index d1bd74d..0e43de5 100644 --- a/debian/patches/pve/0038-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch +++ b/debian/patches/pve/0038-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch @@ -17,17 +17,17 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 4 insertions(+) diff --git a/block/io.c b/block/io.c -index 2e267a85ab..449a44bf20 100644 +index 055fcf7438..63f7b3ad3e 100644 --- a/block/io.c +++ b/block/io.c -@@ -1576,6 +1576,10 @@ static int bdrv_pad_request(BlockDriverState *bs, - { - int ret; +@@ -1710,6 +1710,10 @@ static int bdrv_pad_request(BlockDriverState *bs, + int sliced_niov; + size_t sliced_head, sliced_tail; + if (!qiov) { + return 0; + } + - bdrv_check_qiov_request(*offset, *bytes, *qiov, *qiov_offset, &error_abort); - - if (!bdrv_init_padding(bs, *offset, *bytes, pad)) { + /* Should have been checked by the caller already */ + ret = bdrv_check_request32(*offset, *bytes, *qiov, *qiov_offset); + if (ret < 0) { diff --git a/debian/patches/pve/0039-block-add-alloc-track-driver.patch b/debian/patches/pve/0039-block-add-alloc-track-driver.patch index 823be87..ea5f105 100644 --- a/debian/patches/pve/0039-block-add-alloc-track-driver.patch +++ b/debian/patches/pve/0039-block-add-alloc-track-driver.patch @@ -393,7 +393,7 @@ index 0000000000..b75d7c6460 + +block_init(bdrv_alloc_track_init); diff --git a/block/meson.build b/block/meson.build -index eece0d5743..8a68162cc0 100644 +index becc99ac4e..0a69836593 100644 --- a/block/meson.build +++ b/block/meson.build @@ -2,6 +2,7 @@ block_ss.add(genh) diff --git a/debian/patches/pve/0044-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch b/debian/patches/pve/0044-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch index 04ef6cb..a7f6e4d 100644 --- a/debian/patches/pve/0044-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch +++ b/debian/patches/pve/0044-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch @@ -52,7 +52,7 @@ Signed-off-by: Fiona Ebner 5 files changed, 38 insertions(+), 12 deletions(-) diff --git a/include/migration/register.h b/include/migration/register.h -index a8dfd8fefd..fa9b0b0f10 100644 +index 90914f32f5..c728fd9120 100644 --- a/include/migration/register.h +++ b/include/migration/register.h @@ -43,9 +43,9 @@ typedef struct SaveVMHandlers { @@ -67,10 +67,10 @@ index a8dfd8fefd..fa9b0b0f10 100644 * must_precopy: * - must be migrated in precopy or in stopped state diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c -index 509f3df0a6..42dc4a8d61 100644 +index 285dd1d148..f7ee5a74d9 100644 --- a/migration/block-dirty-bitmap.c +++ b/migration/block-dirty-bitmap.c -@@ -1220,10 +1220,17 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque) +@@ -1219,10 +1219,17 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque) { DBMSaveState *s = &((DBMState *)opaque)->save; SaveBitmapState *dbms = NULL; @@ -90,7 +90,7 @@ index 509f3df0a6..42dc4a8d61 100644 return -1; } -@@ -1231,7 +1238,9 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque) +@@ -1230,7 +1237,9 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque) send_bitmap_start(f, s, dbms); } qemu_put_bitmap_flags(f, DIRTY_BITMAP_MIG_FLAG_EOS); @@ -102,10 +102,10 @@ index 509f3df0a6..42dc4a8d61 100644 } diff --git a/migration/block.c b/migration/block.c -index b2497bbd32..c9d55be642 100644 +index 86c2256a2b..8423e0c9f9 100644 --- a/migration/block.c +++ b/migration/block.c -@@ -716,21 +716,30 @@ static void block_migration_cleanup(void *opaque) +@@ -725,21 +725,30 @@ static void block_migration_cleanup(void *opaque) static int block_save_setup(QEMUFile *f, void *opaque) { int ret; @@ -140,10 +140,10 @@ index b2497bbd32..c9d55be642 100644 if (ret) { return ret; diff --git a/migration/ram.c b/migration/ram.c -index 79d881f735..0ecbbc3202 100644 +index 9040d66e61..01532c9fc9 100644 --- a/migration/ram.c +++ b/migration/ram.c -@@ -3117,8 +3117,16 @@ static void migration_bitmap_clear_discarded_pages(RAMState *rs) +@@ -2895,8 +2895,16 @@ static void migration_bitmap_clear_discarded_pages(RAMState *rs) static void ram_init_bitmaps(RAMState *rs) { @@ -162,7 +162,7 @@ index 79d881f735..0ecbbc3202 100644 qemu_mutex_lock_ramlist(); WITH_RCU_READ_LOCK_GUARD() { -@@ -3130,7 +3138,9 @@ static void ram_init_bitmaps(RAMState *rs) +@@ -2908,7 +2916,9 @@ static void ram_init_bitmaps(RAMState *rs) } } qemu_mutex_unlock_ramlist(); @@ -174,11 +174,11 @@ index 79d881f735..0ecbbc3202 100644 /* * After an eventual first bitmap sync, fixup the initial bitmap diff --git a/migration/savevm.c b/migration/savevm.c -index aa54a67fda..fc6a82a555 100644 +index a2cb8855e2..ea8b30a630 100644 --- a/migration/savevm.c +++ b/migration/savevm.c -@@ -1621,10 +1621,8 @@ static int qemu_savevm_state(QEMUFile *f, Error **errp) - memset(&compression_counters, 0, sizeof(compression_counters)); +@@ -1625,10 +1625,8 @@ static int qemu_savevm_state(QEMUFile *f, Error **errp) + reset_vfio_bytes_transferred(); ms->to_dst_file = f; - qemu_mutex_unlock_iothread(); diff --git a/debian/patches/pve/0045-savevm-async-don-t-hold-BQL-during-setup.patch b/debian/patches/pve/0045-savevm-async-don-t-hold-BQL-during-setup.patch index f5fa200..3ff0bf7 100644 --- a/debian/patches/pve/0045-savevm-async-don-t-hold-BQL-during-setup.patch +++ b/debian/patches/pve/0045-savevm-async-don-t-hold-BQL-during-setup.patch @@ -13,10 +13,10 @@ Signed-off-by: Fiona Ebner 1 file changed, 2 deletions(-) diff --git a/migration/savevm-async.c b/migration/savevm-async.c -index b97f2c4f14..87ea0573d3 100644 +index 80624fada8..b1d85a4b41 100644 --- a/migration/savevm-async.c +++ b/migration/savevm-async.c -@@ -403,10 +403,8 @@ void qmp_savevm_start(const char *statefile, Error **errp) +@@ -401,10 +401,8 @@ void qmp_savevm_start(const char *statefile, Error **errp) snap_state.state = SAVE_STATE_ACTIVE; snap_state.finalize_bh = qemu_bh_new(process_savevm_finalize, &snap_state); snap_state.co = qemu_coroutine_create(&process_savevm_co, NULL); diff --git a/debian/patches/series b/debian/patches/series index 996fa1f..01d4d3c 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,15 +1,8 @@ extra/0001-monitor-qmp-fix-race-with-clients-disconnecting-earl.patch extra/0002-scsi-megasas-Internal-cdbs-have-16-byte-length.patch extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch -extra/0004-ui-return-NULL-when-getting-cursor-without-a-console.patch -extra/0005-memory-prevent-dma-reentracy-issues.patch -extra/0006-lsi53c895a-disable-reentrancy-detection-for-script-R.patch -extra/0007-bcm2835_property-disable-reentrancy-detection-for-io.patch -extra/0008-raven-disable-reentrancy-detection-for-iomem.patch -extra/0009-apic-disable-reentrancy-detection-for-apic-msi.patch -extra/0010-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch -extra/0011-vhost-fix-the-fd-leak.patch -extra/0012-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch +extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch +extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch diff --git a/qemu b/qemu index f7f686b..6bb4a8a 160000 --- a/qemu +++ b/qemu @@ -1 +1 @@ -Subproject commit f7f686b61cf7ee142c9264d2e04ac2c6a96d37f8 +Subproject commit 6bb4a8a47a43f35a345f107227fcd6abed59e62c -- 2.39.2 From f.ebner at proxmox.com Fri Oct 6 13:01:42 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Fri, 6 Oct 2023 13:01:42 +0200 Subject: [pve-devel] [PATCH v2 qemu 3/9] buildsys: use QEMU's keycodemapdb again In-Reply-To: <20231006110148.154914-1-f.ebner@proxmox.com> References: <20231006110148.154914-1-f.ebner@proxmox.com> Message-ID: <20231006110148.154914-4-f.ebner@proxmox.com> instead of the split-out version that was last updated for QEMU 6.0. This reverts the relevant part of 6838f03 ("bump version to 2.11.1-1") which doesn't state a reason why the splitting was done. If something breaks, we can still re-do it and document the reason this time. Alternatively, it would be necessary to adapt the paths, because keycodemapdb lives in subprojects/ rather than ui/ since QEMU commit c53648abba ("meson: use subproject for keycodemapdb"). Signed-off-by: Fiona Ebner --- No changes in v2. Makefile | 15 +- keycodemapdb/LICENSE.BSD | 27 - keycodemapdb/LICENSE.GPL2 | 339 --- keycodemapdb/README | 114 - keycodemapdb/data/README | 89 - keycodemapdb/data/keymaps.csv | 539 ---- keycodemapdb/meson.build | 1 - keycodemapdb/tests/.gitignore | 11 - keycodemapdb/tests/Makefile | 150 -- keycodemapdb/tests/javascript | 53 - keycodemapdb/tests/python2 | 3 - keycodemapdb/tests/python3 | 3 - keycodemapdb/tests/stdc++.cc | 40 - keycodemapdb/tests/stdc.c | 64 - keycodemapdb/tests/test.py | 30 - keycodemapdb/thirdparty/LICENSE-argparse.txt | 20 - keycodemapdb/thirdparty/__init__.py | 0 keycodemapdb/thirdparty/argparse.py | 2392 ------------------ keycodemapdb/tools/keymap-gen | 1147 --------- 19 files changed, 1 insertion(+), 5036 deletions(-) delete mode 100644 keycodemapdb/LICENSE.BSD delete mode 100644 keycodemapdb/LICENSE.GPL2 delete mode 100644 keycodemapdb/README delete mode 100644 keycodemapdb/data/README delete mode 100644 keycodemapdb/data/keymaps.csv delete mode 100644 keycodemapdb/meson.build delete mode 100644 keycodemapdb/tests/.gitignore delete mode 100644 keycodemapdb/tests/Makefile delete mode 100755 keycodemapdb/tests/javascript delete mode 100755 keycodemapdb/tests/python2 delete mode 100755 keycodemapdb/tests/python3 delete mode 100644 keycodemapdb/tests/stdc++.cc delete mode 100644 keycodemapdb/tests/stdc.c delete mode 100644 keycodemapdb/tests/test.py delete mode 100644 keycodemapdb/thirdparty/LICENSE-argparse.txt delete mode 100644 keycodemapdb/thirdparty/__init__.py delete mode 100644 keycodemapdb/thirdparty/argparse.py delete mode 100755 keycodemapdb/tools/keymap-gen diff --git a/Makefile b/Makefile index e389a9c..cad130e 100644 --- a/Makefile +++ b/Makefile @@ -39,16 +39,14 @@ PC_BIOS_FW_PURGE_LIST_IN = \ BLOB_PURGE_SED_CMDS = $(foreach FILE,$(PC_BIOS_FW_PURGE_LIST_IN),-e "/$(FILE)/d") BLOB_PURGE_FILTER = $(foreach FILE,$(PC_BIOS_FW_PURGE_LIST_IN),-e "$(FILE)") -$(BUILDDIR): keycodemapdb | submodule +$(BUILDDIR): submodule # check if qemu/ was used for a build # if so, please run 'make distclean' in the submodule and try again test ! -f $(SRCDIR)/build/config.status rm -rf $@.tmp $@ cp -a $(SRCDIR) $@.tmp cp -a debian $@.tmp/debian - rm -rf $@.tmp/ui/keycodemapdb rm -rf $@.tmp/roms/edk2 # packaged separately - cp -a keycodemapdb $@.tmp/ui/ find $@.tmp/pc-bios -type f | grep $(BLOB_PURGE_FILTER) | xargs rm -f sed -i $(BLOB_PURGE_SED_CMDS) $@.tmp/pc-bios/meson.build echo "git clone git://git.proxmox.com/git/pve-qemu.git\\ngit checkout $(GITVERSION)" > $@.tmp/debian/SOURCE @@ -76,17 +74,6 @@ dsc: $(DSC): $(ORIG_SRC_TAR) $(BUILDDIR) cd $(BUILDDIR); dpkg-buildpackage -S -us -uc -d -.PHONY: update -update: - cd $(SRCDIR) && git submodule deinit ui/keycodemapdb || true - rm -rf $(SRCDIR)/ui/keycodemapdb - mkdir $(SRCDIR)/ui/keycodemapdb - cd $(SRCDIR) && git submodule update --init ui/keycodemapdb - rm -rf keycodemapdb - mkdir keycodemapdb - cp -R $(SRCDIR)/ui/keycodemapdb/* keycodemapdb/ - git add keycodemapdb - .PHONY: upload upload: UPLOAD_DIST ?= $(DEB_DISTRIBUTION) upload: $(DEBS) diff --git a/keycodemapdb/LICENSE.BSD b/keycodemapdb/LICENSE.BSD deleted file mode 100644 index ec1a29d..0000000 --- a/keycodemapdb/LICENSE.BSD +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) Individual contributors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of PyCA Cryptography nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/keycodemapdb/LICENSE.GPL2 b/keycodemapdb/LICENSE.GPL2 deleted file mode 100644 index d511905..0000000 --- a/keycodemapdb/LICENSE.GPL2 +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/keycodemapdb/README b/keycodemapdb/README deleted file mode 100644 index a3c3c9f..0000000 --- a/keycodemapdb/README +++ /dev/null @@ -1,114 +0,0 @@ - Key code / scan code / key symbol mapping database - ================================================== - -This module provides a database that maps between different -key code / scan code / key symbol sets: - - - Linux evdev - - OS-X - - AT Set 1 - - AT Set 2 - - AT Set 3 - - XT - - Linux XT KBD driver - - USB HID - - Win32 - - XWin XT - - XKBD XT - - Xorg Evdev - - Xorg KBD - - Xorg OS-X - - XOrg Cygwin - - RFB - -Licensing ---------- - -The contents of this package are dual licensed under the terms of: - - - GNU General Public License (version 2 or later) - - 3-clause BSD License - -The output files generated by keymap-gen may be distributed & used under -the terms of either of the above licenses. - -Data formats ------------- - -The following output formats are possible - - - Code map - - An array mapping between key code sets values - - Indexes in the array are values from the source code set. - Entries in the array are values from the target code set - - - - Code table - - An array listing all values in a key code set - - Indexes in the array are simply a numeric counter - Entries in the array are values from the key code set - - The size of the array matches the total number of entries in - the keycode database. - - - - Name map - - An array mapping between key code sets values and names - - Indexes in the array are values from the source code set - Entries in the array are names from the target code set - - - - Name table - - An array listing all names in a key code set - - Indexes in the array are simply a numeric counter - Entries in the array are values from the key code set - - The size of the array matches the total number of entries in - the keycode database. - - -Output languages ----------------- - -The tool is capable of generating data tables for the following -programming languages / environments - - - Standard C - - GLib2 (standard C, but with GLib2 data types) - - Python - - Perl - - -Usage ------ - -Map values from AT Set 1 to USB HID, generating tables for the -C programming language - - $ keymap-gen --lang stdc code-map data/keymaps.csv atset1 usb - -Generate a tables of names for Linux key codes, OS-X key codes, -in python - equivalent array indexes map between the two sets. -A variable name override is used - - $ keymap-gen --varname linux_keycodes --lang stdc \ - code-table data/keymaps.csv linux - $ keymap-gen --varname osx_keycodes --lang stdc \ - code-table data/keymaps.csv os-x - -Generate a mapping from XOrg XWin values to Win32 names - - $ keymap-gen --lang perl name-map data/keymaps.csv xorgxwin win32 - -Generate a table of names for Linux key codes in Perl - - $ keymap-gen --lang perl name-table data/keymaps.csv linux - diff --git a/keycodemapdb/data/README b/keycodemapdb/data/README deleted file mode 100644 index 6b56534..0000000 --- a/keycodemapdb/data/README +++ /dev/null @@ -1,89 +0,0 @@ -This directory contains the raw data for mapping between different -keyboard codes. Naming if often based on the US keyboard layout, but -does not indicate the symbol actually generated by the key. - -The columns currently in this data set are: - -Linux ------ - -Name and value of the hardware independent keycodes used by the linux -kernel and exposed through the input subsystem. - -References: linux/input.h - -macOS ------ - -Low level key codes as exposed by Mac OS X/macOS. - -References: Carbon/HIToolbox/Events.h - -PC scan code sets ------------------ - -Scan codes for the three orignal PC keyboard generations: - - Set 1: XT - Set 2: AT - Set 3: PS/2 - -The sets include codes for modern keys as well and not just the keys -present on those original keyboards. - -References: linux/drivers/input/keyboard/atkbd.c - -USB HID -------- - -Codes as specified by the HID profile in USB. - -References: linux/drivers/hid/usbhid/usbkbd.c - -Windows Virtual-key codes -------------------------- - -The low level, hardware independent "VKEYs" exposed by Windows. - -References: mingw32/winuser.h - -XWin XT -------- - -X11 keycodes generated by the XWin server. Based on the XT scan code -set. - -References: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h} - -Xfree86 KBD XT --------------- - -X11 keycodes generated by the Xfree86 keyboard drivers. Based on the XT -scan code set. - -References: xf86-input-keyboard/src/at_scancode.c - -X11 keysyms ------------ - -Corresponding X11 keysym value(s) for a US keyboard layout. - -WARNING: These columns represent symbols, not physical keys, and should - be used with extreme care. - -References: http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h - -HTML KeyboardEvent.code ------------------------ - -Key codes seen in the KeyboardEvent.code attribute as part of the -UI Events specification. - -References: https://www.w3.org/TR/uievents-code/ - -XKEYBOARD key names -------------------- - -Hardware independent key names as used in the XKEYBOARD extension. - -References: /usr/share/X11/xkb/keycodes/ diff --git a/keycodemapdb/data/keymaps.csv b/keycodemapdb/data/keymaps.csv deleted file mode 100644 index 6b1631e..0000000 --- a/keycodemapdb/data/keymaps.csv +++ /dev/null @@ -1,539 +0,0 @@ -"Linux Name","Linux Keycode","OS-X Name","OS-X Keycode","AT set1 keycode","AT set2 keycode","AT set3 keycode","USB Keycodes","Win32 Name","Win32 Keycode","Xwin XT","Xfree86 KBD XT","X11 keysym name","X11 keysym","HTML code","XKB key name","QEMU QKeyCode","Sun KBD","Apple ADB" -KEY_RESERVED,0,,0xff,,,,,,,,,,,,,unmapped,,0xff -KEY_ESC,1,Escape,0x35,0x01,0x76,0x08,41,VK_ESCAPE,0x1b,1,1,XK_Escape,0xff1b,Escape,ESC,esc,0x1d,0x35 -KEY_1,2,ANSI_1,0x12,0x02,0x16,0x16,30,VK_1,0x31,2,2,XK_1,0x0031,Digit1,AE01,1,0x1e,0x12 -KEY_1,2,ANSI_1,0x12,0x02,0x16,0x16,30,VK_1,0x31,2,2,XK_exclam,0x0021,Digit1,AE01,1,0x1e,0x12 -KEY_2,3,ANSI_2,0x13,0x03,0x1e,0x1e,31,VK_2,0x32,3,3,XK_2,0x0032,Digit2,AE02,2,0x1f,0x13 -KEY_2,3,ANSI_2,0x13,0x03,0x1e,0x1e,31,VK_2,0x32,3,3,XK_at,0x0040,Digit2,AE02,2,0x1f,0x13 -KEY_3,4,ANSI_3,0x14,0x04,0x26,0x26,32,VK_3,0x33,4,4,XK_3,0x0033,Digit3,AE03,3,0x20,0x14 -KEY_3,4,ANSI_3,0x14,0x04,0x26,0x26,32,VK_3,0x33,4,4,XK_numbersign,0x0023,Digit3,AE03,3,0x20,0x14 -KEY_4,5,ANSI_4,0x15,0x05,0x25,0x25,33,VK_4,0x34,5,5,XK_4,0x0034,Digit4,AE04,4,0x21,0x15 -KEY_4,5,ANSI_4,0x15,0x05,0x25,0x25,33,VK_4,0x34,5,5,XK_dollar,0x0024,Digit4,AE04,4,0x21,0x15 -KEY_5,6,ANSI_5,0x17,0x06,0x2e,0x2e,34,VK_5,0x35,6,6,XK_5,0x0035,Digit5,AE05,5,0x22,0x17 -KEY_5,6,ANSI_5,0x17,0x06,0x2e,0x2e,34,VK_5,0x35,6,6,XK_percent,0x0025,Digit5,AE05,5,0x22,0x17 -KEY_6,7,ANSI_6,0x16,0x07,0x36,0x36,35,VK_6,0x36,7,7,XK_6,0x0036,Digit6,AE06,6,0x23,0x16 -KEY_6,7,ANSI_6,0x16,0x07,0x36,0x36,35,VK_6,0x36,7,7,XK_asciicircum,0x005e,Digit6,AE06,6,0x23,0x16 -KEY_7,8,ANSI_7,0x1a,0x08,0x3d,0x3d,36,VK_7,0x37,8,8,XK_7,0x0037,Digit7,AE07,7,0x24,0x1a -KEY_7,8,ANSI_7,0x1a,0x08,0x3d,0x3d,36,VK_7,0x37,8,8,XK_ampersand,0x0026,Digit7,AE07,7,0x24,0x1a -KEY_8,9,ANSI_8,0x1c,0x09,0x3e,0x3e,37,VK_8,0x38,9,9,XK_8,0x0038,Digit8,AE08,8,0x25,0x1c -KEY_8,9,ANSI_8,0x1c,0x09,0x3e,0x3e,37,VK_8,0x38,9,9,XK_asterisk,0x002a,Digit8,AE08,8,0x25,0x1c -KEY_9,10,ANSI_9,0x19,0x0a,0x46,0x46,38,VK_9,0x39,10,10,XK_9,0x0039,Digit9,AE09,9,0x26,0x19 -KEY_9,10,ANSI_9,0x19,0x0a,0x46,0x46,38,VK_9,0x39,10,10,XK_parenleft,0x0028,Digit9,AE09,9,0x26,0x19 -KEY_0,11,ANSI_0,0x1d,0x0b,0x45,0x45,39,VK_0,0x30,11,11,XK_0,0x0030,Digit0,AE10,0,0x27,0x1d -KEY_0,11,ANSI_0,0x1d,0x0b,0x45,0x45,39,VK_0,0x30,11,11,XK_parenright,0x0029,Digit0,AE10,0,0x27,0x1d -KEY_MINUS,12,ANSI_Minus,0x1b,0x0c,0x4e,0x4e,45,VK_OEM_MINUS,0xbd,12,12,XK_minus,0x002d,Minus,AE11,minus,0x28,0x1b -KEY_MINUS,12,ANSI_Minus,0x1b,0x0c,0x4e,0x4e,45,VK_OEM_MINUS,0xbd,12,12,XK_underscore,0x005f,Minus,AE11,minus,0x28,0x1b -KEY_EQUAL,13,ANSI_Equal,0x18,0x0d,0x55,0x55,46,VK_OEM_PLUS,0xbb,13,13,XK_equal,0x003d,Equal,AE12,equal,0x29,0x18 -KEY_EQUAL,13,ANSI_Equal,0x18,0x0d,0x55,0x55,46,VK_OEM_PLUS,0xbb,13,13,XK_plus,0x002b,Equal,AE12,equal,0x29,0x18 -KEY_BACKSPACE,14,Delete,0x33,0x0e,0x66,0x66,42,VK_BACK,0x08,14,14,XK_BackSpace,0xff08,Backspace,BKSP,backspace,0x2b,0x33 -KEY_TAB,15,Tab,0x30,0x0f,0x0d,0x0d,43,VK_TAB,0x09,15,15,XK_Tab,0xff09,Tab,TAB,tab,0x35,0x30 -KEY_Q,16,ANSI_Q,0xc,0x10,0x15,0x15,20,VK_Q,0x51,16,16,XK_Q,0x0051,KeyQ,AD01,q,0x36,0xc -KEY_Q,16,ANSI_Q,0xc,0x10,0x15,0x15,20,VK_Q,0x51,16,16,XK_q,0x0071,KeyQ,AD01,q,0x36,0xc -KEY_W,17,ANSI_W,0xd,0x11,0x1d,0x1d,26,VK_W,0x57,17,17,XK_W,0x0057,KeyW,AD02,w,0x37,0xd -KEY_W,17,ANSI_W,0xd,0x11,0x1d,0x1d,26,VK_W,0x57,17,17,XK_w,0x0077,KeyW,AD02,w,0x37,0xd -KEY_E,18,ANSI_E,0xe,0x12,0x24,0x24,8,VK_E,0x45,18,18,XK_E,0x0045,KeyE,AD03,e,0x38,0xe -KEY_E,18,ANSI_E,0xe,0x12,0x24,0x24,8,VK_E,0x45,18,18,XK_e,0x0065,KeyE,AD03,e,0x38,0xe -KEY_R,19,ANSI_R,0xf,0x13,0x2d,0x2d,21,VK_R,0x52,19,19,XK_R,0x0052,KeyR,AD04,r,0x39,0xf -KEY_R,19,ANSI_R,0xf,0x13,0x2d,0x2d,21,VK_R,0x52,19,19,XK_r,0x0072,KeyR,AD04,r,0x39,0xf -KEY_T,20,ANSI_T,0x11,0x14,0x2c,0x2c,23,VK_T,0x54,20,20,XK_T,0x0054,KeyT,AD05,t,0x3a,0x11 -KEY_T,20,ANSI_T,0x11,0x14,0x2c,0x2c,23,VK_T,0x54,20,20,XK_t,0x0074,KeyT,AD05,t,0x3a,0x11 -KEY_Y,21,ANSI_Y,0x10,0x15,0x35,0x35,28,VK_Y,0x59,21,21,XK_Y,0x0059,KeyY,AD06,y,0x3b,0x10 -KEY_Y,21,ANSI_Y,0x10,0x15,0x35,0x35,28,VK_Y,0x59,21,21,XK_y,0x0079,KeyY,AD06,y,0x3b,0x10 -KEY_U,22,ANSI_U,0x20,0x16,0x3c,0x3c,24,VK_U,0x55,22,22,XK_U,0x0055,KeyU,AD07,u,0x3c,0x20 -KEY_U,22,ANSI_U,0x20,0x16,0x3c,0x3c,24,VK_U,0x55,22,22,XK_u,0x0075,KeyU,AD07,u,0x3c,0x20 -KEY_I,23,ANSI_I,0x22,0x17,0x43,0x43,12,VK_I,0x49,23,23,XK_I,0x0049,KeyI,AD08,i,0x3d,0x22 -KEY_I,23,ANSI_I,0x22,0x17,0x43,0x43,12,VK_I,0x49,23,23,XK_i,0x0069,KeyI,AD08,i,0x3d,0x22 -KEY_O,24,ANSI_O,0x1f,0x18,0x44,0x44,18,VK_O,0x4f,24,24,XK_O,0x004f,KeyO,AD09,o,0x3e,0x1f -KEY_O,24,ANSI_O,0x1f,0x18,0x44,0x44,18,VK_O,0x4f,24,24,XK_o,0x006f,KeyO,AD09,o,0x3e,0x1f -KEY_P,25,ANSI_P,0x23,0x19,0x4d,0x4d,19,VK_P,0x50,25,25,XK_P,0x0050,KeyP,AD10,p,0x3f,0x23 -KEY_P,25,ANSI_P,0x23,0x19,0x4d,0x4d,19,VK_P,0x50,25,25,XK_p,0x0070,KeyP,AD10,p,0x3f,0x23 -KEY_LEFTBRACE,26,ANSI_LeftBracket,0x21,0x1a,0x54,0x54,47,VK_OEM_4,0xdb,26,26,XK_bracketleft,0x005b,BracketLeft,AD11,bracket_left,0x40,0x21 -KEY_LEFTBRACE,26,ANSI_LeftBracket,0x21,0x1a,0x54,0x54,47,VK_OEM_4,0xdb,26,26,XK_braceleft,0x007b,BracketLeft,AD11,bracket_left,0x40,0x21 -KEY_RIGHTBRACE,27,ANSI_RightBracket,0x1e,0x1b,0x5b,0x5b,48,VK_OEM_6,0xdd,27,27,XK_bracketright,0x005d,BracketRight,AD12,bracket_right,0x41,0x1e -KEY_RIGHTBRACE,27,ANSI_RightBracket,0x1e,0x1b,0x5b,0x5b,48,VK_OEM_6,0xdd,27,27,XK_braceright,0x007d,BracketRight,AD12,bracket_right,0x41,0x1e -KEY_ENTER,28,Return,0x24,0x1c,0x5a,0x5a,40,VK_RETURN,0x0d,28,28,XK_Return,0xff0d,Enter,RTRN,ret,0x59,0x24 -KEY_LEFTCTRL,29,Control,0x3b,0x1d,0x14,0x11,224,VK_LCONTROL,0xa2,29,29,XK_Control_L,0xffe3,ControlLeft,LCTL,ctrl,0x4c,0x36 -KEY_LEFTCTRL,29,Control,0x3b,0x1d,0x14,0x11,224,VK_CONTROL,0x11,29,29,XK_Control_L,0xffe3,ControlLeft,LCTL,ctrl,0x4c,0x36 -KEY_A,30,ANSI_A,0x0,0x1e,0x1c,0x1c,4,VK_A,0x41,30,30,XK_A,0x0041,KeyA,AC01,a,0x4d,0x0 -KEY_A,30,ANSI_A,0x0,0x1e,0x1c,0x1c,4,VK_A,0x41,30,30,XK_a,0x0061,KeyA,AC01,a,0x4d,0x0 -KEY_S,31,ANSI_S,0x1,0x1f,0x1b,0x1b,22,VK_S,0x53,31,31,XK_S,0x0053,KeyS,AC02,s,0x4e,0x1 -KEY_S,31,ANSI_S,0x1,0x1f,0x1b,0x1b,22,VK_S,0x53,31,31,XK_s,0x0073,KeyS,AC02,s,0x4e,0x1 -KEY_D,32,ANSI_D,0x2,0x20,0x23,0x23,7,VK_D,0x44,32,32,XK_D,0x0044,KeyD,AC03,d,0x4f,0x2 -KEY_D,32,ANSI_D,0x2,0x20,0x23,0x23,7,VK_D,0x44,32,32,XK_d,0x0064,KeyD,AC03,d,0x4f,0x2 -KEY_F,33,ANSI_F,0x3,0x21,0x2b,0x2b,9,VK_F,0x46,33,33,XK_F,0x0046,KeyF,AC04,f,0x50,0x3 -KEY_F,33,ANSI_F,0x3,0x21,0x2b,0x2b,9,VK_F,0x46,33,33,XK_f,0x0066,KeyF,AC04,f,0x50,0x3 -KEY_G,34,ANSI_G,0x5,0x22,0x34,0x34,10,VK_G,0x47,34,34,XK_G,0x0047,KeyG,AC05,g,0x51,0x5 -KEY_G,34,ANSI_G,0x5,0x22,0x34,0x34,10,VK_G,0x47,34,34,XK_g,0x0067,KeyG,AC05,g,0x51,0x5 -KEY_H,35,ANSI_H,0x4,0x23,0x33,0x33,11,VK_H,0x48,35,35,XK_H,0x0048,KeyH,AC06,h,0x52,0x4 -KEY_H,35,ANSI_H,0x4,0x23,0x33,0x33,11,VK_H,0x48,35,35,XK_h,0x0068,KeyH,AC06,h,0x52,0x4 -KEY_J,36,ANSI_J,0x26,0x24,0x3b,0x3b,13,VK_J,0x4a,36,36,XK_J,0x004a,KeyJ,AC07,j,0x53,0x26 -KEY_J,36,ANSI_J,0x26,0x24,0x3b,0x3b,13,VK_J,0x4a,36,36,XK_j,0x006a,KeyJ,AC07,j,0x53,0x26 -KEY_K,37,ANSI_K,0x28,0x25,0x42,0x42,14,VK_K,0x4b,37,37,XK_K,0x004b,KeyK,AC08,k,0x54,0x28 -KEY_K,37,ANSI_K,0x28,0x25,0x42,0x42,14,VK_K,0x4b,37,37,XK_k,0x006b,KeyK,AC08,k,0x54,0x28 -KEY_L,38,ANSI_L,0x25,0x26,0x4b,0x4b,15,VK_L,0x4c,38,38,XK_L,0x004c,KeyL,AC09,l,0x55,0x25 -KEY_L,38,ANSI_L,0x25,0x26,0x4b,0x4b,15,VK_L,0x4c,38,38,XK_l,0x006c,KeyL,AC09,l,0x55,0x25 -KEY_SEMICOLON,39,ANSI_Semicolon,0x29,0x27,0x4c,0x4c,51,VK_OEM_1,0xba,39,39,XK_semicolon,0x003b,Semicolon,AC10,semicolon,0x56,0x29 -KEY_SEMICOLON,39,ANSI_Semicolon,0x29,0x27,0x4c,0x4c,51,VK_OEM_1,0xba,39,39,XK_colon,0x003a,Semicolon,AC10,semicolon,0x56,0x29 -KEY_APOSTROPHE,40,ANSI_Quote,0x27,0x28,0x52,0x52,52,VK_OEM_7,0xde,40,40,XK_apostrophe,0x0027,Quote,AC11,apostrophe,0x57,0x27 -KEY_APOSTROPHE,40,ANSI_Quote,0x27,0x28,0x52,0x52,52,VK_OEM_7,0xde,40,40,XK_quotedbl,0x0022,Quote,AC11,apostrophe,0x57,0x27 -KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_grave,0x0060,Backquote,TLDE,grave_accent,0x2a,0x32 -KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_grave,0x0060,Backquote,AB00,grave_accent,0x2a,0x32 -KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_asciitilde,0x007e,Backquote,TLDE,grave_accent,0x2a,0x32 -KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_asciitilde,0x007e,Backquote,AB00,grave_accent,0x2a,0x32 -KEY_SHIFT,42,Shift,0x38,0x2a,0x12,0x12,225,VK_SHIFT,0x10,42,42,XK_Shift_L,0xffe1,ShiftLeft,LFSH,shift,0x63,0x38 -KEY_LEFTSHIFT,42,Shift,0x38,0x2a,0x12,0x12,225,VK_LSHIFT,0xa0,42,42,XK_Shift_L,0xffe1,ShiftLeft,LFSH,shift,0x63,0x38 -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,BKSL,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,AC12,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,BKSL,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,AC12,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,BKSL,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,AC12,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,BKSL,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,AC12,backslash,0x58,0x2a -KEY_Z,44,ANSI_Z,0x6,0x2c,0x1a,0x1a,29,VK_Z,0x5a,44,44,XK_Z,0x005a,KeyZ,AB01,z,0x64,0x6 -KEY_Z,44,ANSI_Z,0x6,0x2c,0x1a,0x1a,29,VK_Z,0x5a,44,44,XK_z,0x007a,KeyZ,AB01,z,0x64,0x6 -KEY_X,45,ANSI_X,0x7,0x2d,0x22,0x22,27,VK_X,0x58,45,45,XK_X,0x0058,KeyX,AB02,x,0x65,0x7 -KEY_X,45,ANSI_X,0x7,0x2d,0x22,0x22,27,VK_X,0x58,45,45,XK_x,0x0078,KeyX,AB02,x,0x65,0x7 -KEY_C,46,ANSI_C,0x8,0x2e,0x21,0x21,6,VK_C,0x43,46,46,XK_C,0x0043,KeyC,AB03,c,0x66,0x8 -KEY_C,46,ANSI_C,0x8,0x2e,0x21,0x21,6,VK_C,0x43,46,46,XK_c,0x0063,KeyC,AB03,c,0x66,0x8 -KEY_V,47,ANSI_V,0x9,0x2f,0x2a,0x2a,25,VK_V,0x56,47,47,XK_V,0x0056,KeyV,AB04,v,0x67,0x9 -KEY_V,47,ANSI_V,0x9,0x2f,0x2a,0x2a,25,VK_V,0x56,47,47,XK_v,0x0076,KeyV,AB04,v,0x67,0x9 -KEY_B,48,ANSI_B,0xb,0x30,0x32,0x32,5,VK_B,0x42,48,48,XK_B,0x0042,KeyB,AB05,b,0x68,0xb -KEY_B,48,ANSI_B,0xb,0x30,0x32,0x32,5,VK_B,0x42,48,48,XK_b,0x0062,KeyB,AB05,b,0x68,0xb -KEY_N,49,ANSI_N,0x2d,0x31,0x31,0x31,17,VK_N,0x4e,49,49,XK_N,0x004e,KeyN,AB06,n,0x69,0x2d -KEY_N,49,ANSI_N,0x2d,0x31,0x31,0x31,17,VK_N,0x4e,49,49,XK_n,0x006e,KeyN,AB06,n,0x69,0x2d -KEY_M,50,ANSI_M,0x2e,0x32,0x3a,0x3a,16,VK_M,0x4d,50,50,XK_M,0x004d,KeyM,AB07,m,0x6a,0x2e -KEY_M,50,ANSI_M,0x2e,0x32,0x3a,0x3a,16,VK_M,0x4d,50,50,XK_m,0x006d,KeyM,AB07,m,0x6a,0x2e -KEY_COMMA,51,ANSI_Comma,0x2b,0x33,0x41,0x41,54,VK_OEM_COMMA,0xbc,51,51,XK_comma,0x002c,Comma,AB08,comma,0x6b,0x2b -KEY_COMMA,51,ANSI_Comma,0x2b,0x33,0x41,0x41,54,VK_OEM_COMMA,0xbc,51,51,XK_less,0x003c,Comma,AB08,comma,0x6b,0x2b -KEY_DOT,52,ANSI_Period,0x2f,0x34,0x49,0x49,55,VK_OEM_PERIOD,0xbe,52,52,XK_period,0x002e,Period,AB09,dot,0x6c,0x2f -KEY_DOT,52,ANSI_Period,0x2f,0x34,0x49,0x49,55,VK_OEM_PERIOD,0xbe,52,52,XK_greater,0x003e,Period,AB09,dot,0x6c,0x2f -KEY_SLASH,53,ANSI_Slash,0x2c,0x35,0x4a,0x4a,56,VK_OEM_2,0xbf,53,53,XK_slash,0x002f,Slash,AB10,slash,0x6d,0x2c -KEY_SLASH,53,ANSI_Slash,0x2c,0x35,0x4a,0x4a,56,VK_OEM_2,0xbf,53,53,XK_question,0x003f,Slash,AB10,slash,0x6d,0x2c -KEY_RIGHTSHIFT,54,RightShift,0x3c,0x36,0x59,0x59,229,VK_RSHIFT,0xa1,54,54,XK_Shift_R,0xffe2,ShiftRight,RTSH,shift_r,0x6e,0x7b -KEY_KPASTERISK,55,ANSI_KeypadMultiply,0x43,0x37,0x7c,0x7e,85,VK_MULTIPLY,0x6a,55,55,XK_multiply,0x00d7,NumpadMultiply,KPMU,asterisk,0x2f,0x43 -KEY_KPASTERISK,55,ANSI_KeypadMultiply,0x43,0x37,0x7c,0x7e,85,VK_MULTIPLY,0x6a,55,55,XK_multiply,0x00d7,NumpadMultiply,KPMU,kp_multiply,0x2f,0x43 -KEY_LEFTALT,56,Option,0x3a,0x38,0x11,0x19,226,VK_LMENU,0xa4,56,56,XK_Alt_L,0xffe9,AltLeft,LALT,alt,0x13,0x3a -KEY_LEFTALT,56,Option,0x3a,0x38,0x11,0x19,226,VK_MENU,0x12,56,56,XK_Alt_L,0xffe9,AltLeft,LALT,alt,0x13,0x3a -KEY_SPACE,57,Space,0x31,0x39,0x29,0x29,44,VK_SPACE,0x20,57,57,XK_space,0x0020,Space,SPCE,spc,0x79,0x31 -KEY_CAPSLOCK,58,CapsLock,0x39,0x3a,0x58,0x14,57,VK_CAPITAL,0x14,58,58,XK_Caps_Lock,0xffe5,CapsLock,CAPS,caps_lock,0x77,0x39 -KEY_F1,59,F1,0x7a,0x3b,0x05,0x07,58,VK_F1,0x70,59,59,XK_F1,0xffbe,F1,FK01,f1,0x05,0x7a -KEY_F2,60,F2,0x78,0x3c,0x06,0x0f,59,VK_F2,0x71,60,60,XK_F2,0xffbf,F2,FK02,f2,0x06,0x78 -KEY_F3,61,F3,0x63,0x3d,0x04,0x17,60,VK_F3,0x72,61,61,XK_F3,0xffc0,F3,FK03,f3,0x08,0x63 -KEY_F4,62,F4,0x76,0x3e,0x0c,0x1f,61,VK_F4,0x73,62,62,XK_F4,0xffc1,F4,FK04,f4,0x0a,0x76 -KEY_F5,63,F5,0x60,0x3f,0x03,0x27,62,VK_F5,0x74,63,63,XK_F5,0xffc2,F5,FK05,f5,0x0c,0x60 -KEY_F6,64,F6,0x61,0x40,0x0b,0x2f,63,VK_F6,0x75,64,64,XK_F6,0xffc3,F6,FK06,f6,0x0e,0x61 -KEY_F7,65,F7,0x62,0x41,0x83,0x37,64,VK_F7,0x76,65,65,XK_F7,0xffc4,F7,FK07,f7,0x10,0x62 -KEY_F8,66,F8,0x64,0x42,0x0a,0x3f,65,VK_F8,0x77,66,66,XK_F8,0xffc5,F8,FK08,f8,0x11,0x64 -KEY_F9,67,F9,0x65,0x43,0x01,0x47,66,VK_F9,0x78,67,67,XK_F9,0xffc6,F9,FK09,f9,0x12,0x65 -KEY_F10,68,F10,0x6d,0x44,0x09,0x4f,67,VK_F10,0x79,68,68,XK_F10,0xffc7,F10,FK10,f10,0x07,0x6d -KEY_NUMLOCK,69,ANSI_KeypadClear,0x47,0x45,0x77,0x76,83,VK_NUMLOCK,0x90,69,69,XK_Num_Lock,0xff7f,NumLock,NMLK,num_lock,0x62,0x47 -KEY_SCROLLLOCK,70,,,0x46,0x7e,0x5f,71,VK_SCROLL,0x91,70,70,XK_Scroll_Lock,0xff14,ScrollLock,SCLK,scroll_lock,0x17,0x6b -KEY_KP7,71,ANSI_Keypad7,0x59,0x47,0x6c,0x6c,95,VK_NUMPAD7,0x67,71,71,XK_KP_7,0xffb7,Numpad7,KP7,kp_7,0x44,0x59 -KEY_KP8,72,ANSI_Keypad8,0x5b,0x48,0x75,0x75,96,VK_NUMPAD8,0x68,72,72,XK_KP_8,0xffb8,Numpad8,KP8,kp_8,0x45,0x5b -KEY_KP9,73,ANSI_Keypad9,0x5c,0x49,0x7d,0x7d,97,VK_NUMPAD9,0x69,73,73,XK_KP_9,0xffb9,Numpad9,KP9,kp_9,0x46,0x5c -KEY_KPMINUS,74,ANSI_KeypadMinus,0x4e,0x4a,0x7b,0x4e,86,VK_SUBTRACT,0x6d,74,74,XK_KP_Subtract,0xffad,NumpadSubtract,KPSU,kp_subtract,0x47,0x4e -KEY_KP4,75,ANSI_Keypad4,0x56,0x4b,0x6b,0x6b,92,VK_NUMPAD4,0x64,75,75,XK_KP_4,0xffb4,Numpad4,KP4,kp_4,0x5b,0x56 -KEY_KP5,76,ANSI_Keypad5,0x57,0x4c,0x73,0x73,93,VK_NUMPAD5,0x65,76,76,XK_KP_5,0xffb5,Numpad5,KP5,kp_5,0x5c,0x57 -KEY_KP6,77,ANSI_Keypad6,0x58,0x4d,0x74,0x74,94,VK_NUMPAD6,0x66,77,77,XK_KP_6,0xffb6,Numpad6,KP6,kp_6,0x5d,0x58 -KEY_KPPLUS,78,ANSI_KeypadPlus,0x45,0x4e,0x79,0x7c,87,VK_ADD,0x6b,78,78,XK_KP_Add,0xffab,NumpadAdd,KPAD,kp_add,0x7d,0x45 -KEY_KP1,79,ANSI_Keypad1,0x53,0x4f,0x69,0x69,89,VK_NUMPAD1,0x61,79,79,XK_KP_1,0xffb1,Numpad1,KP1,kp_1,0x70,0x53 -KEY_KP2,80,ANSI_Keypad2,0x54,0x50,0x72,0x72,90,VK_NUMPAD2,0x62,80,80,XK_KP_2,0xffb2,Numpad2,KP2,kp_2,0x71,0x54 -KEY_KP3,81,ANSI_Keypad3,0x55,0x51,0x7a,0x7a,91,VK_NUMPAD3,0x63,81,81,XK_KP_3,0xffb3,Numpad3,KP3,kp_3,0x72,0x55 -KEY_KP0,82,ANSI_Keypad0,0x52,0x52,0x70,0x70,98,VK_NUMPAD0,0x60,82,82,XK_KP_0,0xffb0,Numpad0,KP0,kp_0,0x5e,0x52 -KEY_KPDOT,83,ANSI_KeypadDecimal,0x41,0x53,0x71,0x71,99,VK_DECIMAL,0x6e,83,83,XK_KP_Decimal,0xffae,NumpadDecimal,KPDL,kp_decimal,0x32,0x41 -KEY_KPDOT,83,ANSI_KeypadDecimal,0x41,0x53,0x71,0x71,99,VK_DECIMAL,0x6e,83,83,XK_KP_Decimal,0xffae,NumpadDecimal,KPDC,kp_decimal,0x32,0x41 -,84,,,0x54,,,,,,,,,,,,,, -KEY_ZENKAKUHANKAKU,85,,,0x76,0x5f,,148,,,,,,,Lang5,HZTG,,, -KEY_102ND,86,,,0x56,0x61,0x13,100,VK_OEM_102,0xe2,86,86,,,IntlBackslash,LSGT,less,0x7c, -KEY_F11,87,F11,0x67,0x57,0x78,0x56,68,VK_F11,0x7a,87,87,XK_F11,0xffc8,F11,FK11,f11,0x09,0x67 -KEY_F12,88,F12,0x6f,0x58,0x07,0x5e,69,VK_F12,0x7b,88,88,XK_F12,0xffc9,F12,FK12,f12,0x0b,0x6f -KEY_RO,89,JIS_Underscore,0x5e,0x73,0x51,,135,,,,,,,IntlRo,AB11,ro,, -KEY_KATAKANA,90,,,0x78,0x63,,146,VK_KANA,0x15,,,,,Katakana,KATA,,, -KEY_KATAKANA,90,,,0x78,0x63,,146,VK_KANA,0x15,,,,,Lang3,KATA,,, -KEY_HIRAGANA,91,,,0x77,0x62,0x87,147,,,,,,,Hiragana,HIRA,hiragana,, -KEY_HIRAGANA,91,,,0x77,0x62,0x87,147,,,,,,,Lang4,HIRA,hiragana,, -KEY_HENKAN,92,,,0x79,0x64,0x86,138,,,,,,,Convert,HENK,henkan,, -KEY_KATAKANAHIRAGANA,93,,,0x70,0x13,0x87,136,,,0xc8,0xc8,,,KanaMode,HKTG,katakanahiragana,, -KEY_MUHENKAN,94,,,0x7b,0x67,0x85,139,,,,,,,NonConvert,NFER,muhenkan,, -KEY_MUHENKAN,94,,,0x7b,0x67,0x85,139,,,,,,,NonConvert,MUHE,muhenkan,, -KEY_KPJPCOMMA,95,JIS_KeypadComma,0x5f,0x5c,0x27,,140,,,,,XK_KP_Separator,0xffac,,KPSP,,, -KEY_KPJPCOMMA,95,JIS_KeypadComma,0x5f,0x5c,0x27,,140,,,,,XK_KP_Separator,0xffac,,JPCM,,, -KEY_KPENTER,96,ANSI_KeypadEnter,0x4c,0xe01c,0xe05a,0x79,88,,,0x64,0x64,XK_KP_Enter,0xff8d,NumpadEnter,KPEN,kp_enter,0x5a,0x4c -KEY_RIGHTCTRL,97,RightControl,0x3e,0xe01d,0xe014,0x58,228,VK_RCONTROL,0xa3,0x65,0x65,XK_Control_R,0xffe4,ControlRight,RCTL,ctrl_r,0x4c,0x7d -KEY_KPSLASH,98,ANSI_KeypadDivide,0x4b,0xe035,0xe04a,0x4a,84,VK_DIVIDE,0x6f,0x68,0x68,XK_KP_Divide,0xffaf,NumpadDivide,KPDV,kp_divide,0x2e,0x4b -KEY_SYSRQ,99,,,0x54,0x7f,0x57,70,VK_SNAPSHOT,0x2c,0x67,0x67,XK_Sys_Req,0xff15,PrintScreen,PRSC,print,0x16,0x69 -KEY_SYSRQ,99,,,0x54,0x7f,0x57,70,VK_SNAPSHOT,0x2c,0x67,0x67,XK_Sys_Req,0xff15,PrintScreen,SYRQ,sysrq,0x16,0x69 -KEY_RIGHTALT,100,RightOption,0x3d,0xe038,0xe011,0x39,230,VK_RMENU,0xa5,0x69,0x69,XK_Alt_R,0xffea,AltRight,ALGR,alt_r,0x0d,0x7c -KEY_RIGHTALT,100,RightOption,0x3d,0xe038,0xe011,0x39,230,VK_RMENU,0xa5,0x69,0x69,XK_Alt_R,0xffea,AltRight,RALT,alt_r,0x0d,0x7c -KEY_LINEFEED,101,,,0x5b,,,,,,,,,,,LNFD,lf,0x6f, -KEY_HOME,102,Home,0x73,0xe047,0xe06c,0x6e,74,VK_HOME,0x24,0x59,0x59,XK_Home,0xff50,Home,HOME,home,0x34,0x73 -KEY_UP,103,UpArrow,0x7e,0xe048,0xe075,0x63,82,VK_UP,0x26,0x5a,0x5a,XK_Up,0xff52,ArrowUp,UP,up,0x14,0x3e -KEY_PAGEUP,104,PageUp,0x74,0xe049,0xe07d,0x6f,75,VK_PRIOR,0x21,0x5b,0x5b,XK_Page_Up,0xff55,PageUp,PGUP,pgup,0x60,0x74 -KEY_LEFT,105,LeftArrow,0x7b,0xe04b,0xe06b,0x61,80,VK_LEFT,0x25,0x5c,0x5c,XK_Left,0xff51,ArrowLeft,LEFT,left,0x18,0x3b -KEY_RIGHT,106,RightArrow,0x7c,0xe04d,0xe074,0x6a,79,VK_RIGHT,0x27,0x5e,0x5e,XK_Right,0xff53,ArrowRight,RGHT,right,0x1c,0x3c -KEY_END,107,End,0x77,0xe04f,0xe069,0x65,77,VK_END,0x23,0x5f,0x5f,XK_End,0xff57,End,END,end,0x4a,0x77 -KEY_DOWN,108,DownArrow,0x7d,0xe050,0xe072,0x60,81,VK_DOWN,0x28,0x60,0x60,XK_Down,0xff54,ArrowDown,DOWN,down,0x1b,0x3d -KEY_PAGEDOWN,109,PageDown,0x79,0xe051,0xe07a,0x6d,78,VK_NEXT,0x22,0x61,0x61,XK_Page_Down,0xff56,PageDown,PGDN,pgdn,0x7b,0x79 -KEY_INSERT,110,,,0xe052,0xe070,0x67,73,VK_INSERT,0x2d,0x62,0x62,XK_Insert,0xff63,Insert,INS,insert,0x2c,0x72 -KEY_DELETE,111,ForwardDelete,0x75,0xe053,0xe071,0x64,76,VK_DELETE,0x2e,0x63,0x63,XK_Delete,0xffff,Delete,DEL,delete,0x42,0x75 -KEY_DELETE,111,ForwardDelete,0x75,0xe053,0xe071,0x64,76,VK_DELETE,0x2e,0x63,0x63,XK_Delete,0xffff,Delete,DELE,,0x42,0x75 -KEY_MACRO,112,,,0xe06f,0xe06f,0x8e,,,,,,,,,I120,,, -KEY_MUTE,113,Mute,0x4a,0xe020,0xe023,0x9c,127,VK_VOLUME_MUTE,0xad,,,,,AudioVolumeMute,MUTE,audiomute,, -KEY_MUTE,113,Mute,0x4a,0xe020,0xe023,0x9c,239,VK_VOLUME_MUTE,0xad,,,,,AudioVolumeMute,MUTE,audiomute,, -KEY_VOLUMEDOWN,114,VolumeDown,0x49,0xe02e,0xe021,0x9d,129,VK_VOLUME_DOWN,0xae,,,,,AudioVolumeDown,VOL-,volumedown,, -KEY_VOLUMEDOWN,114,VolumeDown,0x49,0xe02e,0xe021,0x9d,238,VK_VOLUME_DOWN,0xae,,,,,AudioVolumeDown,VOL-,volumedown,, -KEY_VOLUMEUP,115,VolumeUp,0x48,0xe030,0xe032,0x95,128,VK_VOLUME_UP,0xaf,,,,,AudioVolumeUp,VOL+,volumeup,, -KEY_VOLUMEUP,115,VolumeUp,0x48,0xe030,0xe032,0x95,237,VK_VOLUME_UP,0xaf,,,,,AudioVolumeUp,VOL+,volumeup,, -KEY_POWER,116,,,0xe05e,0xe037,,102,,,,,,,Power,POWR,power,,0x7f7f -KEY_KPEQUAL,117,ANSI_KeypadEquals,0x51,0x59,0x0f,,103,,,0x76,0x76,XK_KP_Equal,0xffbd,NumpadEqual,KPEQ,kp_equals,0x2d,0x51 -KEY_KPPLUSMINUS,118,,,0xe04e,0xe079,,,,,,,,,,I126,,, -KEY_PAUSE,119,,,0xe046,0xe077,0x62,72,VK_PAUSE,0x013,0x66,0x66,XK_Pause,0xff13,Pause,PAUS,pause,0x15,0x71 -KEY_SCALE,120,,,0xe00b,,,,,,,,,,,I128,,, -KEY_KPCOMMA,121,,,0x7e,0x6d,,133,VK_SEPARATOR??,0x6c,,,,,NumpadComma,KPCO,kp_comma,, -KEY_KPCOMMA,121,,,0x7e,0x6d,,133,VK_SEPARATOR??,0x6c,,,,,NumpadComma,I129,,, -KEY_HANGEUL,122,JIS_Kana,0x68,0x72,,,144,VK_HANGEUL,0x15,,0x71,,,Lang1,HNGL,,, -KEY_HANJA,123,JIS_Eisu,0x66,0x71,,,145,VK_HANJA,0x19,,0x72,,,Lang2,HJCV,,, -KEY_YEN,124,JIS_Yen,0x5d,0x7d,0x6a,0x5d,137,,,0x7d,0x7d,,,IntlYen,AE13,yen,, -KEY_LEFTMETA,125,Command,0x37,0xe05b,0xe01f,0x8b,227,VK_LWIN,0x5b,0x6b,0x6b,XK_Meta_L,0xffe7,MetaLeft,LMTA,meta_l,0x78,0x37 -KEY_LEFTMETA,125,Command,0x37,0xe05b,0xe01f,0x8b,227,VK_LWIN,0x5b,0x6b,0x6b,XK_Meta_L,0xffe7,MetaLeft,LWIN,meta_l,0x78,0x37 -KEY_RIGHTMETA,126,RightCommand,0x36,0xe05c,0xe027,0x8c,231,VK_RWIN,0x5c,0x6c,0x6c,XK_Meta_R,0xffe8,MetaRight,RMTA,meta_r,0x7a,0x37 -KEY_RIGHTMETA,126,RightCommand,0x36,0xe05c,0xe027,0x8c,231,VK_RWIN,0x5c,0x6c,0x6c,XK_Meta_R,0xffe8,MetaRight,RWIN,meta_r,0x7a,0x37 -KEY_COMPOSE,127,,0x6e,0xe05d,0xe02f,0x8d,101,VK_APPS,0x5d,0x6d,0x6d,,,ContextMenu,MENU,compose,0x43, -KEY_COMPOSE,127,,0x6e,0xe05d,0xe02f,0x8d,101,VK_APPS,0x5d,0x6d,0x6d,,,ContextMenu,COMP,compose,0x43, -KEY_STOP,128,,,0xe068,0xe028,0x0a,120,VK_BROWSER_STOP,0xa9,,,,,BrowserStop,STOP,stop,0x01, -KEY_STOP,128,,,0xe068,0xe028,0x0a,243,VK_BROWSER_STOP,0xa9,,,,,BrowserStop,STOP,stop,0x01, -KEY_AGAIN,129,,,0xe005,,0x0b,121,,,,,,,Again,AGAI,again,0x03, -KEY_PROPS,130,,,0xe006,,0x0c,,,,,,,,Props,PROP,props,0x19, -KEY_UNDO,131,,,0xe007,,0x10,122,,,,,,,Undo,UNDO,undo,0x1a, -KEY_FRONT,132,,,0xe00c,,,119,,,,,,,,FRNT,front,0x31, -KEY_COPY,133,,,0xe078,,0x18,124,,,,,,,Copy,COPY,copy,0x33, -KEY_OPEN,134,,,0x64,,0x20,116,,,,,,,Open,OPEN,open,0x48, -KEY_PASTE,135,,,0x65,,0x28,125,,,,,,,Paste,PAST,paste,0x49, -KEY_FIND,136,,,0xe041,,0x30,126,,,,,,,Find,FIND,find,0x5f, -KEY_FIND,136,,,0xe041,,0x30,244,,,,,,,Find,FIND,find,0x5f, -KEY_CUT,137,,,0xe03c,,0x38,123,,,,,,,Cut,CUT,cut,0x61, -KEY_HELP,138,Help,0x72,0xe075,,0x09,117,VK_HELP,0x2f,,,XK_Help,0xff6a,Help,HELP,help,0x76, -KEY_MENU,139,,,0xe01e,,0x91,118,,,,,,,,I147,menu,, -KEY_CALC,140,,,0xe021,0xe02b,0xa3,251,,,,,,,LaunchApp2,I148,calculator,, -KEY_SETUP,141,,,0x66,,,,,,,,,,,I149,,, -KEY_SLEEP,142,,,0xe05f,0xe03f,,248,VK_SLEEP,0x5f,,,,,Sleep,I150,sleep,, -KEY_WAKEUP,143,,,0xe063,0xe05e,,,,,,,,,WakeUp,I151,wake,, -KEY_FILE,144,,,0x67,,,,,,,,,,,I152,,, -KEY_SENDFILE,145,,,0x68,,,,,,,,,,,I153,,, -KEY_DELETEFILE,146,,,0x69,,,,,,,,,,,I154,,, -KEY_XFER,147,,,0xe013,,0xa2,,,,,,,,,XFER,,, -KEY_XFER,147,,,0xe013,,0xa2,,,,,,,,,I155,,, -KEY_PROG1,148,,,0xe01f,,0xa0,,,,,,,,,I156,,, -KEY_PROG2,149,,,0xe017,,0xa1,,,,,,,,,I157,,, -KEY_WWW,150,,,0xe002,,,240,,,,,,,,I158,,, -KEY_MSDOS,151,,,0x6a,,,,,,,,,,,I159,,, -KEY_SCREENLOCK,152,,,0xe012,,0x96,249,,,,,,,,I160,,, -KEY_DIRECTION,153,,,0x6b,,,,,,,,,,,I161,,, -KEY_CYCLEWINDOWS,154,,,0xe026,,0x9b,,,,,,,,,I162,,, -KEY_MAIL,155,,,0xe06c,0xe048,,,,,,,,,LaunchMail,I163,mail,, -KEY_BOOKMARKS,156,,,0xe066,0xe018,,,,,,,,,BrowserFavorites,I164,ac_bookmarks,, -KEY_COMPUTER,157,,,0xe06b,0xe040,,,,,,,,,LaunchApp1,I165,computer,, -KEY_BACK,158,,,0xe06a,0xe038,,241,VK_BROWSER_BACK,0xa6,,,,,BrowserBack,I166,ac_back,, -KEY_FORWARD,159,,,0xe069,0xe030,,242,VK_BROWSER_FORWARD,0xa7,,,,,BrowserForward,I167,ac_forward,, -KEY_CLOSECD,160,,,0xe023,,0x9a,,,,,,,,,I168,,, -KEY_EJECTCD,161,,,0x6c,,,236,,,,,,,,I169,,, -KEY_EJECTCLOSECD,162,,,0xe07d,,,,,,,,,,Eject,I170,,, -KEY_NEXTSONG,163,,,0xe019,0xe04d,0x93,235,VK_MEDIA_NEXT_TRACK,0xb0,,,,,MediaTrackNext,I171,audionext,, -KEY_PLAYPAUSE,164,,,0xe022,0xe034,,232,VK_MEDIA_PLAY_PAUSE,0xb3,,,,,MediaPlayPause,I172,audioplay,, -KEY_PREVIOUSSONG,165,,,0xe010,0xe015,0x94,234,VK_MEDIA_PREV_TRACK,0xb1,,,,,MediaTrackPrevious,I173,audioprev,, -KEY_STOPCD,166,,,0xe024,0xe03b,0x98,233,VK_MEDIA_STOP,0xb2,,,,,MediaStop,I174,audiostop,, -KEY_RECORD,167,,,0xe031,,0x9e,,,,,,,,,I175,,, -KEY_REWIND,168,,,0xe018,,0x9f,,,,,,,,,I176,,, -KEY_PHONE,169,,,0x63,,,,,,,,,,,I177,,, -KEY_ISO,170,ISO_Section,0xa,,,,,,,,,,,,I178,,, -KEY_CONFIG,171,,,0xe001,,,,,,,,,,,I179,,, -KEY_HOMEPAGE,172,,,0xe032,0xe03a,0x97,,VK_BROWSER_HOME,0xac,,,,,BrowserHome,I180,ac_home,, -KEY_REFRESH,173,,,0xe067,0xe020,,250,VK_BROWSER_REFRESH,0xa8,,,,,BrowserRefresh,I181,ac_refresh,, -KEY_EXIT,174,,,,,,,,,,,,,,I182,,, -KEY_MOVE,175,,,,,,,,,,,,,,I183,,, -KEY_EDIT,176,,,0xe008,,,247,,,,,,,,I184,,, -KEY_SCROLLUP,177,,,0x75,,,245,,,,,,,,I185,,, -KEY_SCROLLDOWN,178,,,0xe00f,,,246,,,,,,,,I186,,, -KEY_KPLEFTPAREN,179,,,0xe076,,,182,,,,,,,NumpadParenLeft,I187,,, -KEY_KPRIGHTPAREN,180,,,0xe07b,,,183,,,,,,,NumpadParenRight,I188,,, -KEY_NEW,181,,,0xe009,,,,,,,,,,,I189,,, -KEY_REDO,182,,,0xe00a,,,,,,,,,,,I190,,, -KEY_F13,183,F13,0x69,0x5d,0x2f,0x7f,104,VK_F13,0x7c,0x6e,0x6e,,,F13,FK13,,,0x69 -KEY_F14,184,F14,0x6b,0x5e,0x37,0x80,105,VK_F14,0x7d,0x6f,0x6f,,,F14,FK14,,,0x6b -KEY_F15,185,F15,0x71,0x5f,0x3f,0x81,106,VK_F15,0x7e,0x70,0x70,,,F15,FK15,,,0x71 -KEY_F16,186,F16,0x6a,0x55,,0x82,107,VK_F16,0x7f,0x71,0x71,,,F16,FK16,,, -KEY_F17,187,F17,0x40,0xe003,,0x83,108,VK_F17,0x80,0x72,0x72,,,F17,FK17,,, -KEY_F18,188,F18,0x4f,0xe077,,,109,VK_F18,0x81,,,,,F18,FK18,,, -KEY_F19,189,F19,0x50,0xe004,,,110,VK_F19,0x82,,,,,F19,FK19,,, -KEY_F20,190,F20,0x5a,0x5a,,,111,VK_F20,0x83,,,,,F20,FK20,,, -KEY_F21,191,,,0x74,,,112,VK_F21,0x84,,,,,F21,FK21,,, -KEY_F22,192,,,0xe079,,,113,VK_F22,0x85,,,,,F22,FK22,,, -KEY_F23,193,,,0x6d,,,114,VK_F23,0x86,,,,,F23,FK23,,, -KEY_F24,194,,,0x6f,,,115,VK_F24,0x87,,,,,F24,FK24,,, -,195,,,0xe015,,,,,,,,,,,,,, -,196,,,0xe016,,,,,,,,,,,,,, -,197,,,0xe01a,,,,,,,,,,,,,, -,198,,,0xe01b,,,,,,,,,,,,,, -,199,,,0xe027,,,,,,,,,,,,,, -KEY_PLAYCD,200,,,0xe028,,,,,,,,,,,I208,,, -KEY_PAUSECD,201,,,0xe029,,,,,,,,,,,I209,,, -KEY_PROG3,202,,,0xe02b,,,,,,,,,,,I210,,, -KEY_PROG4,203,,,0xe02c,,,,,,,,,,,I211,,, -KEY_DASHBOARD,204,,,0xe02d,,,,,,,,,,,I212,,, -KEY_SUSPEND,205,,,0xe025,,,,,,,,,,Suspend,I213,,, -KEY_CLOSE,206,,,0xe02f,,,,,,,,,,,I214,,, -KEY_PLAY,207,,,0xe033,,,,VK_PLAY,0xfa,,,,,,I215,,, -KEY_FASTFORWARD,208,,,0xe034,,,,,,,,,,,I216,,, -KEY_BASSBOOST,209,,,0xe036,,,,,,,,,,,I217,,, -KEY_PRINT,210,,,0xe039,,,,VK_PRINT,0x2a,,,,,,I218,,, -KEY_HP,211,,,0xe03a,,,,,,,,,,,I219,,, -KEY_CAMERA,212,,,0xe03b,,,,,,,,,,,I220,,, -KEY_SOUND,213,,,0xe03d,,,,,,,,,,,I221,,, -KEY_QUESTION,214,,,0xe03e,,,,,,,,,,,I222,,, -KEY_EMAIL,215,,,0xe03f,,,,VK_LAUNCH_MAIL,0xb4,,,,,,I223,,, -KEY_CHAT,216,,,0xe040,,,,,,,,,,,I224,,, -KEY_SEARCH,217,,,0xe065,0xe010,,,VK_BROWSER_SEARCH,0xaa,,,,,BrowserSearch,I225,,, -KEY_CONNECT,218,,,0xe042,,,,,,,,,,,I226,,, -KEY_FINANCE,219,,,0xe043,,,,,,,,,,,I227,,, -KEY_SPORT,220,,,0xe044,,,,,,,,,,,I228,,, -KEY_SHOP,221,,,0xe045,,,,,,,,,,,I229,,, -KEY_ALTERASE,222,,,0xe014,,,,,,,,,,,I230,,, -KEY_CANCEL,223,,,0xe04a,,,,,,,,,,,I231,,, -KEY_BRIGHTNESSDOWN,224,,,0xe04c,,,,,,,,,,,I232,,, -KEY_BRIGHTNESSUP,225,,,0xe054,,,,,,,,,,,I233,,, -KEY_MEDIA,226,,,0xe06d,0xe050,,,,,,,,,MediaSelect,I234,mediaselect,, -KEY_SWITCHVIDEOMODE,227,,,0xe056,,,,,,,,,,,I235,,, -KEY_KBDILLUMTOGGLE,228,,,0xe057,,,,,,,,,,,I236,,, -KEY_KBDILLUMDOWN,229,,,0xe058,,,,,,,,,,,I237,,, -KEY_KBDILLUMUP,230,,,0xe059,,,,,,,,,,,I238,,, -KEY_SEND,231,,,0xe05a,,,,,,,,,,,I239,,, -KEY_REPLY,232,,,0xe064,,,,,,,,,,,I240,,, -KEY_FORWARDMAIL,233,,,0xe00e,,,,,,,,,,,I241,,, -KEY_SAVE,234,,,0xe055,,,,,,,,,,,I242,,, -KEY_DOCUMENTS,235,,,0xe070,,,,,,,,,,,I243,,, -KEY_BATTERY,236,,,0xe071,,,,,,,,,,,I244,,, -KEY_BLUETOOTH,237,,,0xe072,,,,,,,,,,,I245,,, -KEY_WLAN,238,,,0xe073,,,,,,,,,,,I246,,, -KEY_UWB,239,,,0xe074,,,,,,,,,,,I247,,, -KEY_UNKNOWN,240,,,,,,,,,,,,,,I248,,, -KEY_VIDEO_NEXT,241,,,,,,,,,,,,,,I249,,, -KEY_VIDEO_PREV,242,,,,,,,,,,,,,,I250,,, -KEY_BRIGHTNESS_CYCLE,243,,,,,,,,,,,,,,I251,,, -KEY_BRIGHTNESS_ZERO,244,,,,,,,,,,,,,,I252,,, -KEY_DISPLAY_OFF,245,,,,,,,,,,,,,,I253,,, -KEY_WIMAX,246,,,,,,,,,,,,,,,,, -,247,,,,,,,,,,,,,,,,, -,248,,,,,,,,,,,,,,,,, -,249,,,,,,,,,,,,,,,,, -,250,,,,,,,,,,,,,,,,, -,251,,,,,,,,,,,,,,,,, -,252,,,,,,,,,,,,,,,,, -,253,,,,,,,,,,,,,,,,, -,254,,,,,,,,,,,,,,,,, -,255,,,,0xe012,,,,,,,,,,,,, -BTN_MISC,0x100,,,,,,,,,,,,,,,,, -BTN_0,0x100,,,,,,,VK_LBUTTON,0x01,,,,,,,,, -BTN_1,0x101,,,,,,,VK_RBUTTON,0x02,,,,,,,,, -BTN_2,0x102,,,,,,,VK_MBUTTON,0x04,,,,,,,,, -BTN_3,0x103,,,,,,,VK_XBUTTON1,0x05,,,,,,,,, -BTN_4,0x104,,,,,,,VK_XBUTTON2,0x06,,,,,,,,, -BTN_5,0x105,,,,,,,,,,,,,,,,, -BTN_6,0x106,,,,,,,,,,,,,,,,, -BTN_7,0x107,,,,,,,,,,,,,,,,, -BTN_8,0x108,,,,,,,,,,,,,,,,, -BTN_9,0x109,,,,,,,,,,,,,,,,, -BTN_MOUSE,0x110,,,,,,,,,,,,,,,,, -BTN_LEFT,0x110,,,,,,,,,,,,,,,,, -BTN_RIGHT,0x111,,,,,,,,,,,,,,,,, -BTN_MIDDLE,0x112,,,,,,,,,,,,,,,,, -BTN_SIDE,0x113,,,,,,,,,,,,,,,,, -BTN_EXTRA,0x114,,,,,,,,,,,,,,,,, -BTN_FORWARD,0x115,,,,,,,,,,,,,,,,, -BTN_BACK,0x116,,,,,,,,,,,,,,,,, -BTN_TASK,0x117,,,,,,,,,,,,,,,,, -BTN_JOYSTICK,0x120,,,,,,,,,,,,,,,,, -BTN_TRIGGER,0x120,,,,,,,,,,,,,,,,, -BTN_THUMB,0x121,,,,,,,,,,,,,,,,, -BTN_THUMB2,0x122,,,,,,,,,,,,,,,,, -BTN_TOP,0x123,,,,,,,,,,,,,,,,, -BTN_TOP2,0x124,,,,,,,,,,,,,,,,, -BTN_PINKIE,0x125,,,,,,,,,,,,,,,,, -BTN_BASE,0x126,,,,,,,,,,,,,,,,, -BTN_BASE2,0x127,,,,,,,,,,,,,,,,, -BTN_BASE3,0x128,,,,,,,,,,,,,,,,, -BTN_BASE4,0x129,,,,,,,,,,,,,,,,, -BTN_BASE5,0x12a,,,,,,,,,,,,,,,,, -BTN_BASE6,0x12b,,,,,,,,,,,,,,,,, -BTN_DEAD,0x12f,,,,,,,,,,,,,,,,, -BTN_GAMEPAD,0x130,,,,,,,,,,,,,,,,, -BTN_A,0x130,,,,,,,,,,,,,,,,, -BTN_B,0x131,,,,,,,,,,,,,,,,, -BTN_C,0x132,,,,,,,,,,,,,,,,, -BTN_X,0x133,,,,,,,,,,,,,,,,, -BTN_Y,0x134,,,,,,,,,,,,,,,,, -BTN_Z,0x135,,,,,,,,,,,,,,,,, -BTN_TL,0x136,,,,,,,,,,,,,,,,, -BTN_TR,0x137,,,,,,,,,,,,,,,,, -BTN_TL2,0x138,,,,,,,,,,,,,,,,, -BTN_TR2,0x139,,,,,,,,,,,,,,,,, -BTN_SELECT,0x13a,,,,,,,,,,,,,,,,, -BTN_START,0x13b,,,,,,,,,,,,,,,,, -BTN_MODE,0x13c,,,,,,,,,,,,,,,,, -BTN_THUMBL,0x13d,,,,,,,,,,,,,,,,, -BTN_THUMBR,0x13e,,,,,,,,,,,,,,,,, -BTN_DIGI,0x140,,,,,,,,,,,,,,,,, -BTN_TOOL_PEN,0x140,,,,,,,,,,,,,,,,, -BTN_TOOL_RUBBER,0x141,,,,,,,,,,,,,,,,, -BTN_TOOL_BRUSH,0x142,,,,,,,,,,,,,,,,, -BTN_TOOL_PENCIL,0x143,,,,,,,,,,,,,,,,, -BTN_TOOL_AIRBRUSH,0x144,,,,,,,,,,,,,,,,, -BTN_TOOL_FINGER,0x145,,,,,,,,,,,,,,,,, -BTN_TOOL_MOUSE,0x146,,,,,,,,,,,,,,,,, -BTN_TOOL_LENS,0x147,,,,,,,,,,,,,,,,, -BTN_TOUCH,0x14a,,,,,,,,,,,,,,,,, -BTN_STYLUS,0x14b,,,,,,,,,,,,,,,,, -BTN_STYLUS2,0x14c,,,,,,,,,,,,,,,,, -BTN_TOOL_DOUBLETAP,0x14d,,,,,,,,,,,,,,,,, -BTN_TOOL_TRIPLETAP,0x14e,,,,,,,,,,,,,,,,, -BTN_TOOL_QUADTAP,0x14f,,,,,,,,,,,,,,,,, -BTN_WHEEL,0x150,,,,,,,,,,,,,,,,, -BTN_GEAR_DOWN,0x150,,,,,,,,,,,,,,,,, -BTN_GEAR_UP,0x151,,,,,,,,,,,,,,,,, -KEY_OK,0x160,,,,,,,,,,,,,,,,, -KEY_SELECT,0x161,,,,,,,VK_SELECT,0x29,,,XK_Select,0xff60,Select,SELE,,, -KEY_GOTO,0x162,,,,,,,,,,,,,,,,, -KEY_CLEAR,0x163,,,,,,,,,,,,,NumpadClear,CLR,,, -KEY_POWER2,0x164,,,,,,,,,,,,,,,,, -KEY_OPTION,0x165,,,,,,,,,,,,,,,,, -KEY_INFO,0x166,,,,,,,,,,,,,,,,, -KEY_TIME,0x167,,,,,,,,,,,,,,,,, -KEY_VENDOR,0x168,,,,,,,,,,,,,,,,, -KEY_ARCHIVE,0x169,,,,,,,,,,,,,,,,, -KEY_PROGRAM,0x16a,,,,,,,,,,,,,,,,, -KEY_CHANNEL,0x16b,,,,,,,,,,,,,,,,, -KEY_FAVORITES,0x16c,,,,,,,VK_BROWSER_FAVOURITES,0xab,,,,,,,,, -KEY_EPG,0x16d,,,,,,,,,,,,,,,,, -KEY_PVR,0x16e,,,,,,,,,,,,,,,,, -KEY_MHP,0x16f,,,,,,,,,,,,,,,,, -KEY_LANGUAGE,0x170,,,,,,,,,,,,,,,,, -KEY_TITLE,0x171,,,,,,,,,,,,,,,,, -KEY_SUBTITLE,0x172,,,,,,,,,,,,,,,,, -KEY_ANGLE,0x173,,,,,,,,,,,,,,,,, -KEY_ZOOM,0x174,,,,,,,VK_ZOOM,0xfb,,,,,,,,, -KEY_MODE,0x175,,,,,,,,,,,,,,,,, -KEY_KEYBOARD,0x176,,,,,,,,,,,,,,,,, -KEY_SCREEN,0x177,,,,,,,,,,,,,,,,, -KEY_PC,0x178,,,,,,,,,,,,,,,,, -KEY_TV,0x179,,,,,,,,,,,,,,,,, -KEY_TV2,0x17a,,,,,,,,,,,,,,,,, -KEY_VCR,0x17b,,,,,,,,,,,,,,,,, -KEY_VCR2,0x17c,,,,,,,,,,,,,,,,, -KEY_SAT,0x17d,,,,,,,,,,,,,,,,, -KEY_SAT2,0x17e,,,,,,,,,,,,,,,,, -KEY_CD,0x17f,,,,,,,,,,,,,,,,, -KEY_TAPE,0x180,,,,,,,,,,,,,,,,, -KEY_RADIO,0x181,,,,,,,,,,,,,,,,, -KEY_TUNER,0x182,,,,,,,,,,,,,,,,, -KEY_PLAYER,0x183,,,,,,,,,,,,,,,,, -KEY_TEXT,0x184,,,,,,,,,,,,,,,,, -KEY_DVD,0x185,,,,,,,,,,,,,,,,, -KEY_AUX,0x186,,,,,,,,,,,,,,,,, -KEY_MP3,0x187,,,,,,,,,,,,,,,,, -KEY_AUDIO,0x188,,,,,,,,,,,,,,,,, -KEY_VIDEO,0x189,,,,,,,,,,,,,,,,, -KEY_DIRECTORY,0x18a,,,,,,,,,,,,,,,,, -KEY_LIST,0x18b,,,,,,,,,,,,,,,,, -KEY_MEMO,0x18c,,,,,,,,,,,,,,,,, -KEY_CALENDAR,0x18d,,,,,,,,,,,,,,,,, -KEY_RED,0x18e,,,,,,,,,,,,,,,,, -KEY_GREEN,0x18f,,,,,,,,,,,,,,,,, -KEY_YELLOW,0x190,,,,,,,,,,,,,,,,, -KEY_BLUE,0x191,,,,,,,,,,,,,,,,, -KEY_CHANNELUP,0x192,,,,,,,,,,,,,,,,, -KEY_CHANNELDOWN,0x193,,,,,,,,,,,,,,,,, -KEY_FIRST,0x194,,,,,,,,,,,,,,,,, -KEY_LAST,0x195,,,,,,,,,,,,,,,,, -KEY_AB,0x196,,,,,,,,,,,,,,,,, -KEY_NEXT,0x197,,,,,,,,,,,,,,,,, -KEY_RESTART,0x198,,,,,,,,,,,,,,,,, -KEY_SLOW,0x199,,,,,,,,,,,,,,,,, -KEY_SHUFFLE,0x19a,,,,,,,,,,,,,,,,, -KEY_BREAK,0x19b,,,,,,,,,,,,,,BREA,,, -KEY_BREAK,0x19b,,,,,,,,,,,,,,BRK,,, -KEY_PREVIOUS,0x19c,,,,,,,,,,,,,,,,, -KEY_DIGITS,0x19d,,,,,,,,,,,,,,,,, -KEY_TEEN,0x19e,,,,,,,,,,,,,,,,, -KEY_TWEN,0x19f,,,,,,,,,,,,,,,,, -KEY_VIDEOPHONE,0x1a0,,,,,,,,,,,,,,,,, -KEY_GAMES,0x1a1,,,,,,,,,,,,,,,,, -KEY_ZOOMIN,0x1a2,,,,,,,,,,,,,,,,, -KEY_ZOOMOUT,0x1a3,,,,,,,,,,,,,,,,, -KEY_ZOOMRESET,0x1a4,,,,,,,,,,,,,,,,, -KEY_WORDPROCESSOR,0x1a5,,,,,,,,,,,,,,,,, -KEY_EDITOR,0x1a6,,,,,,,,,,,,,,,,, -KEY_SPREADSHEET,0x1a7,,,,,,,,,,,,,,,,, -KEY_GRAPHICSEDITOR,0x1a8,,,,,,,,,,,,,,,,, -KEY_PRESENTATION,0x1a9,,,,,,,,,,,,,,,,, -KEY_DATABASE,0x1aa,,,,,,,,,,,,,,,,, -KEY_NEWS,0x1ab,,,,,,,,,,,,,,,,, -KEY_VOICEMAIL,0x1ac,,,,,,,,,,,,,,,,, -KEY_ADDRESSBOOK,0x1ad,,,,,,,,,,,,,,,,, -KEY_MESSENGER,0x1ae,,,,,,,,,,,,,,,,, -KEY_DISPLAYTOGGLE,0x1af,,,,,,,,,,,,,,,,, -KEY_SPELLCHECK,0x1b0,,,,,,,,,,,,,,,,, -KEY_LOGOFF,0x1b1,,,,,,,,,,,,,,,,, -KEY_DOLLAR,0x1b2,,,,,,,,,,,,,,,,, -KEY_EURO,0x1b3,,,,,,,,,,,,,,,,, -KEY_FRAMEBACK,0x1b4,,,,,,,,,,,,,,,,, -KEY_FRAMEFORWARD,0x1b5,,,,,,,,,,,,,,,,, -KEY_CONTEXT_MENU,0x1b6,,,,,,,,,,,,,,,,, -KEY_MEDIA_REPEAT,0x1b7,,,,,,,,,,,,,,,,, -KEY_DEL_EOL,0x1c0,,,,,,,,,,,,,,,,, -KEY_DEL_EOS,0x1c1,,,,,,,,,,,,,,,,, -KEY_INS_LINE,0x1c2,,,,,,,,,,,,,,,,, -KEY_DEL_LINE,0x1c3,,,,,,,,,,,,,,,,, -KEY_FN,0x1d0,Function,0x3f,,,,,,,,,,,Fn,,,, -KEY_FN_ESC,0x1d1,,,,,,,,,,,,,,,,, -KEY_FN_F1,0x1d2,,,,,,,,,,,,,,,,, -KEY_FN_F2,0x1d3,,,,,,,,,,,,,,,,, -KEY_FN_F3,0x1d4,,,,,,,,,,,,,,,,, -KEY_FN_F4,0x1d5,,,,,,,,,,,,,,,,, -KEY_FN_F5,0x1d6,,,,,,,,,,,,,,,,, -KEY_FN_F6,0x1d7,,,,,,,,,,,,,,,,, -KEY_FN_F7,0x1d8,,,,,,,,,,,,,,,,, -KEY_FN_F8,0x1d9,,,,,,,,,,,,,,,,, -KEY_FN_F9,0x1da,,,,,,,,,,,,,,,,, -KEY_FN_F10,0x1db,,,,,,,,,,,,,,,,, -KEY_FN_F11,0x1dc,,,,,,,,,,,,,,,,, -KEY_FN_F12,0x1dd,,,,,,,,,,,,,,,,, -KEY_FN_1,0x1de,,,,,,,,,,,,,,,,, -KEY_FN_2,0x1df,,,,,,,,,,,,,,,,, -KEY_FN_D,0x1e0,,,,,,,,,,,,,,,,, -KEY_FN_E,0x1e1,,,,,,,,,,,,,,,,, -KEY_FN_F,0x1e2,,,,,,,,,,,,,,,,, -KEY_FN_S,0x1e3,,,,,,,,,,,,,,,,, -KEY_FN_B,0x1e4,,,,,,,,,,,,,,,,, -KEY_BRL_DOT1,0x1f1,,,,,,,,,,,,,,,,, -KEY_BRL_DOT2,0x1f2,,,,,,,,,,,,,,,,, -KEY_BRL_DOT3,0x1f3,,,,,,,,,,,,,,,,, -KEY_BRL_DOT4,0x1f4,,,,,,,,,,,,,,,,, -KEY_BRL_DOT5,0x1f5,,,,,,,,,,,,,,,,, -KEY_BRL_DOT6,0x1f6,,,,,,,,,,,,,,,,, -KEY_BRL_DOT7,0x1f7,,,,,,,,,,,,,,,,, -KEY_BRL_DOT8,0x1f8,,,,,,,,,,,,,,,,, -KEY_BRL_DOT9,0x1f9,,,,,,,,,,,,,,,,, -KEY_BRL_DOT10,0x1fa,,,,,,,,,,,,,,,,, -KEY_NUMERIC_0,0x200,,,,,,,,,,,,,,,,, -KEY_NUMERIC_1,0x201,,,,,,,,,,,,,,,,, -KEY_NUMERIC_2,0x202,,,,,,,,,,,,,,,,, -KEY_NUMERIC_3,0x203,,,,,,,,,,,,,,,,, -KEY_NUMERIC_4,0x204,,,,,,,,,,,,,,,,, -KEY_NUMERIC_5,0x205,,,,,,,,,,,,,,,,, -KEY_NUMERIC_6,0x206,,,,,,,,,,,,,,,,, -KEY_NUMERIC_7,0x207,,,,,,,,,,,,,,,,, -KEY_NUMERIC_8,0x208,,,,,,,,,,,,,,,,, -KEY_NUMERIC_9,0x209,,,,,,,,,,,,,,,,, -KEY_NUMERIC_STAR,0x20a,,,,,,,,,,,,,NumpadStar,,,, -KEY_NUMERIC_POUND,0x20b,,,,,,,,,,,,,NumpadHash,,,, -KEY_RFKILL,0x20c,,,,,,,,,,,,,,,,, diff --git a/keycodemapdb/meson.build b/keycodemapdb/meson.build deleted file mode 100644 index eb9416b..0000000 --- a/keycodemapdb/meson.build +++ /dev/null @@ -1 +0,0 @@ -project('keycodemapdb') diff --git a/keycodemapdb/tests/.gitignore b/keycodemapdb/tests/.gitignore deleted file mode 100644 index 0562305..0000000 --- a/keycodemapdb/tests/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -osx2win32.* -osx2win32_name.* -osx2xkb.* -osx2xkb_name.* -html2win32.* -html2win32_name.* -osx.* -osx_name.* -stdc -stdc++ -node_modules/ diff --git a/keycodemapdb/tests/Makefile b/keycodemapdb/tests/Makefile deleted file mode 100644 index e1b3875..0000000 --- a/keycodemapdb/tests/Makefile +++ /dev/null @@ -1,150 +0,0 @@ -TESTS := stdc stdc++ python2 python3 javascript - -check: $(TESTS) - @set -e; for fn in $(TESTS); do \ - ./$$fn; \ - echo $$fn: OK; \ - done - @echo Done. - -GEN := ../tools/keymap-gen -DATA := ../data/keymaps.csv -SOURCES := $(GEN) $(DATA) - -.DELETE_ON_ERROR: - -stdc: stdc.c osx2win32.h osx2win32.c osx2win32_name.h osx2win32_name.c \ - osx2xkb.h osx2xkb.c osx2xkb_name.h osx2xkb_name.c \ - html2win32.h html2win32.c html2win32_name.h html2win32_name.c \ - osx.h osx.c osx_name.h osx_name.c - $(CC) -Wall -o $@ $(filter %.c, $^) -osx2win32.c: $(SOURCES) - $(GEN) --lang stdc code-map $(DATA) osx win32 > $@ -osx2win32.h: $(SOURCES) - $(GEN) --lang stdc-header code-map $(DATA) osx win32 > $@ -osx2win32_name.c: $(SOURCES) - $(GEN) --lang stdc name-map $(DATA) osx win32 > $@ -osx2win32_name.h: $(SOURCES) - $(GEN) --lang stdc-header name-map $(DATA) osx win32 > $@ -osx2xkb.c: $(SOURCES) - $(GEN) --lang stdc code-map $(DATA) osx xkb > $@ -osx2xkb.h: $(SOURCES) - $(GEN) --lang stdc-header code-map $(DATA) osx xkb > $@ -osx2xkb_name.c: $(SOURCES) - $(GEN) --lang stdc name-map $(DATA) osx xkb > $@ -osx2xkb_name.h: $(SOURCES) - $(GEN) --lang stdc-header name-map $(DATA) osx xkb > $@ -html2win32.c: $(SOURCES) - $(GEN) --lang stdc code-map $(DATA) html win32 > $@ -html2win32.h: $(SOURCES) - $(GEN) --lang stdc-header code-map $(DATA) html win32 > $@ -html2win32_name.c: $(SOURCES) - $(GEN) --lang stdc name-map $(DATA) html win32 > $@ -html2win32_name.h: $(SOURCES) - $(GEN) --lang stdc-header name-map $(DATA) html win32 > $@ -osx.c: $(SOURCES) - $(GEN) --lang stdc code-table $(DATA) osx > $@ -osx.h: $(SOURCES) - $(GEN) --lang stdc-header code-table $(DATA) osx > $@ -osx_name.c: $(SOURCES) - $(GEN) --lang stdc name-table $(DATA) osx > $@ -osx_name.h: $(SOURCES) - $(GEN) --lang stdc-header name-table $(DATA) osx > $@ - -stdc++: stdc++.cc osx2win32.hh osx2win32.cc osx2win32_name.hh osx2win32_name.cc \ - osx2xkb.hh osx2xkb.cc osx2xkb_name.hh osx2xkb_name.cc \ - html2win32.hh html2win32.cc html2win32_name.hh html2win32_name.cc \ - osx.hh osx.cc osx_name.hh osx_name.cc - $(CXX) -Wall -std=c++11 -o $@ $(filter %.cc, $^) -osx2win32.cc: $(SOURCES) - $(GEN) --lang stdc++ code-map $(DATA) osx win32 > $@ -osx2win32.hh: $(SOURCES) - $(GEN) --lang stdc++-header code-map $(DATA) osx win32 > $@ -osx2win32_name.cc: $(SOURCES) - $(GEN) --lang stdc++ name-map $(DATA) osx win32 > $@ -osx2win32_name.hh: $(SOURCES) - $(GEN) --lang stdc++-header name-map $(DATA) osx win32 > $@ -osx2xkb.cc: $(SOURCES) - $(GEN) --lang stdc++ code-map $(DATA) osx xkb > $@ -osx2xkb.hh: $(SOURCES) - $(GEN) --lang stdc++-header code-map $(DATA) osx xkb > $@ -osx2xkb_name.cc: $(SOURCES) - $(GEN) --lang stdc++ name-map $(DATA) osx xkb > $@ -osx2xkb_name.hh: $(SOURCES) - $(GEN) --lang stdc++-header name-map $(DATA) osx xkb > $@ -html2win32.cc: $(SOURCES) - $(GEN) --lang stdc++ code-map $(DATA) html win32 > $@ -html2win32.hh: $(SOURCES) - $(GEN) --lang stdc++-header code-map $(DATA) html win32 > $@ -html2win32_name.cc: $(SOURCES) - $(GEN) --lang stdc++ name-map $(DATA) html win32 > $@ -html2win32_name.hh: $(SOURCES) - $(GEN) --lang stdc++-header name-map $(DATA) html win32 > $@ -osx.cc: $(SOURCES) - $(GEN) --lang stdc++ code-table $(DATA) osx > $@ -osx.hh: $(SOURCES) - $(GEN) --lang stdc++-header code-table $(DATA) osx > $@ -osx_name.cc: $(SOURCES) - $(GEN) --lang stdc++ name-table $(DATA) osx > $@ -osx_name.hh: $(SOURCES) - $(GEN) --lang stdc++-header name-table $(DATA) osx > $@ - -python2: osx2win32.py osx2win32_name.py \ - osx2xkb.py osx2xkb_name.py \ - html2win32.py html2win32_name.py \ - osx.py osx_name.py -osx2win32.py: $(SOURCES) - $(GEN) --lang python2 code-map $(DATA) osx win32 > $@ -osx2win32_name.py: $(SOURCES) - $(GEN) --lang python2 name-map $(DATA) osx win32 > $@ -osx2xkb.py: $(SOURCES) - $(GEN) --lang python2 code-map $(DATA) osx xkb > $@ -osx2xkb_name.py: $(SOURCES) - $(GEN) --lang python2 name-map $(DATA) osx xkb > $@ -html2win32.py: $(SOURCES) - $(GEN) --lang python2 code-map $(DATA) html win32 > $@ -html2win32_name.py: $(SOURCES) - $(GEN) --lang python2 name-map $(DATA) html win32 > $@ -osx.py: $(SOURCES) - $(GEN) --lang python2 code-table $(DATA) osx > $@ -osx_name.py: $(SOURCES) - $(GEN) --lang python2 name-table $(DATA) osx > $@ - -javascript: node_modules/babel-core \ - node_modules/babel-plugin-transform-es2015-modules-commonjs \ - osx2win32.js osx2win32_name.js \ - osx2xkb.js osx2xkb_name.js \ - html2win32.js html2win32_name.js \ - osx.js osx_name.js -node_modules/babel-core: - npm install babel-core -node_modules/babel-plugin-transform-es2015-modules-commonjs: - npm install babel-plugin-transform-es2015-modules-commonjs -osx2win32.js: $(SOURCES) - $(GEN) --lang js code-map $(DATA) osx win32 > $@ -osx2win32_name.js: $(SOURCES) - $(GEN) --lang js name-map $(DATA) osx win32 > $@ -osx2xkb.js: $(SOURCES) - $(GEN) --lang js code-map $(DATA) osx xkb > $@ -osx2xkb_name.js: $(SOURCES) - $(GEN) --lang js name-map $(DATA) osx xkb > $@ -html2win32.js: $(SOURCES) - $(GEN) --lang js code-map $(DATA) html win32 > $@ -html2win32_name.js: $(SOURCES) - $(GEN) --lang js name-map $(DATA) html win32 > $@ -osx.js: $(SOURCES) - $(GEN) --lang js code-table $(DATA) osx > $@ -osx_name.js: $(SOURCES) - $(GEN) --lang js name-table $(DATA) osx > $@ - -clean: - rm -rf node_modules - rm -f osx2win32.* - rm -f osx2win32_name.* - rm -f osx2xkb.* - rm -f osx2xkb_name.* - rm -f html2win32.* - rm -f html2win32_name.* - rm -f osx.* - rm -f osx_name.* - rm -f stdc stdc++ diff --git a/keycodemapdb/tests/javascript b/keycodemapdb/tests/javascript deleted file mode 100755 index 5179db2..0000000 --- a/keycodemapdb/tests/javascript +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env node -/* - * Keycode Map Generator JavaScript Tests - * - * Copyright 2017 Pierre Ossman for Cendio AB - * - * This file is dual license under the terms of the GPLv2 or later - * and 3-clause BSD licenses. - */ - -"use strict"; - -var assert = require('assert'); -var babel = require('babel-core'); -var fs = require('fs'); - -function include(fn) { - var options = { - plugins: ["transform-es2015-modules-commonjs"] - }; - - var code = babel.transformFileSync(fn, options).code; - fs.writeFileSync("." + fn + "_nodejs.js", code); - var imp = require("./." + fn + "_nodejs.js"); - fs.unlinkSync("./." + fn + "_nodejs.js"); - - return imp -} - -var code_map_osx_to_win32 = include("osx2win32.js").default; -var name_map_osx_to_win32 = include("osx2win32_name.js").default; - -var code_map_osx_to_xkb = include("osx2xkb.js").default; -var name_map_osx_to_xkb = include("osx2xkb_name.js").default; - -var code_map_html_to_win32 = include("html2win32.js").default; -var name_map_html_to_win32 = include("html2win32_name.js").default; - -var code_table_osx = include("osx.js").default; -var name_table_osx = include("osx_name.js").default; - -assert.equal(code_map_osx_to_win32[0x1d], 0x30); -assert.equal(name_map_osx_to_win32[0x1d], "VK_0"); - -assert.equal(code_map_osx_to_xkb[0x1d], "AE10"); -assert.equal(name_map_osx_to_xkb[0x1d], "AE10"); - -assert.equal(code_map_html_to_win32["ControlLeft"], 0x11); -assert.equal(name_map_html_to_win32["ControlLeft"], "VK_CONTROL"); - -assert.equal(code_table_osx[0x1d], 0x3b); -assert.equal(name_table_osx[0x1d], "Control"); - diff --git a/keycodemapdb/tests/python2 b/keycodemapdb/tests/python2 deleted file mode 100755 index 28a5b03..0000000 --- a/keycodemapdb/tests/python2 +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -python ./test.py diff --git a/keycodemapdb/tests/python3 b/keycodemapdb/tests/python3 deleted file mode 100755 index ded1f68..0000000 --- a/keycodemapdb/tests/python3 +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -python3 ./test.py diff --git a/keycodemapdb/tests/stdc++.cc b/keycodemapdb/tests/stdc++.cc deleted file mode 100644 index 5e3e8f5..0000000 --- a/keycodemapdb/tests/stdc++.cc +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Keycode Map Generator C++ Tests - * - * Copyright 2017 Pierre Ossman for Cendio AB - * - * This file is dual license under the terms of the GPLv2 or later - * and 3-clause BSD licenses. - */ - -#include -#include - -#include "osx2win32.hh" -#include "osx2win32_name.hh" - -#include "osx2xkb.hh" -#include "osx2xkb_name.hh" - -#include "html2win32.hh" -#include "html2win32_name.hh" - -#include "osx.hh" -#include "osx_name.hh" - -int main(int argc, char** argv) -{ - assert(code_map_osx_to_win32[0x1d] == 0x30); - assert(strcmp(name_map_osx_to_win32[0x1d], "VK_0") == 0); - - assert(strcmp(code_map_osx_to_xkb[0x1d], "AE10") == 0); - assert(strcmp(name_map_osx_to_xkb[0x1d], "AE10") == 0); - - assert(code_map_html_to_win32.at("ControlLeft") == 0x11); - assert(strcmp(name_map_html_to_win32.at("ControlLeft"), "VK_CONTROL") == 0); - - assert(code_table_osx[0x1d] == 0x3b); - assert(strcmp(name_table_osx[0x1d], "Control") == 0); - - return 0; -} diff --git a/keycodemapdb/tests/stdc.c b/keycodemapdb/tests/stdc.c deleted file mode 100644 index e4946fa..0000000 --- a/keycodemapdb/tests/stdc.c +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Keycode Map Generator C Tests - * - * Copyright 2017 Pierre Ossman for Cendio AB - * - * This file is dual license under the terms of the GPLv2 or later - * and 3-clause BSD licenses. - */ - -#include -#include - -#include "osx2win32.h" -#include "osx2win32_name.h" - -#include "osx2xkb.h" -#include "osx2xkb_name.h" - -#include "html2win32.h" -#include "html2win32_name.h" - -#include "osx.h" -#include "osx_name.h" - -#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) - -int main(int argc, char** argv) -{ - unsigned i; - - assert(code_map_osx_to_win32_len == ARRAY_SIZE(code_map_osx_to_win32)); - assert(code_map_osx_to_win32[0x1d] == 0x30); - assert(name_map_osx_to_win32_len == ARRAY_SIZE(name_map_osx_to_win32)); - assert(strcmp(name_map_osx_to_win32[0x1d], "VK_0") == 0); - - assert(code_map_osx_to_xkb_len == ARRAY_SIZE(code_map_osx_to_xkb)); - assert(strcmp(code_map_osx_to_xkb[0x1d], "AE10") == 0); - assert(name_map_osx_to_xkb_len == ARRAY_SIZE(name_map_osx_to_xkb)); - assert(strcmp(name_map_osx_to_xkb[0x1d], "AE10") == 0); - - assert(code_map_html_to_win32_len == ARRAY_SIZE(code_map_html_to_win32)); - for (i = 0;i < code_map_html_to_win32_len;i++) { - if (strcmp(code_map_html_to_win32[i].from, "ControlLeft") == 0) { - assert(code_map_html_to_win32[i].to == 0x11); - break; - } - } - assert(i != code_map_html_to_win32_len); - assert(name_map_html_to_win32_len == ARRAY_SIZE(name_map_html_to_win32)); - for (i = 0;i < name_map_html_to_win32_len;i++) { - if (strcmp(name_map_html_to_win32[i].from, "ControlLeft") == 0) { - assert(strcmp(name_map_html_to_win32[i].to, "VK_CONTROL") == 0); - break; - } - } - assert(i != name_map_html_to_win32_len); - - assert(code_table_osx_len == ARRAY_SIZE(code_table_osx)); - assert(code_table_osx[0x1d] == 0x3b); - assert(name_table_osx_len == ARRAY_SIZE(name_table_osx)); - assert(strcmp(name_table_osx[0x1d], "Control") == 0); - - return 0; -} diff --git a/keycodemapdb/tests/test.py b/keycodemapdb/tests/test.py deleted file mode 100644 index f265145..0000000 --- a/keycodemapdb/tests/test.py +++ /dev/null @@ -1,30 +0,0 @@ -# Keycode Map Generator Python Tests -# -# Copyright 2017 Pierre Ossman for Cendio AB -# -# This file is dual license under the terms of the GPLv2 or later -# and 3-clause BSD licenses. - -import osx2win32 -import osx2win32_name - -import osx2xkb -import osx2xkb_name - -import html2win32 -import html2win32_name - -import osx -import osx_name - -assert osx2win32.code_map_osx_to_win32[0x1d] == 0x30 -assert osx2win32_name.name_map_osx_to_win32[0x1d] == "VK_0" - -assert osx2xkb.code_map_osx_to_xkb[0x1d] == "AE10" -assert osx2xkb_name.name_map_osx_to_xkb[0x1d] == "AE10" - -assert html2win32.code_map_html_to_win32["ControlLeft"] == 0x11 -assert html2win32_name.name_map_html_to_win32["ControlLeft"] == "VK_CONTROL" - -assert osx.code_table_osx[0x1d] == 0x3b; -assert osx_name.name_table_osx[0x1d] == "Control"; diff --git a/keycodemapdb/thirdparty/LICENSE-argparse.txt b/keycodemapdb/thirdparty/LICENSE-argparse.txt deleted file mode 100644 index 640bc78..0000000 --- a/keycodemapdb/thirdparty/LICENSE-argparse.txt +++ /dev/null @@ -1,20 +0,0 @@ -argparse is (c) 2006-2009 Steven J. Bethard . - -The argparse module was contributed to Python as of Python 2.7 and thus -was licensed under the Python license. Same license applies to all files in -the argparse package project. - -For details about the Python License, please see doc/Python-License.txt. - -History -------- - -Before (and including) argparse 1.1, the argparse package was licensed under -Apache License v2.0. - -After argparse 1.1, all project files from the argparse project were deleted -due to license compatibility issues between Apache License 2.0 and GNU GPL v2. - -The project repository then had a clean start with some files taken from -Python 2.7.1, so definitely all files are under Python License now. - diff --git a/keycodemapdb/thirdparty/__init__.py b/keycodemapdb/thirdparty/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keycodemapdb/thirdparty/argparse.py b/keycodemapdb/thirdparty/argparse.py deleted file mode 100644 index 70a77cc..0000000 --- a/keycodemapdb/thirdparty/argparse.py +++ /dev/null @@ -1,2392 +0,0 @@ -# Author: Steven J. Bethard . -# Maintainer: Thomas Waldmann - -"""Command-line parsing library - -This module is an optparse-inspired command-line parsing library that: - - - handles both optional and positional arguments - - produces highly informative usage messages - - supports parsers that dispatch to sub-parsers - -The following is a simple usage example that sums integers from the -command-line and writes the result to a file:: - - parser = argparse.ArgumentParser( - description='sum the integers at the command line') - parser.add_argument( - 'integers', metavar='int', nargs='+', type=int, - help='an integer to be summed') - parser.add_argument( - '--log', default=sys.stdout, type=argparse.FileType('w'), - help='the file where the sum should be written') - args = parser.parse_args() - args.log.write('%s' % sum(args.integers)) - args.log.close() - -The module contains the following public classes: - - - ArgumentParser -- The main entry point for command-line parsing. As the - example above shows, the add_argument() method is used to populate - the parser with actions for optional and positional arguments. Then - the parse_args() method is invoked to convert the args at the - command-line into an object with attributes. - - - ArgumentError -- The exception raised by ArgumentParser objects when - there are errors with the parser's actions. Errors raised while - parsing the command-line are caught by ArgumentParser and emitted - as command-line messages. - - - FileType -- A factory for defining types of files to be created. As the - example above shows, instances of FileType are typically passed as - the type= argument of add_argument() calls. - - - Action -- The base class for parser actions. Typically actions are - selected by passing strings like 'store_true' or 'append_const' to - the action= argument of add_argument(). However, for greater - customization of ArgumentParser actions, subclasses of Action may - be defined and passed as the action= argument. - - - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, - ArgumentDefaultsHelpFormatter -- Formatter classes which - may be passed as the formatter_class= argument to the - ArgumentParser constructor. HelpFormatter is the default, - RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser - not to change the formatting for help text, and - ArgumentDefaultsHelpFormatter adds information about argument defaults - to the help. - -All other classes in this module are considered implementation details. -(Also note that HelpFormatter and RawDescriptionHelpFormatter are only -considered public as object names -- the API of the formatter objects is -still considered an implementation detail.) -""" - -__version__ = '1.4.0' # we use our own version number independant of the - # one in stdlib and we release this on pypi. - -__external_lib__ = True # to make sure the tests really test THIS lib, - # not the builtin one in Python stdlib - -__all__ = [ - 'ArgumentParser', - 'ArgumentError', - 'ArgumentTypeError', - 'FileType', - 'HelpFormatter', - 'ArgumentDefaultsHelpFormatter', - 'RawDescriptionHelpFormatter', - 'RawTextHelpFormatter', - 'Namespace', - 'Action', - 'ONE_OR_MORE', - 'OPTIONAL', - 'PARSER', - 'REMAINDER', - 'SUPPRESS', - 'ZERO_OR_MORE', -] - - -import copy as _copy -import os as _os -import re as _re -import sys as _sys -import textwrap as _textwrap - -from gettext import gettext as _ - -try: - set -except NameError: - # for python < 2.4 compatibility (sets module is there since 2.3): - from sets import Set as set - -try: - basestring -except NameError: - basestring = str - -try: - sorted -except NameError: - # for python < 2.4 compatibility: - def sorted(iterable, reverse=False): - result = list(iterable) - result.sort() - if reverse: - result.reverse() - return result - - -def _callable(obj): - return hasattr(obj, '__call__') or hasattr(obj, '__bases__') - - -SUPPRESS = '==SUPPRESS==' - -OPTIONAL = '?' -ZERO_OR_MORE = '*' -ONE_OR_MORE = '+' -PARSER = 'A...' -REMAINDER = '...' -_UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' - -# ============================= -# Utility functions and classes -# ============================= - -class _AttributeHolder(object): - """Abstract base class that provides __repr__. - - The __repr__ method returns a string in the format:: - ClassName(attr=name, attr=name, ...) - The attributes are determined either by a class-level attribute, - '_kwarg_names', or by inspecting the instance __dict__. - """ - - def __repr__(self): - type_name = type(self).__name__ - arg_strings = [] - for arg in self._get_args(): - arg_strings.append(repr(arg)) - for name, value in self._get_kwargs(): - arg_strings.append('%s=%r' % (name, value)) - return '%s(%s)' % (type_name, ', '.join(arg_strings)) - - def _get_kwargs(self): - return sorted(self.__dict__.items()) - - def _get_args(self): - return [] - - -def _ensure_value(namespace, name, value): - if getattr(namespace, name, None) is None: - setattr(namespace, name, value) - return getattr(namespace, name) - - -# =============== -# Formatting Help -# =============== - -class HelpFormatter(object): - """Formatter for generating usage messages and argument help strings. - - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - - def __init__(self, - prog, - indent_increment=2, - max_help_position=24, - width=None): - - # default setting for width - if width is None: - try: - width = int(_os.environ['COLUMNS']) - except (KeyError, ValueError): - width = 80 - width -= 2 - - self._prog = prog - self._indent_increment = indent_increment - self._max_help_position = max_help_position - self._width = width - - self._current_indent = 0 - self._level = 0 - self._action_max_length = 0 - - self._root_section = self._Section(self, None) - self._current_section = self._root_section - - self._whitespace_matcher = _re.compile(r'\s+') - self._long_break_matcher = _re.compile(r'\n\n\n+') - - # =============================== - # Section and indentation methods - # =============================== - def _indent(self): - self._current_indent += self._indent_increment - self._level += 1 - - def _dedent(self): - self._current_indent -= self._indent_increment - assert self._current_indent >= 0, 'Indent decreased below 0.' - self._level -= 1 - - class _Section(object): - - def __init__(self, formatter, parent, heading=None): - self.formatter = formatter - self.parent = parent - self.heading = heading - self.items = [] - - def format_help(self): - # format the indented section - if self.parent is not None: - self.formatter._indent() - join = self.formatter._join_parts - for func, args in self.items: - func(*args) - item_help = join([func(*args) for func, args in self.items]) - if self.parent is not None: - self.formatter._dedent() - - # return nothing if the section was empty - if not item_help: - return '' - - # add the heading if the section was non-empty - if self.heading is not SUPPRESS and self.heading is not None: - current_indent = self.formatter._current_indent - heading = '%*s%s:\n' % (current_indent, '', self.heading) - else: - heading = '' - - # join the section-initial newline, the heading and the help - return join(['\n', heading, item_help, '\n']) - - def _add_item(self, func, args): - self._current_section.items.append((func, args)) - - # ======================== - # Message building methods - # ======================== - def start_section(self, heading): - self._indent() - section = self._Section(self, self._current_section, heading) - self._add_item(section.format_help, []) - self._current_section = section - - def end_section(self): - self._current_section = self._current_section.parent - self._dedent() - - def add_text(self, text): - if text is not SUPPRESS and text is not None: - self._add_item(self._format_text, [text]) - - def add_usage(self, usage, actions, groups, prefix=None): - if usage is not SUPPRESS: - args = usage, actions, groups, prefix - self._add_item(self._format_usage, args) - - def add_argument(self, action): - if action.help is not SUPPRESS: - - # find all invocations - get_invocation = self._format_action_invocation - invocations = [get_invocation(action)] - for subaction in self._iter_indented_subactions(action): - invocations.append(get_invocation(subaction)) - - # update the maximum item length - invocation_length = max([len(s) for s in invocations]) - action_length = invocation_length + self._current_indent - self._action_max_length = max(self._action_max_length, - action_length) - - # add the item to the list - self._add_item(self._format_action, [action]) - - def add_arguments(self, actions): - for action in actions: - self.add_argument(action) - - # ======================= - # Help-formatting methods - # ======================= - def format_help(self): - help = self._root_section.format_help() - if help: - help = self._long_break_matcher.sub('\n\n', help) - help = help.strip('\n') + '\n' - return help - - def _join_parts(self, part_strings): - return ''.join([part - for part in part_strings - if part and part is not SUPPRESS]) - - def _format_usage(self, usage, actions, groups, prefix): - if prefix is None: - prefix = _('usage: ') - - # if usage is specified, use that - if usage is not None: - usage = usage % dict(prog=self._prog) - - # if no optionals or positionals are available, usage is just prog - elif usage is None and not actions: - usage = '%(prog)s' % dict(prog=self._prog) - - # if optionals and positionals are available, calculate usage - elif usage is None: - prog = '%(prog)s' % dict(prog=self._prog) - - # split optionals from positionals - optionals = [] - positionals = [] - for action in actions: - if action.option_strings: - optionals.append(action) - else: - positionals.append(action) - - # build full usage string - format = self._format_actions_usage - action_usage = format(optionals + positionals, groups) - usage = ' '.join([s for s in [prog, action_usage] if s]) - - # wrap the usage parts if it's too long - text_width = self._width - self._current_indent - if len(prefix) + len(usage) > text_width: - - # break usage into wrappable parts - part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' - opt_usage = format(optionals, groups) - pos_usage = format(positionals, groups) - opt_parts = _re.findall(part_regexp, opt_usage) - pos_parts = _re.findall(part_regexp, pos_usage) - assert ' '.join(opt_parts) == opt_usage - assert ' '.join(pos_parts) == pos_usage - - # helper for wrapping lines - def get_lines(parts, indent, prefix=None): - lines = [] - line = [] - if prefix is not None: - line_len = len(prefix) - 1 - else: - line_len = len(indent) - 1 - for part in parts: - if line_len + 1 + len(part) > text_width: - lines.append(indent + ' '.join(line)) - line = [] - line_len = len(indent) - 1 - line.append(part) - line_len += len(part) + 1 - if line: - lines.append(indent + ' '.join(line)) - if prefix is not None: - lines[0] = lines[0][len(indent):] - return lines - - # if prog is short, follow it with optionals or positionals - if len(prefix) + len(prog) <= 0.75 * text_width: - indent = ' ' * (len(prefix) + len(prog) + 1) - if opt_parts: - lines = get_lines([prog] + opt_parts, indent, prefix) - lines.extend(get_lines(pos_parts, indent)) - elif pos_parts: - lines = get_lines([prog] + pos_parts, indent, prefix) - else: - lines = [prog] - - # if prog is long, put it on its own line - else: - indent = ' ' * len(prefix) - parts = opt_parts + pos_parts - lines = get_lines(parts, indent) - if len(lines) > 1: - lines = [] - lines.extend(get_lines(opt_parts, indent)) - lines.extend(get_lines(pos_parts, indent)) - lines = [prog] + lines - - # join lines into usage - usage = '\n'.join(lines) - - # prefix with 'usage:' - return '%s%s\n\n' % (prefix, usage) - - def _format_actions_usage(self, actions, groups): - # find group indices and identify actions in groups - group_actions = set() - inserts = {} - for group in groups: - try: - start = actions.index(group._group_actions[0]) - except ValueError: - continue - else: - end = start + len(group._group_actions) - if actions[start:end] == group._group_actions: - for action in group._group_actions: - group_actions.add(action) - if not group.required: - if start in inserts: - inserts[start] += ' [' - else: - inserts[start] = '[' - inserts[end] = ']' - else: - if start in inserts: - inserts[start] += ' (' - else: - inserts[start] = '(' - inserts[end] = ')' - for i in range(start + 1, end): - inserts[i] = '|' - - # collect all actions format strings - parts = [] - for i, action in enumerate(actions): - - # suppressed arguments are marked with None - # remove | separators for suppressed arguments - if action.help is SUPPRESS: - parts.append(None) - if inserts.get(i) == '|': - inserts.pop(i) - elif inserts.get(i + 1) == '|': - inserts.pop(i + 1) - - # produce all arg strings - elif not action.option_strings: - part = self._format_args(action, action.dest) - - # if it's in a group, strip the outer [] - if action in group_actions: - if part[0] == '[' and part[-1] == ']': - part = part[1:-1] - - # add the action string to the list - parts.append(part) - - # produce the first way to invoke the option in brackets - else: - option_string = action.option_strings[0] - - # if the Optional doesn't take a value, format is: - # -s or --long - if action.nargs == 0: - part = '%s' % option_string - - # if the Optional takes a value, format is: - # -s ARGS or --long ARGS - else: - default = action.dest.upper() - args_string = self._format_args(action, default) - part = '%s %s' % (option_string, args_string) - - # make it look optional if it's not required or in a group - if not action.required and action not in group_actions: - part = '[%s]' % part - - # add the action string to the list - parts.append(part) - - # insert things at the necessary indices - for i in sorted(inserts, reverse=True): - parts[i:i] = [inserts[i]] - - # join all the action items with spaces - text = ' '.join([item for item in parts if item is not None]) - - # clean up separators for mutually exclusive groups - open = r'[\[(]' - close = r'[\])]' - text = _re.sub(r'(%s) ' % open, r'\1', text) - text = _re.sub(r' (%s)' % close, r'\1', text) - text = _re.sub(r'%s *%s' % (open, close), r'', text) - text = _re.sub(r'\(([^|]*)\)', r'\1', text) - text = text.strip() - - # return the text - return text - - def _format_text(self, text): - if '%(prog)' in text: - text = text % dict(prog=self._prog) - text_width = self._width - self._current_indent - indent = ' ' * self._current_indent - return self._fill_text(text, text_width, indent) + '\n\n' - - def _format_action(self, action): - # determine the required width and the entry label - help_position = min(self._action_max_length + 2, - self._max_help_position) - help_width = self._width - help_position - action_width = help_position - self._current_indent - 2 - action_header = self._format_action_invocation(action) - - # ho nelp; start on same line and add a final newline - if not action.help: - tup = self._current_indent, '', action_header - action_header = '%*s%s\n' % tup - - # short action name; start on the same line and pad two spaces - elif len(action_header) <= action_width: - tup = self._current_indent, '', action_width, action_header - action_header = '%*s%-*s ' % tup - indent_first = 0 - - # long action name; start on the next line - else: - tup = self._current_indent, '', action_header - action_header = '%*s%s\n' % tup - indent_first = help_position - - # collect the pieces of the action help - parts = [action_header] - - # if there was help for the action, add lines of help text - if action.help: - help_text = self._expand_help(action) - help_lines = self._split_lines(help_text, help_width) - parts.append('%*s%s\n' % (indent_first, '', help_lines[0])) - for line in help_lines[1:]: - parts.append('%*s%s\n' % (help_position, '', line)) - - # or add a newline if the description doesn't end with one - elif not action_header.endswith('\n'): - parts.append('\n') - - # if there are any sub-actions, add their help as well - for subaction in self._iter_indented_subactions(action): - parts.append(self._format_action(subaction)) - - # return a single string - return self._join_parts(parts) - - def _format_action_invocation(self, action): - if not action.option_strings: - metavar, = self._metavar_formatter(action, action.dest)(1) - return metavar - - else: - parts = [] - - # if the Optional doesn't take a value, format is: - # -s, --long - if action.nargs == 0: - parts.extend(action.option_strings) - - # if the Optional takes a value, format is: - # -s ARGS, --long ARGS - else: - default = action.dest.upper() - args_string = self._format_args(action, default) - for option_string in action.option_strings: - parts.append('%s %s' % (option_string, args_string)) - - return ', '.join(parts) - - def _metavar_formatter(self, action, default_metavar): - if action.metavar is not None: - result = action.metavar - elif action.choices is not None: - choice_strs = [str(choice) for choice in action.choices] - result = '{%s}' % ','.join(choice_strs) - else: - result = default_metavar - - def format(tuple_size): - if isinstance(result, tuple): - return result - else: - return (result, ) * tuple_size - return format - - def _format_args(self, action, default_metavar): - get_metavar = self._metavar_formatter(action, default_metavar) - if action.nargs is None: - result = '%s' % get_metavar(1) - elif action.nargs == OPTIONAL: - result = '[%s]' % get_metavar(1) - elif action.nargs == ZERO_OR_MORE: - result = '[%s [%s ...]]' % get_metavar(2) - elif action.nargs == ONE_OR_MORE: - result = '%s [%s ...]' % get_metavar(2) - elif action.nargs == REMAINDER: - result = '...' - elif action.nargs == PARSER: - result = '%s ...' % get_metavar(1) - else: - formats = ['%s' for _ in range(action.nargs)] - result = ' '.join(formats) % get_metavar(action.nargs) - return result - - def _expand_help(self, action): - params = dict(vars(action), prog=self._prog) - for name in list(params): - if params[name] is SUPPRESS: - del params[name] - for name in list(params): - if hasattr(params[name], '__name__'): - params[name] = params[name].__name__ - if params.get('choices') is not None: - choices_str = ', '.join([str(c) for c in params['choices']]) - params['choices'] = choices_str - return self._get_help_string(action) % params - - def _iter_indented_subactions(self, action): - try: - get_subactions = action._get_subactions - except AttributeError: - pass - else: - self._indent() - for subaction in get_subactions(): - yield subaction - self._dedent() - - def _split_lines(self, text, width): - text = self._whitespace_matcher.sub(' ', text).strip() - return _textwrap.wrap(text, width) - - def _fill_text(self, text, width, indent): - text = self._whitespace_matcher.sub(' ', text).strip() - return _textwrap.fill(text, width, initial_indent=indent, - subsequent_indent=indent) - - def _get_help_string(self, action): - return action.help - - -class RawDescriptionHelpFormatter(HelpFormatter): - """Help message formatter which retains any formatting in descriptions. - - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - - def _fill_text(self, text, width, indent): - return ''.join([indent + line for line in text.splitlines(True)]) - - -class RawTextHelpFormatter(RawDescriptionHelpFormatter): - """Help message formatter which retains formatting of all help text. - - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - - def _split_lines(self, text, width): - return text.splitlines() - - -class ArgumentDefaultsHelpFormatter(HelpFormatter): - """Help message formatter which adds default values to argument help. - - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - - def _get_help_string(self, action): - help = action.help - if '%(default)' not in action.help: - if action.default is not SUPPRESS: - defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] - if action.option_strings or action.nargs in defaulting_nargs: - help += ' (default: %(default)s)' - return help - - -# ===================== -# Options and Arguments -# ===================== - -def _get_action_name(argument): - if argument is None: - return None - elif argument.option_strings: - return '/'.join(argument.option_strings) - elif argument.metavar not in (None, SUPPRESS): - return argument.metavar - elif argument.dest not in (None, SUPPRESS): - return argument.dest - else: - return None - - -class ArgumentError(Exception): - """An error from creating or using an argument (optional or positional). - - The string value of this exception is the message, augmented with - information about the argument that caused it. - """ - - def __init__(self, argument, message): - self.argument_name = _get_action_name(argument) - self.message = message - - def __str__(self): - if self.argument_name is None: - format = '%(message)s' - else: - format = 'argument %(argument_name)s: %(message)s' - return format % dict(message=self.message, - argument_name=self.argument_name) - - -class ArgumentTypeError(Exception): - """An error from trying to convert a command line string to a type.""" - pass - - -# ============== -# Action classes -# ============== - -class Action(_AttributeHolder): - """Information about how to convert command line strings to Python objects. - - Action objects are used by an ArgumentParser to represent the information - needed to parse a single argument from one or more strings from the - command line. The keyword arguments to the Action constructor are also - all attributes of Action instances. - - Keyword Arguments: - - - option_strings -- A list of command-line option strings which - should be associated with this action. - - - dest -- The name of the attribute to hold the created object(s) - - - nargs -- The number of command-line arguments that should be - consumed. By default, one argument will be consumed and a single - value will be produced. Other values include: - - N (an integer) consumes N arguments (and produces a list) - - '?' consumes zero or one arguments - - '*' consumes zero or more arguments (and produces a list) - - '+' consumes one or more arguments (and produces a list) - Note that the difference between the default and nargs=1 is that - with the default, a single value will be produced, while with - nargs=1, a list containing a single value will be produced. - - - const -- The value to be produced if the option is specified and the - option uses an action that takes no values. - - - default -- The value to be produced if the option is not specified. - - - type -- The type which the command-line arguments should be converted - to, should be one of 'string', 'int', 'float', 'complex' or a - callable object that accepts a single string argument. If None, - 'string' is assumed. - - - choices -- A container of values that should be allowed. If not None, - after a command-line argument has been converted to the appropriate - type, an exception will be raised if it is not a member of this - collection. - - - required -- True if the action must always be specified at the - command line. This is only meaningful for optional command-line - arguments. - - - help -- The help string describing the argument. - - - metavar -- The name to be used for the option's argument with the - help string. If None, the 'dest' value will be used as the name. - """ - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - self.option_strings = option_strings - self.dest = dest - self.nargs = nargs - self.const = const - self.default = default - self.type = type - self.choices = choices - self.required = required - self.help = help - self.metavar = metavar - - def _get_kwargs(self): - names = [ - 'option_strings', - 'dest', - 'nargs', - 'const', - 'default', - 'type', - 'choices', - 'help', - 'metavar', - ] - return [(name, getattr(self, name)) for name in names] - - def __call__(self, parser, namespace, values, option_string=None): - raise NotImplementedError(_('.__call__() not defined')) - - -class _StoreAction(Action): - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - if nargs == 0: - raise ValueError('nargs for store actions must be > 0; if you ' - 'have nothing to store, actions such as store ' - 'true or store const may be more appropriate') - if const is not None and nargs != OPTIONAL: - raise ValueError('nargs must be %r to supply const' % OPTIONAL) - super(_StoreAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, values) - - -class _StoreConstAction(Action): - - def __init__(self, - option_strings, - dest, - const, - default=None, - required=False, - help=None, - metavar=None): - super(_StoreConstAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - const=const, - default=default, - required=required, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, self.const) - - -class _StoreTrueAction(_StoreConstAction): - - def __init__(self, - option_strings, - dest, - default=False, - required=False, - help=None): - super(_StoreTrueAction, self).__init__( - option_strings=option_strings, - dest=dest, - const=True, - default=default, - required=required, - help=help) - - -class _StoreFalseAction(_StoreConstAction): - - def __init__(self, - option_strings, - dest, - default=True, - required=False, - help=None): - super(_StoreFalseAction, self).__init__( - option_strings=option_strings, - dest=dest, - const=False, - default=default, - required=required, - help=help) - - -class _AppendAction(Action): - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - if nargs == 0: - raise ValueError('nargs for append actions must be > 0; if arg ' - 'strings are not supplying the value to append, ' - 'the append const action may be more appropriate') - if const is not None and nargs != OPTIONAL: - raise ValueError('nargs must be %r to supply const' % OPTIONAL) - super(_AppendAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - items = _copy.copy(_ensure_value(namespace, self.dest, [])) - items.append(values) - setattr(namespace, self.dest, items) - - -class _AppendConstAction(Action): - - def __init__(self, - option_strings, - dest, - const, - default=None, - required=False, - help=None, - metavar=None): - super(_AppendConstAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - const=const, - default=default, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - items = _copy.copy(_ensure_value(namespace, self.dest, [])) - items.append(self.const) - setattr(namespace, self.dest, items) - - -class _CountAction(Action): - - def __init__(self, - option_strings, - dest, - default=None, - required=False, - help=None): - super(_CountAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - default=default, - required=required, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - new_count = _ensure_value(namespace, self.dest, 0) + 1 - setattr(namespace, self.dest, new_count) - - -class _HelpAction(Action): - - def __init__(self, - option_strings, - dest=SUPPRESS, - default=SUPPRESS, - help=None): - super(_HelpAction, self).__init__( - option_strings=option_strings, - dest=dest, - default=default, - nargs=0, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - parser.print_help() - parser.exit() - - -class _VersionAction(Action): - - def __init__(self, - option_strings, - version=None, - dest=SUPPRESS, - default=SUPPRESS, - help="show program's version number and exit"): - super(_VersionAction, self).__init__( - option_strings=option_strings, - dest=dest, - default=default, - nargs=0, - help=help) - self.version = version - - def __call__(self, parser, namespace, values, option_string=None): - version = self.version - if version is None: - version = parser.version - formatter = parser._get_formatter() - formatter.add_text(version) - parser.exit(message=formatter.format_help()) - - -class _SubParsersAction(Action): - - class _ChoicesPseudoAction(Action): - - def __init__(self, name, aliases, help): - metavar = dest = name - if aliases: - metavar += ' (%s)' % ', '.join(aliases) - sup = super(_SubParsersAction._ChoicesPseudoAction, self) - sup.__init__(option_strings=[], dest=dest, help=help, - metavar=metavar) - - def __init__(self, - option_strings, - prog, - parser_class, - dest=SUPPRESS, - help=None, - metavar=None): - - self._prog_prefix = prog - self._parser_class = parser_class - self._name_parser_map = {} - self._choices_actions = [] - - super(_SubParsersAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=PARSER, - choices=self._name_parser_map, - help=help, - metavar=metavar) - - def add_parser(self, name, **kwargs): - # set prog from the existing prefix - if kwargs.get('prog') is None: - kwargs['prog'] = '%s %s' % (self._prog_prefix, name) - - aliases = kwargs.pop('aliases', ()) - - # create a pseudo-action to hold the choice help - if 'help' in kwargs: - help = kwargs.pop('help') - choice_action = self._ChoicesPseudoAction(name, aliases, help) - self._choices_actions.append(choice_action) - - # create the parser and add it to the map - parser = self._parser_class(**kwargs) - self._name_parser_map[name] = parser - - # make parser available under aliases also - for alias in aliases: - self._name_parser_map[alias] = parser - - return parser - - def _get_subactions(self): - return self._choices_actions - - def __call__(self, parser, namespace, values, option_string=None): - parser_name = values[0] - arg_strings = values[1:] - - # set the parser name if requested - if self.dest is not SUPPRESS: - setattr(namespace, self.dest, parser_name) - - # select the parser - try: - parser = self._name_parser_map[parser_name] - except KeyError: - tup = parser_name, ', '.join(self._name_parser_map) - msg = _('unknown parser %r (choices: %s)' % tup) - raise ArgumentError(self, msg) - - # parse all the remaining options into the namespace - # store any unrecognized options on the object, so that the top - # level parser can decide what to do with them - namespace, arg_strings = parser.parse_known_args(arg_strings, namespace) - if arg_strings: - vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, []) - getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) - - -# ============== -# Type classes -# ============== - -class FileType(object): - """Factory for creating file object types - - Instances of FileType are typically passed as type= arguments to the - ArgumentParser add_argument() method. - - Keyword Arguments: - - mode -- A string indicating how the file is to be opened. Accepts the - same values as the builtin open() function. - - bufsize -- The file's desired buffer size. Accepts the same values as - the builtin open() function. - """ - - def __init__(self, mode='r', bufsize=None): - self._mode = mode - self._bufsize = bufsize - - def __call__(self, string): - # the special argument "-" means sys.std{in,out} - if string == '-': - if 'r' in self._mode: - return _sys.stdin - elif 'w' in self._mode: - return _sys.stdout - else: - msg = _('argument "-" with mode %r' % self._mode) - raise ValueError(msg) - - try: - # all other arguments are used as file names - if self._bufsize: - return open(string, self._mode, self._bufsize) - else: - return open(string, self._mode) - except IOError: - err = _sys.exc_info()[1] - message = _("can't open '%s': %s") - raise ArgumentTypeError(message % (string, err)) - - def __repr__(self): - args = [self._mode, self._bufsize] - args_str = ', '.join([repr(arg) for arg in args if arg is not None]) - return '%s(%s)' % (type(self).__name__, args_str) - -# =========================== -# Optional and Positional Parsing -# =========================== - -class Namespace(_AttributeHolder): - """Simple object for storing attributes. - - Implements equality by attribute names and values, and provides a simple - string representation. - """ - - def __init__(self, **kwargs): - for name in kwargs: - setattr(self, name, kwargs[name]) - - __hash__ = None - - def __eq__(self, other): - return vars(self) == vars(other) - - def __ne__(self, other): - return not (self == other) - - def __contains__(self, key): - return key in self.__dict__ - - -class _ActionsContainer(object): - - def __init__(self, - description, - prefix_chars, - argument_default, - conflict_handler): - super(_ActionsContainer, self).__init__() - - self.description = description - self.argument_default = argument_default - self.prefix_chars = prefix_chars - self.conflict_handler = conflict_handler - - # set up registries - self._registries = {} - - # register actions - self.register('action', None, _StoreAction) - self.register('action', 'store', _StoreAction) - self.register('action', 'store_const', _StoreConstAction) - self.register('action', 'store_true', _StoreTrueAction) - self.register('action', 'store_false', _StoreFalseAction) - self.register('action', 'append', _AppendAction) - self.register('action', 'append_const', _AppendConstAction) - self.register('action', 'count', _CountAction) - self.register('action', 'help', _HelpAction) - self.register('action', 'version', _VersionAction) - self.register('action', 'parsers', _SubParsersAction) - - # raise an exception if the conflict handler is invalid - self._get_handler() - - # action storage - self._actions = [] - self._option_string_actions = {} - - # groups - self._action_groups = [] - self._mutually_exclusive_groups = [] - - # defaults storage - self._defaults = {} - - # determines whether an "option" looks like a negative number - self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$') - - # whether or not there are any optionals that look like negative - # numbers -- uses a list so it can be shared and edited - self._has_negative_number_optionals = [] - - # ==================== - # Registration methods - # ==================== - def register(self, registry_name, value, object): - registry = self._registries.setdefault(registry_name, {}) - registry[value] = object - - def _registry_get(self, registry_name, value, default=None): - return self._registries[registry_name].get(value, default) - - # ================================== - # Namespace default accessor methods - # ================================== - def set_defaults(self, **kwargs): - self._defaults.update(kwargs) - - # if these defaults match any existing arguments, replace - # the previous default on the object with the new one - for action in self._actions: - if action.dest in kwargs: - action.default = kwargs[action.dest] - - def get_default(self, dest): - for action in self._actions: - if action.dest == dest and action.default is not None: - return action.default - return self._defaults.get(dest, None) - - - # ======================= - # Adding argument actions - # ======================= - def add_argument(self, *args, **kwargs): - """ - add_argument(dest, ..., name=value, ...) - add_argument(option_string, option_string, ..., name=value, ...) - """ - - # if no positional args are supplied or only one is supplied and - # it doesn't look like an option string, parse a positional - # argument - chars = self.prefix_chars - if not args or len(args) == 1 and args[0][0] not in chars: - if args and 'dest' in kwargs: - raise ValueError('dest supplied twice for positional argument') - kwargs = self._get_positional_kwargs(*args, **kwargs) - - # otherwise, we're adding an optional argument - else: - kwargs = self._get_optional_kwargs(*args, **kwargs) - - # if no default was supplied, use the parser-level default - if 'default' not in kwargs: - dest = kwargs['dest'] - if dest in self._defaults: - kwargs['default'] = self._defaults[dest] - elif self.argument_default is not None: - kwargs['default'] = self.argument_default - - # create the action object, and add it to the parser - action_class = self._pop_action_class(kwargs) - if not _callable(action_class): - raise ValueError('unknown action "%s"' % action_class) - action = action_class(**kwargs) - - # raise an error if the action type is not callable - type_func = self._registry_get('type', action.type, action.type) - if not _callable(type_func): - raise ValueError('%r is not callable' % type_func) - - return self._add_action(action) - - def add_argument_group(self, *args, **kwargs): - group = _ArgumentGroup(self, *args, **kwargs) - self._action_groups.append(group) - return group - - def add_mutually_exclusive_group(self, **kwargs): - group = _MutuallyExclusiveGroup(self, **kwargs) - self._mutually_exclusive_groups.append(group) - return group - - def _add_action(self, action): - # resolve any conflicts - self._check_conflict(action) - - # add to actions list - self._actions.append(action) - action.container = self - - # index the action by any option strings it has - for option_string in action.option_strings: - self._option_string_actions[option_string] = action - - # set the flag if any option strings look like negative numbers - for option_string in action.option_strings: - if self._negative_number_matcher.match(option_string): - if not self._has_negative_number_optionals: - self._has_negative_number_optionals.append(True) - - # return the created action - return action - - def _remove_action(self, action): - self._actions.remove(action) - - def _add_container_actions(self, container): - # collect groups by titles - title_group_map = {} - for group in self._action_groups: - if group.title in title_group_map: - msg = _('cannot merge actions - two groups are named %r') - raise ValueError(msg % (group.title)) - title_group_map[group.title] = group - - # map each action to its group - group_map = {} - for group in container._action_groups: - - # if a group with the title exists, use that, otherwise - # create a new group matching the container's group - if group.title not in title_group_map: - title_group_map[group.title] = self.add_argument_group( - title=group.title, - description=group.description, - conflict_handler=group.conflict_handler) - - # map the actions to their new group - for action in group._group_actions: - group_map[action] = title_group_map[group.title] - - # add container's mutually exclusive groups - # NOTE: if add_mutually_exclusive_group ever gains title= and - # description= then this code will need to be expanded as above - for group in container._mutually_exclusive_groups: - mutex_group = self.add_mutually_exclusive_group( - required=group.required) - - # map the actions to their new mutex group - for action in group._group_actions: - group_map[action] = mutex_group - - # add all actions to this container or their group - for action in container._actions: - group_map.get(action, self)._add_action(action) - - def _get_positional_kwargs(self, dest, **kwargs): - # make sure required is not specified - if 'required' in kwargs: - msg = _("'required' is an invalid argument for positionals") - raise TypeError(msg) - - # mark positional arguments as required if at least one is - # always required - if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: - kwargs['required'] = True - if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: - kwargs['required'] = True - - # return the keyword arguments with no option strings - return dict(kwargs, dest=dest, option_strings=[]) - - def _get_optional_kwargs(self, *args, **kwargs): - # determine short and long option strings - option_strings = [] - long_option_strings = [] - for option_string in args: - # error on strings that don't start with an appropriate prefix - if not option_string[0] in self.prefix_chars: - msg = _('invalid option string %r: ' - 'must start with a character %r') - tup = option_string, self.prefix_chars - raise ValueError(msg % tup) - - # strings starting with two prefix characters are long options - option_strings.append(option_string) - if option_string[0] in self.prefix_chars: - if len(option_string) > 1: - if option_string[1] in self.prefix_chars: - long_option_strings.append(option_string) - - # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' - dest = kwargs.pop('dest', None) - if dest is None: - if long_option_strings: - dest_option_string = long_option_strings[0] - else: - dest_option_string = option_strings[0] - dest = dest_option_string.lstrip(self.prefix_chars) - if not dest: - msg = _('dest= is required for options like %r') - raise ValueError(msg % option_string) - dest = dest.replace('-', '_') - - # return the updated keyword arguments - return dict(kwargs, dest=dest, option_strings=option_strings) - - def _pop_action_class(self, kwargs, default=None): - action = kwargs.pop('action', default) - return self._registry_get('action', action, action) - - def _get_handler(self): - # determine function from conflict handler string - handler_func_name = '_handle_conflict_%s' % self.conflict_handler - try: - return getattr(self, handler_func_name) - except AttributeError: - msg = _('invalid conflict_resolution value: %r') - raise ValueError(msg % self.conflict_handler) - - def _check_conflict(self, action): - - # find all options that conflict with this option - confl_optionals = [] - for option_string in action.option_strings: - if option_string in self._option_string_actions: - confl_optional = self._option_string_actions[option_string] - confl_optionals.append((option_string, confl_optional)) - - # resolve any conflicts - if confl_optionals: - conflict_handler = self._get_handler() - conflict_handler(action, confl_optionals) - - def _handle_conflict_error(self, action, conflicting_actions): - message = _('conflicting option string(s): %s') - conflict_string = ', '.join([option_string - for option_string, action - in conflicting_actions]) - raise ArgumentError(action, message % conflict_string) - - def _handle_conflict_resolve(self, action, conflicting_actions): - - # remove all conflicting options - for option_string, action in conflicting_actions: - - # remove the conflicting option - action.option_strings.remove(option_string) - self._option_string_actions.pop(option_string, None) - - # if the option now has no option string, remove it from the - # container holding it - if not action.option_strings: - action.container._remove_action(action) - - -class _ArgumentGroup(_ActionsContainer): - - def __init__(self, container, title=None, description=None, **kwargs): - # add any missing keyword arguments by checking the container - update = kwargs.setdefault - update('conflict_handler', container.conflict_handler) - update('prefix_chars', container.prefix_chars) - update('argument_default', container.argument_default) - super_init = super(_ArgumentGroup, self).__init__ - super_init(description=description, **kwargs) - - # group attributes - self.title = title - self._group_actions = [] - - # share most attributes with the container - self._registries = container._registries - self._actions = container._actions - self._option_string_actions = container._option_string_actions - self._defaults = container._defaults - self._has_negative_number_optionals = \ - container._has_negative_number_optionals - - def _add_action(self, action): - action = super(_ArgumentGroup, self)._add_action(action) - self._group_actions.append(action) - return action - - def _remove_action(self, action): - super(_ArgumentGroup, self)._remove_action(action) - self._group_actions.remove(action) - - -class _MutuallyExclusiveGroup(_ArgumentGroup): - - def __init__(self, container, required=False): - super(_MutuallyExclusiveGroup, self).__init__(container) - self.required = required - self._container = container - - def _add_action(self, action): - if action.required: - msg = _('mutually exclusive arguments must be optional') - raise ValueError(msg) - action = self._container._add_action(action) - self._group_actions.append(action) - return action - - def _remove_action(self, action): - self._container._remove_action(action) - self._group_actions.remove(action) - - -class ArgumentParser(_AttributeHolder, _ActionsContainer): - """Object for parsing command line strings into Python objects. - - Keyword Arguments: - - prog -- The name of the program (default: sys.argv[0]) - - usage -- A usage message (default: auto-generated from arguments) - - description -- A description of what the program does - - epilog -- Text following the argument descriptions - - parents -- Parsers whose arguments should be copied into this one - - formatter_class -- HelpFormatter class for printing help messages - - prefix_chars -- Characters that prefix optional arguments - - fromfile_prefix_chars -- Characters that prefix files containing - additional arguments - - argument_default -- The default value for all arguments - - conflict_handler -- String indicating how to handle conflicts - - add_help -- Add a -h/-help option - """ - - def __init__(self, - prog=None, - usage=None, - description=None, - epilog=None, - version=None, - parents=[], - formatter_class=HelpFormatter, - prefix_chars='-', - fromfile_prefix_chars=None, - argument_default=None, - conflict_handler='error', - add_help=True): - - if version is not None: - import warnings - warnings.warn( - """The "version" argument to ArgumentParser is deprecated. """ - """Please use """ - """"add_argument(..., action='version', version="N", ...)" """ - """instead""", DeprecationWarning) - - superinit = super(ArgumentParser, self).__init__ - superinit(description=description, - prefix_chars=prefix_chars, - argument_default=argument_default, - conflict_handler=conflict_handler) - - # default setting for prog - if prog is None: - prog = _os.path.basename(_sys.argv[0]) - - self.prog = prog - self.usage = usage - self.epilog = epilog - self.version = version - self.formatter_class = formatter_class - self.fromfile_prefix_chars = fromfile_prefix_chars - self.add_help = add_help - - add_group = self.add_argument_group - self._positionals = add_group(_('positional arguments')) - self._optionals = add_group(_('optional arguments')) - self._subparsers = None - - # register types - def identity(string): - return string - self.register('type', None, identity) - - # add help and version arguments if necessary - # (using explicit default to override global argument_default) - if '-' in prefix_chars: - default_prefix = '-' - else: - default_prefix = prefix_chars[0] - if self.add_help: - self.add_argument( - default_prefix+'h', default_prefix*2+'help', - action='help', default=SUPPRESS, - help=_('show this help message and exit')) - if self.version: - self.add_argument( - default_prefix+'v', default_prefix*2+'version', - action='version', default=SUPPRESS, - version=self.version, - help=_("show program's version number and exit")) - - # add parent arguments and defaults - for parent in parents: - self._add_container_actions(parent) - try: - defaults = parent._defaults - except AttributeError: - pass - else: - self._defaults.update(defaults) - - # ======================= - # Pretty __repr__ methods - # ======================= - def _get_kwargs(self): - names = [ - 'prog', - 'usage', - 'description', - 'version', - 'formatter_class', - 'conflict_handler', - 'add_help', - ] - return [(name, getattr(self, name)) for name in names] - - # ================================== - # Optional/Positional adding methods - # ================================== - def add_subparsers(self, **kwargs): - if self._subparsers is not None: - self.error(_('cannot have multiple subparser arguments')) - - # add the parser class to the arguments if it's not present - kwargs.setdefault('parser_class', type(self)) - - if 'title' in kwargs or 'description' in kwargs: - title = _(kwargs.pop('title', 'subcommands')) - description = _(kwargs.pop('description', None)) - self._subparsers = self.add_argument_group(title, description) - else: - self._subparsers = self._positionals - - # prog defaults to the usage message of this parser, skipping - # optional arguments and with no "usage:" prefix - if kwargs.get('prog') is None: - formatter = self._get_formatter() - positionals = self._get_positional_actions() - groups = self._mutually_exclusive_groups - formatter.add_usage(self.usage, positionals, groups, '') - kwargs['prog'] = formatter.format_help().strip() - - # create the parsers action and add it to the positionals list - parsers_class = self._pop_action_class(kwargs, 'parsers') - action = parsers_class(option_strings=[], **kwargs) - self._subparsers._add_action(action) - - # return the created parsers action - return action - - def _add_action(self, action): - if action.option_strings: - self._optionals._add_action(action) - else: - self._positionals._add_action(action) - return action - - def _get_optional_actions(self): - return [action - for action in self._actions - if action.option_strings] - - def _get_positional_actions(self): - return [action - for action in self._actions - if not action.option_strings] - - # ===================================== - # Command line argument parsing methods - # ===================================== - def parse_args(self, args=None, namespace=None): - args, argv = self.parse_known_args(args, namespace) - if argv: - msg = _('unrecognized arguments: %s') - self.error(msg % ' '.join(argv)) - return args - - def parse_known_args(self, args=None, namespace=None): - # args default to the system args - if args is None: - args = _sys.argv[1:] - - # default Namespace built from parser defaults - if namespace is None: - namespace = Namespace() - - # add any action defaults that aren't present - for action in self._actions: - if action.dest is not SUPPRESS: - if not hasattr(namespace, action.dest): - if action.default is not SUPPRESS: - setattr(namespace, action.dest, action.default) - - # add any parser defaults that aren't present - for dest in self._defaults: - if not hasattr(namespace, dest): - setattr(namespace, dest, self._defaults[dest]) - - # parse the arguments and exit if there are any errors - try: - namespace, args = self._parse_known_args(args, namespace) - if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR): - args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) - delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) - return namespace, args - except ArgumentError: - err = _sys.exc_info()[1] - self.error(str(err)) - - def _parse_known_args(self, arg_strings, namespace): - # replace arg strings that are file references - if self.fromfile_prefix_chars is not None: - arg_strings = self._read_args_from_files(arg_strings) - - # map all mutually exclusive arguments to the other arguments - # they can't occur with - action_conflicts = {} - for mutex_group in self._mutually_exclusive_groups: - group_actions = mutex_group._group_actions - for i, mutex_action in enumerate(mutex_group._group_actions): - conflicts = action_conflicts.setdefault(mutex_action, []) - conflicts.extend(group_actions[:i]) - conflicts.extend(group_actions[i + 1:]) - - # find all option indices, and determine the arg_string_pattern - # which has an 'O' if there is an option at an index, - # an 'A' if there is an argument, or a '-' if there is a '--' - option_string_indices = {} - arg_string_pattern_parts = [] - arg_strings_iter = iter(arg_strings) - for i, arg_string in enumerate(arg_strings_iter): - - # all args after -- are non-options - if arg_string == '--': - arg_string_pattern_parts.append('-') - for arg_string in arg_strings_iter: - arg_string_pattern_parts.append('A') - - # otherwise, add the arg to the arg strings - # and note the index if it was an option - else: - option_tuple = self._parse_optional(arg_string) - if option_tuple is None: - pattern = 'A' - else: - option_string_indices[i] = option_tuple - pattern = 'O' - arg_string_pattern_parts.append(pattern) - - # join the pieces together to form the pattern - arg_strings_pattern = ''.join(arg_string_pattern_parts) - - # converts arg strings to the appropriate and then takes the action - seen_actions = set() - seen_non_default_actions = set() - - def take_action(action, argument_strings, option_string=None): - seen_actions.add(action) - argument_values = self._get_values(action, argument_strings) - - # error if this argument is not allowed with other previously - # seen arguments, assuming that actions that use the default - # value don't really count as "present" - if argument_values is not action.default: - seen_non_default_actions.add(action) - for conflict_action in action_conflicts.get(action, []): - if conflict_action in seen_non_default_actions: - msg = _('not allowed with argument %s') - action_name = _get_action_name(conflict_action) - raise ArgumentError(action, msg % action_name) - - # take the action if we didn't receive a SUPPRESS value - # (e.g. from a default) - if argument_values is not SUPPRESS: - action(self, namespace, argument_values, option_string) - - # function to convert arg_strings into an optional action - def consume_optional(start_index): - - # get the optional identified at this index - option_tuple = option_string_indices[start_index] - action, option_string, explicit_arg = option_tuple - - # identify additional optionals in the same arg string - # (e.g. -xyz is the same as -x -y -z if no args are required) - match_argument = self._match_argument - action_tuples = [] - while True: - - # if we found no optional action, skip it - if action is None: - extras.append(arg_strings[start_index]) - return start_index + 1 - - # if there is an explicit argument, try to match the - # optional's string arguments to only this - if explicit_arg is not None: - arg_count = match_argument(action, 'A') - - # if the action is a single-dash option and takes no - # arguments, try to parse more single-dash options out - # of the tail of the option string - chars = self.prefix_chars - if arg_count == 0 and option_string[1] not in chars: - action_tuples.append((action, [], option_string)) - char = option_string[0] - option_string = char + explicit_arg[0] - new_explicit_arg = explicit_arg[1:] or None - optionals_map = self._option_string_actions - if option_string in optionals_map: - action = optionals_map[option_string] - explicit_arg = new_explicit_arg - else: - msg = _('ignored explicit argument %r') - raise ArgumentError(action, msg % explicit_arg) - - # if the action expect exactly one argument, we've - # successfully matched the option; exit the loop - elif arg_count == 1: - stop = start_index + 1 - args = [explicit_arg] - action_tuples.append((action, args, option_string)) - break - - # error if a double-dash option did not use the - # explicit argument - else: - msg = _('ignored explicit argument %r') - raise ArgumentError(action, msg % explicit_arg) - - # if there is no explicit argument, try to match the - # optional's string arguments with the following strings - # if successful, exit the loop - else: - start = start_index + 1 - selected_patterns = arg_strings_pattern[start:] - arg_count = match_argument(action, selected_patterns) - stop = start + arg_count - args = arg_strings[start:stop] - action_tuples.append((action, args, option_string)) - break - - # add the Optional to the list and return the index at which - # the Optional's string args stopped - assert action_tuples - for action, args, option_string in action_tuples: - take_action(action, args, option_string) - return stop - - # the list of Positionals left to be parsed; this is modified - # by consume_positionals() - positionals = self._get_positional_actions() - - # function to convert arg_strings into positional actions - def consume_positionals(start_index): - # match as many Positionals as possible - match_partial = self._match_arguments_partial - selected_pattern = arg_strings_pattern[start_index:] - arg_counts = match_partial(positionals, selected_pattern) - - # slice off the appropriate arg strings for each Positional - # and add the Positional and its args to the list - for action, arg_count in zip(positionals, arg_counts): - args = arg_strings[start_index: start_index + arg_count] - start_index += arg_count - take_action(action, args) - - # slice off the Positionals that we just parsed and return the - # index at which the Positionals' string args stopped - positionals[:] = positionals[len(arg_counts):] - return start_index - - # consume Positionals and Optionals alternately, until we have - # passed the last option string - extras = [] - start_index = 0 - if option_string_indices: - max_option_string_index = max(option_string_indices) - else: - max_option_string_index = -1 - while start_index <= max_option_string_index: - - # consume any Positionals preceding the next option - next_option_string_index = min([ - index - for index in option_string_indices - if index >= start_index]) - if start_index != next_option_string_index: - positionals_end_index = consume_positionals(start_index) - - # only try to parse the next optional if we didn't consume - # the option string during the positionals parsing - if positionals_end_index > start_index: - start_index = positionals_end_index - continue - else: - start_index = positionals_end_index - - # if we consumed all the positionals we could and we're not - # at the index of an option string, there were extra arguments - if start_index not in option_string_indices: - strings = arg_strings[start_index:next_option_string_index] - extras.extend(strings) - start_index = next_option_string_index - - # consume the next optional and any arguments for it - start_index = consume_optional(start_index) - - # consume any positionals following the last Optional - stop_index = consume_positionals(start_index) - - # if we didn't consume all the argument strings, there were extras - extras.extend(arg_strings[stop_index:]) - - # if we didn't use all the Positional objects, there were too few - # arg strings supplied. - if positionals: - self.error(_('too few arguments')) - - # make sure all required actions were present, and convert defaults. - for action in self._actions: - if action not in seen_actions: - if action.required: - name = _get_action_name(action) - self.error(_('argument %s is required') % name) - else: - # Convert action default now instead of doing it before - # parsing arguments to avoid calling convert functions - # twice (which may fail) if the argument was given, but - # only if it was defined already in the namespace - if (action.default is not None and - isinstance(action.default, basestring) and - hasattr(namespace, action.dest) and - action.default is getattr(namespace, action.dest)): - setattr(namespace, action.dest, - self._get_value(action, action.default)) - - # make sure all required groups had one option present - for group in self._mutually_exclusive_groups: - if group.required: - for action in group._group_actions: - if action in seen_non_default_actions: - break - - # if no actions were used, report the error - else: - names = [_get_action_name(action) - for action in group._group_actions - if action.help is not SUPPRESS] - msg = _('one of the arguments %s is required') - self.error(msg % ' '.join(names)) - - # return the updated namespace and the extra arguments - return namespace, extras - - def _read_args_from_files(self, arg_strings): - # expand arguments referencing files - new_arg_strings = [] - for arg_string in arg_strings: - - # for regular arguments, just add them back into the list - if arg_string[0] not in self.fromfile_prefix_chars: - new_arg_strings.append(arg_string) - - # replace arguments referencing files with the file content - else: - try: - args_file = open(arg_string[1:]) - try: - arg_strings = [] - for arg_line in args_file.read().splitlines(): - for arg in self.convert_arg_line_to_args(arg_line): - arg_strings.append(arg) - arg_strings = self._read_args_from_files(arg_strings) - new_arg_strings.extend(arg_strings) - finally: - args_file.close() - except IOError: - err = _sys.exc_info()[1] - self.error(str(err)) - - # return the modified argument list - return new_arg_strings - - def convert_arg_line_to_args(self, arg_line): - return [arg_line] - - def _match_argument(self, action, arg_strings_pattern): - # match the pattern for this action to the arg strings - nargs_pattern = self._get_nargs_pattern(action) - match = _re.match(nargs_pattern, arg_strings_pattern) - - # raise an exception if we weren't able to find a match - if match is None: - nargs_errors = { - None: _('expected one argument'), - OPTIONAL: _('expected at most one argument'), - ONE_OR_MORE: _('expected at least one argument'), - } - default = _('expected %s argument(s)') % action.nargs - msg = nargs_errors.get(action.nargs, default) - raise ArgumentError(action, msg) - - # return the number of arguments matched - return len(match.group(1)) - - def _match_arguments_partial(self, actions, arg_strings_pattern): - # progressively shorten the actions list by slicing off the - # final actions until we find a match - result = [] - for i in range(len(actions), 0, -1): - actions_slice = actions[:i] - pattern = ''.join([self._get_nargs_pattern(action) - for action in actions_slice]) - match = _re.match(pattern, arg_strings_pattern) - if match is not None: - result.extend([len(string) for string in match.groups()]) - break - - # return the list of arg string counts - return result - - def _parse_optional(self, arg_string): - # if it's an empty string, it was meant to be a positional - if not arg_string: - return None - - # if it doesn't start with a prefix, it was meant to be positional - if not arg_string[0] in self.prefix_chars: - return None - - # if the option string is present in the parser, return the action - if arg_string in self._option_string_actions: - action = self._option_string_actions[arg_string] - return action, arg_string, None - - # if it's just a single character, it was meant to be positional - if len(arg_string) == 1: - return None - - # if the option string before the "=" is present, return the action - if '=' in arg_string: - option_string, explicit_arg = arg_string.split('=', 1) - if option_string in self._option_string_actions: - action = self._option_string_actions[option_string] - return action, option_string, explicit_arg - - # search through all possible prefixes of the option string - # and all actions in the parser for possible interpretations - option_tuples = self._get_option_tuples(arg_string) - - # if multiple actions match, the option string was ambiguous - if len(option_tuples) > 1: - options = ', '.join([option_string - for action, option_string, explicit_arg in option_tuples]) - tup = arg_string, options - self.error(_('ambiguous option: %s could match %s') % tup) - - # if exactly one action matched, this segmentation is good, - # so return the parsed action - elif len(option_tuples) == 1: - option_tuple, = option_tuples - return option_tuple - - # if it was not found as an option, but it looks like a negative - # number, it was meant to be positional - # unless there are negative-number-like options - if self._negative_number_matcher.match(arg_string): - if not self._has_negative_number_optionals: - return None - - # if it contains a space, it was meant to be a positional - if ' ' in arg_string: - return None - - # it was meant to be an optional but there is no such option - # in this parser (though it might be a valid option in a subparser) - return None, arg_string, None - - def _get_option_tuples(self, option_string): - result = [] - - # option strings starting with two prefix characters are only - # split at the '=' - chars = self.prefix_chars - if option_string[0] in chars and option_string[1] in chars: - if '=' in option_string: - option_prefix, explicit_arg = option_string.split('=', 1) - else: - option_prefix = option_string - explicit_arg = None - for option_string in self._option_string_actions: - if option_string.startswith(option_prefix): - action = self._option_string_actions[option_string] - tup = action, option_string, explicit_arg - result.append(tup) - - # single character options can be concatenated with their arguments - # but multiple character options always have to have their argument - # separate - elif option_string[0] in chars and option_string[1] not in chars: - option_prefix = option_string - explicit_arg = None - short_option_prefix = option_string[:2] - short_explicit_arg = option_string[2:] - - for option_string in self._option_string_actions: - if option_string == short_option_prefix: - action = self._option_string_actions[option_string] - tup = action, option_string, short_explicit_arg - result.append(tup) - elif option_string.startswith(option_prefix): - action = self._option_string_actions[option_string] - tup = action, option_string, explicit_arg - result.append(tup) - - # shouldn't ever get here - else: - self.error(_('unexpected option string: %s') % option_string) - - # return the collected option tuples - return result - - def _get_nargs_pattern(self, action): - # in all examples below, we have to allow for '--' args - # which are represented as '-' in the pattern - nargs = action.nargs - - # the default (None) is assumed to be a single argument - if nargs is None: - nargs_pattern = '(-*A-*)' - - # allow zero or one arguments - elif nargs == OPTIONAL: - nargs_pattern = '(-*A?-*)' - - # allow zero or more arguments - elif nargs == ZERO_OR_MORE: - nargs_pattern = '(-*[A-]*)' - - # allow one or more arguments - elif nargs == ONE_OR_MORE: - nargs_pattern = '(-*A[A-]*)' - - # allow any number of options or arguments - elif nargs == REMAINDER: - nargs_pattern = '([-AO]*)' - - # allow one argument followed by any number of options or arguments - elif nargs == PARSER: - nargs_pattern = '(-*A[-AO]*)' - - # all others should be integers - else: - nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) - - # if this is an optional action, -- is not allowed - if action.option_strings: - nargs_pattern = nargs_pattern.replace('-*', '') - nargs_pattern = nargs_pattern.replace('-', '') - - # return the pattern - return nargs_pattern - - # ======================== - # Value conversion methods - # ======================== - def _get_values(self, action, arg_strings): - # for everything but PARSER args, strip out '--' - if action.nargs not in [PARSER, REMAINDER]: - arg_strings = [s for s in arg_strings if s != '--'] - - # optional argument produces a default when not present - if not arg_strings and action.nargs == OPTIONAL: - if action.option_strings: - value = action.const - else: - value = action.default - if isinstance(value, basestring): - value = self._get_value(action, value) - self._check_value(action, value) - - # when nargs='*' on a positional, if there were no command-line - # args, use the default if it is anything other than None - elif (not arg_strings and action.nargs == ZERO_OR_MORE and - not action.option_strings): - if action.default is not None: - value = action.default - else: - value = arg_strings - self._check_value(action, value) - - # single argument or optional argument produces a single value - elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: - arg_string, = arg_strings - value = self._get_value(action, arg_string) - self._check_value(action, value) - - # REMAINDER arguments convert all values, checking none - elif action.nargs == REMAINDER: - value = [self._get_value(action, v) for v in arg_strings] - - # PARSER arguments convert all values, but check only the first - elif action.nargs == PARSER: - value = [self._get_value(action, v) for v in arg_strings] - self._check_value(action, value[0]) - - # all other types of nargs produce a list - else: - value = [self._get_value(action, v) for v in arg_strings] - for v in value: - self._check_value(action, v) - - # return the converted value - return value - - def _get_value(self, action, arg_string): - type_func = self._registry_get('type', action.type, action.type) - if not _callable(type_func): - msg = _('%r is not callable') - raise ArgumentError(action, msg % type_func) - - # convert the value to the appropriate type - try: - result = type_func(arg_string) - - # ArgumentTypeErrors indicate errors - except ArgumentTypeError: - name = getattr(action.type, '__name__', repr(action.type)) - msg = str(_sys.exc_info()[1]) - raise ArgumentError(action, msg) - - # TypeErrors or ValueErrors also indicate errors - except (TypeError, ValueError): - name = getattr(action.type, '__name__', repr(action.type)) - msg = _('invalid %s value: %r') - raise ArgumentError(action, msg % (name, arg_string)) - - # return the converted value - return result - - def _check_value(self, action, value): - # converted value must be one of the choices (if specified) - if action.choices is not None and value not in action.choices: - tup = value, ', '.join(map(repr, action.choices)) - msg = _('invalid choice: %r (choose from %s)') % tup - raise ArgumentError(action, msg) - - # ======================= - # Help-formatting methods - # ======================= - def format_usage(self): - formatter = self._get_formatter() - formatter.add_usage(self.usage, self._actions, - self._mutually_exclusive_groups) - return formatter.format_help() - - def format_help(self): - formatter = self._get_formatter() - - # usage - formatter.add_usage(self.usage, self._actions, - self._mutually_exclusive_groups) - - # description - formatter.add_text(self.description) - - # positionals, optionals and user-defined groups - for action_group in self._action_groups: - formatter.start_section(action_group.title) - formatter.add_text(action_group.description) - formatter.add_arguments(action_group._group_actions) - formatter.end_section() - - # epilog - formatter.add_text(self.epilog) - - # determine help from format above - return formatter.format_help() - - def format_version(self): - import warnings - warnings.warn( - 'The format_version method is deprecated -- the "version" ' - 'argument to ArgumentParser is no longer supported.', - DeprecationWarning) - formatter = self._get_formatter() - formatter.add_text(self.version) - return formatter.format_help() - - def _get_formatter(self): - return self.formatter_class(prog=self.prog) - - # ===================== - # Help-printing methods - # ===================== - def print_usage(self, file=None): - if file is None: - file = _sys.stdout - self._print_message(self.format_usage(), file) - - def print_help(self, file=None): - if file is None: - file = _sys.stdout - self._print_message(self.format_help(), file) - - def print_version(self, file=None): - import warnings - warnings.warn( - 'The print_version method is deprecated -- the "version" ' - 'argument to ArgumentParser is no longer supported.', - DeprecationWarning) - self._print_message(self.format_version(), file) - - def _print_message(self, message, file=None): - if message: - if file is None: - file = _sys.stderr - file.write(message) - - # =============== - # Exiting methods - # =============== - def exit(self, status=0, message=None): - if message: - self._print_message(message, _sys.stderr) - _sys.exit(status) - - def error(self, message): - """error(message: string) - - Prints a usage message incorporating the message to stderr and - exits. - - If you override this in a subclass, it should not return -- it - should either exit or raise an exception. - """ - self.print_usage(_sys.stderr) - self.exit(2, _('%s: error: %s\n') % (self.prog, message)) diff --git a/keycodemapdb/tools/keymap-gen b/keycodemapdb/tools/keymap-gen deleted file mode 100755 index 22b4f71..0000000 --- a/keycodemapdb/tools/keymap-gen +++ /dev/null @@ -1,1147 +0,0 @@ -#!/usr/bin/python -# -*- python -*- -# -# Keycode Map Generator -# -# Copyright (C) 2009-2017 Red Hat, Inc. -# -# This file is dual license under the terms of the GPLv2 or later -# and 3-clause BSD licenses. -# - -# Requires >= 2.6 -from __future__ import print_function - -import csv -try: - import argparse -except: - import os, sys - sys.path.append(os.path.join(os.path.dirname(__file__), "../thirdparty")) - import argparse -import hashlib -import time -import sys - -class Database: - - # Linux: linux/input.h - MAP_LINUX = "linux" - - # OS-X: Carbon/HIToolbox/Events.h - MAP_OSX = "osx" - - # AT Set 1: linux/drivers/input/keyboard/atkbd.c - # (atkbd_set2_keycode + atkbd_unxlate_table) - MAP_ATSET1 = "atset1" - - # AT Set 2: linux/drivers/input/keyboard/atkbd.c - # (atkbd_set2_keycode) - MAP_ATSET2 = "atset2" - - # AT Set 3: linux/drivers/input/keyboard/atkbd.c - # (atkbd_set3_keycode) - MAP_ATSET3 = "atset3" - - # Linux RAW: linux/drivers/char/keyboard.c (x86_keycodes) - MAP_XTKBD = "xtkbd" - - # USB HID: linux/drivers/hid/usbhid/usbkbd.c (usb_kbd_keycode) - MAP_USB = "usb" - - # Win32: mingw32/winuser.h - MAP_WIN32 = "win32" - - # XWin XT: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h} - # (xt + manually transcribed) - MAP_XWINXT = "xwinxt" - - # X11: http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h - MAP_X11 = "x11" - - # XKBD XT: xf86-input-keyboard/src/at_scancode.c - # (xt + manually transcribed) - MAP_XKBDXT = "xkbdxt" - - # Xorg with evdev: linux + an offset - MAP_XORGEVDEV = "xorgevdev" - - # Xorg with kbd: xkbdxt + an offset - MAP_XORGKBD = "xorgkbd" - - # Xorg with OS-X: osx + an offset - MAP_XORGXQUARTZ = "xorgxquartz" - - # Xorg + Cygwin: xwinxt + an offset - MAP_XORGXWIN = "xorgxwin" - - # QEMU key numbers: xtkbd + special re-encoding of high bit - MAP_QNUM = "qnum" - - # HTML codes - MAP_HTML = "html" - - # XKB key names - MAP_XKB = "xkb" - - # QEMU keycodes - MAP_QCODE = "qcode" - - # Sun / Sparc scan codes - # Reference: "SPARC International Keyboard Spec 1", page 7 "US scan set" - MAP_SUN = "sun" - - # Apple Desktop Bus - # Reference: http://www.archive.org/stream/apple-guide-macintosh-family-hardware/Apple_Guide_to_the_Macintosh_Family_Hardware_2e#page/n345/mode/2up - MAP_ADB = "adb" - - MAP_LIST = ( - MAP_LINUX, - MAP_OSX, - MAP_ATSET1, - MAP_ATSET2, - MAP_ATSET3, - MAP_USB, - MAP_WIN32, - MAP_XWINXT, - MAP_XKBDXT, - MAP_X11, - MAP_HTML, - MAP_XKB, - MAP_QCODE, - MAP_SUN, - MAP_ADB, - - # These are derived from maps above - MAP_XTKBD, - MAP_XORGEVDEV, - MAP_XORGKBD, - MAP_XORGXQUARTZ, - MAP_XORGXWIN, - MAP_QNUM, - ) - - CODE_COLUMNS = { - MAP_LINUX: 1, - MAP_OSX: 3, - MAP_ATSET1: 4, - MAP_ATSET2: 5, - MAP_ATSET3: 6, - MAP_USB: 7, - MAP_WIN32: 9, - MAP_XWINXT: 10, - MAP_XKBDXT: 11, - MAP_X11: 13, - MAP_HTML: 14, - MAP_XKB: 15, - MAP_SUN: 17, - MAP_ADB: 18, - } - - ENUM_COLUMNS = { - MAP_QCODE: 14, - } - - NAME_COLUMNS = { - MAP_LINUX: 0, - MAP_OSX: 2, - MAP_WIN32: 8, - MAP_X11: 12, - MAP_HTML: 14, - MAP_XKB: 15, - MAP_QCODE: 16, - } - - ENUM_BOUND = { - MAP_QCODE: "Q_KEY_CODE__MAX", - } - - def __init__(self): - - self.mapto = {} - self.mapfrom = {} - self.mapname = {} - self.mapchecksum = None - - for name in self.MAP_LIST: - # Key is a MAP_LINUX, value is a MAP_XXX - self.mapto[name] = {} - # key is a MAP_XXX, value is a MAP_LINUX - self.mapfrom[name] = {} - - for name in self.NAME_COLUMNS.keys(): - # key is a MAP_LINUX, value is a string - self.mapname[name] = {} - - def _generate_checksum(self, filename): - hash = hashlib.sha256() - with open(filename, "rb") as f: - for chunk in iter(lambda: f.read(4096), b""): - hash.update(chunk) - self.mapchecksum = hash.hexdigest() - - def load(self, filename): - self._generate_checksum(filename) - - with open(filename, 'r') as f: - reader = csv.reader(f) - - first = True - - for row in reader: - # Discard column headings - if first: - first = False - continue - - # We special case MAP_LINUX since that is out - # master via which all other mappings are done - linux = self.load_linux(row) - - # Now load all the remaining master data values - self.load_data(row, linux) - - # Then load all the keycode names - self.load_names(row, linux) - - # Finally calculate derived key maps - self.derive_data(row, linux) - - def load_linux(self, row): - col = self.CODE_COLUMNS[self.MAP_LINUX] - linux = row[col] - - if linux.startswith("0x"): - linux = int(linux, 16) - else: - linux = int(linux, 10) - - self.mapto[self.MAP_LINUX][linux] = linux - self.mapfrom[self.MAP_LINUX][linux] = linux - - return linux - - - def load_data(self, row, linux): - for mapname in self.CODE_COLUMNS: - if mapname == self.MAP_LINUX: - continue - - col = self.CODE_COLUMNS[mapname] - val = row[col] - - if val == "": - continue - - if val.startswith("0x"): - val = int(val, 16) - elif val.isdigit(): - val = int(val, 10) - - self.mapto[mapname][linux] = val - self.mapfrom[mapname][val] = linux - - def load_names(self, row, linux): - for mapname in self.NAME_COLUMNS: - col = self.NAME_COLUMNS[mapname] - val = row[col] - - if val == "": - continue - - self.mapname[mapname][linux] = val - - - def derive_data(self, row, linux): - # Linux RAW is XT scan codes with special encoding of the - # 0xe0 scan codes - if linux in self.mapto[self.MAP_ATSET1]: - at1 = self.mapto[self.MAP_ATSET1][linux] - if at1 > 0x7f: - assert((at1 & ~0x7f) == 0xe000) - xtkbd = 0x100 | (at1 & 0x7f) - else: - xtkbd = at1 - self.mapto[self.MAP_XTKBD][linux] = xtkbd - self.mapfrom[self.MAP_XTKBD][xtkbd] = linux - - # Xorg KBD is XKBD XT offset by 8 - if linux in self.mapto[self.MAP_XKBDXT]: - xorgkbd = self.mapto[self.MAP_XKBDXT][linux] + 8 - self.mapto[self.MAP_XORGKBD][linux] = xorgkbd - self.mapfrom[self.MAP_XORGKBD][xorgkbd] = linux - - # Xorg evdev is Linux offset by 8 - self.mapto[self.MAP_XORGEVDEV][linux] = linux + 8 - self.mapfrom[self.MAP_XORGEVDEV][linux + 8] = linux - - # Xorg XQuartx is OS-X offset by 8 - if linux in self.mapto[self.MAP_OSX]: - xorgxquartz = self.mapto[self.MAP_OSX][linux] + 8 - self.mapto[self.MAP_XORGXQUARTZ][linux] = xorgxquartz - self.mapfrom[self.MAP_XORGXQUARTZ][xorgxquartz] = linux - - # Xorg Xwin (aka Cygwin) is XWin XT offset by 8 - if linux in self.mapto[self.MAP_XWINXT]: - xorgxwin = self.mapto[self.MAP_XWINXT][linux] + 8 - self.mapto[self.MAP_XORGXWIN][linux] = xorgxwin - self.mapfrom[self.MAP_XORGXWIN][xorgxwin] = linux - - # QNUM keycodes are XT scan codes with a slightly - # different encoding of 0xe0 scan codes - if linux in self.mapto[self.MAP_ATSET1]: - at1 = self.mapto[self.MAP_ATSET1][linux] - if at1 > 0x7f: - assert((at1 & ~0x7f) == 0xe000) - qnum = 0x80 | (at1 & 0x7f) - else: - qnum = at1 - self.mapto[self.MAP_QNUM][linux] = qnum - self.mapfrom[self.MAP_QNUM][qnum] = linux - - # Hack for compatibility with previous mistakes in handling - # Print/SysRq. The preferred qnum for Print/SysRq is 0x54, - # but QEMU previously allowed 0xb7 too - if qnum == 0x54: - self.mapfrom[self.MAP_QNUM][0xb7] = self.mapfrom[self.MAP_QNUM][0x54] - - if linux in self.mapname[self.MAP_QCODE]: - qcodeenum = self.mapname[self.MAP_QCODE][linux] - qcodeenum = "Q_KEY_CODE_" + qcodeenum.upper() - self.mapto[self.MAP_QCODE][linux] = qcodeenum - self.mapfrom[self.MAP_QCODE][qcodeenum] = linux - -class LanguageGenerator(object): - - def _boilerplate(self, lines): - raise NotImplementedError() - - def generate_header(self, database, args): - self._boilerplate([ - "This file is auto-generated from keymaps.csv", - "Database checksum sha256(%s)" % database.mapchecksum, - "To re-generate, run:", - " %s" % args, - ]) - -class LanguageSrcGenerator(LanguageGenerator): - - TYPE_INT = "integer" - TYPE_STRING = "string" - TYPE_ENUM = "enum" - - def _array_start(self, varname, length, defvalue, fromtype, totype): - raise NotImplementedError() - - def _array_end(self, fromtype, totype): - raise NotImplementedError() - - def _array_entry(self, index, value, comment, fromtype, totype): - raise NotImplementedError() - - def generate_code_map(self, varname, database, frommapname, tomapname): - if frommapname not in database.mapfrom: - raise Exception("Unknown map %s, expected one of %s" % ( - frommapname, ", ".join(database.mapfrom.keys()))) - if tomapname not in database.mapto: - raise Exception("Unknown map %s, expected one of %s" % ( - tomapname, ", ".join(database.mapto.keys()))) - - tolinux = database.mapfrom[frommapname] - fromlinux = database.mapto[tomapname] - - if varname is None: - varname = "code_map_%s_to_%s" % (frommapname, tomapname) - - if frommapname in database.ENUM_COLUMNS: - fromtype = self.TYPE_ENUM - elif type(list(tolinux.keys())[0]) == str: - fromtype = self.TYPE_STRING - else: - fromtype = self.TYPE_INT - - if tomapname in database.ENUM_COLUMNS: - totype = self.TYPE_ENUM - elif type(list(fromlinux.values())[0]) == str: - totype = self.TYPE_STRING - else: - totype = self.TYPE_INT - - keys = list(tolinux.keys()) - keys.sort() - if fromtype == self.TYPE_INT: - keys = range(keys[-1] + 1) - - if fromtype == self.TYPE_ENUM: - keymax = database.ENUM_BOUND[frommapname] - else: - keymax = len(keys) - - defvalue = fromlinux.get(0, None) - if fromtype == self.TYPE_ENUM: - self._array_start(varname, keymax, defvalue, fromtype, totype) - else: - self._array_start(varname, keymax, None, fromtype, totype) - - for src in keys: - linux = tolinux.get(src, None) - if linux is None: - dst = None - else: - dst = fromlinux.get(linux, defvalue) - - comment = "%s -> %s -> %s" % (self._label(database, frommapname, src, linux), - self._label(database, Database.MAP_LINUX, linux, linux), - self._label(database, tomapname, dst, linux)) - self._array_entry(src, dst, comment, fromtype, totype) - self._array_end(fromtype, totype) - - def generate_code_table(self, varname, database, mapname): - if mapname not in database.mapto: - raise Exception("Unknown map %s, expected one of %s" % ( - mapname, ", ".join(database.mapto.keys()))) - - keys = list(database.mapto[Database.MAP_LINUX].keys()) - keys.sort() - names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys] - - if varname is None: - varname = "code_table_%s" % mapname - - if mapname in database.ENUM_COLUMNS: - totype = self.TYPE_ENUM - elif type(list(database.mapto[mapname].values())[0]) == str: - totype = self.TYPE_STRING - else: - totype = self.TYPE_INT - - self._array_start(varname, len(keys), None, self.TYPE_INT, totype) - - defvalue = database.mapto[mapname].get(0, None) - for i in range(len(keys)): - key = keys[i] - dst = database.mapto[mapname].get(key, defvalue) - self._array_entry(i, dst, names[i], self.TYPE_INT, totype) - - self._array_end(self.TYPE_INT, totype) - - def generate_name_map(self, varname, database, frommapname, tomapname): - if frommapname not in database.mapfrom: - raise Exception("Unknown map %s, expected one of %s" % ( - frommapname, ", ".join(database.mapfrom.keys()))) - if tomapname not in database.mapname: - raise Exception("Unknown map %s, expected one of %s" % ( - tomapname, ", ".join(database.mapname.keys()))) - - tolinux = database.mapfrom[frommapname] - fromlinux = database.mapname[tomapname] - - if varname is None: - varname = "name_map_%s_to_%s" % (frommapname, tomapname) - - keys = list(tolinux.keys()) - keys.sort() - if type(keys[0]) == int: - keys = range(keys[-1] + 1) - - if type(keys[0]) == int: - fromtype = self.TYPE_INT - else: - fromtype = self.TYPE_STRING - - self._array_start(varname, len(keys), None, fromtype, self.TYPE_STRING) - - for src in keys: - linux = tolinux.get(src, None) - if linux is None: - dst = None - else: - dst = fromlinux.get(linux, None) - - comment = "%s -> %s -> %s" % (self._label(database, frommapname, src, linux), - self._label(database, Database.MAP_LINUX, linux, linux), - self._label(database, tomapname, dst, linux)) - self._array_entry(src, dst, comment, fromtype, self.TYPE_STRING) - self._array_end(fromtype, self.TYPE_STRING) - - def generate_name_table(self, varname, database, mapname): - if mapname not in database.mapname: - raise Exception("Unknown map %s, expected one of %s" % ( - mapname, ", ".join(database.mapname.keys()))) - - keys = list(database.mapto[Database.MAP_LINUX].keys()) - keys.sort() - names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys] - - if varname is None: - varname = "name_table_%s" % mapname - - self._array_start(varname, len(keys), None, self.TYPE_INT, self.TYPE_STRING) - - for i in range(len(keys)): - key = keys[i] - dst = database.mapname[mapname].get(key, None) - self._array_entry(i, dst, names[i], self.TYPE_INT, self.TYPE_STRING) - - self._array_end(self.TYPE_INT, self.TYPE_STRING) - - def _label(self, database, mapname, val, linux): - if mapname in database.mapname: - return "%s:%s (%s)" % (mapname, val, database.mapname[mapname].get(linux, "unnamed")) - else: - return "%s:%s" % (mapname, val) - -class LanguageDocGenerator(LanguageGenerator): - - def _array_start_name_doc(self, varname, namemap): - raise NotImplementedError() - - def _array_start_code_doc(self, varname, namemap, codemap): - raise NotImplementedError() - - def _array_end(self): - raise NotImplementedError() - - def _array_name_entry(self, value, name): - raise NotImplementedError() - - def _array_code_entry(self, value, name): - raise NotImplementedError() - - def generate_name_docs(self, title, subtitle, database, mapname): - if mapname not in database.mapname: - raise Exception("Unknown map %s, expected one of %s" % ( - mapname, ", ".join(database.mapname.keys()))) - - keys = list(database.mapto[Database.MAP_LINUX].keys()) - keys.sort() - names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys] - - if title is None: - title = mapname - if subtitle is None: - subtitle = "Docs for %s" % mapname - - self._array_start_name_doc(title, subtitle, mapname) - - for i in range(len(keys)): - key = keys[i] - dst = database.mapname[mapname].get(key, None) - self._array_name_entry(key, dst) - - self._array_end() - - - def generate_code_docs(self, title, subtitle, database, mapname): - if mapname not in database.mapfrom: - raise Exception("Unknown map %s, expected one of %s" % ( - mapname, ", ".join(database.mapfrom.keys()))) - - tolinux = database.mapfrom[mapname] - keys = list(tolinux.keys()) - keys.sort() - if mapname in database.mapname: - names = database.mapname[mapname] - namemap = mapname - else: - names = database.mapname[Database.MAP_LINUX] - namemap = Database.MAP_LINUX - - if title is None: - title = mapname - if subtitle is None: - subtitle = "Docs for %s" % mapname - - self._array_start_code_doc(title, subtitle, mapname, namemap) - - for i in range(len(keys)): - key = keys[i] - self._array_code_entry(key, names.get(tolinux[key], "unnamed")) - - self._array_end() - -class CLanguageGenerator(LanguageSrcGenerator): - - def __init__(self, inttypename, strtypename, lentypename): - self.inttypename = inttypename - self.strtypename = strtypename - self.lentypename = lentypename - - def _boilerplate(self, lines): - print("/*") - for line in lines: - print(" * %s" % line) - print("*/") - - def _array_start(self, varname, length, defvalue, fromtype, totype): - self._varname = varname; - totypename = self.strtypename if totype == self.TYPE_STRING else self.inttypename - if fromtype in (self.TYPE_INT, self.TYPE_ENUM): - if type(length) == str: - print("const %s %s[%s] = {" % (totypename, varname, length)) - else: - print("const %s %s[%d] = {" % (totypename, varname, length)) - else: - print("const struct _%s {" % varname) - print(" const %s from;" % self.strtypename) - print(" const %s to;" % totypename) - print("} %s[] = {" % varname) - - if defvalue != None: - if totype == self.TYPE_ENUM: - if type(length) == str: - print(" [0 ... %s-1] = %s," % (length, defvalue)) - else: - print(" [0 ... 0x%x-1] = %s," % (length, defvalue)) - else: - if type(length) == str: - print(" [0 ... %s-1] = 0x%x," % (length, defvalue)) - else: - print(" [0 ... 0x%x-1] = 0x%x," % (length, defvalue)) - - def _array_end(self, fromtype, totype): - print("};") - print("const %s %s_len = sizeof(%s)/sizeof(%s[0]);" % - (self.lentypename, self._varname, self._varname, self._varname)) - - def _array_entry(self, index, value, comment, fromtype, totype): - if value is None: - return - if fromtype == self.TYPE_INT: - indexfmt = "0x%x" - elif fromtype == self.TYPE_ENUM: - indexfmt = "%s" - else: - indexfmt = "\"%s\"" - - if totype == self.TYPE_INT: - valuefmt = "0x%x" - elif totype == self.TYPE_ENUM: - valuefmt = "%s" - else: - valuefmt = "\"%s\"" - - if fromtype != self.TYPE_STRING: - print((" [" + indexfmt + "] = " + valuefmt + ", /* %s */") % (index, value, comment)) - else: - print((" {" + indexfmt + ", " + valuefmt + "}, /* %s */") % (index, value, comment)) - -class StdCLanguageGenerator(CLanguageGenerator): - - def __init__(self): - super(StdCLanguageGenerator, self).__init__("unsigned short", "char *", "unsigned int") - -class GLib2LanguageGenerator(CLanguageGenerator): - - def __init__(self): - super(GLib2LanguageGenerator, self).__init__("guint16", "gchar *", "guint") - -class CHeaderLanguageGenerator(LanguageSrcGenerator): - - def __init__(self, inttypename, strtypename, lentypename): - self.inttypename = inttypename - self.strtypename = strtypename - self.lentypename = lentypename - - def _boilerplate(self, lines): - print("/*") - for line in lines: - print(" * %s" % line) - print("*/") - - def _array_start(self, varname, length, defvalue, fromtype, totype): - self._varname = varname - if fromtype == self.TYPE_STRING: - self._length = 0 - else: - self._length = length - - def _array_end(self, fromtype, totype): - totypename = self.strtypename if totype == self.TYPE_STRING else self.inttypename - if fromtype == self.TYPE_STRING: - vartypename = "struct _%s" % self._varname - print("%s {" % vartypename) - print(" const %s from;" % self.strtypename) - print(" const %s to;" % totypename) - print("};") - else: - vartypename = totypename - if type(self._length) == str: - print("extern const %s %s[%s];" % (vartypename, self._varname, self._length)) - else: - print("extern const %s %s[%d];" % (vartypename, self._varname, self._length)) - print("extern const %s %s_len;" % (self.lentypename, self._varname)) - - def _array_entry(self, index, value, comment, fromtype, totype): - if value is None: - return - if fromtype == self.TYPE_STRING: - self._length += 1 - -class StdCHeaderLanguageGenerator(CHeaderLanguageGenerator): - - def __init__(self): - super(StdCHeaderLanguageGenerator, self).__init__("unsigned short", "char *", "unsigned int") - -class GLib2HeaderLanguageGenerator(CHeaderLanguageGenerator): - - def __init__(self): - super(GLib2HeaderLanguageGenerator, self).__init__("guint16", "gchar *", "guint") - -class CppLanguageGenerator(CLanguageGenerator): - - def _array_start(self, varname, length, defvalue, fromtype, totype): - if fromtype == self.TYPE_ENUM: - raise NotImplementedError("Enums not supported as source in C++ generator") - totypename = "const " + self.strtypename if totype == self.TYPE_STRING else self.inttypename - if fromtype == self.TYPE_INT: - print("#include ") - print("extern const std::vector<%s> %s;" % (totypename, varname)); - print("const std::vector<%s> %s = {" % (totypename, varname)) - else: - print("#include ") - print("#include ") - print("extern const std::map %s;" % (totypename, varname)) - print("const std::map %s = {" % (totypename, varname)) - - def _array_end(self, fromtype, totype): - print("};") - - # designated initializers not available in C++ - def _array_entry(self, index, value, comment, fromtype, totype): - if fromtype == self.TYPE_STRING: - return super(CppLanguageGenerator, self)._array_entry(index, value, comment, fromtype, totype) - - if value is None: - print(" 0, /* %s */" % comment) - elif totype == self.TYPE_INT: - print(" 0x%x, /* %s */" % (value, comment)) - elif totype == self.TYPE_ENUM: - print(" %s, /* %s */" % (value, comment)) - else: - print(" \"%s\", /* %s */" % (value, comment)) - -class StdCppLanguageGenerator(CppLanguageGenerator): - - def __init__(self): - super(StdCppLanguageGenerator, self).__init__("unsigned short", "char *", "unsigned int") - -class CppHeaderLanguageGenerator(CHeaderLanguageGenerator): - - def _array_start(self, varname, length, defvalue, fromtype, totype): - if fromtype == self.TYPE_ENUM: - raise NotImplementedError("Enums not supported as source in C++ generator") - totypename = "const " + self.strtypename if totype == self.TYPE_STRING else self.inttypename - if fromtype == self.TYPE_INT: - print("#include ") - print("extern const std::vector<%s> %s;" % (totypename, varname)); - else: - print("#include ") - print("#include ") - print("extern const std::map %s;" % (totypename, varname)) - - def _array_end(self, fromtype, totype): - pass - - # designated initializers not available in C++ - def _array_entry(self, index, value, comment, fromtype, totype): - pass - -class StdCppHeaderLanguageGenerator(CppHeaderLanguageGenerator): - - def __init__(self): - super(StdCppHeaderLanguageGenerator, self).__init__("unsigned short", "char *", "unsigned int") - -class PythonLanguageGenerator(LanguageSrcGenerator): - - def _boilerplate(self, lines): - print("#") - for line in lines: - print("# %s" % line) - print("#") - - def _array_start(self, varname, length, defvalue, fromtype, totype): - if fromtype == self.TYPE_ENUM: - raise NotImplementedError("Enums not supported as source in Python generator") - - if fromtype != self.TYPE_STRING: - print("%s = [" % varname) - else: - print("%s = {" % varname) - - def _array_end(self, fromtype, totype): - if fromtype != self.TYPE_STRING: - print("]") - else: - print("}") - - def _array_entry(self, index, value, comment, fromtype, totype): - if fromtype == self.TYPE_INT: - if value is None: - print(" None, # %s" % (comment)) - elif totype == self.TYPE_INT: - print(" 0x%x, # %s" % (value, comment)) - elif totype == self.TYPE_ENUM: - print(" %s, # %s" % (value, comment)) - else: - print(" \"%s\", # %s" % (value, comment)) - else: - if value is None: - print(" \"%s\": None, # %s" % (index, comment)) - elif totype == self.TYPE_INT: - print(" \"%s\": 0x%x, # %s" % (index, value, comment)) - elif totype == self.TYPE_ENUM: - print(" \"%s\": %s, # %s" % (index, value, comment)) - else: - print(" \"%s\": \"%s\", # %s" % (index, value, comment)) - -class PerlLanguageGenerator(LanguageSrcGenerator): - - def _boilerplate(self, lines): - print("#") - for line in lines: - print("# %s" % line) - print("#") - - def _array_start(self, varname, length, defvalue, fromtype, totype): - if fromtype == self.TYPE_ENUN: - raise NotImplementedError("Enums not supported as source in Python generator") - if fromtype == self.TYPE_INT: - print("my @%s = (" % varname) - else: - print("my %%%s = (" % varname) - - def _array_end(self, fromtype, totype): - print(");") - - def _array_entry(self, index, value, comment, fromtype, totype): - if fromtype == self.TYPE_INT: - if value is None: - print(" undef, # %s" % (comment)) - elif totype == self.TYPE_INT: - print(" 0x%x, # %s" % (value, comment)) - elif totype == self.TYPE_ENUM: - print(" %s, # %s" % (value, comment)) - else: - print(" \"%s\", # %s" % (value, comment)) - else: - if value is None: - print(" \"%s\", undef, # %s" % (index, comment)) - elif totype == self.TYPE_INT: - print(" \"%s\", 0x%x, # %s" % (index, value, comment)) - elif totype == self.TYPE_ENUM: - print(" \"%s\", 0x%x, # %s" % (index, value, comment)) - else: - print(" \"%s\", \"%s\", # %s" % (index, value, comment)) - -class JavaScriptLanguageGenerator(LanguageSrcGenerator): - - def _boilerplate(self, lines): - print("/*") - for line in lines: - print(" * %s" % line) - print("*/") - - def _array_start(self, varname, length, defvalue, fromtype, totype): - print("export default {") - - def _array_end(self, fromtype, totype): - print("};") - - def _array_entry(self, index, value, comment, fromtype, totype): - if value is None: - return - - if fromtype == self.TYPE_INT: - fromfmt = "0x%x" - elif fromtype == self.TYPE_ENUM: - fromfmt = "%s" - else: - fromfmt = "\"%s\"" - - if totype == self.TYPE_INT: - tofmt = "0x%x" - elif totype == self.TYPE_ENUM: - tofmt = "%s" - else: - tofmt = "\"%s\"" - - print((" " + fromfmt + ": " + tofmt + ", /* %s */") % (index, value, comment)) - -class PodLanguageGenerator(LanguageDocGenerator): - - def _boilerplate(self, lines): - print("#") - for line in lines: - print("# %s" % line) - print("#") - - def _array_start_name_doc(self, title, subtitle, namemap): - print("=head1 NAME") - print("") - print("%s - %s" % (title, subtitle)) - print("") - print("=head1 DESCRIPTION") - print("") - print("List of %s key code names, with corresponding key code values" % namemap) - print("") - print("=over 4") - print("") - - def _array_start_code_doc(self, title, subtitle, codemap, namemap): - print("=head1 NAME") - print("") - print("%s - %s" % (title, subtitle)) - print("") - print("=head1 DESCRIPTION") - print("") - print("List of %s key code values, with corresponding %s key code names" % (codemap, namemap)) - print("") - print("=over 4") - print("") - - def _array_end(self): - print("=back") - print("") - - def _array_name_entry(self, value, name): - print("=item %s" % name) - print("") - print("Key value %d (0x%x)" % (value, value)) - print("") - - def _array_code_entry(self, value, name): - print("=item %d (0x%x)" % (value, value)) - print("") - print("Key name %s" % name) - print("") - -class RSTLanguageGenerator(LanguageDocGenerator): - - def _boilerplate(self, lines): - print("..") - for line in lines: - print(" %s" % line) - print("") - - def _array_start_name_doc(self, title, subtitle, namemap): - print("=" * len(title)) - print(title) - print("=" * len(title)) - print("") - print("-" * len(subtitle)) - print(subtitle) - print("-" * len(subtitle)) - print("") - print(":Manual section: 7") - print(":Manual group: Virtualization Support") - print("") - print("DESCRIPTION") - print("===========") - print("List of %s key code names, with corresponding key code values" % namemap) - print("") - - def _array_start_code_doc(self, title, subtitle, codemap, namemap): - print("=" * len(title)) - print(title) - print("=" * len(title)) - print("") - print("-" * len(subtitle)) - print(subtitle) - print("-" * len(subtitle)) - print("") - print(":Manual section: 7") - print(":Manual group: Virtualization Support") - print("") - print("DESCRIPTION") - print("===========") - print("List of %s key code values, with corresponding %s key code names" % (codemap, namemap)) - print("") - - def _array_end(self): - print("") - - def _array_name_entry(self, value, name): - print("* %s" % name) - print("") - print(" Key value %d (0x%x)" % (value, value)) - print("") - - def _array_code_entry(self, value, name): - print("* %d (0x%x)" % (value, value)) - print("") - print(" Key name %s" % name) - print("") - -SRC_GENERATORS = { - "stdc": StdCLanguageGenerator(), - "stdc-header": StdCHeaderLanguageGenerator(), - "stdc++": StdCppLanguageGenerator(), - "stdc++-header": StdCppHeaderLanguageGenerator(), - "glib2": GLib2LanguageGenerator(), - "glib2-header": GLib2HeaderLanguageGenerator(), - "python2": PythonLanguageGenerator(), - "python3": PythonLanguageGenerator(), - "perl": PerlLanguageGenerator(), - "js": JavaScriptLanguageGenerator(), -} -DOC_GENERATORS = { - "pod": PodLanguageGenerator(), - "rst": RSTLanguageGenerator(), -} - -def code_map(args): - database = Database() - database.load(args.keymaps) - - cliargs = ["keymap-gen", "code-map", "--lang=%s" % args.lang] - if args.varname is not None: - cliargs.append("--varname=%s" % args.varname) - cliargs.extend(["keymaps.csv", args.frommapname, args.tomapname]) - SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) - - SRC_GENERATORS[args.lang].generate_code_map(args.varname, database, args.frommapname, args.tomapname) - -def code_table(args): - database = Database() - database.load(args.keymaps) - - cliargs = ["keymap-gen", "code-table", "--lang=%s" % args.lang] - if args.varname is not None: - cliargs.append("--varname=%s" % args.varname) - cliargs.extend(["keymaps.csv", args.mapname]) - SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) - - SRC_GENERATORS[args.lang].generate_code_table(args.varname, database, args.mapname) - -def name_map(args): - database = Database() - database.load(args.keymaps) - - cliargs = ["keymap-gen", "name-map", "--lang=%s" % args.lang] - if args.varname is not None: - cliargs.append("--varname=%s" % args.varname) - cliargs.extend(["keymaps.csv", args.frommapname, args.tomapname]) - SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) - - SRC_GENERATORS[args.lang].generate_name_map(args.varname, database, args.frommapname, args.tomapname) - -def name_table(args): - database = Database() - database.load(args.keymaps) - - - cliargs = ["keymap-gen", "name-table", "--lang=%s" % args.lang] - if args.varname is not None: - cliargs.append("--varname=%s" % args.varname) - cliargs.extend(["keymaps.csv", args.mapname]) - SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) - - SRC_GENERATORS[args.lang].generate_name_table(args.varname, database, args.mapname) - -def code_docs(args): - database = Database() - database.load(args.keymaps) - - - cliargs = ["keymap-gen", "code-docs", "--lang=%s" % args.lang] - if args.title is not None: - cliargs.append("--title=%s" % args.title) - if args.subtitle is not None: - cliargs.append("--subtitle=%s" % args.subtitle) - cliargs.extend(["keymaps.csv", args.mapname]) - DOC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) - - DOC_GENERATORS[args.lang].generate_code_docs(args.title, args.subtitle, database, args.mapname) - -def name_docs(args): - database = Database() - database.load(args.keymaps) - - - cliargs = ["keymap-gen", "name-docs", "--lang=%s" % args.lang] - if args.title is not None: - cliargs.append("--title=%s" % args.title) - if args.subtitle is not None: - cliargs.append("--subtitle=%s" % args.subtitle) - cliargs.extend(["keymaps.csv", args.mapname]) - DOC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) - - DOC_GENERATORS[args.lang].generate_name_docs(args.title, args.subtitle, database, args.mapname) - -def usage(): - print ("Please select a command:") - print (" 'code-map', 'code-table', 'name-map', 'name-table', 'docs'") - sys.exit(1) - -def main(): - parser = argparse.ArgumentParser() - - subparsers = parser.add_subparsers(help="sub-command help") - - codemapparser = subparsers.add_parser("code-map", help="Generate a mapping between code tables") - codemapparser.add_argument("--varname", default=None, help="Data variable name") - codemapparser.add_argument("--lang", default="stdc", - help="Output language (%s)" % ( - ",".join(SRC_GENERATORS.keys()))) - codemapparser.add_argument("keymaps", help="Path to keymap CSV data file") - codemapparser.add_argument("frommapname", help="Source code table name") - codemapparser.add_argument("tomapname", help="Target code table name") - codemapparser.set_defaults(func=code_map) - - codetableparser = subparsers.add_parser("code-table", help="Generate a flat code table") - codetableparser.add_argument("--lang", default="stdc", - help="Output language (%s)" % ( - ",".join(SRC_GENERATORS.keys()))) - codetableparser.add_argument("--varname", default=None, help="Data variable name") - codetableparser.add_argument("keymaps", help="Path to keymap CSV data file") - codetableparser.add_argument("mapname", help="Code table name") - codetableparser.set_defaults(func=code_table) - - namemapparser = subparsers.add_parser("name-map", help="Generate a mapping to names") - namemapparser.add_argument("--lang", default="stdc", - help="Output language (%s)" % ( - ",".join(SRC_GENERATORS.keys()))) - namemapparser.add_argument("--varname", default=None, help="Data variable name") - namemapparser.add_argument("keymaps", help="Path to keymap CSV data file") - namemapparser.add_argument("frommapname", help="Source code table name") - namemapparser.add_argument("tomapname", help="Target name table name") - namemapparser.set_defaults(func=name_map) - - nametableparser = subparsers.add_parser("name-table", help="Generate a flat name table") - nametableparser.add_argument("--lang", default="stdc", - help="Output language, (%s)" % ( - ",".join(SRC_GENERATORS.keys()))) - nametableparser.add_argument("--varname", default=None, help="Data variable name") - nametableparser.add_argument("keymaps", help="Path to keymap CSV data file") - nametableparser.add_argument("mapname", help="Name table name") - nametableparser.set_defaults(func=name_table) - - codedocsparser = subparsers.add_parser("code-docs", help="Generate code documentation") - codedocsparser.add_argument("--lang", default="pod", - help="Output language (%s)" % ( - ",".join(DOC_GENERATORS.keys()))) - codedocsparser.add_argument("--title", default=None, help="Document title") - codedocsparser.add_argument("--subtitle", default=None, help="Document subtitle") - codedocsparser.add_argument("keymaps", help="Path to keymap CSV data file") - codedocsparser.add_argument("mapname", help="Code table name") - codedocsparser.set_defaults(func=code_docs) - - namedocsparser = subparsers.add_parser("name-docs", help="Generate name documentation") - namedocsparser.add_argument("--lang", default="pod", - help="Output language (%s)" % ( - ",".join(DOC_GENERATORS.keys()))) - namedocsparser.add_argument("--title", default=None, help="Document title") - namedocsparser.add_argument("--subtitle", default=None, help="Document subtitle") - namedocsparser.add_argument("keymaps", help="Path to keymap CSV data file") - namedocsparser.add_argument("mapname", help="Name table name") - namedocsparser.set_defaults(func=name_docs) - - args = parser.parse_args() - if hasattr(args, "func"): - args.func(args) - else: - usage() - - -main() -- 2.39.2 From f.weber at proxmox.com Fri Oct 6 14:15:33 2023 From: f.weber at proxmox.com (Friedrich Weber) Date: Fri, 6 Oct 2023 14:15:33 +0200 Subject: [pve-devel] [PATCH qemu-server v2] vm start: set higher timeout if using PCI passthrough Message-ID: <20231006121532.90772-1-f.weber@proxmox.com> The default VM startup timeout is `max(30, VM memory in GiB)` seconds. Multiple reports in the forum [0] [1] and the bug tracker [2] suggest this is too short when using PCI passthrough with a large amount of VM memory, since QEMU needs to map the whole memory during startup (see comment #2 in [2]). As a result, VM startup fails with "got timeout". To work around this, set a larger default timeout if at least one PCI device is passed through. The question remains how to choose an appropriate timeout. Users reported the following startup times: ref | RAM | time | ratio (s/GiB) --------------------------------- [1] | 60G | 135s | 2.25 [1] | 70G | 157s | 2.24 [1] | 80G | 277s | 3.46 [2] | 65G | 213s | 3.28 [2] | 96G | >290s | >3.02 The data does not really indicate any simple (e.g. linear) relationship between RAM and startup time (even data from the same source). However, to keep the heuristic simple, assume linear growth and multiply the default timeout by 4 if at least one `hostpci[n]` option is present, obtaining `4 * max(30, VM memory in GiB)`. This covers all cases above, and should still leave some headroom. [0]: https://forum.proxmox.com/threads/83765/post-552071 [1]: https://forum.proxmox.com/threads/126398/post-592826 [2]: https://bugzilla.proxmox.com/show_bug.cgi?id=3502 Suggested-by: Fiona Ebner Signed-off-by: Friedrich Weber --- Notes: changes since v1 (was called "vm start: set minimum timeout of 300s if using PCI passthrough", 20230503133723.165739-1-f.weber at proxmox.com): * Use a constant multiplier as suggested by Fiona (thx!) Another workaround is offered by an unapplied patch series [3] of bug 3502 [2] that makes it possible to set VM-specific timeouts (also in the GUI). Users could use this option to manually set a higher timeout for VMs that use PCI passthrough. However, it is not immediately obvious that a higher timeout is necessary when using PCI passthrough. Since the problem seems to come up somewhat frequently, I think it makes sense to have the heuristic choose a higher timeout by default. As discussed in v1, I'll also pick up the patch series to allow users to set custom timeouts [3], also to offer a workaround for cases where the new heuristic chooses a timeout that is still too short. [2]: https://bugzilla.proxmox.com/show_bug.cgi?id=3502 [3]: https://lists.proxmox.com/pipermail/pve-devel/2023-January/055352.html PVE/QemuServer/Helpers.pm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/PVE/QemuServer/Helpers.pm b/PVE/QemuServer/Helpers.pm index 8817427..0afb631 100644 --- a/PVE/QemuServer/Helpers.pm +++ b/PVE/QemuServer/Helpers.pm @@ -152,6 +152,13 @@ sub config_aware_timeout { $timeout = int($memory/1024); } + # When using PCI passthrough, users reported much higher startup times, + # growing with the amount of memory configured. Constant factor chosen + # based on user reports. + if (grep(/^hostpci[0-9]+$/, keys %$config)) { + $timeout *= 4; + } + if ($is_suspended && $timeout < 300) { $timeout = 300; } -- 2.39.2 From l.wagner at proxmox.com Fri Oct 6 15:16:14 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Fri, 6 Oct 2023 15:16:14 +0200 Subject: [pve-devel] [PATCH manager v1 1/1] fix #4546: ui: notify user if their user account expires soon In-Reply-To: <20230922143658.1639173-2-p.hufnagl@proxmox.com> References: <20230922143658.1639173-1-p.hufnagl@proxmox.com> <20230922143658.1639173-2-p.hufnagl@proxmox.com> Message-ID: <118ba2e9-8c9e-4029-8738-9f806f49f73b@proxmox.com> Comments inline. On 9/22/23 16:36, Philipp Hufnagl wrote: > When the user account that is currently logged in will expire soon, the > user icon will turn into a yellow exclamation mark. In the user menu > there will be a new element informing the user briefly about it. If the ^ Well, the warning is permanent, so I wouldn't call it 'briefly' ;) > element is clicked, a popup will appear informing the user in detail > about it > > Signed-off-by: Philipp Hufnagl > --- > www/manager6/Workspace.js | 28 ++++++++++++++++++++++++++++ > 1 file changed, 28 insertions(+) > > diff --git a/www/manager6/Workspace.js b/www/manager6/Workspace.js > index 18d574b7..9d166493 100644 > --- a/www/manager6/Workspace.js > +++ b/www/manager6/Workspace.js > @@ -42,6 +42,7 @@ Ext.define('PVE.Workspace', { > Proxmox.Utils.authClear(); > Ext.state.Manager.clear('GuiCap'); > Proxmox.UserName = null; > + Proxmox.UserExpires = null; Another variable name might convey the meaning better, as this name somewhat reads booleanesque. How about Proxmox.AccountExpiresAt or Proxmox.AccountExpirationDate? > me.loginData = null; > > if (!me.login) { > @@ -198,6 +199,18 @@ Ext.define('PVE.StdWorkspace', { > let me = this; > let ui = me.query('#userinfo')[0]; > ui.setText(Ext.String.htmlEncode(Proxmox.UserName || '')); > + let label = me.query('#expirewarning')[0]; > + if (Proxmox.UserExpires !== null) { > + let expieWariningThreshold = Ext.Date.add(new Date(), Ext.Date.DAY, 7); ^ Typo in 'Warning' Nit: Also I'd not abbreviate: expiryWarningThreshold > + let expireDate = new Date(Proxmox.UserExpires * 1000); > + if (expieWariningThreshold >= expireDate) { > + ui.setIconCls('fa fa-exclamation-triangle warning'); > + label.setHidden(false); > + } > + } else { > + label.setHidden(true); > + ui.setIconCls('fa fa-user'); > + } > ui.updateLayout(); > }, > > @@ -367,6 +380,21 @@ Ext.define('PVE.StdWorkspace', { > }, > iconCls: 'fa fa-user', > menu: [ > + { > + iconCls: 'fa fa-exclamation-triangle warning', > + itemId: 'expirewarning', > + text: gettext('Account expiring soon!'), > + hidden: true, > + handler: function() { > + let expireDate = new Date(Proxmox.UserExpires * 1000); > + Ext.Msg.show({ > + title: gettext('Account expiring soon!'), > + icon: Ext.Msg.WARNING, > + message: Ext.String.format(gettext("Your account is expiring on {0} . After that you won't be able to log in!"), expireDate), > + buttons: Ext.Msg.OK, > + }); > + }, > + }, > { > iconCls: 'fa fa-gear', > text: gettext('My Settings'), -- - Lukas From l.wagner at proxmox.com Fri Oct 6 15:16:25 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Fri, 6 Oct 2023 15:16:25 +0200 Subject: [pve-devel] [PATCH access-control v1 1/1] fix #4546: api: Return user expiration date on access/ticket API call In-Reply-To: <20230922143658.1639173-3-p.hufnagl@proxmox.com> References: <20230922143658.1639173-1-p.hufnagl@proxmox.com> <20230922143658.1639173-3-p.hufnagl@proxmox.com> Message-ID: <8bb962b1-0078-4b5d-84b6-d0859ad7d5d5@proxmox.com> Comments inline. On 9/22/23 16:36, Philipp Hufnagl wrote: > Adds an additional, optional parameter to the access/tickets api call > which tells when the currently used user account will expire. If it will > not expire, the parameter will not be added. > > Signed-off-by: Philipp Hufnagl > --- > src/PVE/API2/AccessControl.pm | 8 ++++++++ > src/PVE/AccessControl.pm | 8 ++++++++ > 2 files changed, 16 insertions(+) > > diff --git a/src/PVE/API2/AccessControl.pm b/src/PVE/API2/AccessControl.pm > index 74b3910..e562a97 100644 > --- a/src/PVE/API2/AccessControl.pm > +++ b/src/PVE/API2/AccessControl.pm > @@ -267,6 +267,11 @@ __PACKAGE__->register_method ({ > ticket => { type => 'string', optional => 1}, > CSRFPreventionToken => { type => 'string', optional => 1 }, > clustername => { type => 'string', optional => 1 }, > + user_expires => { I'd prefer a different property name. `user_expires` kinda suggests it to be a boolean variable (e.g. whether the user expires at *some point*) How about `account-expiry-date`? Also note that prefer to use hyphens (-) instead of underscores (_) for new properties. > + type => 'number', > + description => "When the user account expires.", I'd maybe elaborate a bit: "Account expiration date as a UNIX timestamp" or something like that. > + optional => 1 , > + }, > # cap => computed api permissions, unless there's a u2f challenge > } > }, > @@ -304,6 +309,9 @@ __PACKAGE__->register_method ({ > die PVE::Exception->new("authentication failure\n", code => 401); > } > > + my $exp = PVE::AccessControl::lookup_user_expiration($username); > + $res->{user_expieres} = $exp if defined($exp); Typo in `user_expieres` Nit: maybe call it $expires/$expiry instead of $exp. Not much longer but easier to follow. > + > $res->{cap} = $rpcenv->compute_api_permission($username) > if !defined($res->{NeedTFA}); > > diff --git a/src/PVE/AccessControl.pm b/src/PVE/AccessControl.pm > index cc0f00b..471cc92 100644 > --- a/src/PVE/AccessControl.pm > +++ b/src/PVE/AccessControl.pm > @@ -1234,6 +1234,14 @@ sub lookup_username { > return $username; > } > > +sub lookup_user_expiration { > + my ($username) = @_; > + my $usercfg = cfs_read_file('user.cfg'); > + my $exp = $usercfg->{users}->{$username}->{expire}; > + return undef if $exp == 0; > + return $exp; > +} Nit: Same as above -- - Lukas From l.wagner at proxmox.com Fri Oct 6 15:16:27 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Fri, 6 Oct 2023 15:16:27 +0200 Subject: [pve-devel] [PATCH manager/access-control/proxmox-widget-toolkit v1 0/4] fix #4546: Show warning hint/badge if user account is expiring in next few days In-Reply-To: <20230922143658.1639173-1-p.hufnagl@proxmox.com> References: <20230922143658.1639173-1-p.hufnagl@proxmox.com> Message-ID: <7de71173-6944-4842-9f3f-56250b34fd95@proxmox.com> On 9/22/23 16:36, Philipp Hufnagl wrote: > Currently, when an user account expires, it catches the users by > surprise. It would be helpful to notify the the user as well as the > administrator. > > This patch highlights such accounts in the user account pannel and also > shows for regular user an exclamation mark in the user info pannel. It > also adds in this case a new entry briefly saing that the account > expires. When this entry is clicked it states this in more detail > including the experation date. > > Sending this for PVE only now to get feedback before implementing this > for PMG and PBS > Tested this on the latest master. Seems to work as adverised with the exception of a small rendering bug (decribed in one of the other messages). -- - Lukas From l.wagner at proxmox.com Fri Oct 6 15:16:31 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Fri, 6 Oct 2023 15:16:31 +0200 Subject: [pve-devel] [PATCH proxmox-widget-toolkit v1 2/2] fix #4546: utils: save expiring date of user account for UI In-Reply-To: <20230922143658.1639173-5-p.hufnagl@proxmox.com> References: <20230922143658.1639173-1-p.hufnagl@proxmox.com> <20230922143658.1639173-5-p.hufnagl@proxmox.com> Message-ID: <5bca3155-3fc9-4f80-878a-31c4369d20c3@proxmox.com> Comments inline. On 9/22/23 16:36, Philipp Hufnagl wrote: > When an user experation date is send with the /accesss/tickets POST API ^ ^ ^ some minor typos: expiration sent access > call, it will be stored in a global variable like the username > > Signed-off-by: Philipp Hufnagl > --- (...) > diff --git a/src/Utils.js b/src/Utils.js > index a7ded2a..5481a32 100644 > --- a/src/Utils.js > +++ b/src/Utils.js > @@ -309,6 +309,9 @@ utilities: { > > setAuthData: function(data) { > Proxmox.UserName = data.username; > + if (data.user_expieres !== '') { Shouldn't this be !== null? So maybe just do a if (data['account-expiry-date']) { ... } > + Proxmox.UserExpires = data.user_expieres; typo, and same general remark regarding the naming as in the `access-control` patch.> + } > Proxmox.LoggedOut = data.LoggedOut; > // creates a session cookie (expire = null) > // that way the cookie gets deleted after the browser window is closed Also, the CSS changes found in this commit should probably be in another commit. Furthermore, I'd probably send the widget-toolkit patches before the pve-manager patches, since you require the `Proxmox.UserExpires` variable to be set in your changes for `pve-manager`. -- - Lukas From l.wagner at proxmox.com Fri Oct 6 15:16:21 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Fri, 6 Oct 2023 15:16:21 +0200 Subject: [pve-devel] [PATCH proxmox-widget-toolkit v1 1/2] fix #4546: css: Inform user administrator about user accounts expiring soon In-Reply-To: <20230922143658.1639173-4-p.hufnagl@proxmox.com> References: <20230922143658.1639173-1-p.hufnagl@proxmox.com> <20230922143658.1639173-4-p.hufnagl@proxmox.com> Message-ID: Comments inline. On 9/22/23 16:36, Philipp Hufnagl wrote: > Adds a new css class to underlay information urgency in table columns > for dark and light mode. This underlay color then is used to notifiy Typo in 'notify' > user administrators about user accounts that will expire soon > > Signed-off-by: Philipp Hufnagl > --- > src/Utils.js | 6 +++++- > src/css/ext6-pmx.css | 4 ++++ > src/proxmox-dark/scss/abstracts/_variables.scss | 1 + > src/proxmox-dark/scss/proxmox/_general.scss | 5 +++++ > 4 files changed, 15 insertions(+), 1 deletion(-) > > diff --git a/src/Utils.js b/src/Utils.js > index f269607..a7ded2a 100644 > --- a/src/Utils.js > +++ b/src/Utils.js > @@ -174,10 +174,14 @@ utilities: { > return value ? Proxmox.Utils.enabledText : Proxmox.Utils.disabledText; > }, > > - format_expire: function(date) { > + format_expire: function(date, meta) { > if (!date) { > return Proxmox.Utils.neverText; > } > + let expieWariningThreshold = Ext.Date.add(new Date(), Ext.Date.DAY, 7); ^ Typo, also same remarks as in the other patch. > + if (expieWariningThreshold >= date) { > + meta.tdCls += 'proxmox-hint-row'; > + } > return Ext.Date.format(date, "Y-m-d"); > }, > > diff --git a/src/css/ext6-pmx.css b/src/css/ext6-pmx.css > index 2ffd2a8..439f3a0 100644 > --- a/src/css/ext6-pmx.css > +++ b/src/css/ext6-pmx.css > @@ -76,6 +76,10 @@ > background-color: #f3d6d7; > } > > +.proxmox-hint-row { > + background-color: #5eb9ff; > +} > + > .proxmox-warning-row { > background-color: #f5e5d8; > } > diff --git a/src/proxmox-dark/scss/abstracts/_variables.scss b/src/proxmox-dark/scss/abstracts/_variables.scss > index cac51eb..8bcae09 100644 > --- a/src/proxmox-dark/scss/abstracts/_variables.scss > +++ b/src/proxmox-dark/scss/abstracts/_variables.scss > @@ -26,6 +26,7 @@ $background-darker: hsl(0deg, 0%, 15%); > $background-darkest: hsl(0deg, 0%, 10%); > $background-invalid: hsl(360deg, 60%, 20%); > $background-warning: hsl(40deg, 100%, 20%); > +$background-hint: hsl(233deg, 99%, 60%); That particular color tone looks pretty out of place to me in dark mode. In light mode, you use the same hue as other interface elements, is there a reason why you use a different color in dark mode? Playing around a bit, hsl(205, 100%, 40%) or hsl(205, 100%, 45%) would look about right for me, that's the same hue as other elements, while being a bit toned down (reduced lightness). @sterzy, maybe you can provide some feedback as well? Also, small bug: the hint-color seems to disappear when the 'expiry' is clicked, only seems to affect dark mode. > > // Buttons > $neutral-button-color: hsl(0deg, 0%, 25%); > diff --git a/src/proxmox-dark/scss/proxmox/_general.scss b/src/proxmox-dark/scss/proxmox/_general.scss > index c319f6d..8b2a4d0 100644 > --- a/src/proxmox-dark/scss/proxmox/_general.scss > +++ b/src/proxmox-dark/scss/proxmox/_general.scss > @@ -25,6 +25,11 @@ div[id^="versioninfo-"] + div[id^="panel-"] > div[id^="panel-"][id$="-bodyWrap"] > background-color: $background-warning; > } > > +// Info rows, e.g. when an user account expires soon > +.proxmox-hint-row { > + background-color: $background-hint; > +} > + > // Disabled rows (e.g. disabled repos in Repository view) > .proxmox-disabled-row td { > color: $text-color-inactive; -- - Lukas From s.sterz at proxmox.com Fri Oct 6 16:41:09 2023 From: s.sterz at proxmox.com (Stefan Sterz) Date: Fri, 06 Oct 2023 16:41:09 +0200 Subject: [pve-devel] [PATCH proxmox-widget-toolkit v1 1/2] fix #4546: css: Inform user administrator about user accounts expiring soon In-Reply-To: References: <20230922143658.1639173-1-p.hufnagl@proxmox.com> <20230922143658.1639173-4-p.hufnagl@proxmox.com> Message-ID: On Fri Oct 6, 2023 at 3:16 PM CEST, Lukas Wagner wrote: -- snip 8< -- > > +$background-hint: hsl(233deg, 99%, 60%); > > That particular color tone looks pretty out of place to me in dark mode. > In light mode, you use the same hue as other interface elements, is > there a reason why you use a different color in dark mode? > > Playing around a bit, hsl(205, 100%, 40%) or hsl(205, 100%, 45%) would > look about right for me, that's the same hue as other elements, > while being a bit toned down (reduced lightness). > > @sterzy, maybe you can provide some feedback as well? > > Also, small bug: the hint-color seems to disappear when the 'expiry' > is clicked, only seems to affect dark mode. in general: please refrain from defining new colors. you are very likely not the first person that needs to highlight something in a row. in this case: why not use either `$primary-dark` if it needs to be blue. or in my opinion: use `$background-warning`. this would be semantically fitting as you want to warn an admin that this user is about to expire. for the light theme you could simply use the `.warning` class instead then you can simply overwrite that with a more specific selector in the dark theme again, like we already do in a couple of places. defining new colors each time you want to highlight something in the ui quikly leads to a visual mess, instead try to re-use colors that already have some kind of semantic meaning. this will make it easier to visually parse the ui quikly. -- snip 8< -- From s.sterz at proxmox.com Fri Oct 6 16:41:46 2023 From: s.sterz at proxmox.com (Stefan Sterz) Date: Fri, 06 Oct 2023 16:41:46 +0200 Subject: [pve-devel] [PATCH proxmox-widget-toolkit v1 2/2] fix #4546: utils: save expiring date of user account for UI In-Reply-To: <20230922143658.1639173-5-p.hufnagl@proxmox.com> References: <20230922143658.1639173-1-p.hufnagl@proxmox.com> <20230922143658.1639173-5-p.hufnagl@proxmox.com> Message-ID: On Fri Sep 22, 2023 at 4:36 PM CEST, Philipp Hufnagl wrote: -- snip 8< -- > +$text-color-warning: hsl(48deg, 100%, 50%); > > // Borders > $border-color: hsl(0deg, 0%, 40%); > diff --git a/src/proxmox-dark/scss/extjs/_menu.scss b/src/proxmox-dark/scss/extjs/_menu.scss > index 2983f60..aa51260 100644 > --- a/src/proxmox-dark/scss/extjs/_menu.scss > +++ b/src/proxmox-dark/scss/extjs/_menu.scss > @@ -33,6 +33,10 @@ > color: $icon-color; > } > > +.x-menu-item-icon-default.warning { > + color: $text-color-warning; > +} > + same here, please don't define new colors unless you must. in this case there is an argument to be made that this needs to be a new color as we don't have a text color for warnings. except, we kind of do: gauges use a brigther version of the warning and invalid background colors. please factor these out into their own variables and use those here and in `src/proxmox-dark/scss/_charts.scss`, which is where we curretnly define the brigther colors. -- snip 8< -- From t.lamprecht at proxmox.com Fri Oct 6 18:10:34 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 6 Oct 2023 18:10:34 +0200 Subject: [pve-devel] applied-series: [PATCH ha-manager 1/2] fix #4984: recompute online usage: add service usage to migration target only if online In-Reply-To: <20231005140546.88771-1-f.ebner@proxmox.com> References: <20231005140546.88771-1-f.ebner@proxmox.com> Message-ID: <08051548-fddc-4fa5-984e-fc3e34469328@proxmox.com> Am 05/10/2023 um 16:05 schrieb Fiona Ebner: > Otherwise, when using the 'basic' plugin, this would lead to > autovivification of the $target node in the Perl hash tracking the > usage and it would wrongly be considered online when selecting the > recovery node. > > The 'static' plugin was not affected, because it would check and warn > before adding usage to a node that was not registered with add_node() > first. Doing the same in the 'basic' plugin will be done by another > patch. > > Signed-off-by: Fiona Ebner > --- > src/PVE/HA/Manager.pm | 3 ++- > 1 file changed, 2 insertions(+), 1 deletion(-) > > applied both patches with slightly rewording/shortening the commit message subject, thanks! From t.lamprecht at proxmox.com Fri Oct 6 18:13:17 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 6 Oct 2023 18:13:17 +0200 Subject: [pve-devel] applied: [PATCH qemu-server v2] vm start: set higher timeout if using PCI passthrough In-Reply-To: <20231006121532.90772-1-f.weber@proxmox.com> References: <20231006121532.90772-1-f.weber@proxmox.com> Message-ID: <52129e97-92c3-4d5b-af05-7a15a5bca167@proxmox.com> Am 06/10/2023 um 14:15 schrieb Friedrich Weber: > The default VM startup timeout is `max(30, VM memory in GiB)` seconds. > Multiple reports in the forum [0] [1] and the bug tracker [2] suggest > this is too short when using PCI passthrough with a large amount of VM > memory, since QEMU needs to map the whole memory during startup (see > comment #2 in [2]). As a result, VM startup fails with "got timeout". > > To work around this, set a larger default timeout if at least one PCI > device is passed through. The question remains how to choose an > appropriate timeout. Users reported the following startup times: > > ref | RAM | time | ratio (s/GiB) > --------------------------------- > [1] | 60G | 135s | 2.25 > [1] | 70G | 157s | 2.24 > [1] | 80G | 277s | 3.46 > [2] | 65G | 213s | 3.28 > [2] | 96G | >290s | >3.02 > > The data does not really indicate any simple (e.g. linear) > relationship between RAM and startup time (even data from the same > source). However, to keep the heuristic simple, assume linear growth > and multiply the default timeout by 4 if at least one `hostpci[n]` > option is present, obtaining `4 * max(30, VM memory in GiB)`. This > covers all cases above, and should still leave some headroom. > > [0]: https://forum.proxmox.com/threads/83765/post-552071 > [1]: https://forum.proxmox.com/threads/126398/post-592826 > [2]: https://bugzilla.proxmox.com/show_bug.cgi?id=3502 > > Suggested-by: Fiona Ebner > Signed-off-by: Friedrich Weber > --- > > Notes: > changes since v1 (was called "vm start: set minimum timeout of 300s if > using PCI passthrough", 20230503133723.165739-1-f.weber at proxmox.com): > * Use a constant multiplier as suggested by Fiona (thx!) > > Another workaround is offered by an unapplied patch series [3] of bug > 3502 [2] that makes it possible to set VM-specific timeouts (also in > the GUI). Users could use this option to manually set a higher > timeout for VMs that use PCI passthrough. However, it is not > immediately obvious that a higher timeout is necessary when using > PCI passthrough. Since the problem seems to come up somewhat > frequently, I think it makes sense to have the heuristic choose a > higher timeout by default. yeah, I think so too. > > As discussed in v1, I'll also pick up the patch series to allow users > to set custom timeouts [3], also to offer a workaround for cases where > the new heuristic chooses a timeout that is still too short. > > [2]: https://bugzilla.proxmox.com/show_bug.cgi?id=3502 > [3]: https://lists.proxmox.com/pipermail/pve-devel/2023-January/055352.html > > PVE/QemuServer/Helpers.pm | 7 +++++++ > 1 file changed, 7 insertions(+) > > applied, thanks! From f.ebner at proxmox.com Mon Oct 9 08:45:06 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Mon, 9 Oct 2023 08:45:06 +0200 Subject: [pve-devel] applied-series: [PATCH ha-manager 1/2] fix #4984: recompute online usage: add service usage to migration target only if online In-Reply-To: <08051548-fddc-4fa5-984e-fc3e34469328@proxmox.com> References: <20231005140546.88771-1-f.ebner@proxmox.com> <08051548-fddc-4fa5-984e-fc3e34469328@proxmox.com> Message-ID: <899c8911-3f07-28d8-2e28-b035e5b17e21@proxmox.com> Am 06.10.23 um 18:10 schrieb Thomas Lamprecht: > Am 05/10/2023 um 16:05 schrieb Fiona Ebner: >> Otherwise, when using the 'basic' plugin, this would lead to >> autovivification of the $target node in the Perl hash tracking the >> usage and it would wrongly be considered online when selecting the >> recovery node. >> >> The 'static' plugin was not affected, because it would check and warn >> before adding usage to a node that was not registered with add_node() >> first. Doing the same in the 'basic' plugin will be done by another >> patch. >> >> Signed-off-by: Fiona Ebner >> --- >> src/PVE/HA/Manager.pm | 3 ++- >> 1 file changed, 2 insertions(+), 1 deletion(-) >> >> > > applied both patches with slightly rewording/shortening the commit message > subject, thanks! Does it make sense for stable-7 too or is this too much of an edge case? The bug report was with Proxmox VE 7. From f.weber at proxmox.com Mon Oct 9 09:07:25 2023 From: f.weber at proxmox.com (Friedrich Weber) Date: Mon, 9 Oct 2023 09:07:25 +0200 Subject: [pve-devel] [PATCH 2/2] proxmox-boot-tool: check if correct grub metapackage is installed In-Reply-To: <20231004120558.382180-3-s.ivanov@proxmox.com> References: <20231004120558.382180-1-s.ivanov@proxmox.com> <20231004120558.382180-3-s.ivanov@proxmox.com> Message-ID: <6f2bca54-e79d-4854-9670-2b704762c537@proxmox.com> On 04/10/2023 14:05, Stoiko Ivanov wrote: > diff --git a/src/proxmox-boot/zz-proxmox-boot b/src/proxmox-boot/zz-proxmox-boot > index 1adc1b1..0d08dbf 100755 > --- a/src/proxmox-boot/zz-proxmox-boot > +++ b/src/proxmox-boot/zz-proxmox-boot > @@ -215,6 +215,23 @@ disable_systemd_boot_hook() { > > } > > +check_grub_efi_package() { > + > + if [ -f "${ESP_LIST}" ]; then > + return > + fi > + > + if [ ! -d /sys/firmware/efi ]; then > + return > + fi > + > + if [ ! -f /usr/share/doc/grub-efi-amd64/changelog.Debian.gz ]; then > + return > + fi If I understand correctly, this check needs to be inverted, since we do want to warn if grub-efi-amd64 is *not* installed? (only noticed because my UEFI-based test machine did not print the warning below on upgrade) > + warn "uefi-booted system, without grub-efi-amd64 package - /boot/efi will not be updated" > + > +} > + > set -- $DEB_MAINT_PARAMS > mode="${1#\'}" > mode="${mode%\'}" > @@ -228,6 +245,7 @@ case $0:$mode in > BOOT_KVERS="$(boot_kernel_list "$@")" > update_esps > disable_systemd_boot_hook > + check_grub_efi_package > ;; > */postrm.d/*:|*/postrm.d/*:remove) > reexec_in_mountns "$@" > @@ -235,6 +253,7 @@ case $0:$mode in > BOOT_KVERS="$(boot_kernel_list)" > update_esps > disable_systemd_boot_hook > + check_grub_efi_package > ;; > esac > From f.weber at proxmox.com Mon Oct 9 09:11:45 2023 From: f.weber at proxmox.com (Friedrich Weber) Date: Mon, 9 Oct 2023 09:11:45 +0200 Subject: [pve-devel] [PATCH 1/1] pve7to8: check for proper grub meta-package for bootmode In-Reply-To: <20231004120558.382180-4-s.ivanov@proxmox.com> References: <20231004120558.382180-1-s.ivanov@proxmox.com> <20231004120558.382180-4-s.ivanov@proxmox.com> Message-ID: On 04/10/2023 14:05, Stoiko Ivanov wrote: > + } elsif ( ! -f "/usr/share/doc/grub-efi-amd64/changelog.Debian.gz" ) { > + log_warn( > + "System booted in uefi mode but grub-efi-amd64 meta-package not installed" > + . " new grub versions will not be installed to /boot/efi -" > + . " install grub-efi-amd64" > ); Nit: The message is printed in one line, might be a bit more readable with a newline (or full stop/exclamation mark) after "not installed". > + return; > + } else { > + log_pass("bootloader packages installed correctly"); > } > } > From m.sandoval at proxmox.com Mon Oct 9 11:35:58 2023 From: m.sandoval at proxmox.com (Maximiliano Sandoval) Date: Mon, 9 Oct 2023 11:35:58 +0200 Subject: [pve-devel] [PATCH manager] api2tools: check if file changed before reusing its hash Message-ID: <20231009093558.84300-1-m.sandoval@proxmox.com> We cache the hash of this file, it makes sense to first check if the file changed via `stat` and recompute the hash if needed. This is to be in sync with the changes made in https://git.proxmox.com/?p=pmg-api.git;a=commit;h=16d2ff9f8e90db64114a66d78672f5a03f5ee990. Signed-off-by: Maximiliano Sandoval --- PVE/API2Tools.pm | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/PVE/API2Tools.pm b/PVE/API2Tools.pm index a3d7ca84..618c13b3 100644 --- a/PVE/API2Tools.pm +++ b/PVE/API2Tools.pm @@ -17,14 +17,22 @@ use PVE::SafeSyslog; use PVE::Storage::Plugin; my $hwaddress; +my $hwaddress_st = {}; sub get_hwaddress { + my $fn = '/etc/ssh/ssh_host_rsa_key.pub'; + my $st = stat($fn); - return $hwaddress if defined ($hwaddress); + if (defined($hwaddress) + && $hwaddress_st->{mtime} == $st->mtime + && $hwaddress_st->{ino} == $st->ino + && $hwaddress_st->{dev} == $st->dev) { + return $hwaddress; + } - my $fn = '/etc/ssh/ssh_host_rsa_key.pub'; my $sshkey = PVE::Tools::file_get_contents($fn); $hwaddress = uc(md5_hex($sshkey)); + $hwaddress_st->@{'mtime', 'ino', 'dev'} = ($st->mtime, $st->ino, $st->dev); return $hwaddress; } -- 2.39.2 From h.duerr at proxmox.com Mon Oct 9 11:38:22 2023 From: h.duerr at proxmox.com (hd) Date: Mon, 9 Oct 2023 11:38:22 +0200 Subject: [pve-devel] [PATCH proxmox-i18n 0/1] update German translation Message-ID: <20231009093823.74910-1-h.duerr@proxmox.com> I suggest an update of the german translation hd (1): update German translation de.po | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) -- 2.39.2 From h.duerr at proxmox.com Mon Oct 9 11:38:23 2023 From: h.duerr at proxmox.com (hd) Date: Mon, 9 Oct 2023 11:38:23 +0200 Subject: [pve-devel] [PATCH proxmox-i18n 1/1] update German translation In-Reply-To: <20231009093823.74910-1-h.duerr@proxmox.com> References: <20231009093823.74910-1-h.duerr@proxmox.com> Message-ID: <20231009093823.74910-2-h.duerr@proxmox.com> --- de.po | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/de.po b/de.po index fea74f1..c647f31 100644 --- a/de.po +++ b/de.po @@ -747,9 +747,8 @@ msgid "Authentication mode" msgstr "Authentifikationsmodus" #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:111 -#, fuzzy msgid "Author" -msgstr "Auth-ID" +msgstr "Autor" #: pmg-gui/js/TFAView.js:60 pve-manager/www/manager6/dc/OptionView.js:241 #: proxmox-backup/www/config/WebauthnView.js:109 -- 2.39.2 From l.wagner at proxmox.com Mon Oct 9 12:46:30 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 9 Oct 2023 12:46:30 +0200 Subject: [pve-devel] [PATCH proxmox-i18n 1/1] update German translation In-Reply-To: <20231009093823.74910-2-h.duerr@proxmox.com> References: <20231009093823.74910-1-h.duerr@proxmox.com> <20231009093823.74910-2-h.duerr@proxmox.com> Message-ID: <0a80e621-7f96-474e-9ed7-573b7c4bd46b@proxmox.com> Hello Hannes, please configure your git client so that the commit author is your full name [1]: git config --global user.name "Firstname Lastname" Also, this patch is missing the 'Signed-off-by' line. That line is auto-generated by git if you provide the right flags to `git commit` or `git format-patch` (e.g. `git commit -s`, maybe create a git alias for that) Also, for single patches there is no need to send a cover-letter. If there is anything to be said about the patch that should *not* be part of the commit message, you can write it under the the '---' marker. Hope this helps :) [1] https://pve.proxmox.com/wiki/Developer_Documentation#Using_git On 10/9/23 11:38, hd wrote: > --- ^ I mean this marker here. > de.po | 3 +-- > 1 file changed, 1 insertion(+), 2 deletions(-) > > diff --git a/de.po b/de.po > index fea74f1..c647f31 100644 > --- a/de.po > +++ b/de.po > @@ -747,9 +747,8 @@ msgid "Authentication mode" > msgstr "Authentifikationsmodus" > > #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:111 > -#, fuzzy > msgid "Author" > -msgstr "Auth-ID" > +msgstr "Autor" > > #: pmg-gui/js/TFAView.js:60 pve-manager/www/manager6/dc/OptionView.js:241 > #: proxmox-backup/www/config/WebauthnView.js:109 -- - Lukas From f.ebner at proxmox.com Mon Oct 9 13:11:08 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Mon, 9 Oct 2023 13:11:08 +0200 Subject: [pve-devel] [PATCH v2 qemu-server 1/2] Add missing checks for paused runstates In-Reply-To: <20230825121851.103307-2-f.schauer@proxmox.com> References: <20230825121851.103307-1-f.schauer@proxmox.com> <20230825121851.103307-2-f.schauer@proxmox.com> Message-ID: <52e62cc8-325d-d43e-25e1-42b23f0009b4@proxmox.com> Am 25.08.23 um 14:18 schrieb Filip Schauer: > Add checks for "suspended" and "prelaunch" runstates when checking > whether a VM is paused. > > This fixes the following issues: > * ACPI-suspended VMs automatically resuming after migration > * Shutdown and reboot commands timing out instead of failing > immediately on suspended VMs > > Signed-off-by: Filip Schauer > --- > PVE/QemuServer.pm | 6 +++++- > 1 file changed, 5 insertions(+), 1 deletion(-) > > diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm > index bf1de17..954fed7 100644 > --- a/PVE/QemuServer.pm > +++ b/PVE/QemuServer.pm > @@ -8596,7 +8596,11 @@ sub vm_is_paused { > mon_cmd($vmid, "query-status"); > }; > warn "$@\n" if $@; > - return $qmpstatus && $qmpstatus->{status} eq "paused"; > + return $qmpstatus && ( > + $qmpstatus->{status} eq "paused" or > + $qmpstatus->{status} eq "suspended" or Style nit: mixing '&&' and 'or' is not too nice. For boolean expressions like here, '&&' and '||' should always be used. For assertions like $variable or die "error" usually 'or' is used in our code base. Once that's fixed: Reviewed-by: Fiona Ebner > + $qmpstatus->{status} eq "prelaunch" > + ); > } > > sub check_volume_storage_type { From f.ebner at proxmox.com Mon Oct 9 13:11:09 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Mon, 9 Oct 2023 13:11:09 +0200 Subject: [pve-devel] [PATCH v2 qemu-server 2/2] Rename vm_is_paused to vm_is_frozen In-Reply-To: <20230825121851.103307-3-f.schauer@proxmox.com> References: <20230825121851.103307-1-f.schauer@proxmox.com> <20230825121851.103307-3-f.schauer@proxmox.com> Message-ID: <4cb3d3da-96da-506a-a40d-8a031b997d93@proxmox.com> Am 25.08.23 um 14:18 schrieb Filip Schauer: > Rename vm_is_paused to vm_is_frozen to avoid confusion with the "paused" > runstate. > I know I suggested renaming the function, but with 'frozen' there is potential for new confusion with fsfreeze, especially with the user-facing warnings/errors. It's also that 'paused' sounds nicer to users here IMHO. Maybe we can call it vcpus_(are_)paused and keep the user-facing messages the same? Or we can just keep the current name. Right now, there is no case where we need to distinguish between having runstate 'paused' and the return value of vm_is_paused(). From t.lamprecht at proxmox.com Mon Oct 9 13:11:53 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Mon, 9 Oct 2023 13:11:53 +0200 Subject: [pve-devel] [PATCH proxmox-i18n 1/1] update German translation In-Reply-To: <20231009093823.74910-2-h.duerr@proxmox.com> References: <20231009093823.74910-1-h.duerr@proxmox.com> <20231009093823.74910-2-h.duerr@proxmox.com> Message-ID: <2f93f9c0-fbe7-4bc3-b49a-10a36d3fb612@proxmox.com> Am 09/10/2023 um 11:38 schrieb hd: > --- > de.po | 3 +-- > 1 file changed, 1 insertion(+), 2 deletions(-) > On-top of what Lukas mentioned: there are dozens of fuzzy strings in de.po, and applying/reviewing patches has a slight overhead independent of the amount of changes, so I'd be great if you send an update for a few more than just a single change in one patch, so we get a better "return of (review/apply time) investment". From f.ebner at proxmox.com Mon Oct 9 13:25:58 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Mon, 9 Oct 2023 13:25:58 +0200 Subject: [pve-devel] [PATCH v4 qemu-server 1/2] migration: move livemigration code in a dedicated sub In-Reply-To: <20230928144556.2023558-2-aderumier@odiso.com> References: <20230928144556.2023558-1-aderumier@odiso.com> <20230928144556.2023558-2-aderumier@odiso.com> Message-ID: <7f9ae89c-5f3d-48d2-c646-667a6459ec5f@proxmox.com> Missing your Signed-off-by trailer. Apart from that Reviewed-by: Fiona Ebner From f.ebner at proxmox.com Mon Oct 9 14:13:44 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Mon, 9 Oct 2023 14:13:44 +0200 Subject: [pve-devel] [PATCH v4 qemu-server 2/2] remote-migration: add target-cpu && target-reboot params In-Reply-To: <20230928144556.2023558-3-aderumier@odiso.com> References: <20230928144556.2023558-1-aderumier@odiso.com> <20230928144556.2023558-3-aderumier@odiso.com> Message-ID: <5ecfa7d0-4525-5f1e-75a2-a6ae1a93356b@proxmox.com> There can be other reasons to do a restart-migration, see also https://bugzilla.proxmox.com/show_bug.cgi?id=4530, so I feel like this should be split. One for introducing target-reboot and one for introducing target-cpu. For 'target-reboot', the question is if we should call it 'restart' like for container migration for consistency? Could also be introduced for normal migration while we're at it. One could argue that a true 'restart' migration would migrate the volumes also offline, but right now, I don't see a big downside to do it via NBD like in this patch. Still, something we should think about. If it turns out to be really needed, we'd need two different ways to do a restart migration :/ Am 28.09.23 um 16:45 schrieb Alexandre Derumier: > This patch add support for remote migration when target > cpu model is different. > > target-reboot param need to be defined to allow migration > whens source vm is online. > > When defined, only the live storage migration is done, > and instead to transfert memory, we cleanly shutdown source vm > and restart the target vm. (like a virtual reboot between source/dest) Missing your Signed-off-by > --- > PVE/API2/Qemu.pm | 23 ++++++++++++++++++++++- > PVE/CLI/qm.pm | 11 +++++++++++ > PVE/QemuMigrate.pm | 31 +++++++++++++++++++++++++++++-- > 3 files changed, 62 insertions(+), 3 deletions(-) > > diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm > index 774b0c7..38991e9 100644 > --- a/PVE/API2/Qemu.pm > +++ b/PVE/API2/Qemu.pm > @@ -4586,6 +4586,17 @@ __PACKAGE__->register_method({ > optional => 1, > default => 0, > }, > + 'target-cpu' => { > + optional => 1, > + description => "Target Emulated CPU model. For online migration, this require target-reboot option", To enforce it, you can use: requires => 'target-reboot', > + type => 'string', > + format => 'pve-vm-cpu-conf',> + }, > + 'target-reboot' => { > + type => 'boolean', > + description => "For online migration , don't migrate memory, only storage. Then, the source vm is shutdown and the target vm is restarted.", > + optional => 1, > + }, > 'target-storage' => get_standard_option('pve-targetstorage', { > completion => \&PVE::QemuServer::complete_migration_storage, > optional => 0, > @@ -4666,7 +4677,7 @@ __PACKAGE__->register_method({ > > if (PVE::QemuServer::check_running($source_vmid)) { > die "can't migrate running VM without --online\n" if !$param->{online}; > - > + die "can't migrate running VM without --target-reboot when target cpu is different" if $param->{'target-cpu'} && !$param->{'target-reboot'}; > } else { > warn "VM isn't running. Doing offline migration instead.\n" if $param->{online}; > $param->{online} = 0; > @@ -4683,6 +4694,7 @@ __PACKAGE__->register_method({ > raise_param_exc({ 'target-bridge' => "failed to parse bridge map: $@" }) > if $@; > > + > die "remote migration requires explicit storage mapping!\n" > if $storagemap->{identity}; > Nit: unrelated change > @@ -5732,6 +5744,15 @@ __PACKAGE__->register_method({ > PVE::QemuServer::nbd_stop($state->{vmid}); > return; > }, > + 'restart' => sub { > + PVE::QemuServer::vm_stop(undef, $state->{vmid}, 1, 1); The first parameter is $storecfg and is not optional. To avoid deactivating the volumes, use the $keepActive parameter. > + my $info = PVE::QemuServer::vm_start_nolock( > + $state->{storecfg}, > + $state->{vmid}, > + $state->{conf}, > + ); > + return; > + }, > 'resume' => sub { > if (PVE::QemuServer::Helpers::vm_running_locally($state->{vmid})) { > PVE::QemuServer::vm_resume($state->{vmid}, 1, 1); > diff --git a/PVE/CLI/qm.pm b/PVE/CLI/qm.pm > index b17b4fe..9d89cfe 100755 > --- a/PVE/CLI/qm.pm > +++ b/PVE/CLI/qm.pm > @@ -189,6 +189,17 @@ __PACKAGE__->register_method({ > optional => 1, > default => 0, > }, > + 'target-cpu' => { > + optional => 1, > + description => "Target Emulated CPU model. For online migration, this require target-reboot option", Again, can be enforced requires => 'target-reboot', > + type => 'string', > + format => 'pve-vm-cpu-conf', > + }, > + 'target-reboot' => { > + type => 'boolean', > + description => "For online migration , don't migrate memory, only storage. Then, the source vm is shutdown and the target vm is restarted.", > + optional => 1, > + }, > 'target-storage' => get_standard_option('pve-targetstorage', { > completion => \&PVE::QemuServer::complete_migration_storage, > optional => 0, > diff --git a/PVE/QemuMigrate.pm b/PVE/QemuMigrate.pm > index 5ea78a7..8131b0b 100644 > --- a/PVE/QemuMigrate.pm > +++ b/PVE/QemuMigrate.pm > @@ -729,6 +729,11 @@ sub cleanup_bitmaps { > sub live_migration { > my ($self, $vmid, $migrate_uri, $spice_port) = @_; > > + if($self->{opts}->{'target-reboot'}){ > + $self->log('info', "target reboot - skip live migration."); using restart migration - skipping live migration > + return; > + } > + > my $conf = $self->{vmconf}; > > $self->log('info', "starting online/live migration on $migrate_uri"); > @@ -993,6 +998,7 @@ sub phase1_remote { > my $remote_conf = PVE::QemuConfig->load_config($vmid); > PVE::QemuConfig->update_volume_ids($remote_conf, $self->{volume_map}); > > + $remote_conf->{cpu} = $self->{opts}->{'target-cpu'} if $self->{opts}->{'target-cpu'}; > my $bridges = map_bridges($remote_conf, $self->{opts}->{bridgemap}); > for my $target (keys $bridges->%*) { > for my $nic (keys $bridges->{$target}->%*) { > @@ -1356,7 +1362,14 @@ sub phase2 { > # finish block-job with block-job-cancel, to disconnect source VM from NBD > # to avoid it trying to re-establish it. We are in blockjob ready state, > # thus, this command changes to it to blockjob complete (see qapi docs) > - eval { PVE::QemuServer::qemu_drive_mirror_monitor($vmid, undef, $self->{storage_migration_jobs}, 'cancel'); }; > + my $finish_cmd = "cancel"; > + if ($self->{opts}->{'target-reboot'}) { > + # no live migration. > + # finish block-job with block-job-complete, the source will switch to remote NDB > + # then we cleanly stop the source vm at phase3 Nit: "during phase3" or "in phase3" > + $finish_cmd = "complete"; > + } > + eval { PVE::QemuServer::qemu_drive_mirror_monitor($vmid, undef, $self->{storage_migration_jobs}, $finish_cmd); }; > if (my $err = $@) { > die "Failed to complete storage migration: $err\n"; > } > @@ -1573,7 +1586,17 @@ sub phase3_cleanup { > }; > > # always stop local VM with nocheck, since config is moved already > - eval { PVE::QemuServer::vm_stop($self->{storecfg}, $vmid, 1, 1); }; > + my $shutdown_timeout = undef; > + my $shutdown = undef; > + my $force_stop = undef; > + if ($self->{opts}->{'target-reboot'}) { > + $shutdown_timeout = 180; > + $shutdown = 1; > + $force_stop = 1; > + $self->log('info', "clean shutdown of source vm."); Sounds like it already happened and with force_stop=1 it might not be as clean in the worst case ;). Maybe just "shutting down source VM"? > + } > + > + eval { PVE::QemuServer::vm_stop($self->{storecfg}, $vmid, 1, 1, $shutdown_timeout, $shutdown, $force_stop); }; > if (my $err = $@) { > $self->log('err', "stopping vm failed - $err"); > $self->{errors} = 1; > @@ -1607,6 +1630,10 @@ sub phase3_cleanup { > # clear migrate lock > if ($tunnel && $tunnel->{version} >= 2) { > PVE::Tunnel::write_tunnel($tunnel, 10, "unlock"); > + if ($self->{opts}->{'target-reboot'}) { > + $self->log('info', "restart target vm."); > + PVE::Tunnel::write_tunnel($tunnel, 10, 'restart'); > + } > > PVE::Tunnel::finish_tunnel($tunnel); > } else { From s.ivanov at proxmox.com Mon Oct 9 14:52:42 2023 From: s.ivanov at proxmox.com (Stoiko Ivanov) Date: Mon, 9 Oct 2023 14:52:42 +0200 Subject: [pve-devel] [PATCH manager v2 1/1] pve7to8: check for proper grub meta-package for bootmode In-Reply-To: <20231009125242.3857753-1-s.ivanov@proxmox.com> References: <20231009125242.3857753-1-s.ivanov@proxmox.com> Message-ID: <20231009125242.3857753-4-s.ivanov@proxmox.com> This should catch installations from our ISO on non-ZFS in uefi mode, which won't get the updated grub efi binary installed upon upgrade, because grub-pc is installed instead of grub-efi-amd64. Adding this to pve7to8 should make this even more visible, than the corresponding patch for promxox-kernel-helper (warnings printed during regular package upgrades might be overlooked more easily than a yellow line in the major upgrade checkscript) The if/else order was chosen to limit the nesting level of the long messages. Signed-off-by: Stoiko Ivanov --- PVE/CLI/pve7to8.pm | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/PVE/CLI/pve7to8.pm b/PVE/CLI/pve7to8.pm index d1a71eff..ff7825b3 100644 --- a/PVE/CLI/pve7to8.pm +++ b/PVE/CLI/pve7to8.pm @@ -1302,29 +1302,36 @@ sub check_time_sync { sub check_bootloader { log_info("Checking bootloader configuration..."); - if (!$upgraded) { - log_skip("not yet upgraded, no need to check the presence of systemd-boot"); - return; - } - if (! -f "/etc/kernel/proxmox-boot-uuids") { - log_skip("proxmox-boot-tool not used for bootloader configuration"); + if (! -d '/sys/firmware/efi') { + log_skip("System booted in legacy-mode - no need for additional packages"); return; } - if (! -d "/sys/firmware/efi") { - log_skip("System booted in legacy-mode - no need for systemd-boot"); - return; - } - - if ( -f "/usr/share/doc/systemd-boot/changelog.Debian.gz") { - log_pass("systemd-boot is installed"); - } else { + if ( -f "/etc/kernel/proxmox-boot-uuids") { + if (!$upgraded) { + log_skip("not yet upgraded, no need to check the presence of systemd-boot"); + return; + } + if ( -f "/usr/share/doc/systemd-boot/changelog.Debian.gz") { + log_pass("bootloader packages installed correctly"); + return; + } log_warn( "proxmox-boot-tool is used for bootloader configuration in uefi mode" - . "but the separate systemd-boot package, existing in Debian Bookworm is not installed" - . "initializing new ESPs will not work until the package is installed" + . " but the separate systemd-boot package, existing in Debian Bookworm is not installed" + . " initializing new ESPs will not work until the package is installed" + ); + return; + } elsif ( ! -f "/usr/share/doc/grub-efi-amd64/changelog.Debian.gz" ) { + log_warn( + "System booted in uefi mode but grub-efi-amd64 meta-package not installed" + . " new grub versions will not be installed to /boot/efi!" + . " Install grub-efi-amd64." ); + return; + } else { + log_pass("bootloader packages installed correctly"); } } -- 2.39.2 From s.ivanov at proxmox.com Mon Oct 9 14:52:39 2023 From: s.ivanov at proxmox.com (Stoiko Ivanov) Date: Mon, 9 Oct 2023 14:52:39 +0200 Subject: [pve-devel] [PATCH kernel-helper/manager v2] check for fitting grub-meta package on uefi systems Message-ID: <20231009125242.3857753-1-s.ivanov@proxmox.com> v1->v2: * adapted Friedrich's feedback (huge thanks!) ** fixed the wrongly negated check for installed grub-efi-amd64 in the boot-tool hook. ** Rephrased the error-message in pve7to8 to 2 sentences. I tried adding a newline as well, however this results in the message not being printed in the warning color anymore (most likely due to [0]) - and I felt this to be more important than having it on a separate line. [0] https://perldoc.perl.org/Term::ANSIColor#RESTRICTIONS original cover-letter for v1: The following patchset is a followup to the one for the installer: https://lists.proxmox.com/pipermail/pve-devel/2023-September/059270.html As suggested by Thomas - adding the check to proxmox-kernel-helper seems like a good idea. While adding it to d/postinst I thought that this might not be the best place - and that getting the warning upon every kernel-upgrade would be better vs. upon every upgrade of proxmox-kernel-helper (which are far less often). (Can gladly send the version with d/postinst as well) If the pve-manager patch gets applied - I'd push the equivalent change to pmg and provide one for pbs. Tested on legacy and uefi VMs installed with pve-8.0 iso and grub-efi-amd64 (and systemd-boot) removed vs. installed. proxmox-kernel-helper: Stoiko Ivanov (2): proxmox-boot-tool: do not exit early in kernel-hook proxmox-boot-tool: check if correct grub metapackage is installed src/proxmox-boot/zz-proxmox-boot | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) pve-manager: Stoiko Ivanov (1): pve7to8: check for proper grub meta-package for bootmode PVE/CLI/pve7to8.pm | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) -- 2.39.2 From s.ivanov at proxmox.com Mon Oct 9 14:52:40 2023 From: s.ivanov at proxmox.com (Stoiko Ivanov) Date: Mon, 9 Oct 2023 14:52:40 +0200 Subject: [pve-devel] [PATCH kernel-helper v2 1/2] proxmox-boot-tool: do not exit early in kernel-hook In-Reply-To: <20231009125242.3857753-1-s.ivanov@proxmox.com> References: <20231009125242.3857753-1-s.ivanov@proxmox.com> Message-ID: <20231009125242.3857753-2-s.ivanov@proxmox.com> update_esps is called first in the actual execution below - exiting early does not work for systems that don't use proxmox-boot-tool if a check added later needs to work there too. Signed-off-by: Stoiko Ivanov --- src/proxmox-boot/zz-proxmox-boot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proxmox-boot/zz-proxmox-boot b/src/proxmox-boot/zz-proxmox-boot index 793882b..1adc1b1 100755 --- a/src/proxmox-boot/zz-proxmox-boot +++ b/src/proxmox-boot/zz-proxmox-boot @@ -44,7 +44,7 @@ fi update_esps() { if [ ! -f "${ESP_LIST}" ]; then warn "No ${ESP_LIST} found, skipping ESP sync." - exit 0 + return fi if [ -f /etc/kernel/cmdline ]; then # we can have cmdline files with multiple or no new line at all, handle both! -- 2.39.2 From s.ivanov at proxmox.com Mon Oct 9 14:52:41 2023 From: s.ivanov at proxmox.com (Stoiko Ivanov) Date: Mon, 9 Oct 2023 14:52:41 +0200 Subject: [pve-devel] [PATCH kernel-helper v2 2/2] proxmox-boot-tool: check if correct grub metapackage is installed In-Reply-To: <20231009125242.3857753-1-s.ivanov@proxmox.com> References: <20231009125242.3857753-1-s.ivanov@proxmox.com> Message-ID: <20231009125242.3857753-3-s.ivanov@proxmox.com> this part of the hook applies only to systems not using pbt for bootmangement. Currently our ISO installs grub-pc unconditionally - and never the conflicting grub-efi-amd64. Both packages are responsible for running grub-install (for the appropriate disks) upon an upgrade of grub. This results in grub currently not getting updated on uefi-booted systems (which do not use proxmox-boot-tool). The patch causes a warning to be printed to notify the user. Also considered putting the check+warning in d/postinst - but this way it will get triggered more often (upon every kernel-upgrade/update-initramfs, instead of only on proxmox-kernel-helper updates, which are less often), increasing the chances of being noticed. checking for the changelog-presence was chosen, over `dpkg-query` for the status, for consistency with the similar patch for pve7to8 (and potentially a small speed-gain). Suggested-by: Thomas Lamprecht Signed-off-by: Stoiko Ivanov --- src/proxmox-boot/zz-proxmox-boot | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/proxmox-boot/zz-proxmox-boot b/src/proxmox-boot/zz-proxmox-boot index 1adc1b1..4dfa765 100755 --- a/src/proxmox-boot/zz-proxmox-boot +++ b/src/proxmox-boot/zz-proxmox-boot @@ -215,6 +215,23 @@ disable_systemd_boot_hook() { } +check_grub_efi_package() { + + if [ -f "${ESP_LIST}" ]; then + return + fi + + if [ ! -d /sys/firmware/efi ]; then + return + fi + + if [ -f /usr/share/doc/grub-efi-amd64/changelog.Debian.gz ]; then + return + fi + warn "uefi-booted system, without grub-efi-amd64 package - /boot/efi will not be updated" + +} + set -- $DEB_MAINT_PARAMS mode="${1#\'}" mode="${mode%\'}" @@ -228,6 +245,7 @@ case $0:$mode in BOOT_KVERS="$(boot_kernel_list "$@")" update_esps disable_systemd_boot_hook + check_grub_efi_package ;; */postrm.d/*:|*/postrm.d/*:remove) reexec_in_mountns "$@" @@ -235,6 +253,7 @@ case $0:$mode in BOOT_KVERS="$(boot_kernel_list)" update_esps disable_systemd_boot_hook + check_grub_efi_package ;; esac -- 2.39.2 From p.hufnagl at proxmox.com Mon Oct 9 15:07:06 2023 From: p.hufnagl at proxmox.com (Philipp Hufnagl) Date: Mon, 9 Oct 2023 15:07:06 +0200 Subject: [pve-devel] [PATCH manager/access-control/proxmox-widget-toolkit v1 0/4] fix #4546: Show warning hint/badge if user account is expiring in next few days In-Reply-To: <7de71173-6944-4842-9f3f-56250b34fd95@proxmox.com> References: <20230922143658.1639173-1-p.hufnagl@proxmox.com> <7de71173-6944-4842-9f3f-56250b34fd95@proxmox.com> Message-ID: <0d2b2c1a-9037-4524-b3d1-8d3b34015b00@proxmox.com> On 10/6/23 15:16, Lukas Wagner wrote: > > > On 9/22/23 16:36, Philipp Hufnagl wrote: >> Currently, when an user account expires, it catches the users by >> surprise. It would be helpful to notify the the user as well as the >> administrator. >> >> This patch highlights such accounts in the user account pannel and also >> shows for regular user an exclamation mark in the user info pannel. It >> also adds in this case a new entry briefly saing that the account >> expires. When this entry is clicked it states this in more detail >> including the experation date. >> >> Sending this for PVE only now to get feedback before implementing this >> for PMG and PBS >> > > Tested this on the latest master. Seems to work as adverised with the > exception of a small rendering bug (decribed in one of the other > messages). > > > Thanks for all the valuable feedback! I am currently working on a v2. Please don't merge! From h.duerr at proxmox.com Mon Oct 9 15:21:26 2023 From: h.duerr at proxmox.com (Hannes Duerr) Date: Mon, 9 Oct 2023 15:21:26 +0200 Subject: [pve-devel] [PATCH v2 proxmox-i18n] update German translation Message-ID: <20231009132126.125795-1-h.duerr@proxmox.com> Signed-off-by: Hannes Duerr --- de.po | 111 ++++++++++++++++++++-------------------------------------- 1 file changed, 37 insertions(+), 74 deletions(-) diff --git a/de.po b/de.po index fea74f1..4f6de7d 100644 --- a/de.po +++ b/de.po @@ -747,9 +747,8 @@ msgid "Authentication mode" msgstr "Authentifikationsmodus" #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:111 -#, fuzzy msgid "Author" -msgstr "Auth-ID" +msgstr "Autor" #: pmg-gui/js/TFAView.js:60 pve-manager/www/manager6/dc/OptionView.js:241 #: proxmox-backup/www/config/WebauthnView.js:109 @@ -1400,9 +1399,8 @@ msgstr "Testen" #: pve-manager/www/manager6/dc/AuthEditAD.js:93 #: pve-manager/www/manager6/dc/AuthEditLDAP.js:93 -#, fuzzy msgid "Check connection" -msgstr "Schutz ?ndern" +msgstr "Teste Verbindung" #: pve-manager/www/manager6/window/DownloadUrlToStorage.js:188 #: pve-manager/www/manager6/window/UploadToStorage.js:225 @@ -2100,9 +2098,8 @@ msgid "Current Auth ID" msgstr "Aktuelle Auth-ID" #: pve-manager/www/manager6/grid/PoolMembers.js:73 -#, fuzzy msgid "Current Pool" -msgstr "Aktuelles Layout" +msgstr "Aktueller Bestand" #: proxmox-backup/www/tape/window/TapeRestore.js:431 msgid "Current User" @@ -2674,11 +2671,10 @@ msgid "Disabled" msgstr "Deaktiviert" #: pve-manager/www/manager6/dc/NotificationEvents.js:32 -#, fuzzy msgid "Disabling notifications is not recommended for production systems!" msgstr "" -"Das {0}no-subscription Repository ist nicht f?r die Verwendung in " -"Produktivsystemen empfohlen!" +"Das Deaktivieren von Benachrichtigungen in Produktivsystemen ist" +"nicht empfohlen!" #: pve-manager/www/manager6/qemu/RNGEdit.js:90 msgid "" @@ -2789,9 +2785,8 @@ msgid "Do not use any media" msgstr "Kein Medium verwenden" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:91 -#, fuzzy msgid "Do you want to send a test notification to '{0}'?" -msgstr "M?chten Sie Replikation Job {0} wirklich entfernen?" +msgstr "M?chten Sie eine Testbenachrichtigung an '{0}' senden?" #: pmg-gui/js/MainView.js:187 pve-manager/www/manager6/Workspace.js:352 #: proxmox-backup/www/MainView.js:226 @@ -3287,9 +3282,8 @@ msgstr "Endzeit" #: proxmox-widget-toolkit/src/panel/GotifyEditPanel.js:16 #: proxmox-widget-toolkit/src/panel/NotificationGroupEditPanel.js:67 #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:27 -#, fuzzy msgid "Endpoint Name" -msgstr "Bind-Dom?nenname" +msgstr "Endpointname" #: proxmox-widget-toolkit/src/Utils.js:70 msgid "English" @@ -3737,9 +3731,8 @@ msgstr "Filter" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:265 #: proxmox-widget-toolkit/src/window/NotificationFilterEdit.js:14 -#, fuzzy msgid "Filter Name" -msgstr "Cluster-Name" +msgstr "Filtername" #: proxmox-backup/www/form/GroupFilter.js:281 msgid "Filter Type" @@ -3747,7 +3740,7 @@ msgstr "Filtertyp" #: pve-manager/www/manager6/grid/BackupView.js:150 msgid "Filter VMID" -msgstr "Filtere VMID" +msgstr "Filter-VMID" #: proxmox-backup/www/form/GroupFilter.js:291 msgid "Filter Value" @@ -3966,9 +3959,8 @@ msgid "From" msgstr "Von" #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:121 -#, fuzzy msgid "From Address" -msgstr "Front Adresse" +msgstr "Von Adresse" #: pve-manager/www/manager6/window/Restore.js:260 msgid "From Backup" @@ -4137,9 +4129,8 @@ msgid "Group Guest Types" msgstr "Gruppiere Gast-Typ" #: proxmox-widget-toolkit/src/panel/NotificationGroupEditPanel.js:16 -#, fuzzy msgid "Group Name" -msgstr "Gruppenmitglied" +msgstr "Gruppenname" #: pve-manager/www/manager6/dc/ACLView.js:26 #: pve-manager/www/manager6/dc/ACLView.js:199 @@ -4481,7 +4472,6 @@ msgid "IOMMU Group" msgstr "IOMMU-Gruppe" #: pve-manager/www/manager6/dc/PCIMapView.js:88 -#, fuzzy msgid "IOMMU-Group" msgstr "IOMMU-Gruppe" @@ -5475,9 +5465,8 @@ msgid "Manufacturer" msgstr "Hersteller" #: pve-manager/www/manager6/qemu/PCIEdit.js:193 -#, fuzzy msgid "Mapped Device" -msgstr "Gemapptes Devices" +msgstr "Gemapptes Device" #: pve-manager/www/manager6/form/PCIMapSelector.js:52 #: pve-manager/www/manager6/form/USBMapSelector.js:37 @@ -5646,9 +5635,8 @@ msgid "Memory usage" msgstr "Speicherverbrauch" #: pve-manager/www/manager6/ceph/OSDDetails.js:151 -#, fuzzy msgid "Memory usage (PSS)" -msgstr "Speicherverbrauch" +msgstr "Speicherverbrauch (PSS)" #: proxmox-widget-toolkit/src/window/ZFSDetail.js:151 #: pve-manager/www/manager6/dc/Log.js:79 @@ -5730,9 +5718,8 @@ msgid "Min. Size" msgstr "Min. Gr??e" #: proxmox-widget-toolkit/src/window/NotificationFilterEdit.js:20 -#, fuzzy msgid "Minimum Severity" -msgstr "Min. Speicher" +msgstr "Min. Schweregrad" #: proxmox-widget-toolkit/src/Toolkit.js:100 #: proxmox-widget-toolkit/src/Toolkit.js:108 @@ -6435,9 +6422,8 @@ msgid "No default available" msgstr "Kein Standardwert verf?gbar" #: proxmox-widget-toolkit/src/panel/NotificationGroupEditPanel.js:170 -#, fuzzy msgid "No endpoint selected" -msgstr "Keine Disk ausgew?hlt" +msgstr "Kein Endpoint ausgew?hlt" #: pmg-gui/js/QuarantineList.js:265 msgid "No match found" @@ -6463,14 +6449,12 @@ msgid "No network information" msgstr "Keine Information ?ber Netzwerk" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:260 -#, fuzzy msgid "No notification filters configured" -msgstr "Keine Domains konfiguriert" +msgstr "Keine Benachrichtigungsfilter konfiguriert" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:123 -#, fuzzy msgid "No notification targets configured" -msgstr "Keine Datastores konfiguriert" +msgstr "Keine Benachrichtigungsziele konfiguriert" #: pve-manager/www/manager6/sdn/zones/Base.js:45 #: pve-manager/www/manager6/storage/Base.js:42 @@ -6500,9 +6484,8 @@ msgid "No such service configured." msgstr "Kein solcher Dienst eingerichtet." #: pve-manager/www/manager6/dc/BackupJobDetail.js:223 -#, fuzzy msgid "No target configured" -msgstr "Noch nicht konfiguriert" +msgstr "Kein Ziel konfiguriert" #: proxmox-widget-toolkit/src/node/APT.js:185 msgid "No updates available." @@ -6726,47 +6709,39 @@ msgid "Notification" msgstr "Notifikation" #: proxmox-widget-toolkit/src/window/NotificationFilterEdit.js:92 -#, fuzzy msgid "Notification Filter" -msgstr "Notifikation" +msgstr "Benachrichtigungsfilter" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:220 -#, fuzzy msgid "Notification Filters" -msgstr "Notifikation" +msgstr "Benachrichtigungsfilter" #: proxmox-widget-toolkit/src/Schema.js:52 -#, fuzzy msgid "Notification Group" -msgstr "Notifikation" +msgstr "Benachrichtigungsgruppe" #: pve-manager/www/manager6/dc/Backup.js:346 #: pve-manager/www/manager6/dc/NotificationEvents.js:38 -#, fuzzy msgid "Notification Target" -msgstr "Notifikation" +msgstr "Benachrichtigungsziel" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:90 #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:103 -#, fuzzy msgid "Notification Target Test" -msgstr "Notifikation" +msgstr "Benachrichtigungszieltest" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:36 #: pve-manager/www/manager6/dc/Config.js:338 -#, fuzzy msgid "Notification Targets" -msgstr "Notifikation" +msgstr "Benachrichtigungsziele" #: pve-manager/www/manager6/window/Backup.js:34 -#, fuzzy msgid "Notification target" -msgstr "Notifikation" +msgstr "Benachrichtigungsziel" #: pve-manager/www/manager6/dc/Config.js:324 -#, fuzzy msgid "Notifications" -msgstr "Notifikation" +msgstr "Benachrichtigungen" #: pmg-gui/js/PBSRemoteEdit.js:85 pve-manager/www/manager6/dc/Backup.js:329 #: pve-manager/www/manager6/dc/NotificationEvents.js:158 @@ -6789,15 +6764,13 @@ msgid "Notify always" msgstr "Immer benachrichtigen" #: pve-manager/www/manager6/form/NotificationPolicySelector.js:7 -#, fuzzy msgid "Notify never" -msgstr "Benutzer benachrichtigen" +msgstr "Niemals benachrichtigen" #: pve-manager/www/manager6/dc/Backup.js:338 #: pve-manager/www/manager6/window/Backup.js:47 -#, fuzzy msgid "Notify via" -msgstr "Benachrichtigungen" +msgstr "Benachrichtige via" #: pve-manager/www/manager6/form/IPProtocolSelector.js:16 msgid "Number" @@ -7683,9 +7656,8 @@ msgid "Proxmox Mail Gateway Login" msgstr "Proxmox Mail Gateway Anmeldung" #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:114 -#, fuzzy msgid "Proxmox VE" -msgstr "Proxmox VE Anmeldung" +msgstr "Proxmox VE" #: pve-manager/www/manager6/window/LoginWindow.js:323 msgid "Proxmox VE Login" @@ -8736,9 +8708,8 @@ msgid "SSH public key" msgstr "?ffentlicher SSH-Schl?ssel" #: pve-manager/www/manager6/lxc/CreateWizard.js:126 -#, fuzzy msgid "SSH public key(s)" -msgstr "?ffentlicher SSH-Schl?ssel" +msgstr "?ffentliche(r) SSH-Schl?ssel" #: pmg-gui/js/dashboard/NodeInfo.js:67 #: pve-manager/www/manager6/node/StatusView.js:81 @@ -9026,14 +8997,12 @@ msgid "Sender/Subject" msgstr "Sender/Betreff" #: proxmox-widget-toolkit/src/Schema.js:42 -#, fuzzy msgid "Sendmail" -msgstr "Sende E-Mail an" +msgstr "Sende E-Mail" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:105 -#, fuzzy msgid "Sent test notification to '{0}'." -msgstr "Schutz von '{0}' ?ndern" +msgstr "Sende Testbenachrichtigung an '{0}'." #: proxmox-backup/www/tape/BackupOverview.js:345 msgid "Seq. Nr." @@ -9106,9 +9075,8 @@ msgid "Server Status" msgstr "Server-Status" #: proxmox-widget-toolkit/src/panel/GotifyEditPanel.js:21 -#, fuzzy msgid "Server URL" -msgstr "Server" +msgstr "Server URL" #: pve-manager/www/manager6/form/ViewSelector.js:22 msgid "Server View" @@ -10093,9 +10061,8 @@ msgid "TFA" msgstr "TFA" #: pmg-gui/js/UserView.js:197 proxmox-backup/www/config/UserView.js:234 -#, fuzzy msgid "TFA Lock" -msgstr "Lock" +msgstr "TFA-Lock" #: proxmox-widget-toolkit/src/panel/TfaView.js:229 msgid "TFA Type" @@ -10264,9 +10231,8 @@ msgid "Target Guest" msgstr "Ziel-Gast" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:128 -#, fuzzy msgid "Target Name" -msgstr "Ziel-Namespace" +msgstr "Ziel-Name" #: proxmox-backup/www/tape/window/TapeRestore.js:42 #: proxmox-backup/www/tape/window/TapeRestore.js:684 @@ -10504,9 +10470,8 @@ msgstr "Das ist kein g?ltiges CPU-Set" #: proxmox-widget-toolkit/src/Toolkit.js:132 #: proxmox-widget-toolkit/src/Toolkit.js:137 -#, fuzzy msgid "This is not a valid hostname" -msgstr "Kein g?ltiger DNS-Name" +msgstr "Kein g?ltiger Hostname" #: pve-manager/www/manager6/grid/BackupView.js:210 #: pve-manager/www/manager6/lxc/Resources.js:237 @@ -11213,9 +11178,8 @@ msgid "Use local time for RTC" msgstr "Verwende lokale Zeit f?r RTC" #: pve-manager/www/manager6/qemu/USBEdit.js:85 -#, fuzzy msgid "Use mapped Device" -msgstr "Mediated Devices" +msgstr "Verwende gemappte Devices" #: pve-manager/www/manager6/qemu/CDEdit.js:133 msgid "Use physical CD/DVD Drive" @@ -11375,7 +11339,6 @@ msgid "User statistic lifetime (days)" msgstr "Benutzer Statistik Lebensdauer (Tage)" #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:38 -#, fuzzy msgid "User(s)" msgstr "Benutzer" -- 2.39.2 From f.schauer at proxmox.com Mon Oct 9 15:25:19 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Mon, 9 Oct 2023 15:25:19 +0200 Subject: [pve-devel] [PATCH v3 qemu-server] Fix ACPI-suspended VMs resuming after migration Message-ID: <20231009132519.115727-1-f.schauer@proxmox.com> Add checks for "suspended" and "prelaunch" runstates when checking whether a VM is paused. This fixes the following issues: * ACPI-suspended VMs automatically resuming after migration * Shutdown and reboot commands timing out instead of failing immediately on suspended VMs Signed-off-by: Filip Schauer --- Changes since v2: * Make && and || operators consistent * Revert rename vm_is_paused to vm_is_frozen We could have renamed vm_is_paused to vcpus_are_paused, but I do not consider that necessary. PVE/QemuServer.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 1b1ccf4..2895675 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -8503,7 +8503,11 @@ sub vm_is_paused { mon_cmd($vmid, "query-status"); }; warn "$@\n" if $@; - return $qmpstatus && $qmpstatus->{status} eq "paused"; + return $qmpstatus && ( + $qmpstatus->{status} eq "paused" || + $qmpstatus->{status} eq "suspended" || + $qmpstatus->{status} eq "prelaunch" + ); } sub check_volume_storage_type { -- 2.39.2 From f.schauer at proxmox.com Mon Oct 9 15:27:41 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Mon, 9 Oct 2023 15:27:41 +0200 Subject: [pve-devel] [PATCH v2 qemu-server 0/2] Fix ACPI-suspended VMs resuming after migration In-Reply-To: <20230825121851.103307-1-f.schauer@proxmox.com> References: <20230825121851.103307-1-f.schauer@proxmox.com> Message-ID: <512b8980-9631-05a1-1a7e-ff18fa13e03e@proxmox.com> v3 patch available: https://lists.proxmox.com/pipermail/pve-devel/2023-October/059387.html On 25/08/2023 14:18, Filip Schauer wrote: > Shutdown and reboot commands do infact time out instead of failing > immediately on suspended VMs. This is fixed by extending vm_is_paused > with the "suspended" runstate. It also make sense to check for the > "prelaunch" runstate, as the VM is not running in that state. To > accommodate the changes, the function is renamed to vm_is_frozen. > > Filip Schauer (2): > Add missing checks for paused runstates > Rename vm_is_paused to vm_is_frozen > > PVE/API2/Qemu.pm | 8 ++++---- > PVE/QemuMigrate.pm | 8 ++++---- > PVE/QemuServer.pm | 8 ++++++-- > PVE/VZDump/QemuServer.pm | 14 +++++++------- > 4 files changed, 21 insertions(+), 17 deletions(-) > From alexandre.derumier at groupe-cyllene.com Mon Oct 9 15:47:53 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Mon, 9 Oct 2023 13:47:53 +0000 Subject: [pve-devel] [PATCH v4 qemu-server 2/2] remote-migration: add target-cpu && target-reboot params In-Reply-To: <5ecfa7d0-4525-5f1e-75a2-a6ae1a93356b@proxmox.com> References: <20230928144556.2023558-1-aderumier@odiso.com> <20230928144556.2023558-3-aderumier@odiso.com> <5ecfa7d0-4525-5f1e-75a2-a6ae1a93356b@proxmox.com> Message-ID: Hi Fiona, Thanks for the review. >>There can be other reasons to do a restart-migration, see also >>is?so I feel like this >>should be split. One for introducing target-reboot and one for >>introducing target-cpu. yes, sure it can be split in 2 patch, no problem. (v1 had only targetcpu param, but I have splitted it in 2 differents param, I just forgot to split in 2 patches >>For 'target-reboot', the question is if we should call it 'restart' >>like for container migration for consistency? Could also be >>introduced for normal migration while we're at it. Well, we have pct reboot and pct migrate --restart ^_^ We can use "restart", no problem, It make sense if we already using it in pct migrate. >>One could argue that a true 'restart' migration would migrate the >>volumes also offline, but right now, I don't see a big downside to do >>it >>via NBD like in this patch. Still, something we should think about. >>If >>it turns out to be really needed, we'd need two different ways to do >>a >>restart migration :/ I think that a true offline migration (without starting any source/target vm) can be done with qemu-storage-daemon running nbd server on target && qemu-img on source. This could be also used for online migration with unused/detached disks. I'll send a v5 next week, as I'm going to begin a 4 days proxmox training tomorrow. From l.wagner at proxmox.com Mon Oct 9 16:15:16 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 9 Oct 2023 16:15:16 +0200 Subject: [pve-devel] [PATCH proxmox-widget-toolkit] panel: sendmail edit: don't translate 'Proxmox VE' in author field Message-ID: <20231009141516.432903-1-l.wagner@proxmox.com> The default value is determined by the backend and is never translated (which does not make sense any way for a product name). This was likely just a copy/paste mistake from other from fields. Signed-off-by: Lukas Wagner --- src/panel/SendmailEditPanel.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panel/SendmailEditPanel.js b/src/panel/SendmailEditPanel.js index ace6129..253ee3e 100644 --- a/src/panel/SendmailEditPanel.js +++ b/src/panel/SendmailEditPanel.js @@ -111,7 +111,7 @@ Ext.define('Proxmox.panel.SendmailEditPanel', { fieldLabel: gettext('Author'), name: 'author', allowBlank: true, - emptyText: gettext('Proxmox VE'), + emptyText: 'Proxmox VE', cbind: { deleteEmpty: '{!isCreate}', }, -- 2.39.2 From l.wagner at proxmox.com Mon Oct 9 16:35:52 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 9 Oct 2023 16:35:52 +0200 Subject: [pve-devel] [PATCH v2 proxmox-i18n] update German translation In-Reply-To: <20231009132126.125795-1-h.duerr@proxmox.com> References: <20231009132126.125795-1-h.duerr@proxmox.com> Message-ID: <7df4df2e-3351-442c-ba5e-ae7ce22ba464@proxmox.com> Some comments inline. On 10/9/23 15:21, Hannes Duerr wrote: > Signed-off-by: Hannes Duerr > --- > de.po | 111 ++++++++++++++++++++-------------------------------------- > 1 file changed, 37 insertions(+), 74 deletions(-) > > diff --git a/de.po b/de.po > index fea74f1..4f6de7d 100644 > --- a/de.po > +++ b/de.po > @@ -747,9 +747,8 @@ msgid "Authentication mode" > msgstr "Authentifikationsmodus" > > #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:111 > -#, fuzzy > msgid "Author" > -msgstr "Auth-ID" > +msgstr "Autor" > > #: pmg-gui/js/TFAView.js:60 pve-manager/www/manager6/dc/OptionView.js:241 > #: proxmox-backup/www/config/WebauthnView.js:109 > @@ -1400,9 +1399,8 @@ msgstr "Testen" > > #: pve-manager/www/manager6/dc/AuthEditAD.js:93 > #: pve-manager/www/manager6/dc/AuthEditLDAP.js:93 > -#, fuzzy > msgid "Check connection" > -msgstr "Schutz ?ndern" > +msgstr "Teste Verbindung" > > #: pve-manager/www/manager6/window/DownloadUrlToStorage.js:188 > #: pve-manager/www/manager6/window/UploadToStorage.js:225 > @@ -2100,9 +2098,8 @@ msgid "Current Auth ID" > msgstr "Aktuelle Auth-ID" > > #: pve-manager/www/manager6/grid/PoolMembers.js:73 > -#, fuzzy > msgid "Current Pool" > -msgstr "Aktuelles Layout" > +msgstr "Aktueller Bestand" > > #: proxmox-backup/www/tape/window/TapeRestore.js:431 > msgid "Current User" > @@ -2674,11 +2671,10 @@ msgid "Disabled" > msgstr "Deaktiviert" > > #: pve-manager/www/manager6/dc/NotificationEvents.js:32 > -#, fuzzy > msgid "Disabling notifications is not recommended for production systems!" > msgstr "" > -"Das {0}no-subscription Repository ist nicht f?r die Verwendung in " > -"Produktivsystemen empfohlen!" > +"Das Deaktivieren von Benachrichtigungen in Produktivsystemen ist" > +"nicht empfohlen!" > > #: pve-manager/www/manager6/qemu/RNGEdit.js:90 > msgid "" > @@ -2789,9 +2785,8 @@ msgid "Do not use any media" > msgstr "Kein Medium verwenden" > > #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:91 > -#, fuzzy > msgid "Do you want to send a test notification to '{0}'?" > -msgstr "M?chten Sie Replikation Job {0} wirklich entfernen?" > +msgstr "M?chten Sie eine Testbenachrichtigung an '{0}' senden?" > > #: pmg-gui/js/MainView.js:187 pve-manager/www/manager6/Workspace.js:352 > #: proxmox-backup/www/MainView.js:226 > @@ -3287,9 +3282,8 @@ msgstr "Endzeit" > #: proxmox-widget-toolkit/src/panel/GotifyEditPanel.js:16 > #: proxmox-widget-toolkit/src/panel/NotificationGroupEditPanel.js:67 > #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:27 > -#, fuzzy > msgid "Endpoint Name" > -msgstr "Bind-Dom?nenname" > +msgstr "Endpointname" > > #: proxmox-widget-toolkit/src/Utils.js:70 > msgid "English" > @@ -3737,9 +3731,8 @@ msgstr "Filter" > > #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:265 > #: proxmox-widget-toolkit/src/window/NotificationFilterEdit.js:14 > -#, fuzzy > msgid "Filter Name" > -msgstr "Cluster-Name" > +msgstr "Filtername" > > #: proxmox-backup/www/form/GroupFilter.js:281 > msgid "Filter Type" > @@ -3747,7 +3740,7 @@ msgstr "Filtertyp" > > #: pve-manager/www/manager6/grid/BackupView.js:150 > msgid "Filter VMID" > -msgstr "Filtere VMID" > +msgstr "Filter-VMID" The old translation was correct, 'filter' is supposed to be a verb here. This text is shown as a description for a checkbox in the Backup tab for a VM/CT. > > #: proxmox-backup/www/form/GroupFilter.js:291 > msgid "Filter Value" > @@ -3966,9 +3959,8 @@ msgid "From" > msgstr "Von" > > #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:121 > -#, fuzzy > msgid "From Address" > -msgstr "Front Adresse" > +msgstr "Von Adresse" > > #: pve-manager/www/manager6/window/Restore.js:260 > msgid "From Backup" > @@ -4137,9 +4129,8 @@ msgid "Group Guest Types" > msgstr "Gruppiere Gast-Typ" > > #: proxmox-widget-toolkit/src/panel/NotificationGroupEditPanel.js:16 > -#, fuzzy > msgid "Group Name" > -msgstr "Gruppenmitglied" > +msgstr "Gruppenname" > > #: pve-manager/www/manager6/dc/ACLView.js:26 > #: pve-manager/www/manager6/dc/ACLView.js:199 > @@ -4481,7 +4472,6 @@ msgid "IOMMU Group" > msgstr "IOMMU-Gruppe" > > #: pve-manager/www/manager6/dc/PCIMapView.js:88 > -#, fuzzy > msgid "IOMMU-Group" > msgstr "IOMMU-Gruppe" > > @@ -5475,9 +5465,8 @@ msgid "Manufacturer" > msgstr "Hersteller" > > #: pve-manager/www/manager6/qemu/PCIEdit.js:193 > -#, fuzzy > msgid "Mapped Device" > -msgstr "Gemapptes Devices" > +msgstr "Gemapptes Device" > > #: pve-manager/www/manager6/form/PCIMapSelector.js:52 > #: pve-manager/www/manager6/form/USBMapSelector.js:37 > @@ -5646,9 +5635,8 @@ msgid "Memory usage" > msgstr "Speicherverbrauch" > > #: pve-manager/www/manager6/ceph/OSDDetails.js:151 > -#, fuzzy > msgid "Memory usage (PSS)" > -msgstr "Speicherverbrauch" > +msgstr "Speicherverbrauch (PSS)" > > #: proxmox-widget-toolkit/src/window/ZFSDetail.js:151 > #: pve-manager/www/manager6/dc/Log.js:79 > @@ -5730,9 +5718,8 @@ msgid "Min. Size" > msgstr "Min. Gr??e" > > #: proxmox-widget-toolkit/src/window/NotificationFilterEdit.js:20 > -#, fuzzy > msgid "Minimum Severity" > -msgstr "Min. Speicher" > +msgstr "Min. Schweregrad" > > #: proxmox-widget-toolkit/src/Toolkit.js:100 > #: proxmox-widget-toolkit/src/Toolkit.js:108 > @@ -6435,9 +6422,8 @@ msgid "No default available" > msgstr "Kein Standardwert verf?gbar" > > #: proxmox-widget-toolkit/src/panel/NotificationGroupEditPanel.js:170 > -#, fuzzy > msgid "No endpoint selected" > -msgstr "Keine Disk ausgew?hlt" > +msgstr "Kein Endpoint ausgew?hlt" > > #: pmg-gui/js/QuarantineList.js:265 > msgid "No match found" > @@ -6463,14 +6449,12 @@ msgid "No network information" > msgstr "Keine Information ?ber Netzwerk" > > #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:260 > -#, fuzzy > msgid "No notification filters configured" > -msgstr "Keine Domains konfiguriert" > +msgstr "Keine Benachrichtigungsfilter konfiguriert" > > #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:123 > -#, fuzzy > msgid "No notification targets configured" > -msgstr "Keine Datastores konfiguriert" > +msgstr "Keine Benachrichtigungsziele konfiguriert" > > #: pve-manager/www/manager6/sdn/zones/Base.js:45 > #: pve-manager/www/manager6/storage/Base.js:42 > @@ -6500,9 +6484,8 @@ msgid "No such service configured." > msgstr "Kein solcher Dienst eingerichtet." > > #: pve-manager/www/manager6/dc/BackupJobDetail.js:223 > -#, fuzzy > msgid "No target configured" > -msgstr "Noch nicht konfiguriert" > +msgstr "Kein Ziel konfiguriert" > > #: proxmox-widget-toolkit/src/node/APT.js:185 > msgid "No updates available." > @@ -6726,47 +6709,39 @@ msgid "Notification" > msgstr "Notifikation" > > #: proxmox-widget-toolkit/src/window/NotificationFilterEdit.js:92 > -#, fuzzy > msgid "Notification Filter" > -msgstr "Notifikation" > +msgstr "Benachrichtigungsfilter" > > #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:220 > -#, fuzzy > msgid "Notification Filters" > -msgstr "Notifikation" > +msgstr "Benachrichtigungsfilter" > > #: proxmox-widget-toolkit/src/Schema.js:52 > -#, fuzzy > msgid "Notification Group" > -msgstr "Notifikation" > +msgstr "Benachrichtigungsgruppe" > > #: pve-manager/www/manager6/dc/Backup.js:346 > #: pve-manager/www/manager6/dc/NotificationEvents.js:38 > -#, fuzzy > msgid "Notification Target" > -msgstr "Notifikation" > +msgstr "Benachrichtigungsziel" > > #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:90 > #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:103 > -#, fuzzy > msgid "Notification Target Test" > -msgstr "Notifikation" > +msgstr "Benachrichtigungszieltest" Urgh, German. Maybe hyphenate this? This looks a bit better imho: Benachrichtungsziel-Test > > #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:36 > #: pve-manager/www/manager6/dc/Config.js:338 > -#, fuzzy > msgid "Notification Targets" > -msgstr "Notifikation" > +msgstr "Benachrichtigungsziele" > > #: pve-manager/www/manager6/window/Backup.js:34 > -#, fuzzy > msgid "Notification target" > -msgstr "Notifikation" > +msgstr "Benachrichtigungsziel" > > #: pve-manager/www/manager6/dc/Config.js:324 > -#, fuzzy > msgid "Notifications" > -msgstr "Notifikation" > +msgstr "Benachrichtigungen" > > #: pmg-gui/js/PBSRemoteEdit.js:85 pve-manager/www/manager6/dc/Backup.js:329 > #: pve-manager/www/manager6/dc/NotificationEvents.js:158 > @@ -6789,15 +6764,13 @@ msgid "Notify always" > msgstr "Immer benachrichtigen" > > #: pve-manager/www/manager6/form/NotificationPolicySelector.js:7 > -#, fuzzy > msgid "Notify never" > -msgstr "Benutzer benachrichtigen" > +msgstr "Niemals benachrichtigen" > > #: pve-manager/www/manager6/dc/Backup.js:338 > #: pve-manager/www/manager6/window/Backup.js:47 > -#, fuzzy > msgid "Notify via" > -msgstr "Benachrichtigungen" > +msgstr "Benachrichtige via" > > #: pve-manager/www/manager6/form/IPProtocolSelector.js:16 > msgid "Number" > @@ -7683,9 +7656,8 @@ msgid "Proxmox Mail Gateway Login" > msgstr "Proxmox Mail Gateway Anmeldung" > > #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:114 > -#, fuzzy > msgid "Proxmox VE" > -msgstr "Proxmox VE Anmeldung" > +msgstr "Proxmox VE" > > #: pve-manager/www/manager6/window/LoginWindow.js:323 > msgid "Proxmox VE Login" > @@ -8736,9 +8708,8 @@ msgid "SSH public key" > msgstr "?ffentlicher SSH-Schl?ssel" > > #: pve-manager/www/manager6/lxc/CreateWizard.js:126 > -#, fuzzy > msgid "SSH public key(s)" > -msgstr "?ffentlicher SSH-Schl?ssel" > +msgstr "?ffentliche(r) SSH-Schl?ssel" > > #: pmg-gui/js/dashboard/NodeInfo.js:67 > #: pve-manager/www/manager6/node/StatusView.js:81 > @@ -9026,14 +8997,12 @@ msgid "Sender/Subject" > msgstr "Sender/Betreff" > > #: proxmox-widget-toolkit/src/Schema.js:42 > -#, fuzzy > msgid "Sendmail" > -msgstr "Sende E-Mail an" > +msgstr "Sende E-Mail" > > #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:105 > -#, fuzzy > msgid "Sent test notification to '{0}'." > -msgstr "Schutz von '{0}' ?ndern" > +msgstr "Sende Testbenachrichtigung an '{0}'." Should be past tense, as this is shown after a test notification has been sent. e.g. 'Testbenachrichtigung an ... versendet' > > #: proxmox-backup/www/tape/BackupOverview.js:345 > msgid "Seq. Nr." > @@ -9106,9 +9075,8 @@ msgid "Server Status" > msgstr "Server-Status" > > #: proxmox-widget-toolkit/src/panel/GotifyEditPanel.js:21 > -#, fuzzy > msgid "Server URL" > -msgstr "Server" > +msgstr "Server URL" > > #: pve-manager/www/manager6/form/ViewSelector.js:22 > msgid "Server View" > @@ -10093,9 +10061,8 @@ msgid "TFA" > msgstr "TFA" > > #: pmg-gui/js/UserView.js:197 proxmox-backup/www/config/UserView.js:234 > -#, fuzzy > msgid "TFA Lock" > -msgstr "Lock" > +msgstr "TFA-Lock" > > #: proxmox-widget-toolkit/src/panel/TfaView.js:229 > msgid "TFA Type" > @@ -10264,9 +10231,8 @@ msgid "Target Guest" > msgstr "Ziel-Gast" > > #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:128 > -#, fuzzy > msgid "Target Name" > -msgstr "Ziel-Namespace" > +msgstr "Ziel-Name" > > #: proxmox-backup/www/tape/window/TapeRestore.js:42 > #: proxmox-backup/www/tape/window/TapeRestore.js:684 > @@ -10504,9 +10470,8 @@ msgstr "Das ist kein g?ltiges CPU-Set" > > #: proxmox-widget-toolkit/src/Toolkit.js:132 > #: proxmox-widget-toolkit/src/Toolkit.js:137 > -#, fuzzy > msgid "This is not a valid hostname" > -msgstr "Kein g?ltiger DNS-Name" > +msgstr "Kein g?ltiger Hostname" > > #: pve-manager/www/manager6/grid/BackupView.js:210 > #: pve-manager/www/manager6/lxc/Resources.js:237 > @@ -11213,9 +11178,8 @@ msgid "Use local time for RTC" > msgstr "Verwende lokale Zeit f?r RTC" > > #: pve-manager/www/manager6/qemu/USBEdit.js:85 > -#, fuzzy > msgid "Use mapped Device" > -msgstr "Mediated Devices" > +msgstr "Verwende gemappte Devices" > > #: pve-manager/www/manager6/qemu/CDEdit.js:133 > msgid "Use physical CD/DVD Drive" > @@ -11375,7 +11339,6 @@ msgid "User statistic lifetime (days)" > msgstr "Benutzer Statistik Lebensdauer (Tage)" > > #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:38 > -#, fuzzy > msgid "User(s)" > msgstr "Benutzer" > -- - Lukas From l.wagner at proxmox.com Mon Oct 9 16:38:29 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 9 Oct 2023 16:38:29 +0200 Subject: [pve-devel] [PATCH proxmox-widget-toolkit] schema: endpoint types: don't translate endpoint type names Message-ID: <20231009143829.453881-1-l.wagner@proxmox.com> ... that are not really translatable. Signed-off-by: Lukas Wagner --- src/Schema.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Schema.js b/src/Schema.js index a7ffdf8..e0f583a 100644 --- a/src/Schema.js +++ b/src/Schema.js @@ -39,12 +39,12 @@ Ext.define('Proxmox.Schema', { // a singleton notificationEndpointTypes: { sendmail: { - name: gettext('Sendmail'), + name: 'Sendmail', ipanel: 'pmxSendmailEditPanel', iconCls: 'fa-envelope-o', }, gotify: { - name: gettext('Gotify'), + name: 'Gotify', ipanel: 'pmxGotifyEditPanel', iconCls: 'fa-bell-o', }, -- 2.39.2 From h.duerr at proxmox.com Tue Oct 10 09:42:59 2023 From: h.duerr at proxmox.com (Hannes Duerr) Date: Tue, 10 Oct 2023 09:42:59 +0200 Subject: [pve-devel] [PATCH v3 proxmox-i18n] update German translation Message-ID: <20231010074259.28301-1-h.duerr@proxmox.com> update German translation Signed-off-by: Hannes Duerr --- Hab die ?nderungen aufgenommen de.po | 121 +++++++++++++++++++--------------------------------------- 1 file changed, 40 insertions(+), 81 deletions(-) diff --git a/de.po b/de.po index fea74f1..e5202af 100644 --- a/de.po +++ b/de.po @@ -747,9 +747,8 @@ msgid "Authentication mode" msgstr "Authentifikationsmodus" #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:111 -#, fuzzy msgid "Author" -msgstr "Auth-ID" +msgstr "Autor" #: pmg-gui/js/TFAView.js:60 pve-manager/www/manager6/dc/OptionView.js:241 #: proxmox-backup/www/config/WebauthnView.js:109 @@ -1400,9 +1399,8 @@ msgstr "Testen" #: pve-manager/www/manager6/dc/AuthEditAD.js:93 #: pve-manager/www/manager6/dc/AuthEditLDAP.js:93 -#, fuzzy msgid "Check connection" -msgstr "Schutz ?ndern" +msgstr "Teste Verbindung" #: pve-manager/www/manager6/window/DownloadUrlToStorage.js:188 #: pve-manager/www/manager6/window/UploadToStorage.js:225 @@ -2100,9 +2098,8 @@ msgid "Current Auth ID" msgstr "Aktuelle Auth-ID" #: pve-manager/www/manager6/grid/PoolMembers.js:73 -#, fuzzy msgid "Current Pool" -msgstr "Aktuelles Layout" +msgstr "Aktueller Bestand" #: proxmox-backup/www/tape/window/TapeRestore.js:431 msgid "Current User" @@ -2674,11 +2671,10 @@ msgid "Disabled" msgstr "Deaktiviert" #: pve-manager/www/manager6/dc/NotificationEvents.js:32 -#, fuzzy msgid "Disabling notifications is not recommended for production systems!" msgstr "" -"Das {0}no-subscription Repository ist nicht f?r die Verwendung in " -"Produktivsystemen empfohlen!" +"Das Deaktivieren von Benachrichtigungen in Produktivsystemen ist" +"nicht empfohlen!" #: pve-manager/www/manager6/qemu/RNGEdit.js:90 msgid "" @@ -2789,9 +2785,8 @@ msgid "Do not use any media" msgstr "Kein Medium verwenden" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:91 -#, fuzzy msgid "Do you want to send a test notification to '{0}'?" -msgstr "M?chten Sie Replikation Job {0} wirklich entfernen?" +msgstr "M?chten Sie eine Testbenachrichtigung an '{0}' senden?" #: pmg-gui/js/MainView.js:187 pve-manager/www/manager6/Workspace.js:352 #: proxmox-backup/www/MainView.js:226 @@ -3287,9 +3282,8 @@ msgstr "Endzeit" #: proxmox-widget-toolkit/src/panel/GotifyEditPanel.js:16 #: proxmox-widget-toolkit/src/panel/NotificationGroupEditPanel.js:67 #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:27 -#, fuzzy msgid "Endpoint Name" -msgstr "Bind-Dom?nenname" +msgstr "Endpointname" #: proxmox-widget-toolkit/src/Utils.js:70 msgid "English" @@ -3737,9 +3731,8 @@ msgstr "Filter" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:265 #: proxmox-widget-toolkit/src/window/NotificationFilterEdit.js:14 -#, fuzzy msgid "Filter Name" -msgstr "Cluster-Name" +msgstr "Filtername" #: proxmox-backup/www/form/GroupFilter.js:281 msgid "Filter Type" @@ -3966,9 +3959,8 @@ msgid "From" msgstr "Von" #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:121 -#, fuzzy msgid "From Address" -msgstr "Front Adresse" +msgstr "Von Adresse" #: pve-manager/www/manager6/window/Restore.js:260 msgid "From Backup" @@ -4089,9 +4081,8 @@ msgid "Global flags limiting the self healing of Ceph are enabled." msgstr "Globale Flags schr?nken das Selbstheilen von Ceph ein." #: proxmox-widget-toolkit/src/Schema.js:47 -#, fuzzy msgid "Gotify" -msgstr "Benachrichtigungen" +msgstr "Gotify" #: proxmox-widget-toolkit/src/panel/PermissionView.js:144 #: pve-manager/www/manager6/dc/PermissionView.js:144 @@ -4137,9 +4128,8 @@ msgid "Group Guest Types" msgstr "Gruppiere Gast-Typ" #: proxmox-widget-toolkit/src/panel/NotificationGroupEditPanel.js:16 -#, fuzzy msgid "Group Name" -msgstr "Gruppenmitglied" +msgstr "Gruppenname" #: pve-manager/www/manager6/dc/ACLView.js:26 #: pve-manager/www/manager6/dc/ACLView.js:199 @@ -4481,7 +4471,6 @@ msgid "IOMMU Group" msgstr "IOMMU-Gruppe" #: pve-manager/www/manager6/dc/PCIMapView.js:88 -#, fuzzy msgid "IOMMU-Group" msgstr "IOMMU-Gruppe" @@ -5475,9 +5464,8 @@ msgid "Manufacturer" msgstr "Hersteller" #: pve-manager/www/manager6/qemu/PCIEdit.js:193 -#, fuzzy msgid "Mapped Device" -msgstr "Gemapptes Devices" +msgstr "Gemapptes Device" #: pve-manager/www/manager6/form/PCIMapSelector.js:52 #: pve-manager/www/manager6/form/USBMapSelector.js:37 @@ -5646,9 +5634,8 @@ msgid "Memory usage" msgstr "Speicherverbrauch" #: pve-manager/www/manager6/ceph/OSDDetails.js:151 -#, fuzzy msgid "Memory usage (PSS)" -msgstr "Speicherverbrauch" +msgstr "Speicherverbrauch (PSS)" #: proxmox-widget-toolkit/src/window/ZFSDetail.js:151 #: pve-manager/www/manager6/dc/Log.js:79 @@ -5730,9 +5717,8 @@ msgid "Min. Size" msgstr "Min. Gr??e" #: proxmox-widget-toolkit/src/window/NotificationFilterEdit.js:20 -#, fuzzy msgid "Minimum Severity" -msgstr "Min. Speicher" +msgstr "Min. Schweregrad" #: proxmox-widget-toolkit/src/Toolkit.js:100 #: proxmox-widget-toolkit/src/Toolkit.js:108 @@ -5791,9 +5777,8 @@ msgstr "Modifiziert" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:184 #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:307 -#, fuzzy msgid "Modify" -msgstr "Modifiziert" +msgstr "?ndern" #: proxmox-widget-toolkit/src/window/TfaEdit.js:10 msgid "Modify a TFA entry's description" @@ -6435,9 +6420,8 @@ msgid "No default available" msgstr "Kein Standardwert verf?gbar" #: proxmox-widget-toolkit/src/panel/NotificationGroupEditPanel.js:170 -#, fuzzy msgid "No endpoint selected" -msgstr "Keine Disk ausgew?hlt" +msgstr "Kein Endpoint ausgew?hlt" #: pmg-gui/js/QuarantineList.js:265 msgid "No match found" @@ -6463,14 +6447,12 @@ msgid "No network information" msgstr "Keine Information ?ber Netzwerk" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:260 -#, fuzzy msgid "No notification filters configured" -msgstr "Keine Domains konfiguriert" +msgstr "Keine Benachrichtigungsfilter konfiguriert" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:123 -#, fuzzy msgid "No notification targets configured" -msgstr "Keine Datastores konfiguriert" +msgstr "Keine Benachrichtigungsziele konfiguriert" #: pve-manager/www/manager6/sdn/zones/Base.js:45 #: pve-manager/www/manager6/storage/Base.js:42 @@ -6500,9 +6482,8 @@ msgid "No such service configured." msgstr "Kein solcher Dienst eingerichtet." #: pve-manager/www/manager6/dc/BackupJobDetail.js:223 -#, fuzzy msgid "No target configured" -msgstr "Noch nicht konfiguriert" +msgstr "Kein Ziel konfiguriert" #: proxmox-widget-toolkit/src/node/APT.js:185 msgid "No updates available." @@ -6577,9 +6558,8 @@ msgid "Node" msgstr "Knoten" #: pve-manager/www/manager6/dc/NotificationEvents.js:150 -#, fuzzy msgid "Node Fencing" -msgstr "Fencing" +msgstr "Node Fencing" #: pve-manager/www/manager6/dc/Backup.js:620 msgid "Node is offline" @@ -6726,47 +6706,39 @@ msgid "Notification" msgstr "Notifikation" #: proxmox-widget-toolkit/src/window/NotificationFilterEdit.js:92 -#, fuzzy msgid "Notification Filter" -msgstr "Notifikation" +msgstr "Benachrichtigungsfilter" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:220 -#, fuzzy msgid "Notification Filters" -msgstr "Notifikation" +msgstr "Benachrichtigungsfilter" #: proxmox-widget-toolkit/src/Schema.js:52 -#, fuzzy msgid "Notification Group" -msgstr "Notifikation" +msgstr "Benachrichtigungsgruppe" #: pve-manager/www/manager6/dc/Backup.js:346 #: pve-manager/www/manager6/dc/NotificationEvents.js:38 -#, fuzzy msgid "Notification Target" -msgstr "Notifikation" +msgstr "Benachrichtigungsziel" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:90 #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:103 -#, fuzzy msgid "Notification Target Test" -msgstr "Notifikation" +msgstr "Benachrichtigungsziel-Test" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:36 #: pve-manager/www/manager6/dc/Config.js:338 -#, fuzzy msgid "Notification Targets" -msgstr "Notifikation" +msgstr "Benachrichtigungsziele" #: pve-manager/www/manager6/window/Backup.js:34 -#, fuzzy msgid "Notification target" -msgstr "Notifikation" +msgstr "Benachrichtigungsziel" #: pve-manager/www/manager6/dc/Config.js:324 -#, fuzzy msgid "Notifications" -msgstr "Notifikation" +msgstr "Benachrichtigungen" #: pmg-gui/js/PBSRemoteEdit.js:85 pve-manager/www/manager6/dc/Backup.js:329 #: pve-manager/www/manager6/dc/NotificationEvents.js:158 @@ -6789,15 +6761,13 @@ msgid "Notify always" msgstr "Immer benachrichtigen" #: pve-manager/www/manager6/form/NotificationPolicySelector.js:7 -#, fuzzy msgid "Notify never" -msgstr "Benutzer benachrichtigen" +msgstr "Niemals benachrichtigen" #: pve-manager/www/manager6/dc/Backup.js:338 #: pve-manager/www/manager6/window/Backup.js:47 -#, fuzzy msgid "Notify via" -msgstr "Benachrichtigungen" +msgstr "Benachrichtige via" #: pve-manager/www/manager6/form/IPProtocolSelector.js:16 msgid "Number" @@ -7683,9 +7653,8 @@ msgid "Proxmox Mail Gateway Login" msgstr "Proxmox Mail Gateway Anmeldung" #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:114 -#, fuzzy msgid "Proxmox VE" -msgstr "Proxmox VE Anmeldung" +msgstr "Proxmox VE" #: pve-manager/www/manager6/window/LoginWindow.js:323 msgid "Proxmox VE Login" @@ -8736,9 +8705,8 @@ msgid "SSH public key" msgstr "?ffentlicher SSH-Schl?ssel" #: pve-manager/www/manager6/lxc/CreateWizard.js:126 -#, fuzzy msgid "SSH public key(s)" -msgstr "?ffentlicher SSH-Schl?ssel" +msgstr "?ffentliche(r) SSH-Schl?ssel" #: pmg-gui/js/dashboard/NodeInfo.js:67 #: pve-manager/www/manager6/node/StatusView.js:81 @@ -9026,14 +8994,12 @@ msgid "Sender/Subject" msgstr "Sender/Betreff" #: proxmox-widget-toolkit/src/Schema.js:42 -#, fuzzy msgid "Sendmail" -msgstr "Sende E-Mail an" +msgstr "Sende E-Mail" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:105 -#, fuzzy msgid "Sent test notification to '{0}'." -msgstr "Schutz von '{0}' ?ndern" +msgstr "Testbenachrichtigung an '{0}' versendet." #: proxmox-backup/www/tape/BackupOverview.js:345 msgid "Seq. Nr." @@ -9106,9 +9072,8 @@ msgid "Server Status" msgstr "Server-Status" #: proxmox-widget-toolkit/src/panel/GotifyEditPanel.js:21 -#, fuzzy msgid "Server URL" -msgstr "Server" +msgstr "Server URL" #: pve-manager/www/manager6/form/ViewSelector.js:22 msgid "Server View" @@ -10093,9 +10058,8 @@ msgid "TFA" msgstr "TFA" #: pmg-gui/js/UserView.js:197 proxmox-backup/www/config/UserView.js:234 -#, fuzzy msgid "TFA Lock" -msgstr "Lock" +msgstr "TFA-Lock" #: proxmox-widget-toolkit/src/panel/TfaView.js:229 msgid "TFA Type" @@ -10264,9 +10228,8 @@ msgid "Target Guest" msgstr "Ziel-Gast" #: proxmox-widget-toolkit/src/panel/NotificationConfigView.js:128 -#, fuzzy msgid "Target Name" -msgstr "Ziel-Namespace" +msgstr "Ziel-Name" #: proxmox-backup/www/tape/window/TapeRestore.js:42 #: proxmox-backup/www/tape/window/TapeRestore.js:684 @@ -10504,9 +10467,8 @@ msgstr "Das ist kein g?ltiges CPU-Set" #: proxmox-widget-toolkit/src/Toolkit.js:132 #: proxmox-widget-toolkit/src/Toolkit.js:137 -#, fuzzy msgid "This is not a valid hostname" -msgstr "Kein g?ltiger DNS-Name" +msgstr "Kein g?ltiger Hostname" #: pve-manager/www/manager6/grid/BackupView.js:210 #: pve-manager/www/manager6/lxc/Resources.js:237 @@ -11213,9 +11175,8 @@ msgid "Use local time for RTC" msgstr "Verwende lokale Zeit f?r RTC" #: pve-manager/www/manager6/qemu/USBEdit.js:85 -#, fuzzy msgid "Use mapped Device" -msgstr "Mediated Devices" +msgstr "Verwende gemappte Devices" #: pve-manager/www/manager6/qemu/CDEdit.js:133 msgid "Use physical CD/DVD Drive" @@ -11238,9 +11199,8 @@ msgid "Use watchdog based fencing." msgstr "Verwendung von Self-Fencing durch Watchdog" #: pve-manager/www/manager6/window/PCIMapEdit.js:236 -#, fuzzy msgid "Use with Mediated Devices" -msgstr "Mediated Devices" +msgstr "Mit Mediated Devices verwenden" #: pve-manager/www/manager6/form/AgentFeatureSelector.js:10 msgid "Use {0}" @@ -11375,7 +11335,6 @@ msgid "User statistic lifetime (days)" msgstr "Benutzer Statistik Lebensdauer (Tage)" #: proxmox-widget-toolkit/src/panel/SendmailEditPanel.js:38 -#, fuzzy msgid "User(s)" msgstr "Benutzer" -- 2.39.2 From f.ebner at proxmox.com Tue Oct 10 10:57:03 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 10 Oct 2023 10:57:03 +0200 Subject: [pve-devel] [PATCH qemu-server] qmeventd: also treat 'prelaunch' and 'suspended' states as active Message-ID: <20231010085703.27139-1-f.ebner@proxmox.com> Otherwise, a VM in those states would be terminated after a backup in handle_qmp_return() with QMP 'quit', which is pretty bad in case of the 'suspended' state. Does not change the fact that a VM started in prelaunch mode for backup is terminated later (that is handled by the Perl code). Signed-off-by: Fiona Ebner --- qmeventd/qmeventd.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qmeventd/qmeventd.c b/qmeventd/qmeventd.c index 002b2ac4..d8f3ee72 100644 --- a/qmeventd/qmeventd.c +++ b/qmeventd/qmeventd.c @@ -272,8 +272,12 @@ handle_qmp_return(struct Client *client, struct json_object *data, bool error) bool active = false; if (has_status) { const char *status_str = json_object_get_string(status); - active = status_str && - (!strcmp(status_str, "running") || !strcmp(status_str, "paused")); + active = status_str && ( + !strcmp(status_str, "running") + || !strcmp(status_str, "paused") + || !strcmp(status_str, "suspended") + || !strcmp(status_str, "prelaunch") + ); } switch (client->state) { -- 2.39.2 From f.ebner at proxmox.com Tue Oct 10 10:58:28 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 10 Oct 2023 10:58:28 +0200 Subject: [pve-devel] applied: [PATCH v3 qemu-server] Fix ACPI-suspended VMs resuming after migration In-Reply-To: <20231009132519.115727-1-f.schauer@proxmox.com> References: <20231009132519.115727-1-f.schauer@proxmox.com> Message-ID: <71c228c4-7622-67f3-184b-bd8a8bd8369b@proxmox.com> Am 09.10.23 um 15:25 schrieb Filip Schauer: > Add checks for "suspended" and "prelaunch" runstates when checking > whether a VM is paused. > > This fixes the following issues: > * ACPI-suspended VMs automatically resuming after migration > * Shutdown and reboot commands timing out instead of failing > immediately on suspended VMs > > Signed-off-by: Filip Schauer > --- applied, thanks! It was not enough to fix the issue with backup however, because qmeventd didn't play nice, so I sent a follow-up: https://lists.proxmox.com/pipermail/pve-devel/2023-October/059394.html From l.wagner at proxmox.com Tue Oct 10 11:07:12 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Tue, 10 Oct 2023 11:07:12 +0200 Subject: [pve-devel] [PATCH v3 proxmox-i18n] update German translation In-Reply-To: <20231010074259.28301-1-h.duerr@proxmox.com> References: <20231010074259.28301-1-h.duerr@proxmox.com> Message-ID: <57a309c1-6e96-4ba4-b2b5-bae4067d0935@proxmox.com> Hi again, On 10/10/23 09:42, Hannes Duerr wrote: > update German translation > > Signed-off-by: Hannes Duerr > --- > > Hab die ?nderungen aufgenommen not a blocker for this patch (so no need to send another version, as this won't be visible in the commit message), but usually you'd write something like this here: Changes v2 -> v3: - changed ... - added ... Changes v1 -> v2: - changed ... - removed ... The format is not really standardized/important, as long the information is there ;) It helps your colleagues when reviewing your (updated) patches. > > de.po | 121 +++++++++++++++++++--------------------------------------- > 1 file changed, 40 insertions(+), 81 deletions(-) > --- SNIP 8< --- -- - Lukas From f.ebner at proxmox.com Tue Oct 10 11:19:46 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 10 Oct 2023 11:19:46 +0200 Subject: [pve-devel] [PATCH v4 qemu-server 2/2] remote-migration: add target-cpu && target-reboot params In-Reply-To: References: <20230928144556.2023558-1-aderumier@odiso.com> <20230928144556.2023558-3-aderumier@odiso.com> <5ecfa7d0-4525-5f1e-75a2-a6ae1a93356b@proxmox.com> Message-ID: <73e0a3a6-f978-ac24-5f6b-16af759ee209@proxmox.com> Am 09.10.23 um 15:47 schrieb DERUMIER, Alexandre: >>> One could argue that a true 'restart' migration would migrate the >>> volumes also offline, but right now, I don't see a big downside to do >>> it >>> via NBD like in this patch. Still, something we should think about. >>> If >>> it turns out to be really needed, we'd need two different ways to do >>> a >>> restart migration :/ > > I think that a true offline migration (without starting any > source/target vm) can be done with qemu-storage-daemon running nbd > server on target && qemu-img on source. > > This could be also used for online migration with unused/detached > disks. > Yes, we could, but would require additional logic. So the question is if there are enough advantages to do it rather than via a full VM. Starting a VM of course requires more resources. In case of 'restart' migration, we do want to start the VM anyways, so it's actually better, because we can catch config issues early :) Now that I think about it, can we also just start the target VM in prelaunch mode (instead of incoming migration mode), do the NBD migration, shut down the source VM, stop the NBD server and then resume the target? That would avoid the need to stop and start the target again. And therefore might be quite a bit less downtime. In case of offline migration, it can make sense to go via the storage daemon instead to avoid using more resources than necessary. But can always be added later. From p.hufnagl at proxmox.com Tue Oct 10 12:15:15 2023 From: p.hufnagl at proxmox.com (Philipp Hufnagl) Date: Tue, 10 Oct 2023 12:15:15 +0200 Subject: [pve-devel] [PATCH manager v1 1/1] fix #4546: ui: notify user if their user account expires soon In-Reply-To: <118ba2e9-8c9e-4029-8738-9f806f49f73b@proxmox.com> References: <20230922143658.1639173-1-p.hufnagl@proxmox.com> <20230922143658.1639173-2-p.hufnagl@proxmox.com> <118ba2e9-8c9e-4029-8738-9f806f49f73b@proxmox.com> Message-ID: <36a57641-bba9-4c19-aa5e-a4e1b568ba46@proxmox.com> On 10/6/23 15:16, Lukas Wagner wrote: > Comments inline. > > On 9/22/23 16:36, Philipp Hufnagl wrote: >> When the user account that is currently logged in will expire soon, the >> user icon will turn into a yellow exclamation mark. In the user menu >> there will be a new element informing the user briefly about it. If the > ????????????????????????????????????????????????????????? ^ > Well, the warning is permanent, so I wouldn't call it 'briefly' ;) >> element is clicked, a popup will appear informing the user in detail >> about it I meant briefly as in "in brief words" :) From p.hufnagl at proxmox.com Tue Oct 10 12:18:02 2023 From: p.hufnagl at proxmox.com (Philipp Hufnagl) Date: Tue, 10 Oct 2023 12:18:02 +0200 Subject: [pve-devel] [PATCH proxmox-widget-toolkit v1 1/2] fix #4546: css: Inform user administrator about user accounts expiring soon In-Reply-To: References: <20230922143658.1639173-1-p.hufnagl@proxmox.com> <20230922143658.1639173-4-p.hufnagl@proxmox.com> Message-ID: <342ebaab-de7b-4aa5-8993-a95f1a04c23e@proxmox.com> > for the light theme you could simply use the `.warning` class instead > then you can simply overwrite that with a more specific selector in the > dark theme again, like we already do in a couple of places. Thank you. I tried using .warning. Unfortunately the result was yellow on white background, with very poor readability. Therefore I decided to use the warning highlighting for columns in both themes. From p.hufnagl at proxmox.com Tue Oct 10 12:40:32 2023 From: p.hufnagl at proxmox.com (Philipp Hufnagl) Date: Tue, 10 Oct 2023 12:40:32 +0200 Subject: [pve-devel] [PATCH proxmox-widget-toolkit v2 3/3] fix #4546: utils: Expand authentication data with account expiry date In-Reply-To: <20231010104034.932760-1-p.hufnagl@proxmox.com> References: <20231010104034.932760-1-p.hufnagl@proxmox.com> Message-ID: <20231010104034.932760-4-p.hufnagl@proxmox.com> When the authentication data of a session are set, the account expiry date will be set also (provided there is one). Signed-off-by: Philipp Hufnagl --- src/Utils.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Utils.js b/src/Utils.js index f5769a0..654c907 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -309,6 +309,9 @@ utilities: { setAuthData: function(data) { Proxmox.UserName = data.username; + if (data['account-expiry-date']) { + Proxmox.AccountExpiryDate = data['account-expiry-date']; + } Proxmox.LoggedOut = data.LoggedOut; // creates a session cookie (expire = null) // that way the cookie gets deleted after the browser window is closed -- 2.39.2 From p.hufnagl at proxmox.com Tue Oct 10 12:40:29 2023 From: p.hufnagl at proxmox.com (Philipp Hufnagl) Date: Tue, 10 Oct 2023 12:40:29 +0200 Subject: [pve-devel] [PATCH proxmox-widget-toolkit/manager/access-control v2 0/5] fix #4546: Show warning hint/badge if user account is expiring in next few days Message-ID: <20231010104034.932760-1-p.hufnagl@proxmox.com> Currently, when an user account expires, it catches the users by surprise. It would be helpful to notify the the user as well as the administrator. This patch highlights such accounts in the user account pannel and also shows for regular user an exclamation mark in the user info pannel. It also adds in this case a new entry briefly saing that the account expires. When this entry is clicked it states this in more detail including the experation date. Sending this for PVE only now to get feedback before implementing this for PMG and PBS I hope I managed to implement all the feedback. If I have forgotten something please tell _menu Changes since v1: * Rename variables according to feedback * Reuse existing css colors according to feedback * Rework API parameters according to feedback * Minor code changes Philipp Hufnagl (3): fix #4546: css: create a new css class for warning texts fix #4546: utils: Highlight accounts in user management that exprie soon fix #4546: utils: Expand authentication data with account expiry date src/Utils.js | 9 ++++++++- .../scss/abstracts/_variables.scss | 20 ++++++++++--------- src/proxmox-dark/scss/extjs/_menu.scss | 4 ++++ src/proxmox-dark/scss/other/_charts.scss | 4 ++-- 4 files changed, 25 insertions(+), 12 deletions(-) Philipp Hufnagl (1): fix #4546: ui: notify user if there usser account expires soon www/manager6/Workspace.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) Philipp Hufnagl (1): fix #4546: api: Return user expiration date on access/ticket API call src/PVE/API2/AccessControl.pm | 8 ++++++++ src/PVE/AccessControl.pm | 8 ++++++++ 2 files changed, 16 insertions(+) -- 2.39.2 From p.hufnagl at proxmox.com Tue Oct 10 12:40:30 2023 From: p.hufnagl at proxmox.com (Philipp Hufnagl) Date: Tue, 10 Oct 2023 12:40:30 +0200 Subject: [pve-devel] [PATCH proxmox-widget-toolkit v2 1/3] fix #4546: css: create a new css class for warning texts In-Reply-To: <20231010104034.932760-1-p.hufnagl@proxmox.com> References: <20231010104034.932760-1-p.hufnagl@proxmox.com> Message-ID: <20231010104034.932760-2-p.hufnagl@proxmox.com> Creates a new css class 'text-color-warning' for the dark theme, which will be used to propperly show a warning icon later. For this purpose the 'pwt-gauge-warn' color is extracted in a variable and reused. Also the variable 'pwd-gauge-crit' has been extracted for consistency. Signed-off-by: Philipp Hufnagl --- .../scss/abstracts/_variables.scss | 20 ++++++++++--------- src/proxmox-dark/scss/extjs/_menu.scss | 4 ++++ src/proxmox-dark/scss/other/_charts.scss | 4 ++-- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/proxmox-dark/scss/abstracts/_variables.scss b/src/proxmox-dark/scss/abstracts/_variables.scss index cac51eb..cf7cda4 100644 --- a/src/proxmox-dark/scss/abstracts/_variables.scss +++ b/src/proxmox-dark/scss/abstracts/_variables.scss @@ -8,9 +8,20 @@ $highlighted-text: hsl(205deg, 100%, 65%); $highlighted-text-alt: hsl(205deg, 100%, 80%); $highlighted-text-crit: hsl(360deg, 100%, 65%); +// Backgrounds +$content-background-color: hsl(0deg, 0%, 20%); +$content-background-selected: hsl(0deg, 0%, 30%); +$background-dark: hsl(0deg, 0%, 20%); +$background-darker: hsl(0deg, 0%, 15%); +$background-darkest: hsl(0deg, 0%, 10%); +$background-invalid: hsl(360deg, 60%, 20%); +$background-warning: hsl(40deg, 100%, 20%); + // Icon and Text colors $text-color: hsl(0deg, 0%, 95%); $text-color-inactive: hsl(0deg, 0%, 60%); +$text-color-warning: adjust-color($background-warning, $lightness: lightness($primary-color)); +$text-color-invalid: adjust-color($background-invalid, $lightness: lightness($primary-color)); $icon-color: hsl(0deg, 0%, 90%); $icon-color-alt: hsl(0deg, 0%, 55%); @@ -18,15 +29,6 @@ $icon-color-alt: hsl(0deg, 0%, 55%); $border-color: hsl(0deg, 0%, 40%); $border-color-alt: hsl(0deg, 0%, 25%); -// Backgrounds -$content-background-color: hsl(0deg, 0%, 20%); -$content-background-selected: hsl(0deg, 0%, 30%); -$background-dark: hsl(0deg, 0%, 20%); -$background-darker: hsl(0deg, 0%, 15%); -$background-darkest: hsl(0deg, 0%, 10%); -$background-invalid: hsl(360deg, 60%, 20%); -$background-warning: hsl(40deg, 100%, 20%); - // Buttons $neutral-button-color: hsl(0deg, 0%, 25%); $neutral-button-color-alt: hsl(0deg, 0%, 35%); diff --git a/src/proxmox-dark/scss/extjs/_menu.scss b/src/proxmox-dark/scss/extjs/_menu.scss index 2983f60..aa51260 100644 --- a/src/proxmox-dark/scss/extjs/_menu.scss +++ b/src/proxmox-dark/scss/extjs/_menu.scss @@ -33,6 +33,10 @@ color: $icon-color; } +.x-menu-item-icon-default.warning { + color: $text-color-warning; +} + // Vertical divider (e.g. in UserInfo between icons and text) .x-menu-icon-separator-default { background-color: $background-dark; diff --git a/src/proxmox-dark/scss/other/_charts.scss b/src/proxmox-dark/scss/other/_charts.scss index 26b0104..a3a5b12 100644 --- a/src/proxmox-dark/scss/other/_charts.scss +++ b/src/proxmox-dark/scss/other/_charts.scss @@ -7,8 +7,8 @@ --pwt-text-color: #{$text-color}; --pwt-gauge-default: #{$primary-color}; --pwt-gauge-back: #{$background-dark}; - --pwt-gauge-warn: #{adjust-color($background-warning, $lightness: lightness($primary-color))}; - --pwt-gauge-crit: #{adjust-color($background-invalid, $lightness: lightness($primary-color))}; + --pwt-gauge-warn: #{$text-color-warning}; + --pwt-gauge-crit: #{$text-color-invalid}; --pwt-chart-primary: #{$primary-color}; --pwt-chart-grid-stroke: #{$content-background-selected}; } -- 2.39.2 From p.hufnagl at proxmox.com Tue Oct 10 12:40:33 2023 From: p.hufnagl at proxmox.com (Philipp Hufnagl) Date: Tue, 10 Oct 2023 12:40:33 +0200 Subject: [pve-devel] [PATCH proxmox-widget-toolkit v2 1/1] fix #4546: ui: notify user if there usser account expires soon In-Reply-To: <20231010104034.932760-1-p.hufnagl@proxmox.com> References: <20231010104034.932760-1-p.hufnagl@proxmox.com> Message-ID: <20231010104034.932760-5-p.hufnagl@proxmox.com> When the user account that is currently logged in will expire soon, the user icon will turn into a yellow exclamation mark. In the user menu there will be a new element informing the user briefly about it. If the element is clicked, a popup will appear informing the user in detail about it Signed-off-by: Philipp Hufnagl --- www/manager6/Workspace.js | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/www/manager6/Workspace.js b/www/manager6/Workspace.js index 18d574b7..41d44a00 100644 --- a/www/manager6/Workspace.js +++ b/www/manager6/Workspace.js @@ -42,6 +42,7 @@ Ext.define('PVE.Workspace', { Proxmox.Utils.authClear(); Ext.state.Manager.clear('GuiCap'); Proxmox.UserName = null; + Proxmox.AccountExpiryDate = null; me.loginData = null; if (!me.login) { @@ -198,6 +199,18 @@ Ext.define('PVE.StdWorkspace', { let me = this; let ui = me.query('#userinfo')[0]; ui.setText(Ext.String.htmlEncode(Proxmox.UserName || '')); + let label = me.query('#expirewarning')[0]; + if (Proxmox.AccountExpiryDate !== null) { + let expiryWarningThreshold = Ext.Date.add(new Date(), Ext.Date.DAY, 7); + let expireDate = new Date(Proxmox.AccountExpiryDate * 1000); + if (expiryWarningThreshold >= expireDate) { + ui.setIconCls('fa fa-exclamation-triangle warning'); + label.setHidden(false); + } + } else { + label.setHidden(true); + ui.setIconCls('fa fa-user'); + } ui.updateLayout(); }, @@ -367,6 +380,21 @@ Ext.define('PVE.StdWorkspace', { }, iconCls: 'fa fa-user', menu: [ + { + iconCls: 'fa fa-exclamation-triangle warning', + itemId: 'expirewarning', + text: gettext('Account expiring soon!'), + hidden: true, + handler: function() { + let expireDate = new Date(Proxmox.AccountExpiryDate * 1000); + Ext.Msg.show({ + title: gettext('Account expiring soon!'), + icon: Ext.Msg.WARNING, + message: Ext.String.format(gettext("Your account is expiring on {0} . After that you won't be able to log in!"), expireDate), + buttons: Ext.Msg.OK, + }); + }, + }, { iconCls: 'fa fa-gear', text: gettext('My Settings'), -- 2.39.2 From p.hufnagl at proxmox.com Tue Oct 10 12:40:34 2023 From: p.hufnagl at proxmox.com (Philipp Hufnagl) Date: Tue, 10 Oct 2023 12:40:34 +0200 Subject: [pve-devel] [PATCH access-control v2 1/1] fix #4546: api: Return user expiration date on access/ticket API call In-Reply-To: <20231010104034.932760-1-p.hufnagl@proxmox.com> References: <20231010104034.932760-1-p.hufnagl@proxmox.com> Message-ID: <20231010104034.932760-6-p.hufnagl@proxmox.com> Adds an additional, optional parameter to the access/tickets api call which tells when the currently used user account will expire. If it will not expire, the parameter will not be added. Signed-off-by: Philipp Hufnagl --- src/PVE/API2/AccessControl.pm | 8 ++++++++ src/PVE/AccessControl.pm | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/src/PVE/API2/AccessControl.pm b/src/PVE/API2/AccessControl.pm index 74b3910..caba720 100644 --- a/src/PVE/API2/AccessControl.pm +++ b/src/PVE/API2/AccessControl.pm @@ -267,6 +267,11 @@ __PACKAGE__->register_method ({ ticket => { type => 'string', optional => 1}, CSRFPreventionToken => { type => 'string', optional => 1 }, clustername => { type => 'string', optional => 1 }, + 'account-expiry-date' => { + type => 'number', + description => "Account expiration date as a UNIX timestamp", + optional => 1 , + }, # cap => computed api permissions, unless there's a u2f challenge } }, @@ -304,6 +309,9 @@ __PACKAGE__->register_method ({ die PVE::Exception->new("authentication failure\n", code => 401); } + my $expires = PVE::AccessControl::lookup_user_expiration($username); + $res->{'account-expiry-date'} = $expires if defined($expires); + $res->{cap} = $rpcenv->compute_api_permission($username) if !defined($res->{NeedTFA}); diff --git a/src/PVE/AccessControl.pm b/src/PVE/AccessControl.pm index cc0f00b..b30570b 100644 --- a/src/PVE/AccessControl.pm +++ b/src/PVE/AccessControl.pm @@ -1234,6 +1234,14 @@ sub lookup_username { return $username; } +sub lookup_user_expiration { + my ($username) = @_; + my $usercfg = cfs_read_file('user.cfg'); + my $expires = $usercfg->{users}->{$username}->{expire}; + return undef if $expires == 0; + return $expires; +} + sub normalize_path { my $path = shift; -- 2.39.2 From p.hufnagl at proxmox.com Tue Oct 10 12:40:31 2023 From: p.hufnagl at proxmox.com (Philipp Hufnagl) Date: Tue, 10 Oct 2023 12:40:31 +0200 Subject: [pve-devel] [PATCH proxmox-widget-toolkit v2 2/3] fix #4546: utils: Highlight accounts in user management that exprie soon In-Reply-To: <20231010104034.932760-1-p.hufnagl@proxmox.com> References: <20231010104034.932760-1-p.hufnagl@proxmox.com> Message-ID: <20231010104034.932760-3-p.hufnagl@proxmox.com> When a user account is about to expire in the next 7 days, the date column will be highlighted with warning color. Signed-off-by: Philipp Hufnagl --- src/Utils.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Utils.js b/src/Utils.js index f269607..f5769a0 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -174,10 +174,14 @@ utilities: { return value ? Proxmox.Utils.enabledText : Proxmox.Utils.disabledText; }, - format_expire: function(date) { + format_expire: function(date, meta) { if (!date) { return Proxmox.Utils.neverText; } + let expiryWarningThreshold = Ext.Date.add(new Date(), Ext.Date.DAY, 7); + if (expiryWarningThreshold >= date) { + meta.tdCls += 'proxmox-warning-row'; + } return Ext.Date.format(date, "Y-m-d"); }, -- 2.39.2 From t.lamprecht at proxmox.com Tue Oct 10 12:46:56 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 10 Oct 2023 12:46:56 +0200 Subject: [pve-devel] applied: [PATCH v3 proxmox-i18n] update German translation In-Reply-To: <20231010074259.28301-1-h.duerr@proxmox.com> References: <20231010074259.28301-1-h.duerr@proxmox.com> Message-ID: <0c1e1154-9464-4bf3-9b54-8e5dd2f166e6@proxmox.com> Am 10/10/2023 um 09:42 schrieb Hannes Duerr: > update German translation I drop that part on applying, as having the subject and commit message the same thing is not really adding anything. > > Signed-off-by: Hannes Duerr > --- > > Hab die ?nderungen aufgenommen > > de.po | 121 +++++++++++++++++++--------------------------------------- > 1 file changed, 40 insertions(+), 81 deletions(-) > > applied, thanks! From c.heiss at proxmox.com Tue Oct 10 13:33:45 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 10 Oct 2023 13:33:45 +0200 Subject: [pve-devel] [PATCH installer 1/1] fix #4869: Show state in management interface ComboBox In-Reply-To: <20230804102646.34999-2-f.schauer@proxmox.com> References: <20230804102646.34999-1-f.schauer@proxmox.com> <20230804102646.34999-2-f.schauer@proxmox.com> Message-ID: Needs to be done for the TUI installer as well. Easiest way here is to add `state` to each interface in Proxmox::Sys::RunEnv::query_netdevs(), then deserialize that in `setup::Interface`. Looking at it, adding the MAC address there too would be great, if you are already at it. On Fri, Aug 04, 2023 at 12:26:46PM +0200, Filip Schauer wrote: > > Signed-off-by: Filip Schauer > --- > proxinstall | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/proxinstall b/proxinstall > index d5b2565..9316578 100755 > --- a/proxinstall > +++ b/proxinstall > @@ -347,7 +347,7 @@ sub create_ipconf_view { > > my $get_device_desc = sub { > my $iface = shift; > - return "$iface->{name} - $iface->{mac} ($iface->{driver})"; > + return "$iface->{name} - $iface->{mac} ($iface->{driver}) - $iface->{state}"; I think it would be better to only show the state if the interface is actually UP - thus drawing immediate attention to these list entries and not completely overloading that dropdown. Maybe even append it as "(UP)" instead of by dash (as it does not really identify the interface, but rather is a property of it). > }; > > my $run_env = Proxmox::Install::RunEnv::get(); > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > From t.lamprecht at proxmox.com Tue Oct 10 13:45:44 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 10 Oct 2023 13:45:44 +0200 Subject: [pve-devel] [PATCH v3 qemu-server] Fix ACPI-suspended VMs resuming after migration In-Reply-To: <20231009132519.115727-1-f.schauer@proxmox.com> References: <20231009132519.115727-1-f.schauer@proxmox.com> Message-ID: <01c649c9-0717-404c-98d8-17dc8a5cc4eb@proxmox.com> Am 09/10/2023 um 15:25 schrieb Filip Schauer: > Add checks for "suspended" and "prelaunch" runstates when checking > whether a VM is paused. > > This fixes the following issues: > * ACPI-suspended VMs automatically resuming after migration > * Shutdown and reboot commands timing out instead of failing > immediately on suspended VMs > I checked the call-sites and what I'm wondering is, can the VM from those new states get waked up without QMP intervention, say a ACPI-suspension be triggered by some (virtual) RTC or via network (like wake-on-lan), as then, we should add a big notice comment on this method to ensure new users of it are informed about that possibility. Also, with that change we might have added a race for suspend-mode backups, at least if VMs really can wake up without a QMP command (which I find likely). I.e., between the time we checked and set vm_was_paused until we actually suspend, because if the VM would wake up in between we might get inconsistent stuff and skip things like fs-freeze. While we recommend stop and snapshot modes over suspend mode, the latter is still exposed via UI and API and should work OK enough. Note that ACPI S3 suspend and our vm_suspend are very different things, our vm_suspend is doing a "stop" monitor command, resulting in all vCPUs being stopped, while S3 is a (virtual) firmware/machine feature ? I mean, I guess there's a reason that those things are reported as different states.. It doesn't help that our vm_suspend method doesn't actually do a (ACPI S3 like) suspension, but a (vCPU) "stop". The "prelaunch" state, OTOH., seems pretty much like "paused", with the difference that the VM vCPUs never ran in the former, this seems fine to handle the same. But for "suspended" I'm not sure if we'd like to detect that as paused only if conflating both is OK for a call-site, so maybe with an opt-in parameter. Alternatively we could return the status in the "true" case so that call-sites can decide what to do for any such special handling without an extra parameter. From t.lamprecht at proxmox.com Tue Oct 10 13:55:43 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 10 Oct 2023 13:55:43 +0200 Subject: [pve-devel] [PATCH installer 1/1] fix #4869: Show state in management interface ComboBox In-Reply-To: References: <20230804102646.34999-1-f.schauer@proxmox.com> <20230804102646.34999-2-f.schauer@proxmox.com> Message-ID: <2730c950-b0d7-45dc-af83-80ff808da9b7@proxmox.com> Am 10/10/2023 um 13:33 schrieb Christoph Heiss: > On Fri, Aug 04, 2023 at 12:26:46PM +0200, Filip Schauer wrote: >> @@ -347,7 +347,7 @@ sub create_ipconf_view { >> >> my $get_device_desc = sub { >> my $iface = shift; >> - return "$iface->{name} - $iface->{mac} ($iface->{driver})"; >> + return "$iface->{name} - $iface->{mac} ($iface->{driver}) - $iface->{state}"; > > I think it would be better to only show the state if the interface is > actually UP - thus drawing immediate attention to these list entries and > not completely overloading that dropdown. > > Maybe even append it as "(UP)" instead of by dash (as it does not really > identify the interface, but rather is a property of it). > Or, if the GTK component and available fonts support it we could also use unicode UP -> ? https://unicode-explorer.com/c/1F7E2 DOWN -> ? (preferred, easier to differ for vision impaired people) or ? https://unicode-explorer.com/c/25EF or https://unicode-explorer.com/c/2B24 Would use less space and probably look better, if it works that is ^^ From f.ebner at proxmox.com Tue Oct 10 14:10:19 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 10 Oct 2023 14:10:19 +0200 Subject: [pve-devel] [PATCH manager] ui: acl add: show warning if root@pam is selected In-Reply-To: <20230726134145.700213-1-l.wagner@proxmox.com> References: <20230726134145.700213-1-l.wagner@proxmox.com> Message-ID: <59c6e639-1bcd-250d-f53f-3fc55b7f51b6@proxmox.com> Am 26.07.23 um 15:41 schrieb Lukas Wagner: > Currently, users are able to add ACL entries for the root at pam user. > Since this user always has full permissions, no entry in the ACL > tree will be saved, and consequently no new entry shows up in the UI > after pressing 'Add' in the dialog. This can be irritating if the > user does not know about this 'implementation detail'. > Should we filter out the root at pam user from the selection dropdown altogether? Or maybe disable the Add button when root at pam is selected (and reword the warning appropriately)? > This commit adds a little warning that pops up if root at pam is > selected: > > 'root at pam always has full permissions. No entry will be added.' > > The same problem also exists for API token permissions. Here it is > not really easy to add the warning though, since we do not know if > the token has separated privileges enable or not. > It seems we do have that information available as a result of the /access/users?full=1 API call, or? > Signed-off-by: Lukas Wagner > --- > www/manager6/dc/ACLView.js | 14 ++++++++++++++ > 1 file changed, 14 insertions(+) > > diff --git a/www/manager6/dc/ACLView.js b/www/manager6/dc/ACLView.js > index 79f900cd..ec81a487 100644 > --- a/www/manager6/dc/ACLView.js > +++ b/www/manager6/dc/ACLView.js > @@ -35,6 +35,20 @@ Ext.define('PVE.dc.ACLAdd', { > xtype: 'pmxUserSelector', > name: 'users', > fieldLabel: gettext('User'), > + listeners: { > + change: function(field, newVal) { > + this.nextSibling('displayfield[reference=root-selected-warning]') > + .setVisible(newVal === 'root at pam'); > + } eslint complains about a missing trailing comma here > + }, > + }); > + items.push({ > + xtype: 'displayfield', > + reference: 'root-selected-warning', > + userCls: 'pmx-hint', > + hidden: true, > + value: '\'root at pam\' ' + > + gettext('always has full permissions. No entry will be added.'), > }); > } else if (me.aclType === 'token') { > me.subject = gettext("API Token Permission"); From l.wagner at proxmox.com Tue Oct 10 14:40:30 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Tue, 10 Oct 2023 14:40:30 +0200 Subject: [pve-devel] [PATCH manager] ui: acl add: show warning if root@pam is selected In-Reply-To: <59c6e639-1bcd-250d-f53f-3fc55b7f51b6@proxmox.com> References: <20230726134145.700213-1-l.wagner@proxmox.com> <59c6e639-1bcd-250d-f53f-3fc55b7f51b6@proxmox.com> Message-ID: On 10/10/23 14:10, Fiona Ebner wrote: > Am 26.07.23 um 15:41 schrieb Lukas Wagner: >> Currently, users are able to add ACL entries for the root at pam user. >> Since this user always has full permissions, no entry in the ACL >> tree will be saved, and consequently no new entry shows up in the UI >> after pressing 'Add' in the dialog. This can be irritating if the >> user does not know about this 'implementation detail'. >> > > Should we filter out the root at pam user from the selection dropdown > altogether? Or maybe disable the Add button when root at pam is selected > (and reword the warning appropriately)? I think the second approach might be good idea, I'll try that. > >> This commit adds a little warning that pops up if root at pam is >> selected: >> >> 'root at pam always has full permissions. No entry will be added.' >> >> The same problem also exists for API token permissions. Here it is >> not really easy to add the warning though, since we do not know if >> the token has separated privileges enable or not. >> > > It seems we do have that information available as a result of the > /access/users?full=1 API call, or? You are right, I missed that because I did not check the code for pmxUserSelector. I'll send a v2 with the suggested improvements. -- - Lukas From f.ebner at proxmox.com Tue Oct 10 14:54:33 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 10 Oct 2023 14:54:33 +0200 Subject: [pve-devel] [PATCH storage 1/1] iscsi: allow to configure multiple portals In-Reply-To: <20230816115627.59681-2-ykonotopov@gnome.org> References: <20230816115627.59681-1-ykonotopov@gnome.org> <20230816115627.59681-2-ykonotopov@gnome.org> Message-ID: <46997106-a66c-cc16-15b8-a72d2fe7dac5@proxmox.com> Hi, thank you for the contribution! Please prepend the commit title with "fix #254: ". Am 16.08.23 um 13:56 schrieb ykonotopov at gnome.org: > From: Yuri Konotopov > > Fixes: https://bugzilla.proxmox.com/show_bug.cgi?id=254 To accept contributions, we need your Signed-off-by trailer here and you need to agree to the Harmony CLA (assuming you haven't sent it to us already): https://pve.proxmox.com/wiki/Developer_Documentation#Software_License_and_Copyright > --- > PVE/Storage/ISCSIPlugin.pm | 44 +++++++++++++++++++++++--------------- > PVE/Storage/Plugin.pm | 18 ++++++++++++++++ > 2 files changed, 45 insertions(+), 17 deletions(-) > > diff --git a/PVE/Storage/ISCSIPlugin.pm b/PVE/Storage/ISCSIPlugin.pm > index a79fcb0..e056393 100644 > --- a/PVE/Storage/ISCSIPlugin.pm > +++ b/PVE/Storage/ISCSIPlugin.pm > @@ -69,24 +69,30 @@ sub iscsi_test_portal { > } > > sub iscsi_discovery { > - my ($portal) = @_; > + my @portals = @{$_[0]}; Style nit: Usually we'd still write this as my ($portals) = @_; and then use $portals->@* whenever we need to access the array below. Like that it's nicer to extend if a new parameter is added. > > check_iscsi_support (); > > my $res = {}; > - return $res if !iscsi_test_portal($portal); # fixme: raise exception here? > + foreach my $portal (@portals) > + { Style nit: We use 'for' instead of 'foreach' for new code and the opening bracket should be on the same line > + next if !iscsi_test_portal($portal); # fixme: raise exception here? > > - my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; > - run_command($cmd, outfunc => sub { > - my $line = shift; > + my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; > + run_command($cmd, outfunc => sub { > + my $line = shift; > > - if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { > - my $portal = $1; > - my $target = $2; > - # one target can have more than one portal (multipath). > - push @{$res->{$target}}, $portal; > - } > - }); > + if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { > + my $portal = $1; > + my $target = $2; > + # one target can have more than one portal (multipath). > + push @{$res->{$target}}, $portal; > + } > + }); > + > + # In case of multipath we want to exit on any portal available> + last; > + } > > return $res; > } > @@ -96,7 +102,7 @@ sub iscsi_login { > > check_iscsi_support(); > > - eval { iscsi_discovery($portal_in); }; > + eval { iscsi_discovery(PVE::Storage::Plugin::get_portals($portal_in)); }; > warn $@ if $@; > > run_command([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login']); > @@ -245,8 +251,8 @@ sub properties { > type => 'string', > }, > portal => { > - description => "iSCSI portal (IP or DNS name with optional port).", > - type => 'string', format => 'pve-storage-portal-dns', > + description => "iSCSI portal (IP or DNS name with optional port). Multiple portals can be separated by comma (for multipath).", Since separation for lists is (mostly) consistent in our API, you could also just write: "For multipath, multiple portals can be specified." > + type => 'string', format => 'pve-storage-portals-dns', Please note that for lists, you can just use the existing format and append "-list" in our schemas, i.e. using 'pve-storage-portal-dns-list' should give you what you want already and you don't need to register a new format. > }, > }; > } > @@ -403,8 +409,12 @@ sub deactivate_storage { > sub check_connection { > my ($class, $storeid, $scfg) = @_; > > - my $portal = $scfg->{portal}; > - return iscsi_test_portal($portal); > + foreach my $portal (@{PVE::Storage::Plugin::get_portals($scfg->{portal})}) { > + my $result = iscsi_test_portal($portal); > + return $result if $result; > + } > + > + return 0; > } > > sub volume_resize { > diff --git a/PVE/Storage/Plugin.pm b/PVE/Storage/Plugin.pm > index c323085..ee69b14 100644 > --- a/PVE/Storage/Plugin.pm > +++ b/PVE/Storage/Plugin.pm > @@ -205,6 +205,13 @@ sub content_hash_to_string { > return join(',', @cta); > } > > +sub get_portals { > + my $portal_cfg = shift; > + my $portals = [split(',', $portal_cfg)]; > + map { s/^\s+|\s+$//g; } @{$portals}; You can use the PVE::Tools::split_list() helper instead of this one. > + return $portals; > +} > + > sub valid_content_types { > my ($type) = @_; > > @@ -301,6 +308,17 @@ sub verify_portal_dns { > return $portal; > } > > +PVE::JSONSchema::register_format('pve-storage-portals-dns', \&verify_portals_dns); > +sub verify_portals_dns { > + my ($portal_in, $noerr) = @_; > + > + foreach my $portal (@{get_portals($portal_in)}) { > + verify_portal_dns($portal, $noerr); > + } > + > + return $portal_in; > +} > + > PVE::JSONSchema::register_format('pve-storage-content', \&verify_content); > sub verify_content { > my ($ct, $noerr) = @_; Best Regards, Fiona From c.heiss at proxmox.com Tue Oct 10 14:56:28 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 10 Oct 2023 14:56:28 +0200 Subject: [pve-devel] [PATCH installer 1/1] fix #4869: Show state in management interface ComboBox In-Reply-To: <2730c950-b0d7-45dc-af83-80ff808da9b7@proxmox.com> References: <20230804102646.34999-1-f.schauer@proxmox.com> <20230804102646.34999-2-f.schauer@proxmox.com> <2730c950-b0d7-45dc-af83-80ff808da9b7@proxmox.com> Message-ID: On Tue, Oct 10, 2023 at 01:55:43PM +0200, Thomas Lamprecht wrote: > > Am 10/10/2023 um 13:33 schrieb Christoph Heiss: > > On Fri, Aug 04, 2023 at 12:26:46PM +0200, Filip Schauer wrote: > >> @@ -347,7 +347,7 @@ sub create_ipconf_view { > >> > >> my $get_device_desc = sub { > >> my $iface = shift; > >> - return "$iface->{name} - $iface->{mac} ($iface->{driver})"; > >> + return "$iface->{name} - $iface->{mac} ($iface->{driver}) - $iface->{state}"; > > > > I think it would be better to only show the state if the interface is > > actually UP - thus drawing immediate attention to these list entries and > > not completely overloading that dropdown. > > > > Maybe even append it as "(UP)" instead of by dash (as it does not really > > identify the interface, but rather is a property of it). > > > > Or, if the GTK component and available fonts support it we could also use > unicode I like the idea! > > UP -> ? https://unicode-explorer.com/c/1F7E2 A green circle would be problematic/non-optimal for people deuteranopia (red-green color blindness). So maybe some other character/symbol distinctly recognizable as "being connected, up" - but purely color-coding things is not something that should be done IMO. > DOWN -> ? (preferred, easier to differ for vision impaired people) or ? > https://unicode-explorer.com/c/25EF or https://unicode-explorer.com/c/2B24 Do we really need to explicitly mark non-UP interfaces? I'd think the fact of it being not marked UP should provide enough context in this situation. > > Would use less space and probably look better, if it works that is ^^ From f.ebner at proxmox.com Tue Oct 10 15:06:37 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 10 Oct 2023 15:06:37 +0200 Subject: [pve-devel] [PATCH manager v2] ui: DirEdit: LVMEdit: add hint when to enable shared In-Reply-To: <20230821100651.1167084-1-a.lauterer@proxmox.com> References: <20230821100651.1167084-1-a.lauterer@proxmox.com> Message-ID: Am 21.08.23 um 12:06 schrieb Aaron Lauterer: > Signed-off-by: Aaron Lauterer > --- > www/manager6/storage/DirEdit.js | 4 ++++ > www/manager6/storage/LVMEdit.js | 4 ++++ > 2 files changed, 8 insertions(+) > > diff --git a/www/manager6/storage/DirEdit.js b/www/manager6/storage/DirEdit.js > index 7e9ec44d..8469a7c3 100644 > --- a/www/manager6/storage/DirEdit.js > +++ b/www/manager6/storage/DirEdit.js > @@ -30,6 +30,10 @@ Ext.define('PVE.storage.DirInputPanel', { > name: 'shared', > uncheckedValue: 0, > fieldLabel: gettext('Shared'), > + autoEl: { > + tag: 'div', > + 'data-qtip': gettext('Enable if the underlying file system is already shared between nodes.'), > + } eslint complains about missing trailing comma > }, > ]; > > diff --git a/www/manager6/storage/LVMEdit.js b/www/manager6/storage/LVMEdit.js > index 75c7bdb8..0d9efd21 100644 > --- a/www/manager6/storage/LVMEdit.js > +++ b/www/manager6/storage/LVMEdit.js > @@ -227,6 +227,10 @@ Ext.define('PVE.storage.LVMInputPanel', { > name: 'shared', > uncheckedValue: 0, > fieldLabel: gettext('Shared'), > + autoEl: { > + tag: 'div', > + 'data-qtip': gettext('Enable if the LVM is located on a shared LUN.'), > + } eslint complains about missing trailing comma > }, > ], > }); With those fixed: Reviewed-by: Fiona Ebner From t.lamprecht at proxmox.com Tue Oct 10 15:13:23 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 10 Oct 2023 15:13:23 +0200 Subject: [pve-devel] [PATCH installer 1/1] fix #4869: Show state in management interface ComboBox In-Reply-To: References: <20230804102646.34999-1-f.schauer@proxmox.com> <20230804102646.34999-2-f.schauer@proxmox.com> <2730c950-b0d7-45dc-af83-80ff808da9b7@proxmox.com> Message-ID: <4e4c00c7-5412-4bec-9952-8497f609b71d@proxmox.com> Am 10/10/2023 um 14:56 schrieb Christoph Heiss: > On Tue, Oct 10, 2023 at 01:55:43PM +0200, Thomas Lamprecht wrote: >> UP -> ? https://unicode-explorer.com/c/1F7E2 > A green circle would be problematic/non-optimal for people deuteranopia > (red-green color blindness). So maybe some other character/symbol > distinctly recognizable as "being connected, up" - but purely > color-coding things is not something that should be done IMO. but those already get filled/not-filled as hint, i.e., this isn't just purely color coding, that's why I explicitly used a *filled* green circle for UP and for DOWN an *empty* one as preferred one due to vision impairments like color blindness, like hinted ;-) But sure, if you have a better idea for this just say so. IMO the proposed one should be clear enough, as not only color (useful for the majority of people) but also form is distinct, and I saw such circles being used to indicate on/off or plugged/unplugged already in a few UIs (such things are not easy to search for, so sorry, no concrete example), but that doesn't mean there might be something better that is still practical for both TUI and GUI. > >> DOWN -> ? (preferred, easier to differ for vision impaired people) or ? >> https://unicode-explorer.com/c/25EF or https://unicode-explorer.com/c/2B24 > Do we really need to explicitly mark non-UP interfaces? I'd think the > fact of it being not marked UP should provide enough context in this > situation. Yes, to give clear distinction to the UP state and make them more distinct to each other, also to avoid text-alignment (visual symmetry) problems (if the symbol prefixes the iface name, which I guesstimate will look better). From a.lauterer at proxmox.com Tue Oct 10 15:19:28 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Tue, 10 Oct 2023 15:19:28 +0200 Subject: [pve-devel] [PATCH manager v2] ui: DirEdit: LVMEdit: add hint when to enable shared In-Reply-To: References: <20230821100651.1167084-1-a.lauterer@proxmox.com> Message-ID: <2b5d69b7-24e5-409d-8781-30ca2914c800@proxmox.com> On 10/10/23 15:06, Fiona Ebner wrote: > Am 21.08.23 um 12:06 schrieb Aaron Lauterer: >> Signed-off-by: Aaron Lauterer >> --- >> www/manager6/storage/DirEdit.js | 4 ++++ >> www/manager6/storage/LVMEdit.js | 4 ++++ >> 2 files changed, 8 insertions(+) >> >> diff --git a/www/manager6/storage/DirEdit.js b/www/manager6/storage/DirEdit.js >> index 7e9ec44d..8469a7c3 100644 >> --- a/www/manager6/storage/DirEdit.js >> +++ b/www/manager6/storage/DirEdit.js >> @@ -30,6 +30,10 @@ Ext.define('PVE.storage.DirInputPanel', { >> name: 'shared', >> uncheckedValue: 0, >> fieldLabel: gettext('Shared'), >> + autoEl: { >> + tag: 'div', >> + 'data-qtip': gettext('Enable if the underlying file system is already shared between nodes.'), >> + } > > eslint complains about missing trailing comma hmm, when did that happen... now it does it too for me. Will send another version with it fixed. > >> }, >> ]; >> >> diff --git a/www/manager6/storage/LVMEdit.js b/www/manager6/storage/LVMEdit.js >> index 75c7bdb8..0d9efd21 100644 >> --- a/www/manager6/storage/LVMEdit.js >> +++ b/www/manager6/storage/LVMEdit.js >> @@ -227,6 +227,10 @@ Ext.define('PVE.storage.LVMInputPanel', { >> name: 'shared', >> uncheckedValue: 0, >> fieldLabel: gettext('Shared'), >> + autoEl: { >> + tag: 'div', >> + 'data-qtip': gettext('Enable if the LVM is located on a shared LUN.'), >> + } > > eslint complains about missing trailing comma > >> }, >> ], >> }); > > With those fixed: > Reviewed-by: Fiona Ebner From a.lauterer at proxmox.com Tue Oct 10 15:24:52 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Tue, 10 Oct 2023 15:24:52 +0200 Subject: [pve-devel] [PATCH manager v3] ui: DirEdit: LVMEdit: add hint when to enable shared Message-ID: <20231010132452.3340635-1-a.lauterer@proxmox.com> Signed-off-by: Aaron Lauterer Reviewed-by: Fiona Ebner --- changes since v2: fixed missing trailing comma www/manager6/storage/DirEdit.js | 4 ++++ www/manager6/storage/LVMEdit.js | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/www/manager6/storage/DirEdit.js b/www/manager6/storage/DirEdit.js index 7e9ec44d..3e2025fc 100644 --- a/www/manager6/storage/DirEdit.js +++ b/www/manager6/storage/DirEdit.js @@ -30,6 +30,10 @@ Ext.define('PVE.storage.DirInputPanel', { name: 'shared', uncheckedValue: 0, fieldLabel: gettext('Shared'), + autoEl: { + tag: 'div', + 'data-qtip': gettext('Enable if the underlying file system is already shared between nodes.'), + }, }, ]; diff --git a/www/manager6/storage/LVMEdit.js b/www/manager6/storage/LVMEdit.js index 75c7bdb8..75af6bf7 100644 --- a/www/manager6/storage/LVMEdit.js +++ b/www/manager6/storage/LVMEdit.js @@ -227,6 +227,10 @@ Ext.define('PVE.storage.LVMInputPanel', { name: 'shared', uncheckedValue: 0, fieldLabel: gettext('Shared'), + autoEl: { + tag: 'div', + 'data-qtip': gettext('Enable if the LVM is located on a shared LUN.'), + }, }, ], }); -- 2.39.2 From s.lendl at proxmox.com Tue Oct 10 15:30:52 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Tue, 10 Oct 2023 15:30:52 +0200 Subject: [pve-devel] [PATCH pve-manager 1/2] Refer to SDN subnets as 'Subnet' not as ID Message-ID: <20231010133053.1297165-1-s.lendl@proxmox.com> The Subnet's CIDR in the Edit view is called 'Subnet'. Also refer to it as Subnet in the list view. Signed-off-by: Stefan Lendl --- www/manager6/sdn/SubnetView.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/manager6/sdn/SubnetView.js b/www/manager6/sdn/SubnetView.js index 4a8b0754..851583d1 100644 --- a/www/manager6/sdn/SubnetView.js +++ b/www/manager6/sdn/SubnetView.js @@ -101,7 +101,7 @@ Ext.define('PVE.sdn.SubnetView', { ], columns: [ { - header: 'ID', + header: gettext('Subnet'), flex: 2, dataIndex: 'cidr', renderer: function(value, metaData, rec) { -- 2.41.0 From s.lendl at proxmox.com Tue Oct 10 15:30:53 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Tue, 10 Oct 2023 15:30:53 +0200 Subject: [pve-devel] [PATCH pve-manager 2/2] Homogenize UI naming In-Reply-To: <20231010133053.1297165-1-s.lendl@proxmox.com> References: <20231010133053.1297165-1-s.lendl@proxmox.com> Message-ID: <20231010133053.1297165-2-s.lendl@proxmox.com> use title case or upper case appreviations everywhere Signed-off-by: Stefan Lendl --- www/manager6/sdn/OptionsPanel.js | 2 +- www/manager6/sdn/SubnetEdit.js | 2 +- www/manager6/sdn/SubnetView.js | 2 +- www/manager6/sdn/dns/PowerdnsEdit.js | 6 +++--- www/manager6/sdn/ipams/NetboxEdit.js | 2 +- www/manager6/sdn/ipams/PhpIpamEdit.js | 2 +- www/manager6/sdn/zones/Base.js | 8 ++++---- www/manager6/sdn/zones/EvpnEdit.js | 10 +++++----- www/manager6/sdn/zones/QinQEdit.js | 2 +- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/www/manager6/sdn/OptionsPanel.js b/www/manager6/sdn/OptionsPanel.js index 2cc2cff6..58cb0772 100644 --- a/www/manager6/sdn/OptionsPanel.js +++ b/www/manager6/sdn/OptionsPanel.js @@ -21,7 +21,7 @@ Ext.define('PVE.sdn.Options', { }, { xtype: 'pveSDNIpamView', - title: 'IPAMs', + title: 'IPAM', flex: 1, padding: '0 0 20 0', border: 0, diff --git a/www/manager6/sdn/SubnetEdit.js b/www/manager6/sdn/SubnetEdit.js index 154de8ef..b9825d2a 100644 --- a/www/manager6/sdn/SubnetEdit.js +++ b/www/manager6/sdn/SubnetEdit.js @@ -50,7 +50,7 @@ Ext.define('PVE.sdn.SubnetInputPanel', { xtype: 'proxmoxtextfield', name: 'dnszoneprefix', skipEmptyText: true, - fieldLabel: gettext('DNS zone prefix'), + fieldLabel: gettext('DNS Zone Prefix'), allowBlank: true, }, ], diff --git a/www/manager6/sdn/SubnetView.js b/www/manager6/sdn/SubnetView.js index 851583d1..d342f0ba 100644 --- a/www/manager6/sdn/SubnetView.js +++ b/www/manager6/sdn/SubnetView.js @@ -125,7 +125,7 @@ Ext.define('PVE.sdn.SubnetView', { }, }, { - header: gettext('Dns prefix'), + header: gettext('DNS Prefix'), flex: 1, dataIndex: 'dnszoneprefix', renderer: function(value, metaData, rec) { diff --git a/www/manager6/sdn/dns/PowerdnsEdit.js b/www/manager6/sdn/dns/PowerdnsEdit.js index f35b89af..8d5d8872 100644 --- a/www/manager6/sdn/dns/PowerdnsEdit.js +++ b/www/manager6/sdn/dns/PowerdnsEdit.js @@ -30,19 +30,19 @@ Ext.define('PVE.sdn.dns.PowerdnsInputPanel', { { xtype: 'textfield', name: 'url', - fieldLabel: 'url', + fieldLabel: 'URL', allowBlank: false, }, { xtype: 'textfield', name: 'key', - fieldLabel: gettext('api key'), + fieldLabel: gettext('API Key'), allowBlank: false, }, { xtype: 'proxmoxintegerfield', name: 'ttl', - fieldLabel: 'ttl', + fieldLabel: 'TTL', allowBlank: true, }, ]; diff --git a/www/manager6/sdn/ipams/NetboxEdit.js b/www/manager6/sdn/ipams/NetboxEdit.js index 2133b48c..b85043fe 100644 --- a/www/manager6/sdn/ipams/NetboxEdit.js +++ b/www/manager6/sdn/ipams/NetboxEdit.js @@ -30,7 +30,7 @@ Ext.define('PVE.sdn.ipams.NetboxInputPanel', { { xtype: 'textfield', name: 'url', - fieldLabel: gettext('Url'), + fieldLabel: gettext('URL'), allowBlank: false, }, { diff --git a/www/manager6/sdn/ipams/PhpIpamEdit.js b/www/manager6/sdn/ipams/PhpIpamEdit.js index 8726e0c8..a4848578 100644 --- a/www/manager6/sdn/ipams/PhpIpamEdit.js +++ b/www/manager6/sdn/ipams/PhpIpamEdit.js @@ -30,7 +30,7 @@ Ext.define('PVE.sdn.ipams.PhpIpamInputPanel', { { xtype: 'textfield', name: 'url', - fieldLabel: gettext('Url'), + fieldLabel: gettext('URL'), allowBlank: false, }, { diff --git a/www/manager6/sdn/zones/Base.js b/www/manager6/sdn/zones/Base.js index 655352a8..602e4c16 100644 --- a/www/manager6/sdn/zones/Base.js +++ b/www/manager6/sdn/zones/Base.js @@ -48,7 +48,7 @@ Ext.define('PVE.panel.SDNZoneBase', { }, { xtype: 'pveSDNIpamSelector', - fieldLabel: gettext('Ipam'), + fieldLabel: gettext('IPAM'), name: 'ipam', value: me.ipam || 'pve', allowBlank: false, @@ -58,14 +58,14 @@ Ext.define('PVE.panel.SDNZoneBase', { me.advancedItems = [ { xtype: 'pveSDNDnsSelector', - fieldLabel: gettext('Dns server'), + fieldLabel: gettext('DNS Server'), name: 'dns', value: '', allowBlank: true, }, { xtype: 'pveSDNDnsSelector', - fieldLabel: gettext('Reverse Dns server'), + fieldLabel: gettext('Reverse DNS Server'), name: 'reversedns', value: '', allowBlank: true, @@ -74,7 +74,7 @@ Ext.define('PVE.panel.SDNZoneBase', { xtype: 'proxmoxtextfield', name: 'dnszone', skipEmptyText: true, - fieldLabel: gettext('DNS zone'), + fieldLabel: gettext('DNS Zone'), allowBlank: true, }, ]; diff --git a/www/manager6/sdn/zones/EvpnEdit.js b/www/manager6/sdn/zones/EvpnEdit.js index 1d13976c..cac1ef4d 100644 --- a/www/manager6/sdn/zones/EvpnEdit.js +++ b/www/manager6/sdn/zones/EvpnEdit.js @@ -57,7 +57,7 @@ Ext.define('PVE.sdn.zones.EvpnInputPanel', { { xtype: 'textfield', name: 'mac', - fieldLabel: gettext('Vnet MAC address'), + fieldLabel: gettext('VNet MAC Address'), vtype: 'MacAddress', allowBlank: true, emptyText: 'auto', @@ -81,26 +81,26 @@ Ext.define('PVE.sdn.zones.EvpnInputPanel', { name: 'exitnodes-local-routing', uncheckedValue: 0, checked: false, - fieldLabel: gettext('Exit Nodes local routing'), + fieldLabel: gettext('Exit Nodes Local Routing'), }, { xtype: 'proxmoxcheckbox', name: 'advertise-subnets', uncheckedValue: 0, checked: false, - fieldLabel: gettext('Advertise subnets'), + fieldLabel: gettext('Advertise Subnets'), }, { xtype: 'proxmoxcheckbox', name: 'disable-arp-nd-suppression', uncheckedValue: 0, checked: false, - fieldLabel: gettext('Disable arp-nd suppression'), + fieldLabel: gettext('Disable ARP-nd Suppression'), }, { xtype: 'textfield', name: 'rt-import', - fieldLabel: gettext('Route-target import'), + fieldLabel: gettext('Route Target Import'), allowBlank: true, }, ]; diff --git a/www/manager6/sdn/zones/QinQEdit.js b/www/manager6/sdn/zones/QinQEdit.js index c059a7a2..795ff9df 100644 --- a/www/manager6/sdn/zones/QinQEdit.js +++ b/www/manager6/sdn/zones/QinQEdit.js @@ -36,7 +36,7 @@ Ext.define('PVE.sdn.zones.QinQInputPanel', { { xtype: 'proxmoxKVComboBox', name: 'vlan-protocol', - fieldLabel: gettext('Service-VLAN Protocol'), + fieldLabel: gettext('Service VLAN Protocol'), allowBlank: true, value: '802.1q', comboItems: [ -- 2.41.0 From f.ebner at proxmox.com Tue Oct 10 16:19:55 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 10 Oct 2023 16:19:55 +0200 Subject: [pve-devel] [PATCH v3 qemu-server] Fix ACPI-suspended VMs resuming after migration In-Reply-To: <01c649c9-0717-404c-98d8-17dc8a5cc4eb@proxmox.com> References: <20231009132519.115727-1-f.schauer@proxmox.com> <01c649c9-0717-404c-98d8-17dc8a5cc4eb@proxmox.com> Message-ID: Am 10.10.23 um 13:45 schrieb Thomas Lamprecht: > Am 09/10/2023 um 15:25 schrieb Filip Schauer: >> Add checks for "suspended" and "prelaunch" runstates when checking >> whether a VM is paused. >> >> This fixes the following issues: >> * ACPI-suspended VMs automatically resuming after migration >> * Shutdown and reboot commands timing out instead of failing >> immediately on suspended VMs >> > > I checked the call-sites and what I'm wondering is, can the VM from those > new states get waked up without QMP intervention, say a ACPI-suspension > be triggered by some (virtual) RTC or via network (like wake-on-lan), > as then, we should add a big notice comment on this method to ensure > new users of it are informed about that possibility. Sorry! I did not consider this. Yes, it can get woken up without QMP. Even just with keyboard presses in case of my Debian test VM. So our config locks are useless here :/ It'll also be an issue for migration: 1. migration is started and remembers that "vm_was_paused" 2. migration continues 3. during migration VM wakes up 4. migration finishes, but doesn't resume guest on the target, because "vm_was_paused" We could either: 1. tolerate the old behavior (VM might get resumed on target if it was suspended) - if we declare migration an ACPI-wake-up event ;) 2. tolerate the new behavior (VM might wake up and not be resumed on target later) - seems worse then old behavior when it happens 3. disallow migration when in 'suspended' runstate 4. add new check just before moving guest to target - but not sure how we'd do that without races again... > Also, with that change we might have added a race for suspend-mode backups, > at least if VMs really can wake up without a QMP command (which I find likely). > I.e., between the time we checked and set vm_was_paused until we actually > suspend, because if the VM would wake up in between we might get inconsistent > stuff and skip things like fs-freeze. That race is already there without the patch. QEMU does not transition from 'suspended' state to 'paused' state when QMP 'stop' is issued, i.e. what our 'qm suspend' or vm_suspend() actually does. So it doesn't matter if we call that during backup or not when the VM is already in 'suspended' state. The window for the race is a bit larger though: now: VM wakes up between check if paused and 'backup' QMP before: VM wakes up after fsfreeze was skipped because guest agent was detected as not running We could either: 1. (ironically) disallow 'suspend' mode backup when in 'suspended' runstate. 2. resume and pause the VM upon 'suspend' mode backup, but is also surprising 3. make the race window smaller again by doing the qga_check_running() and fs-freeze if true, if VM was in 'suspended' runstate. I don't see how to avoid the possibility of a wake-up during an inconvenient time except with one of the first two options. > While we recommend stop and snapshot modes over suspend mode, the latter is > still exposed via UI and API and should work OK enough. > > Note that ACPI S3 suspend and our vm_suspend are very different things, our > vm_suspend is doing a "stop" monitor command, resulting in all vCPUs being > stopped, while S3 is a (virtual) firmware/machine feature ? I mean, I guess > there's a reason that those things are reported as different states.. > It doesn't help that our vm_suspend method doesn't actually do a (ACPI S3 > like) suspension, but a (vCPU) "stop". > > The "prelaunch" state, OTOH., seems pretty much like "paused", with the > difference that the VM vCPUs never ran in the former, this seems fine to > handle the same. But for "suspended" I'm not sure if we'd like to detect > that as paused only if conflating both is OK for a call-site, so maybe > with an opt-in parameter. Alternatively we could return the status in > the "true" case so that call-sites can decide what to do for any such > special handling without an extra parameter. Yes, I like the opt-in approach. From t.lamprecht at proxmox.com Tue Oct 10 16:31:03 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 10 Oct 2023 16:31:03 +0200 Subject: [pve-devel] [PATCH manager v2] ui: DirEdit: LVMEdit: add hint when to enable shared In-Reply-To: <2b5d69b7-24e5-409d-8781-30ca2914c800@proxmox.com> References: <20230821100651.1167084-1-a.lauterer@proxmox.com> <2b5d69b7-24e5-409d-8781-30ca2914c800@proxmox.com> Message-ID: <906899cc-7583-4ec3-8149-44609f4d6e14@proxmox.com> Am 10/10/2023 um 15:19 schrieb Aaron Lauterer: >>> >>> >>> diff?--git?a/www/manager6/storage/DirEdit.js?b/www/manager6/storage/DirEdit.js >>> index?7e9ec44d..8469a7c3?100644 >>> ---?a/www/manager6/storage/DirEdit.js >>> +++?b/www/manager6/storage/DirEdit.js >>> @@?-30,6?+30,10?@@?Ext.define('PVE.storage.DirInputPanel',?{ >>> ??????????name:?'shared', >>> ??????????uncheckedValue:?0, >>> ??????????fieldLabel:?gettext('Shared'), >>> +????????autoEl:?{ >>> +????????????tag:?'div', >>> +????????????'data-qtip':?gettext('Enable?if?the?underlying?file?system?is?already?shared?between?nodes.'), >>> +????????} >> >> eslint?complains?about?missing?trailing?comma > > hmm,?when?did?that?happen...?now?it?does?it?too?for?me.?Will?send?another?version?with?it?fixed. We always had that rule enabled, it's a warning one, so `make install` will only output the warning but continue, to make modify-build-test cycles easier, but when building a debian package all the eslint warnings become errors. There's a slight possibility that an earlier eslint had a false-negative here, but tbh., I'd find that a bit odd as it's a relatively simple check. From aderumier at odiso.com Tue Oct 10 17:37:17 2023 From: aderumier at odiso.com (Alexandre Derumier) Date: Tue, 10 Oct 2023 17:37:17 +0200 Subject: [pve-devel] [PATCH qemu-server] Fix: cpu hotplug feature can't be changed online Message-ID: <20231010153717.2282543-1-aderumier@odiso.com> The cpus are passed as devices with specific id only when cpu hotplug is enable at start. We can't enable/disable it online or vcpu hotplug api will thrown errors not finding core id. Signed-off-by: Alexandre Derumier --- PVE/QemuServer.pm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 2895675..22d1c71 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -4961,7 +4961,7 @@ sub vmconfig_hotplug_pending { my $force = $pending_delete_hash->{$opt}->{force}; eval { if ($opt eq 'hotplug') { - die "skip\n" if ($conf->{hotplug} =~ /memory/); + die "skip\n" if ($conf->{hotplug} =~ /(cpu|memory)/); } elsif ($opt eq 'tablet') { die "skip\n" if !$hotplug_features->{usb}; if ($defaults->{tablet}) { @@ -5022,6 +5022,7 @@ sub vmconfig_hotplug_pending { eval { if ($opt eq 'hotplug') { die "skip\n" if ($value =~ /memory/) || ($value !~ /memory/ && $conf->{hotplug} =~ /memory/); + die "skip\n" if ($value =~ /cpu/) || ($value !~ /cpu/ && $conf->{hotplug} =~ /cpu/); } elsif ($opt eq 'tablet') { die "skip\n" if !$hotplug_features->{usb}; if ($value == 1) { -- 2.39.2 From t.lamprecht at proxmox.com Tue Oct 10 18:18:34 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 10 Oct 2023 18:18:34 +0200 Subject: [pve-devel] applied: [PATCH manager v5] ui: ceph: improve discoverability of warning details In-Reply-To: <20231002090026.725629-1-a.lauterer@proxmox.com> References: <20231002090026.725629-1-a.lauterer@proxmox.com> Message-ID: Am 02/10/2023 um 11:00 schrieb Aaron Lauterer: > by > * replacing the info button with expandable rows that contain the > details of the warning > * adding two action buttons to copy the summary and details > * making the text selectable > > The row expander works like the one in the mail gateway tracking center > -> doubleclick only opens it. > > The height of the warning grid is limited to not grow too large. > A Diffstore is used to avoid expanded rows being collapsed on an update. > > The rowexpander cannot hide the toggle out of the box. Therefore, if > there is no detailed message for a warning, we show a placeholder text. > We could consider extending it in the future to only show the toggle if > a defined condition is met. > > Signed-off-by: Aaron Lauterer > --- > > changes since > v4: > * rebased so the patch applies again > > v3: > * change the whole approach from tooltips and info window to integrating > it into the grid itself > > www/css/ext6-pve.css | 6 +++ > www/manager6/ceph/Status.js | 89 +++++++++++++++++++++++++------------ > 2 files changed, 67 insertions(+), 28 deletions(-) > > applied, with a few follow-ups as talked off list, thanks! The biggest change is that we now only have one copy button that copies all (severity, summary and details) at once. From alexandre.derumier at groupe-cyllene.com Tue Oct 10 18:29:06 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Tue, 10 Oct 2023 16:29:06 +0000 Subject: [pve-devel] [PATCH v4 qemu-server 2/2] remote-migration: add target-cpu && target-reboot params In-Reply-To: <73e0a3a6-f978-ac24-5f6b-16af759ee209@proxmox.com> References: <20230928144556.2023558-1-aderumier@odiso.com> <20230928144556.2023558-3-aderumier@odiso.com> <5ecfa7d0-4525-5f1e-75a2-a6ae1a93356b@proxmox.com> <73e0a3a6-f978-ac24-5f6b-16af759ee209@proxmox.com> Message-ID: > > I think that a true offline migration (without starting any > source/target vm) can be done with qemu-storage-daemon running nbd > server on target &&? qemu-img on source. > > This could be also used for online migration with unused/detached > disks. > >>Yes, we could, but would require additional logic. So the question is >>if >>there are enough advantages to do it rather than via a full VM. >>Starting >>a VM of course requires more resources. I don't known if it's possible to use the ndb server in the targetvm, if the target disk is not attached ? (I really don't known) >>In case of 'restart' migration, we do want to start the VM anyways, >>so >>it's actually better, because we can catch config issues early :) Now >>that I think about it, can we also just start the target VM in >>prelaunch >>mode (instead of incoming migration mode), do the NBD migration, shut >>down the source VM, stop the NBD server and then resume the target? >>That >>would avoid the need to stop and start the target again. And >>therefore >>might be quite a bit less downtime. Yes, indeed ! I'll work on this for next version. From f.ebner at proxmox.com Wed Oct 11 09:38:39 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Wed, 11 Oct 2023 09:38:39 +0200 Subject: [pve-devel] [PATCH v3 qemu-server] Fix ACPI-suspended VMs resuming after migration In-Reply-To: References: <20231009132519.115727-1-f.schauer@proxmox.com> <01c649c9-0717-404c-98d8-17dc8a5cc4eb@proxmox.com> Message-ID: Am 10.10.23 um 16:19 schrieb Fiona Ebner: >> Also, with that change we might have added a race for suspend-mode backups, >> at least if VMs really can wake up without a QMP command (which I find likely). >> I.e., between the time we checked and set vm_was_paused until we actually >> suspend, because if the VM would wake up in between we might get inconsistent >> stuff and skip things like fs-freeze. > > That race is already there without the patch. QEMU does not transition > from 'suspended' state to 'paused' state when QMP 'stop' is issued, i.e. > what our 'qm suspend' or vm_suspend() actually does. So it doesn't > matter if we call that during backup or not when the VM is already in > 'suspended' state. > > The window for the race is a bit larger though: > now: VM wakes up between check if paused and 'backup' QMP > before: VM wakes up after fsfreeze was skipped because guest agent was > detected as not running > What's written above actually applies to 'snapshot' mode backup, but not to 'suspend' mode backup. 'suspend' mode backup is and was broken in this regard already: > root at pve8a1 ~ # vzdump 100 --storage pbs --mode suspend > INFO: starting new backup job: vzdump 100 --mode suspend --storage pbs > INFO: Starting Backup of VM 100 (qemu) > INFO: Backup started at 2023-10-11 09:25:53 > INFO: status = running > INFO: backup mode: suspend > INFO: ionice priority: 7 > INFO: VM Name: Copy-of-VM-apache > INFO: include disk 'scsi0' 'rbd:vm-100-disk-0' 4618348134 > INFO: suspending guest > INFO: snapshots found (not included into backup) > INFO: creating Proxmox Backup Server archive 'vm/100/2023-10-11T07:25:53Z' > INFO: skipping guest-agent 'fs-freeze', agent configured but not running? > INFO: started backup task '923c95cf-6bb6-4476-825e-390c147a4e76' > INFO: resuming VM again after 3 seconds > INFO: scsi0: dirty-bitmap status: OK (8.0 MiB of 4.3 GiB dirty) > INFO: using fast incremental mode (dirty-bitmap), 8.0 MiB dirty of 4.3 GiB total > INFO: 100% (8.0 MiB of 8.0 MiB) in 1s, read: 8.0 MiB/s, write: 8.0 MiB/s > INFO: backup was done incrementally, reused 4.29 GiB (99%) > INFO: transferred 8.00 MiB in 1 seconds (8.0 MiB/s) > INFO: adding notes to backup > INFO: resume vm > INFO: Finished Backup of VM 100 (00:00:06) > INFO: Backup finished at 2023-10-11 09:25:59 > INFO: Backup job finished successfully We never got the fsfreeze in suspend mode, because we do it after the QMP 'stop' when the vCPUs are paused, so the guest agent won't be running. The backup just ends up with whatever state the filesystem was in when QMP 'stop' is issued. Time to finally deprecate 'suspend' mode backup for VMs for good? It really has just disadvantages compared to 'snapshot' mode and our docs state "This mode is provided for compatibility reason" for a long time now. > We could either: > > 1. (ironically) disallow 'suspend' mode backup when in 'suspended' runstate. > 2. resume and pause the VM upon 'suspend' mode backup, but is also > surprising > 3. make the race window smaller again by doing the qga_check_running() > and fs-freeze if true, if VM was in 'suspended' runstate. > > I don't see how to avoid the possibility of a wake-up during an > inconvenient time except with one of the first two options. > From f.ebner at proxmox.com Wed Oct 11 09:51:16 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Wed, 11 Oct 2023 09:51:16 +0200 Subject: [pve-devel] [PATCH v4 qemu-server 2/2] remote-migration: add target-cpu && target-reboot params In-Reply-To: References: <20230928144556.2023558-1-aderumier@odiso.com> <20230928144556.2023558-3-aderumier@odiso.com> <5ecfa7d0-4525-5f1e-75a2-a6ae1a93356b@proxmox.com> <73e0a3a6-f978-ac24-5f6b-16af759ee209@proxmox.com> Message-ID: <12580a59-53c4-4212-879b-b4f6faff58c2@proxmox.com> Am 10.10.23 um 18:29 schrieb DERUMIER, Alexandre: >> >> I think that a true offline migration (without starting any >> source/target vm) can be done with qemu-storage-daemon running nbd >> server on target &&? qemu-img on source. >> >> This could be also used for online migration with unused/detached >> disks. >> > >>> Yes, we could, but would require additional logic. So the question is >>> if >>> there are enough advantages to do it rather than via a full VM. >>> Starting >>> a VM of course requires more resources. > > I don't known if it's possible to use the ndb server in the targetvm, > if the target disk is not attached ? (I really don't known) Yes, I think need to attach it as a blockdev first. But for 'restart' migration, it should be fine to start out with NBD and migrate non-attached disks with our current method. If we (later) want to add logic for migrating non-attached disks via storage-daemon we can always do that too and switch storage_migrate() to use that (maybe not unconditionally, ZFS -> ZFS still makes more sense to do with send/recv for example). From f.weber at proxmox.com Wed Oct 11 11:38:54 2023 From: f.weber at proxmox.com (Friedrich Weber) Date: Wed, 11 Oct 2023 11:38:54 +0200 Subject: [pve-devel] [PATCH manager v2 1/1] pve7to8: check for proper grub meta-package for bootmode In-Reply-To: <20231009125242.3857753-4-s.ivanov@proxmox.com> References: <20231009125242.3857753-1-s.ivanov@proxmox.com> <20231009125242.3857753-4-s.ivanov@proxmox.com> Message-ID: <01e2d75b-c87a-4bc9-98f4-77f4ccf4df83@proxmox.com> On 09/10/2023 14:52, Stoiko Ivanov wrote: > + } elsif ( ! -f "/usr/share/doc/grub-efi-amd64/changelog.Debian.gz" ) { > + log_warn( > + "System booted in uefi mode but grub-efi-amd64 meta-package not installed" > + . " new grub versions will not be installed to /boot/efi!" > + . " Install grub-efi-amd64." > ); I do like the exclamation mark, but I still think some punctuation (if not newline) between "[...] not installed" and "new grub versions [...]" would be good. Currently, the message reads like this: WARN: System booted in uefi mode but grub-efi-amd64 meta-package notinstalled new grub versions will not be installed to /boot/efi! Install grub-efi-amd64. which is a bit hard to parse -- the following seems easier to parse (note the extra comma): WARN: System booted in uefi mode but grub-efi-amd64 meta-package not installed, new grub versions will not be installed to /boot/efi! Install grub-efi-amd64. Sorry for obsessing over the punctuation here, but I suspect there are a lot of UEFI-booted PVE 7 installs with LVM root, so it would be good to reduce the potential for confusion as much as possible. The exact phrasing aside, consider this: Tested-by: Friedrich Weber Can confirm that with this patch, * pve7to8 prints the warning on UEFI-booted system with root on LVM and grub-pc installed * pve7to8 does *not* print the warning on ** the same system when grub-efi-amd64 is installed ** UEFI-booted system with root on ZFS (using systemd-boot) ** legacy-booted system with root on LVM or ZFS From f.weber at proxmox.com Wed Oct 11 11:39:06 2023 From: f.weber at proxmox.com (Friedrich Weber) Date: Wed, 11 Oct 2023 11:39:06 +0200 Subject: [pve-devel] [PATCH kernel-helper v2 2/2] proxmox-boot-tool: check if correct grub metapackage is installed In-Reply-To: <20231009125242.3857753-3-s.ivanov@proxmox.com> References: <20231009125242.3857753-1-s.ivanov@proxmox.com> <20231009125242.3857753-3-s.ivanov@proxmox.com> Message-ID: Tested-by: Friedrich Weber Can confirm that with this patch, * the warning appears after installing a new kernel on a UEFI-booted system with root on LVM * the warning does *not* appear after installing a new kernel on ** a UEFI-booted system with root on ZFS (using systemd-boot) ** a legacy-booted system with root on LVM or ZFS On 09/10/2023 14:52, Stoiko Ivanov wrote: > this part of the hook applies only to systems not using pbt for > bootmangement. > > Currently our ISO installs grub-pc unconditionally - and never the > conflicting grub-efi-amd64. Both packages are responsible for > running grub-install (for the appropriate disks) upon an upgrade of > grub. > > This results in grub currently not getting updated on uefi-booted > systems (which do not use proxmox-boot-tool). > > The patch causes a warning to be printed to notify the user. > > Also considered putting the check+warning in d/postinst - but this way > it will get triggered more often (upon every > kernel-upgrade/update-initramfs, instead of only on > proxmox-kernel-helper updates, which are less often), increasing the > chances of being noticed. > > checking for the changelog-presence was chosen, over `dpkg-query` for > the status, for consistency with the similar patch for pve7to8 (and > potentially a small speed-gain). > > Suggested-by: Thomas Lamprecht > Signed-off-by: Stoiko Ivanov > --- > src/proxmox-boot/zz-proxmox-boot | 19 +++++++++++++++++++ > 1 file changed, 19 insertions(+) > > diff --git a/src/proxmox-boot/zz-proxmox-boot b/src/proxmox-boot/zz-proxmox-boot > index 1adc1b1..4dfa765 100755 > --- a/src/proxmox-boot/zz-proxmox-boot > +++ b/src/proxmox-boot/zz-proxmox-boot > @@ -215,6 +215,23 @@ disable_systemd_boot_hook() { > > } > > +check_grub_efi_package() { > + > + if [ -f "${ESP_LIST}" ]; then > + return > + fi > + > + if [ ! -d /sys/firmware/efi ]; then > + return > + fi > + > + if [ -f /usr/share/doc/grub-efi-amd64/changelog.Debian.gz ]; then > + return > + fi > + warn "uefi-booted system, without grub-efi-amd64 package - /boot/efi will not be updated" > + > +} > + > set -- $DEB_MAINT_PARAMS > mode="${1#\'}" > mode="${mode%\'}" > @@ -228,6 +245,7 @@ case $0:$mode in > BOOT_KVERS="$(boot_kernel_list "$@")" > update_esps > disable_systemd_boot_hook > + check_grub_efi_package > ;; > */postrm.d/*:|*/postrm.d/*:remove) > reexec_in_mountns "$@" > @@ -235,6 +253,7 @@ case $0:$mode in > BOOT_KVERS="$(boot_kernel_list)" > update_esps > disable_systemd_boot_hook > + check_grub_efi_package > ;; > esac > From f.schauer at proxmox.com Wed Oct 11 11:54:52 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Wed, 11 Oct 2023 11:54:52 +0200 Subject: [pve-devel] [PATCH installer 1/1] fix #4869: Show state in management interface ComboBox In-Reply-To: <4e4c00c7-5412-4bec-9952-8497f609b71d@proxmox.com> References: <20230804102646.34999-1-f.schauer@proxmox.com> <20230804102646.34999-2-f.schauer@proxmox.com> <2730c950-b0d7-45dc-af83-80ff808da9b7@proxmox.com> <4e4c00c7-5412-4bec-9952-8497f609b71d@proxmox.com> Message-ID: <841632e7-f011-9bb9-f29f-60761e02699d@proxmox.com> The green circle is not displayed correctly by the PVE installer as it does not have the corresponding emoji font package. However, the suggested circles for DOWN are rendered correctly. Alternatively we could use an arrow pointing either ? (UP) or ? (DOWN). https://unicode-explorer.com/c/2B06 https://unicode-explorer.com/c/2B07 These arrows are also displayed correctly by the PVE installer. On 10/10/2023 15:13, Thomas Lamprecht wrote: > Am 10/10/2023 um 14:56 schrieb Christoph Heiss: >> On Tue, Oct 10, 2023 at 01:55:43PM +0200, Thomas Lamprecht wrote: >>> UP -> ? https://unicode-explorer.com/c/1F7E2 >> A green circle would be problematic/non-optimal for people deuteranopia >> (red-green color blindness). So maybe some other character/symbol >> distinctly recognizable as "being connected, up" - but purely >> color-coding things is not something that should be done IMO. > but those already get filled/not-filled as hint, i.e., this isn't just > purely color coding, that's why I explicitly used a *filled* green circle > for UP and for DOWN an *empty* one as preferred one due to vision > impairments like color blindness, like hinted ;-) > > But sure, if you have a better idea for this just say so. > IMO the proposed one should be clear enough, as not only color (useful > for the majority of people) but also form is distinct, and I saw such > circles being used to indicate on/off or plugged/unplugged already in > a few UIs (such things are not easy to search for, so sorry, no concrete > example), but that doesn't mean there might be something better that is > still practical for both TUI and GUI. > >>> DOWN -> ? (preferred, easier to differ for vision impaired people) or ? >>> https://unicode-explorer.com/c/25EF or https://unicode-explorer.com/c/2B24 >> Do we really need to explicitly mark non-UP interfaces? I'd think the >> fact of it being not marked UP should provide enough context in this >> situation. > Yes, to give clear distinction to the UP state and make them more > distinct to each other, also to avoid text-alignment (visual symmetry) > problems (if the symbol prefixes the iface name, which I guesstimate > will look better). From f.weber at proxmox.com Wed Oct 11 12:05:43 2023 From: f.weber at proxmox.com (Friedrich Weber) Date: Wed, 11 Oct 2023 12:05:43 +0200 Subject: [pve-devel] [PATCH kernel-helper v2 2/2] proxmox-boot-tool: check if correct grub metapackage is installed In-Reply-To: References: <20231009125242.3857753-1-s.ivanov@proxmox.com> <20231009125242.3857753-3-s.ivanov@proxmox.com> Message-ID: <6efe02d4-7f3b-4e66-a8a1-5000fb23f41f@proxmox.com> On 11/10/2023 11:39, Friedrich Weber wrote: > Can confirm that with this patch, > > * the warning appears after installing a new kernel on a UEFI-booted > system with root on LVM Just to be clear: This is if grub-pc is installed. If i install grub-efi-amd64 instead, the warning does not appear anymore. From c.ebner at proxmox.com Wed Oct 11 12:29:12 2023 From: c.ebner at proxmox.com (Christian Ebner) Date: Wed, 11 Oct 2023 12:29:12 +0200 Subject: [pve-devel] [PATCH pve-manager] ui: makefile: readd compression selector form Message-ID: <20231011102912.98462-1-c.ebner@proxmox.com> Commit 65704cc2a88729479fb15ec2a5b3df683b8f2aac apparently removed by misstake the form/CompressionSelector.js from the Makefile. It is however required by the backup view to select the compression method, so readd it. Signed-off-by: Christian Ebner --- www/manager6/Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 5bb8fa06..79c079ab 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -30,6 +30,7 @@ JSSRC= \ form/CephPoolSelector.js \ form/CephFSSelector.js \ form/ComboBoxSetStoreNode.js \ + form/CompressionSelector.js \ form/ContentTypeSelector.js \ form/ControllerSelector.js \ form/DayOfWeekSelector.js \ -- 2.39.2 From t.lamprecht at proxmox.com Wed Oct 11 12:58:05 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Wed, 11 Oct 2023 12:58:05 +0200 Subject: [pve-devel] [PATCH installer 1/1] fix #4869: Show state in management interface ComboBox In-Reply-To: <841632e7-f011-9bb9-f29f-60761e02699d@proxmox.com> References: <20230804102646.34999-1-f.schauer@proxmox.com> <20230804102646.34999-2-f.schauer@proxmox.com> <2730c950-b0d7-45dc-af83-80ff808da9b7@proxmox.com> <4e4c00c7-5412-4bec-9952-8497f609b71d@proxmox.com> <841632e7-f011-9bb9-f29f-60761e02699d@proxmox.com> Message-ID: <6cfe1ce5-42e9-4965-ab61-e10315062107@proxmox.com> Am 11/10/2023 um 11:54 schrieb Filip Schauer: > The green circle is not displayed correctly by the PVE installer as it > does not have the corresponding emoji font package. However, the > suggested circles for DOWN are rendered correctly. The options I see (on top of that): - we could just install a font package that ships it, e.g., the fonts-noto-color-emoji one, I mean 10.8 MB isn't negligible, but neither _that_ big.. - Use ? for down and ? for up, and color them via CSS, or whatever is the easiest here for GTK combobox entries. > Alternatively we could use an arrow pointing either ? (UP) or ? (DOWN). > > https://unicode-explorer.com/c/2B06 > https://unicode-explorer.com/c/2B07 > > These arrows are also displayed correctly by the PVE installer. I cannot 100% pin it down, but I do not really like arrows for conveying that information even though the map to up/down directly, but arrows are normally used for rather different things in UI context (e.g., sorting, or resize handles), so IMO to overloaded already. From f.ebner at proxmox.com Wed Oct 11 13:41:44 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Wed, 11 Oct 2023 13:41:44 +0200 Subject: [pve-devel] applied: [PATCH pve-manager] ui: makefile: readd compression selector form In-Reply-To: <20231011102912.98462-1-c.ebner@proxmox.com> References: <20231011102912.98462-1-c.ebner@proxmox.com> Message-ID: <636428c1-e3fc-47c9-ada8-8f2bf4dd9ecd@proxmox.com> Am 11.10.23 um 12:29 schrieb Christian Ebner: > Commit 65704cc2a88729479fb15ec2a5b3df683b8f2aac apparently removed by > misstake the form/CompressionSelector.js from the Makefile. > > It is however required by the backup view to select the compression > method, so readd it. > > Signed-off-by: Christian Ebner applied, thanks! From f.ebner at proxmox.com Wed Oct 11 14:30:50 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Wed, 11 Oct 2023 14:30:50 +0200 Subject: [pve-devel] [PATCH qemu-server] Fix: cpu hotplug feature can't be changed online In-Reply-To: <20231010153717.2282543-1-aderumier@odiso.com> References: <20231010153717.2282543-1-aderumier@odiso.com> Message-ID: <1a8ae488-7f5e-4cf3-b63c-d7ba384c98a7@proxmox.com> Am 10.10.23 um 17:37 schrieb Alexandre Derumier: > The cpus are passed as devices with specific id only when cpu hotplug is enable > at start. > We can't enable/disable it online or vcpu hotplug api will thrown errors > not finding core id. When removing cores after enabling the option this is true, but I can 1. start a VM without CPU hotplug and fewer than max vCPUs 2. enable CPU hotplug 3. add more cores And doing the disable of the option online also doesn't seem problematic at a first glance. So I thought this would technically be a breaking change. But actually, the change is completely justified, because of migration. Because the QEMU commandline changes based on the hotplug setting, so the source and target VM will not agree and loading the state on the target will get confused and crash: > Oct 11 14:08:23 pve8a2 QEMU[160882]: kvm: get_pci_config_device: Bad config data: i=0x9a read: 8 device: 3 cmask: ff wmask: 0 w1cmask:0 > Oct 11 14:08:23 pve8a2 QEMU[160882]: kvm: Failed to load PCIDevice:config > Oct 11 14:08:23 pve8a2 QEMU[160882]: kvm: Failed to load virtio-scsi:virtio > Oct 11 14:08:23 pve8a2 QEMU[160882]: kvm: error while loading state for instance 0x0 of device '0000:00:05.0/virtio-scsi' > Oct 11 14:08:23 pve8a2 QEMU[160882]: kvm: load of migration failed: Invalid argument Therefore, Reviewed-by: Fiona Ebner From s.ivanov at proxmox.com Wed Oct 11 15:23:41 2023 From: s.ivanov at proxmox.com (Stoiko Ivanov) Date: Wed, 11 Oct 2023 15:23:41 +0200 Subject: [pve-devel] [PATCH kernel-helper v3 2/2] proxmox-boot-tool: check if correct grub metapackage is installed In-Reply-To: <20231011132342.55518-1-s.ivanov@proxmox.com> References: <20231011132342.55518-1-s.ivanov@proxmox.com> Message-ID: <20231011132342.55518-3-s.ivanov@proxmox.com> this part of the hook applies only to systems not using pbt for bootmangement. Currently our ISO installs grub-pc unconditionally - and never the conflicting grub-efi-amd64. Both packages are responsible for running grub-install (for the appropriate disks) upon an upgrade of grub. This results in grub currently not getting updated on uefi-booted systems (which do not use proxmox-boot-tool). The patch causes a warning to be printed to notify the user. Also considered putting the check+warning in d/postinst - but this way it will get triggered more often (upon every kernel-upgrade/update-initramfs, instead of only on proxmox-kernel-helper updates, which are less often), increasing the chances of being noticed. checking for the changelog-presence was chosen, over `dpkg-query` for the status, for consistency with the similar patch for pve7to8 (and potentially a small speed-gain). Suggested-by: Thomas Lamprecht Signed-off-by: Stoiko Ivanov --- src/proxmox-boot/zz-proxmox-boot | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/proxmox-boot/zz-proxmox-boot b/src/proxmox-boot/zz-proxmox-boot index 1adc1b1..4dfa765 100755 --- a/src/proxmox-boot/zz-proxmox-boot +++ b/src/proxmox-boot/zz-proxmox-boot @@ -215,6 +215,23 @@ disable_systemd_boot_hook() { } +check_grub_efi_package() { + + if [ -f "${ESP_LIST}" ]; then + return + fi + + if [ ! -d /sys/firmware/efi ]; then + return + fi + + if [ -f /usr/share/doc/grub-efi-amd64/changelog.Debian.gz ]; then + return + fi + warn "uefi-booted system, without grub-efi-amd64 package - /boot/efi will not be updated" + +} + set -- $DEB_MAINT_PARAMS mode="${1#\'}" mode="${mode%\'}" @@ -228,6 +245,7 @@ case $0:$mode in BOOT_KVERS="$(boot_kernel_list "$@")" update_esps disable_systemd_boot_hook + check_grub_efi_package ;; */postrm.d/*:|*/postrm.d/*:remove) reexec_in_mountns "$@" @@ -235,6 +253,7 @@ case $0:$mode in BOOT_KVERS="$(boot_kernel_list)" update_esps disable_systemd_boot_hook + check_grub_efi_package ;; esac -- 2.39.2 From s.ivanov at proxmox.com Wed Oct 11 15:23:40 2023 From: s.ivanov at proxmox.com (Stoiko Ivanov) Date: Wed, 11 Oct 2023 15:23:40 +0200 Subject: [pve-devel] [PATCH kernel-helper v3 1/2] proxmox-boot-tool: do not exit early in kernel-hook In-Reply-To: <20231011132342.55518-1-s.ivanov@proxmox.com> References: <20231011132342.55518-1-s.ivanov@proxmox.com> Message-ID: <20231011132342.55518-2-s.ivanov@proxmox.com> update_esps is called first in the actual execution below - exiting early does not work for systems that don't use proxmox-boot-tool if a check added later needs to work there too. Signed-off-by: Stoiko Ivanov --- src/proxmox-boot/zz-proxmox-boot | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proxmox-boot/zz-proxmox-boot b/src/proxmox-boot/zz-proxmox-boot index 793882b..1adc1b1 100755 --- a/src/proxmox-boot/zz-proxmox-boot +++ b/src/proxmox-boot/zz-proxmox-boot @@ -44,7 +44,7 @@ fi update_esps() { if [ ! -f "${ESP_LIST}" ]; then warn "No ${ESP_LIST} found, skipping ESP sync." - exit 0 + return fi if [ -f /etc/kernel/cmdline ]; then # we can have cmdline files with multiple or no new line at all, handle both! -- 2.39.2 From s.ivanov at proxmox.com Wed Oct 11 15:23:42 2023 From: s.ivanov at proxmox.com (Stoiko Ivanov) Date: Wed, 11 Oct 2023 15:23:42 +0200 Subject: [pve-devel] [PATCH manager v3 1/1] pve7to8: check for proper grub meta-package for bootmode In-Reply-To: <20231011132342.55518-1-s.ivanov@proxmox.com> References: <20231011132342.55518-1-s.ivanov@proxmox.com> Message-ID: <20231011132342.55518-4-s.ivanov@proxmox.com> This should catch installations from our ISO on non-ZFS in uefi mode, which won't get the updated grub efi binary installed upon upgrade, because grub-pc is installed instead of grub-efi-amd64. Adding this to pve7to8 should make this even more visible, than the corresponding patch for promxox-kernel-helper (warnings printed during regular package upgrades might be overlooked more easily than a yellow line in the major upgrade checkscript) The if/else order was chosen to limit the nesting level of the long messages. Signed-off-by: Stoiko Ivanov --- PVE/CLI/pve7to8.pm | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/PVE/CLI/pve7to8.pm b/PVE/CLI/pve7to8.pm index d1a71eff..b34c8362 100644 --- a/PVE/CLI/pve7to8.pm +++ b/PVE/CLI/pve7to8.pm @@ -1302,29 +1302,36 @@ sub check_time_sync { sub check_bootloader { log_info("Checking bootloader configuration..."); - if (!$upgraded) { - log_skip("not yet upgraded, no need to check the presence of systemd-boot"); - return; - } - if (! -f "/etc/kernel/proxmox-boot-uuids") { - log_skip("proxmox-boot-tool not used for bootloader configuration"); + if (! -d '/sys/firmware/efi') { + log_skip("System booted in legacy-mode - no need for additional packages"); return; } - if (! -d "/sys/firmware/efi") { - log_skip("System booted in legacy-mode - no need for systemd-boot"); - return; - } - - if ( -f "/usr/share/doc/systemd-boot/changelog.Debian.gz") { - log_pass("systemd-boot is installed"); - } else { + if ( -f "/etc/kernel/proxmox-boot-uuids") { + if (!$upgraded) { + log_skip("not yet upgraded, no need to check the presence of systemd-boot"); + return; + } + if ( -f "/usr/share/doc/systemd-boot/changelog.Debian.gz") { + log_pass("bootloader packages installed correctly"); + return; + } log_warn( "proxmox-boot-tool is used for bootloader configuration in uefi mode" - . "but the separate systemd-boot package, existing in Debian Bookworm is not installed" - . "initializing new ESPs will not work until the package is installed" + . " but the separate systemd-boot package is not installed," + . " initializing new ESPs will not work until the package is installed" + ); + return; + } elsif ( ! -f "/usr/share/doc/grub-efi-amd64/changelog.Debian.gz" ) { + log_warn( + "System booted in uefi mode but grub-efi-amd64 meta-package not installed," + . " new grub versions will not be installed to /boot/efi!" + . " Install grub-efi-amd64." ); + return; + } else { + log_pass("bootloader packages installed correctly"); } } -- 2.39.2 From s.ivanov at proxmox.com Wed Oct 11 15:23:39 2023 From: s.ivanov at proxmox.com (Stoiko Ivanov) Date: Wed, 11 Oct 2023 15:23:39 +0200 Subject: [pve-devel] [PATCH kernel-helper/manager v3] check for fitting grub-meta package on uefi systems Message-ID: <20231011132342.55518-1-s.ivanov@proxmox.com> v2->v3: * adapted Friedrich's feedback (huge thanks for the patience and attention to semantically important details!!) - so that the pve7to8 warning is actually understandable v1->v2: * adapted Friedrich's feedback (huge thanks!) ** fixed the wrongly negated check for installed grub-efi-amd64 in the boot-tool hook. ** Rephrased the error-message in pve7to8 to 2 sentences. I tried adding a newline as well, however this results in the message not being printed in the warning color anymore (most likely due to [0]) - and I felt this to be more important than having it on a separate line. [0] https://perldoc.perl.org/Term::ANSIColor#RESTRICTIONS original cover-letter for v1: The following patchset is a followup to the one for the installer: https://lists.proxmox.com/pipermail/pve-devel/2023-September/059270.html As suggested by Thomas - adding the check to proxmox-kernel-helper seems like a good idea. While adding it to d/postinst I thought that this might not be the best place - and that getting the warning upon every kernel-upgrade would be better vs. upon every upgrade of proxmox-kernel-helper (which are far less often). (Can gladly send the version with d/postinst as well) If the pve-manager patch gets applied - I'd push the equivalent change to pmg and provide one for pbs. Tested on legacy and uefi VMs installed with pve-8.0 iso and grub-efi-amd64 (and systemd-boot) removed vs. installed. proxmox-kernel-helper Stoiko Ivanov (2): proxmox-boot-tool: do not exit early in kernel-hook proxmox-boot-tool: check if correct grub metapackage is installed src/proxmox-boot/zz-proxmox-boot | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) pve-manager: Stoiko Ivanov (1): pve7to8: check for proper grub meta-package for bootmode PVE/CLI/pve7to8.pm | 39 +++++++++++++++++++++++---------------- 1 file changed, 23 insertions(+), 16 deletions(-) -- 2.39.2 From f.weber at proxmox.com Wed Oct 11 16:39:28 2023 From: f.weber at proxmox.com (Friedrich Weber) Date: Wed, 11 Oct 2023 16:39:28 +0200 Subject: [pve-devel] [PATCH kernel-helper/manager v3] check for fitting grub-meta package on uefi systems In-Reply-To: <20231011132342.55518-1-s.ivanov@proxmox.com> References: <20231011132342.55518-1-s.ivanov@proxmox.com> Message-ID: <1cf967f2-267e-463a-8a05-0aa857b09f57@proxmox.com> Looks good to me, thanks a lot! Tested-by: Friedrich Weber Reviewed-by: Friedrich Weber From c.heiss at proxmox.com Wed Oct 11 16:41:28 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 11 Oct 2023 16:41:28 +0200 Subject: [pve-devel] [PATCH font-logos/manager v2 0/5] fix #2435: lxc: show distro and privileged status in summary In-Reply-To: <20230705111257.759836-1-c.heiss@proxmox.com> References: <20230705111257.759836-1-c.heiss@proxmox.com> Message-ID: <3mfnfo42sx7ebjgiz235krmlmhq54mmnskpsjmswytq7fmzuhs@l22hlwzv7udo> Ping - still applies cleanly on current master of both repositories. On Wed, Jul 05, 2023 at 01:12:44PM +0200, Christoph Heiss wrote: > > This implements #2435 [0]. Show the unprivileged status in the summary > panel, the distro logo and name in the title of the summary panel. > > Patch 1 & 2 fix two small typos in the `fonts-font-logos` package. > Patch 3 then prepares the pveproxy to serve the required CSS and font > files for the icon font we use [1] (packaged here [2]), and finally > patch 4 & 5 finally wire everything up into the UI. > > N.B.: The `fonts-font-logos` package is not yet available through the > repos yet (as far as I could see), so it must be built & installed > locally for testing. > > [0] https://bugzilla.proxmox.com/show_bug.cgi?id=2435 > [1] https://github.com/Lukas-W/font-logos > [2] https://git.proxmox.com/?p=fonts-font-logos.git;a=summary > > fonts-font-logos: > > Christoph Heiss (2): > d/install: fix typo in css install path > css: fix missing `@` for font-face rule > > debian/install | 2 +- > src/font-logos.css | 2 +- > 2 files changed, 2 insertions(+), 2 deletions(-) > > pve-manager: > > Christoph Heiss (3): > pveproxy, ui, d/control: add font-logos > ui: GuestStatusView: show privileged status as new row > ui: GuestStatusView: show distro logo and name in summary header > > PVE/Service/pveproxy.pm | 2 ++ > debian/control | 1 + > www/index.html.tpl | 1 + > www/manager6/panel/GuestStatusView.js | 51 ++++++++++++++++++++++++++- > 4 files changed, 54 insertions(+), 1 deletion(-) > > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > From f.schauer at proxmox.com Wed Oct 11 17:20:55 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Wed, 11 Oct 2023 17:20:55 +0200 Subject: [pve-devel] [PATCH installer 1/1] fix #4869: Show state in management interface ComboBox In-Reply-To: <6cfe1ce5-42e9-4965-ab61-e10315062107@proxmox.com> References: <20230804102646.34999-1-f.schauer@proxmox.com> <20230804102646.34999-2-f.schauer@proxmox.com> <2730c950-b0d7-45dc-af83-80ff808da9b7@proxmox.com> <4e4c00c7-5412-4bec-9952-8497f609b71d@proxmox.com> <841632e7-f011-9bb9-f29f-60761e02699d@proxmox.com> <6cfe1ce5-42e9-4965-ab61-e10315062107@proxmox.com> Message-ID: <6d373ac3-4064-0e7f-1190-8ecb6324cc60@proxmox.com> On 11/10/2023 12:58, Thomas Lamprecht wrote: > Am 11/10/2023 um 11:54 schrieb Filip Schauer: >> The green circle is not displayed correctly by the PVE installer as it >> does not have the corresponding emoji font package. However, the >> suggested circles for DOWN are rendered correctly. > The options I see (on top of that): > > - we could just install a font package that ships it, e.g., the > fonts-noto-color-emoji one, I mean 10.8 MB isn't negligible, but > neither _that_ big.. > > - Use ? for down and ? for up, and color them via CSS, or > whatever is the easiest here for GTK combobox entries. Installing the font package for a single symbol seems a bit excessive. However, changing the color of individual characters turns out to be impossible with combo boxes. At least I haven't found a way to do this with Gtk's limited CSS functionality, nor with override_color or a custom cell renderer. That leaves us with the font package. > >> Alternatively we could use an arrow pointing either ? (UP) or ? (DOWN). >> >> https://unicode-explorer.com/c/2B06 >> https://unicode-explorer.com/c/2B07 >> >> These arrows are also displayed correctly by the PVE installer. > I cannot 100% pin it down, but I do not really like arrows for > conveying that information even though the map to up/down directly, > but arrows are normally used for rather different things in UI > context (e.g., sorting, or resize handles), so IMO to overloaded > already. From d.csapak at proxmox.com Thu Oct 12 10:37:48 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Thu, 12 Oct 2023 10:37:48 +0200 Subject: [pve-devel] [PATCH 2/2] backport nettle-sys In-Reply-To: <20231012083748.1751285-1-d.csapak@proxmox.com> References: <20231012083748.1751285-1-d.csapak@proxmox.com> Message-ID: <20231012083748.1751285-3-d.csapak@proxmox.com> Signed-off-by: Dominik Csapak --- src/nettle-sys/debian/changelog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/nettle-sys/debian/changelog b/src/nettle-sys/debian/changelog index ca97618c9..d4a2de38a 100644 --- a/src/nettle-sys/debian/changelog +++ b/src/nettle-sys/debian/changelog @@ -1,3 +1,9 @@ +rust-nettle-sys (2.2.0-2~bpo12+pve1) proxmox-rust; urgency=medium + + * Rebuild for Debian Bookworm / Proxmox + + -- Proxmox Support Team Thu, 12 Oct 2023 09:26:52 +0200 + rust-nettle-sys (2.2.0-2) unstable; urgency=medium * Package nettle-sys 2.2.0 from crates.io using debcargo 2.6.0 -- 2.30.2 From d.csapak at proxmox.com Thu Oct 12 10:37:47 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Thu, 12 Oct 2023 10:37:47 +0200 Subject: [pve-devel] [PATCH 1/2] update nettle-sys In-Reply-To: <20231012083748.1751285-1-d.csapak@proxmox.com> References: <20231012083748.1751285-1-d.csapak@proxmox.com> Message-ID: <20231012083748.1751285-2-d.csapak@proxmox.com> sync with Debian unstable Signed-off-by: Dominik Csapak --- src/nettle-sys/debian/changelog | 19 +++++++++++++++++ src/nettle-sys/debian/copyright | 4 ++-- src/nettle-sys/debian/copyright.debcargo.hint | 12 +++++------ .../0001-Avoid-msvc-dependencies.patch | 21 +++++-------------- .../0002-Relax-dependency-on-bindgen.patch | 16 ++++++++------ 5 files changed, 42 insertions(+), 30 deletions(-) diff --git a/src/nettle-sys/debian/changelog b/src/nettle-sys/debian/changelog index ae38c5df2..ca97618c9 100644 --- a/src/nettle-sys/debian/changelog +++ b/src/nettle-sys/debian/changelog @@ -1,3 +1,22 @@ +rust-nettle-sys (2.2.0-2) unstable; urgency=medium + + * Package nettle-sys 2.2.0 from crates.io using debcargo 2.6.0 + + [ Matthias Geiger ] + * Team upload. + * Package nettle-sys 2.2.0 from crates.io using debcargo 2.6.0 + * Updated patch for newer bindgen + + -- Peter Michael Green Tue, 19 Sep 2023 21:53:54 +0000 + +rust-nettle-sys (2.2.0-1) unstable; urgency=medium + + * Team upload. + * Package nettle-sys 2.2.0 from crates.io using debcargo 2.6.0 + * Adjust patches for new upstream. + + -- Peter Michael Green Thu, 15 Jun 2023 16:02:40 +0000 + rust-nettle-sys (2.1.0-2) unstable; urgency=medium * Team upload. diff --git a/src/nettle-sys/debian/copyright b/src/nettle-sys/debian/copyright index 61534c5f9..780c48bcd 100644 --- a/src/nettle-sys/debian/copyright +++ b/src/nettle-sys/debian/copyright @@ -10,14 +10,14 @@ Source: https://gitlab.com/sequoia-pgp/nettle-sys Files: * Copyright: 2017-2019 juga - 2017-2022 Justus Winter + 2017-2023 Justus Winter 2017-2019 Kai Michaelis 2017-2019 Neal H. Walfield License: LGPL-3.0 or GPL-2.0 or GPL-3.0 Files: debian/* Copyright: - 2019-2022 Debian Rust Maintainers + 2019-2023 Debian Rust Maintainers 2019-2022 Daniel Kahn Gillmor 2019 kpcyrd License: LGPL-3.0 or GPL-2.0 or GPL-3.0 diff --git a/src/nettle-sys/debian/copyright.debcargo.hint b/src/nettle-sys/debian/copyright.debcargo.hint index 7876a380e..893e14d57 100644 --- a/src/nettle-sys/debian/copyright.debcargo.hint +++ b/src/nettle-sys/debian/copyright.debcargo.hint @@ -16,21 +16,21 @@ Comment: be correct information so you should review and fix this before uploading to the archive. -Files: ./LICENSE-GPL2 +Files: LICENSE-GPL2 Copyright: 1989, 1991 Free Software Foundation, Inc., License: UNKNOWN-LICENSE; FIXME (overlay) Comment: FIXME (overlay): These notices are extracted from files. Please review them before uploading to the archive. -Files: ./LICENSE-GPL3 +Files: LICENSE-GPL3 Copyright: 2007 Free Software Foundation, Inc. License: UNKNOWN-LICENSE; FIXME (overlay) Comment: FIXME (overlay): These notices are extracted from files. Please review them before uploading to the archive. -Files: ./LICENSE-LGPL3 +Files: LICENSE-LGPL3 Copyright: 2007 Free Software Foundation, Inc. License: UNKNOWN-LICENSE; FIXME (overlay) Comment: @@ -39,9 +39,9 @@ Comment: Files: debian/* Copyright: - 2019-2022 Debian Rust Maintainers - 2019-2022 Daniel Kahn Gillmor - 2019-2022 kpcyrd + 2019-2023 Debian Rust Maintainers + 2019-2023 Daniel Kahn Gillmor + 2019-2023 kpcyrd License: LGPL-3.0 or GPL-2.0 or GPL-3.0 License: GPL-2.0 diff --git a/src/nettle-sys/debian/patches/0001-Avoid-msvc-dependencies.patch b/src/nettle-sys/debian/patches/0001-Avoid-msvc-dependencies.patch index b88900c78..fb58ab48a 100644 --- a/src/nettle-sys/debian/patches/0001-Avoid-msvc-dependencies.patch +++ b/src/nettle-sys/debian/patches/0001-Avoid-msvc-dependencies.patch @@ -18,20 +18,14 @@ diff --git a/Cargo.toml b/Cargo.toml index be03fed..07dc4f0 100644 --- a/Cargo.toml +++ b/Cargo.toml -@@ -39,5 +39,3 @@ version = "0.3" - - [build-dependencies.tempfile] - version = "3" +@@ -39,2 +39,0 @@ version = "0.3" -[target."cfg(target_env = \"msvc\")".build-dependencies.vcpkg] -version = "0.2.9" diff --git a/build.rs b/build.rs index 7a7c524..e0dbe66 100644 --- a/build.rs +++ b/build.rs -@@ -88,19 +88,6 @@ fn check_cv448(includes: &[PathBuf]) -> bool { - "nettle_ed448_shake256_sign")) - } - +@@ -88,14 +88,0 @@ fn check_cv448(includes: &[PathBuf]) -> bool { -#[cfg(target_env = "msvc")] -fn try_vcpkg() -> Result { - let lib = vcpkg::Config::new() @@ -39,15 +33,10 @@ index 7a7c524..e0dbe66 100644 - .find_package("nettle")?; - - Ok(Config { -- have_cv448: check_cv448(&include_paths), -- include_paths, +- have_cv448: check_cv448(&lib.include_paths), +- have_ocb: check_ocb(&include_paths), +- include_paths: lib.include_paths, - }) -} - -#[cfg(not(target_env = "msvc"))] - fn try_vcpkg() -> Result { Err("not applicable")?; unreachable!() } - - fn print_library(lib: &pkg_config::Library, mode: &str) { --- -2.34.1 - diff --git a/src/nettle-sys/debian/patches/0002-Relax-dependency-on-bindgen.patch b/src/nettle-sys/debian/patches/0002-Relax-dependency-on-bindgen.patch index 121d0898d..abcd36cd0 100644 --- a/src/nettle-sys/debian/patches/0002-Relax-dependency-on-bindgen.patch +++ b/src/nettle-sys/debian/patches/0002-Relax-dependency-on-bindgen.patch @@ -1,13 +1,17 @@ From 41ed20b9703027dfd7c644099bdbe7a9de3775f5 Mon Sep 17 00:00:00 2001 -From: Daniel Kahn Gillmor +From: Daniel Kahn Gillmor , Peter Michael Green Date: Sat, 5 Feb 2022 19:54:40 -0500 Subject: [PATCH 2/2] Relax dependency on bindgen - +Last-Updated: Sat, 12 Aug 2023 21:50:43 +0200 werdahias at riseup.net Forwarded: not-needed This dependency is designed by upstream to keep their MSRV low, but debian doesn't need to worry about that (the distro's MSRV is managed differently). + +However due to Debian bug #967954 the upper limit on the build-dependency in +Debian does need to be restricted to not allow upstream minor versions greater +than the one currently in Debian. --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) @@ -16,12 +20,12 @@ Index: nettle-sys/Cargo.toml =================================================================== --- nettle-sys.orig/Cargo.toml +++ nettle-sys/Cargo.toml -@@ -27,7 +27,7 @@ dependencies = ["pkg-config", "nettle-de - [dependencies.libc] +@@ -42,7 +42,7 @@ dependencies = [ version = "0.2" + [build-dependencies.bindgen] --version = ">= 0.53.1, < 0.58.0" -+version = ">= 0.53.1, < 0.61.0" +-version = ">= 0.58.0, < 0.64.0" ++version = "0.66" features = ["runtime"] default-features = false -- 2.30.2 From d.csapak at proxmox.com Thu Oct 12 10:37:46 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Thu, 12 Oct 2023 10:37:46 +0200 Subject: [pve-devel] [PATCH debcargo-conf boookworm 0/2] update nettle-sys to 2.2 Message-ID: <20231012083748.1751285-1-d.csapak@proxmox.com> current nettle-sys 2.1 does not support our current packaged bindgen version (0.66), an update from debian unstable (and backport) fixes that this packaged is used by sequoia-openpgp (which is used by proxmox-offline-mirror) Dominik Csapak (2): update nettle-sys backport nettle-sys src/nettle-sys/debian/changelog | 25 +++++++++++++++++++ src/nettle-sys/debian/copyright | 4 +-- src/nettle-sys/debian/copyright.debcargo.hint | 12 ++++----- .../0001-Avoid-msvc-dependencies.patch | 21 ++++------------ .../0002-Relax-dependency-on-bindgen.patch | 16 +++++++----- 5 files changed, 48 insertions(+), 30 deletions(-) -- 2.30.2 From m.frank at proxmox.com Thu Oct 12 12:02:06 2023 From: m.frank at proxmox.com (Markus Frank) Date: Thu, 12 Oct 2023 12:02:06 +0200 Subject: [pve-devel] [PATCH docs v7 3/4] added vIOMMU documentation In-Reply-To: <20231012100207.83441-1-m.frank@proxmox.com> References: <20231012100207.83441-1-m.frank@proxmox.com> Message-ID: <20231012100207.83441-4-m.frank@proxmox.com> Signed-off-by: Markus Frank --- qm-pci-passthrough.adoc | 53 +++++++++++++++++++++++++++++++++++++++++ qm.adoc | 1 + 2 files changed, 54 insertions(+) diff --git a/qm-pci-passthrough.adoc b/qm-pci-passthrough.adoc index b90a0b9..7f7d4a7 100644 --- a/qm-pci-passthrough.adoc +++ b/qm-pci-passthrough.adoc @@ -408,6 +408,59 @@ properly used with HA and hardware changes are detected and non root users can configure them. See xref:resource_mapping[Resource Mapping] for details on that. +[[qm_pci_viommu]] +vIOMMU (emulated IOMMU) +~~~~~~~~~~~~~~~~~~~~~~~ + +Using the vIOMMU option allows you to to passthrough PCI devices to layer-2 +VMs in layer-1 VMs via +https://pve.proxmox.com/wiki/Nested_Virtualization[nested virtualization]. +There are currently two vIOMMU implementations available: Intel and VirtIO. + +Host requirement: + +* Add `intel_iommu=on` or `amd_iommu=on` depending on your CPU to your kernel +command line. + +Nested virtualization VM requirement: + +* Enable *kvm* and use the *host* cpu type to use nested virtualization. + +Intel vIOMMU +^^^^^^^^^^^^ + +Intel vIOMMU specific VM requirements: + +* Whether you are using an Intel or AMD CPU on your host, it is important to set +`intel_iommu=on` in the VMs kernel parameters. + +* To use Intel vIOMMU you need to set *q35* as the machine type. + +If all requirements are met, you can add `viommu=intel` to the machine parameter +in the configuration of the VM that should be able to passthrough PCI devices. + +---- +# qm set VMID -machine q35,viommu=intel +---- + +https://wiki.qemu.org/Features/VT-d[QEMU documentation for VT-d] + +VirtIO vIOMMU +^^^^^^^^^^^^^ + +This vIOMMU implementation is more recent and does not have as many limitations +as Intel vIOMMU but is currently less used in production and less documentated. + +With VirtIO vIOMMU there is *no* need to set any kernel parameters. +It is also *not* necessary to use q35 as the machine type, but it is advisable +if you want to use PCIe. + +---- +# qm set VMID -machine q35,viommu=virtio +---- + +https://web.archive.org/web/20230804075844/https://michael2012z.medium.com/virtio-iommu-789369049443[Blog-Post by Michael Zhao explaining virtio-iommu] + ifdef::wiki[] See Also diff --git a/qm.adoc b/qm.adoc index b3c3034..2e5b109 100644 --- a/qm.adoc +++ b/qm.adoc @@ -145,6 +145,7 @@ default https://en.wikipedia.org/wiki/Intel_440FX[Intel 440FX] or the https://ark.intel.com/content/www/us/en/ark/products/31918/intel-82q35-graphics-and-memory-controller.html[Q35] chipset, which also provides a virtual PCIe bus, and thus may be desired if one wants to pass through PCIe hardware. +Additionally, you can select a xref:qm_pci_viommu[vIOMMU] implementation. [[qm_hard_disk]] Hard Disk -- 2.39.2 From m.frank at proxmox.com Thu Oct 12 12:02:03 2023 From: m.frank at proxmox.com (Markus Frank) Date: Thu, 12 Oct 2023 12:02:03 +0200 Subject: [pve-devel] [PATCH qemu-server/docs/manager v7 0/4] vIOMMU-Feature #3784 Message-ID: <20231012100207.83441-1-m.frank@proxmox.com> I was able to clarify any ambiguity regarding vIOMMU: https://lists.gnu.org/archive/html/qemu-devel/2023-10/msg02370.html The iommu_platform parameter could be made an optional parameter for VirtIO devices to add more isolation (with the downside of making them less performant) in a follow-up patch series. qemu-server: v7: * changed viommu to string and added virtio-iommu implementation * seperated "machine as property-string" patch from "feature #3784" patch * moved format definition/parsing/printing to QemuServer::Machine * merged test-cases into "feature #3784" patch * removed kvm check, since it does not prevent vIOMMU from working (only nested virtualization requires kvm & host cpu) v6: * added helper function for machine config validation * replaced old standard option for "pve-qemu-machine" to new property string v5: * set $kvm to 1 if is_native, so that api kvm check works. v4: * added kvm/q35 checks in API * reused pve-qemu-machine v3: * replaced old machine type with property-string with viommu-parameter v2: * moved viommu-parameter inside of machine_fmt and added it the new parameter machine_properties new Config -> machine_properties: viommu=1,etc * check if kvm and q35 are set Markus Frank (2): machine as property-string feature #3784: Parameter for guest vIOMMU + test-cases PVE/API2/Qemu.pm | 11 ++++- PVE/QemuConfig.pm | 3 +- PVE/QemuServer.pm | 28 +++++++----- PVE/QemuServer/Machine.pm | 60 ++++++++++++++++++++++++- test/cfg2cmd/q35-viommu-intel.conf | 1 + test/cfg2cmd/q35-viommu-intel.conf.cmd | 23 ++++++++++ test/cfg2cmd/q35-viommu-virtio.conf | 1 + test/cfg2cmd/q35-viommu-virtio.conf.cmd | 23 ++++++++++ 8 files changed, 135 insertions(+), 15 deletions(-) create mode 100644 test/cfg2cmd/q35-viommu-intel.conf create mode 100644 test/cfg2cmd/q35-viommu-intel.conf.cmd create mode 100644 test/cfg2cmd/q35-viommu-virtio.conf create mode 100644 test/cfg2cmd/q35-viommu-virtio.conf.cmd docs: v7: * added virtio-iommu documentation * changed and seperated requirements v6: * changed capitalization and rephrased the text a bit. v5: * changed Host and VM Requirements Markus Frank (1): added vIOMMU documentation qm-pci-passthrough.adoc | 53 +++++++++++++++++++++++++++++++++++++++++ qm.adoc | 1 + 2 files changed, 54 insertions(+) manager: v7: * changed viommu checkbox to ComboBox to select either none, intel or VirtIO v6: * replaced '=== "1"' check with PVE.Parser.parseBoolean * replaced hidden kvm ui with view property * a few style changes v5: * added check if kvm is undefined or null v4: * check if kvm is enabled * added kvm+q35 hint Markus Frank (1): ui: MachineEdit with viommu ComboBox www/manager6/qemu/MachineEdit.js | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) -- 2.39.2 From m.frank at proxmox.com Thu Oct 12 12:02:05 2023 From: m.frank at proxmox.com (Markus Frank) Date: Thu, 12 Oct 2023 12:02:05 +0200 Subject: [pve-devel] [PATCH qemu-server v7 2/4] feature #3784: Parameter for guest vIOMMU + test-cases In-Reply-To: <20231012100207.83441-1-m.frank@proxmox.com> References: <20231012100207.83441-1-m.frank@proxmox.com> Message-ID: <20231012100207.83441-3-m.frank@proxmox.com> vIOMMU enables the option to passthrough pci devices to L2 VMs in L1 VMs via Nested Virtualisation. Currently there are two vIOMMU implementation in QEMU to choose: intel & virtio virtio-iommu is more recent but less used in production than intel-iommu. Intel IOMMU: -machine ...,kernel-irqchip=split -device intel-iommu Virtio IOMMU: -device virtio-iommu-pci Signed-off-by: Markus Frank --- PVE/API2/Qemu.pm | 2 ++ PVE/QemuServer.pm | 12 ++++++++++++ PVE/QemuServer/Machine.pm | 20 +++++++++++++++++++- test/cfg2cmd/q35-viommu-intel.conf | 1 + test/cfg2cmd/q35-viommu-intel.conf.cmd | 23 +++++++++++++++++++++++ test/cfg2cmd/q35-viommu-virtio.conf | 1 + test/cfg2cmd/q35-viommu-virtio.conf.cmd | 23 +++++++++++++++++++++++ 7 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 test/cfg2cmd/q35-viommu-intel.conf create mode 100644 test/cfg2cmd/q35-viommu-intel.conf.cmd create mode 100644 test/cfg2cmd/q35-viommu-virtio.conf create mode 100644 test/cfg2cmd/q35-viommu-virtio.conf.cmd diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 93f0f9b..7177d55 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -1052,6 +1052,7 @@ __PACKAGE__->register_method({ $conf->{machine} = PVE::QemuServer::Machine::print_machine($machine_conf); } } + PVE::QemuServer::Machine::check_machine_config($conf, $machine_conf); PVE::QemuConfig->write_config($vmid, $conf); @@ -1884,6 +1885,7 @@ my $update_vm_api = sub { $conf->{pending}->{$opt} = $param->{$opt}; } elsif ($opt eq 'machine') { my $machine_conf = PVE::QemuServer::Machine::parse_machine($param->{$opt}); + PVE::QemuServer::Machine::check_machine_config($conf, $machine_conf); $conf->{pending}->{$opt} = $param->{$opt}; } else { $conf->{pending}->{$opt} = $param->{$opt}; diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 7791373..fa2a773 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -4170,6 +4170,18 @@ sub config_to_command { } push @$machineFlags, "type=${machine_type_min}"; + PVE::QemuServer::Machine::check_machine_config($conf, $machine_conf); + + if ($machine_conf->{viommu}) { + if ($machine_conf->{viommu} eq 'intel') { + unshift @$devices, '-device', 'intel-iommu,intremap=on,caching-mode=on'; + push @$machineFlags, 'kernel-irqchip=split'; + } + if ($machine_conf->{viommu} eq 'virtio') { + push @$devices, '-device', 'virtio-iommu-pci'; + } + } + push @$cmd, @$devices; push @$cmd, '-rtc', join(',', @$rtcFlags) if scalar(@$rtcFlags); push @$cmd, '-machine', join(',', @$machineFlags) if scalar(@$machineFlags); diff --git a/PVE/QemuServer/Machine.pm b/PVE/QemuServer/Machine.pm index cb729ca..66e0041 100644 --- a/PVE/QemuServer/Machine.pm +++ b/PVE/QemuServer/Machine.pm @@ -23,12 +23,19 @@ my $machine_fmt = { format_description => 'machine type', optional => 1, }, + viommu => { + type => 'string', + description => "Enable/disable guest vIOMMU" + ." (needs kvm to be enabled and q35 to be set as machine type).", + enum => ['intel', 'virtio'], + optional => 1, + }, }; PVE::JSONSchema::register_format('pve-qemu-machine-fmt', $machine_fmt); PVE::JSONSchema::register_standard_option('pve-qemu-machine', { - description => "Specify the QEMU machine type.", + description => "Specify the QEMU machine type & enable/disable vIOMMU.", type => 'string', optional => 1, format => PVE::JSONSchema::get_format('pve-qemu-machine-fmt'), @@ -48,6 +55,17 @@ sub print_machine { return print_property_string($machine_conf, $machine_fmt); } +sub check_machine_config { + my ($conf, $machine_conf) = @_; + my $q35 = $machine_conf->{type} && ($machine_conf->{type} =~ m/q35/) ? 1 : 0; + my $kvm = $conf->{kvm}; + my $arch = PVE::QemuServer::get_vm_arch($conf); + $kvm //= 1 if PVE::QemuServer::is_native($arch); + if ($machine_conf->{viommu} && $machine_conf->{viommu} eq "intel" && !$q35) { + die "to use Intel vIOMMU please set the machine type to q35\n"; + } +} + sub machine_type_is_q35 { my ($conf) = @_; diff --git a/test/cfg2cmd/q35-viommu-intel.conf b/test/cfg2cmd/q35-viommu-intel.conf new file mode 100644 index 0000000..e500ab0 --- /dev/null +++ b/test/cfg2cmd/q35-viommu-intel.conf @@ -0,0 +1 @@ +machine: q35,viommu=intel diff --git a/test/cfg2cmd/q35-viommu-intel.conf.cmd b/test/cfg2cmd/q35-viommu-intel.conf.cmd new file mode 100644 index 0000000..24e873d --- /dev/null +++ b/test/cfg2cmd/q35-viommu-intel.conf.cmd @@ -0,0 +1,23 @@ +/usr/bin/kvm \ + -id 8006 \ + -name 'vm8006,debug-threads=on' \ + -no-shutdown \ + -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \ + -mon 'chardev=qmp,mode=control' \ + -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect=5' \ + -mon 'chardev=qmp-event,mode=control' \ + -pidfile /var/run/qemu-server/8006.pid \ + -daemonize \ + -smp '1,sockets=1,cores=1,maxcpus=1' \ + -nodefaults \ + -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \ + -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \ + -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \ + -m 512 \ + -device 'intel-iommu,intremap=on,caching-mode=on' \ + -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \ + -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \ + -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \ + -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \ + -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \ + -machine 'type=q35+pve0,kernel-irqchip=split' diff --git a/test/cfg2cmd/q35-viommu-virtio.conf b/test/cfg2cmd/q35-viommu-virtio.conf new file mode 100644 index 0000000..d31b339 --- /dev/null +++ b/test/cfg2cmd/q35-viommu-virtio.conf @@ -0,0 +1 @@ +machine: type=q35,viommu=virtio diff --git a/test/cfg2cmd/q35-viommu-virtio.conf.cmd b/test/cfg2cmd/q35-viommu-virtio.conf.cmd new file mode 100644 index 0000000..294c353 --- /dev/null +++ b/test/cfg2cmd/q35-viommu-virtio.conf.cmd @@ -0,0 +1,23 @@ +/usr/bin/kvm \ + -id 8006 \ + -name 'vm8006,debug-threads=on' \ + -no-shutdown \ + -chardev 'socket,id=qmp,path=/var/run/qemu-server/8006.qmp,server=on,wait=off' \ + -mon 'chardev=qmp,mode=control' \ + -chardev 'socket,id=qmp-event,path=/var/run/qmeventd.sock,reconnect=5' \ + -mon 'chardev=qmp-event,mode=control' \ + -pidfile /var/run/qemu-server/8006.pid \ + -daemonize \ + -smp '1,sockets=1,cores=1,maxcpus=1' \ + -nodefaults \ + -boot 'menu=on,strict=on,reboot-timeout=1000,splash=/usr/share/qemu-server/bootsplash.jpg' \ + -vnc 'unix:/var/run/qemu-server/8006.vnc,password=on' \ + -cpu kvm64,enforce,+kvm_pv_eoi,+kvm_pv_unhalt,+lahf_lm,+sep \ + -m 512 \ + -readconfig /usr/share/qemu-server/pve-q35-4.0.cfg \ + -device 'usb-tablet,id=tablet,bus=ehci.0,port=1' \ + -device 'VGA,id=vga,bus=pcie.0,addr=0x1' \ + -device 'virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x3,free-page-reporting=on' \ + -iscsi 'initiator-name=iqn.1993-08.org.debian:01:aabbccddeeff' \ + -device virtio-iommu-pci \ + -machine 'type=q35+pve0' -- 2.39.2 From m.frank at proxmox.com Thu Oct 12 12:02:07 2023 From: m.frank at proxmox.com (Markus Frank) Date: Thu, 12 Oct 2023 12:02:07 +0200 Subject: [pve-devel] [PATCH manager v7 4/4] ui: MachineEdit with viommu ComboBox In-Reply-To: <20231012100207.83441-1-m.frank@proxmox.com> References: <20231012100207.83441-1-m.frank@proxmox.com> Message-ID: <20231012100207.83441-5-m.frank@proxmox.com> Added a proxmoxKVComboBox for selecting a vIOMMU implementation for a VM. If i440fx is selected, a hint tells that q35 is required for Intel vIOMMU. The UI also needs to parse the new machine parameter as PropertyString. Signed-off-by: Markus Frank --- www/manager6/qemu/MachineEdit.js | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/www/manager6/qemu/MachineEdit.js b/www/manager6/qemu/MachineEdit.js index f928c80c..b9e42f14 100644 --- a/www/manager6/qemu/MachineEdit.js +++ b/www/manager6/qemu/MachineEdit.js @@ -1,6 +1,7 @@ Ext.define('PVE.qemu.MachineInputPanel', { extend: 'Proxmox.panel.InputPanel', xtype: 'pveMachineInputPanel', + onlineHelp: 'qm_system_settings', controller: { xclass: 'Ext.app.ViewController', @@ -12,11 +13,14 @@ Ext.define('PVE.qemu.MachineInputPanel', { onMachineChange: function(field, value) { let me = this; let version = me.lookup('version'); + let q35Hint = me.lookup('q35Hint'); let store = version.getStore(); let oldRec = store.findRecord('id', version.getValue(), 0, false, false, true); let type = value === 'q35' ? 'q35' : 'i440fx'; store.clearFilter(); store.addFilter(val => val.data.id === 'latest' || val.data.type === type); + // show hint when Intel vIOMMU cannot be used + q35Hint.setVisible(type === 'i440fx'); if (!me.getView().isWindows) { version.setValue('latest'); } else { @@ -40,12 +44,30 @@ Ext.define('PVE.qemu.MachineInputPanel', { delete values.delete; } delete values.version; + if (values.machine === undefined) { + if (values.viommu) { + delete values.delete; + values.machine = "pc"; + } else { + values.delete = "machine"; + } + } + if (values.viommu) { + values.machine += ",viommu=" + values.viommu; + } + if (values.delete === "viommu") { + delete values.delete; + } + delete values.viommu; return values; }, setValues: function(values) { let me = this; + let machineConf = PVE.Parser.parsePropertyString(values.machine, "type"); + values.machine = machineConf.type; + me.isWindows = values.isWindows; if (values.machine === 'pc') { values.machine = '__default__'; @@ -58,6 +80,9 @@ Ext.define('PVE.qemu.MachineInputPanel', { values.version = 'pc-q35-5.1'; } } + + values.viommu = machineConf.viommu || "__default__"; + if (values.machine !== '__default__' && values.machine !== 'q35') { values.version = values.machine; values.machine = values.version.match(/q35/) ? 'q35' : '__default__'; @@ -113,6 +138,26 @@ Ext.define('PVE.qemu.MachineInputPanel', { fieldLabel: gettext('Note'), value: gettext('Machine version change may affect hardware layout and settings in the guest OS.'), }, + { + xtype: 'proxmoxKVComboBox', + fieldLabel: gettext('vIOMMU'), + name: 'viommu', + reference: 'viommu', + value: '__default__', + comboItems: [ + ['__default__', 'none'], + ['intel', 'Intel'], + ['virtio', 'VirtIO'], + ], + }, + { + xtype: 'displayfield', + name: 'q35Hint', + reference: 'q35Hint', + userCls: 'pmx-hint', + value: gettext('Intel vIOMMU needs the q35 machine type'), + hidden: true, + }, ], }); -- 2.39.2 From m.frank at proxmox.com Thu Oct 12 12:02:04 2023 From: m.frank at proxmox.com (Markus Frank) Date: Thu, 12 Oct 2023 12:02:04 +0200 Subject: [pve-devel] [PATCH qemu-server v7 1/4] machine as property-string In-Reply-To: <20231012100207.83441-1-m.frank@proxmox.com> References: <20231012100207.83441-1-m.frank@proxmox.com> Message-ID: <20231012100207.83441-2-m.frank@proxmox.com> Signed-off-by: Markus Frank --- PVE/API2/Qemu.pm | 9 +++++++-- PVE/QemuConfig.pm | 3 ++- PVE/QemuServer.pm | 16 ++++++--------- PVE/QemuServer/Machine.pm | 42 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 9606e72..93f0f9b 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -1043,11 +1043,13 @@ __PACKAGE__->register_method({ $conf->{vmgenid} = PVE::QemuServer::generate_uuid(); } - my $machine = $conf->{machine}; + my $machine_conf = PVE::QemuServer::Machine::parse_machine($conf->{machine}); + my $machine = $machine_conf->{type}; if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) { # always pin Windows' machine version on create, they get to easily confused if (PVE::QemuServer::Helpers::windows_version($conf->{ostype})) { - $conf->{machine} = PVE::QemuServer::windows_get_pinned_machine_version($machine); + $machine_conf->{type} = PVE::QemuServer::windows_get_pinned_machine_version($machine); + $conf->{machine} = PVE::QemuServer::Machine::print_machine($machine_conf); } } @@ -1880,6 +1882,9 @@ my $update_vm_api = sub { ); } $conf->{pending}->{$opt} = $param->{$opt}; + } elsif ($opt eq 'machine') { + my $machine_conf = PVE::QemuServer::Machine::parse_machine($param->{$opt}); + $conf->{pending}->{$opt} = $param->{$opt}; } else { $conf->{pending}->{$opt} = $param->{$opt}; diff --git a/PVE/QemuConfig.pm b/PVE/QemuConfig.pm index 10e6929..6f86bb1 100644 --- a/PVE/QemuConfig.pm +++ b/PVE/QemuConfig.pm @@ -433,7 +433,8 @@ sub __snapshot_rollback_hook { } else { # Note: old code did not store 'machine', so we try to be smart # and guess the snapshot was generated with kvm 1.4 (pc-i440fx-1.4). - $data->{forcemachine} = $conf->{machine} || 'pc-i440fx-1.4'; + my $machine_conf = PVE::QemuServer::Machine::parse_machine($conf->{machine}); + $data->{forcemachine} = $machine_conf->{type} || 'pc-i440fx-1.4'; # we remove the 'machine' configuration if not explicitly specified # in the original config. diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index bf1de17..7791373 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -118,14 +118,6 @@ PVE::JSONSchema::register_standard_option('pve-qm-stateuri', { optional => 1, }); -PVE::JSONSchema::register_standard_option('pve-qemu-machine', { - description => "Specifies the QEMU machine type.", - type => 'string', - pattern => '(pc|pc(-i440fx)?-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|q35|pc-q35-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|virt(?:-\d+(\.\d+)+)?(\+pve\d+)?)', - maxLength => 40, - optional => 1, -}); - # FIXME: remove in favor of just using the INotify one, it's cached there exactly the same way my $nodename_cache; sub nodename { @@ -2204,8 +2196,9 @@ sub qemu_created_version_fixups { # check if we need to apply some handling for VMs that always use the latest machine version but # had a machine version transition happen that affected HW such that, e.g., an OS config change # would be required (we do not want to pin machine version for non-windows OS type) + my $machine_conf = PVE::QemuServer::Machine::parse_machine($conf->{machine}); if ( - (!defined($conf->{machine}) || $conf->{machine} =~ m/^(?:pc|q35|virt)$/) # non-versioned machine + (!defined($machine_conf->{type}) || $machine_conf->{type} =~ m/^(?:pc|q35|virt)$/) # non-versioned machine && (!defined($meta->{'creation-qemu'}) || !min_version($meta->{'creation-qemu'}, 6, 1)) # created before 6.1 && (!$forced_vers || min_version($forced_vers, 6, 1)) # handle snapshot-rollback/migrations && min_version($kvmver, 6, 1) # only need to apply the change since 6.1 @@ -3364,7 +3357,8 @@ sub windows_get_pinned_machine_version { sub get_vm_machine { my ($conf, $forcemachine, $arch, $add_pve_version, $kvmversion) = @_; - my $machine = $forcemachine || $conf->{machine}; + my $machine_conf = PVE::QemuServer::Machine::parse_machine($conf->{machine}); + my $machine = $forcemachine || $machine_conf->{type}; if (!$machine || $machine =~ m/^(?:pc|q35|virt)$/) { $kvmversion //= kvm_user_version(); @@ -3609,6 +3603,8 @@ sub config_to_command { my $kvm = $conf->{kvm}; my $nodename = nodename(); + my $machine_conf = PVE::QemuServer::Machine::parse_machine($conf->{machine}); + my $arch = get_vm_arch($conf); my $kvm_binary = get_command_for_arch($arch); my $kvmver = kvm_user_version($kvm_binary); diff --git a/PVE/QemuServer/Machine.pm b/PVE/QemuServer/Machine.pm index d9429ed..cb729ca 100644 --- a/PVE/QemuServer/Machine.pm +++ b/PVE/QemuServer/Machine.pm @@ -5,6 +5,7 @@ use warnings; use PVE::QemuServer::Helpers; use PVE::QemuServer::Monitor; +use PVE::JSONSchema qw(get_standard_option parse_property_string); # Bump this for VM HW layout changes during a release (where the QEMU machine # version stays the same) @@ -12,10 +13,46 @@ our $PVE_MACHINE_VERSION = { '4.1' => 2, }; +my $machine_fmt = { + type => { + default_key => 1, + description => "Specifies the QEMU machine type.", + type => 'string', + pattern => '(pc|pc(-i440fx)?-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|q35|pc-q35-\d+(\.\d+)+(\+pve\d+)?(\.pxe)?|virt(?:-\d+(\.\d+)+)?(\+pve\d+)?)', + maxLength => 40, + format_description => 'machine type', + optional => 1, + }, +}; + +PVE::JSONSchema::register_format('pve-qemu-machine-fmt', $machine_fmt); + +PVE::JSONSchema::register_standard_option('pve-qemu-machine', { + description => "Specify the QEMU machine type.", + type => 'string', + optional => 1, + format => PVE::JSONSchema::get_format('pve-qemu-machine-fmt'), +}); + +sub parse_machine { + my ($value) = @_; + + return if !$value; + + my $res = parse_property_string($machine_fmt, $value); + return $res; +} + +sub print_machine { + my ($machine_conf) = @_; + return print_property_string($machine_conf, $machine_fmt); +} + sub machine_type_is_q35 { my ($conf) = @_; - return $conf->{machine} && ($conf->{machine} =~ m/q35/) ? 1 : 0; + my $machine_conf = parse_machine($conf->{machine}); + return $machine_conf->{type} && ($machine_conf->{type} =~ m/q35/) ? 1 : 0; } sub current_from_query_machines { @@ -120,7 +157,8 @@ sub qemu_machine_pxe { my $machine = get_current_qemu_machine($vmid); - if ($conf->{machine} && $conf->{machine} =~ m/\.pxe$/) { + my $machine_conf = parse_machine($conf->{machine}); + if ($machine_conf->{type} && $machine_conf->{type} =~ m/\.pxe$/) { $machine .= '.pxe'; } -- 2.39.2 From f.schauer at proxmox.com Thu Oct 12 15:02:08 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Thu, 12 Oct 2023 15:02:08 +0200 Subject: [pve-devel] [PATCH v2 installer] fix #4869: Make management interface selection more verbose Message-ID: <20231012130208.181749-1-f.schauer@proxmox.com> Display a circle symbol indicating whether a network interface is up or not. Also show the MAC address of each interface in the proxmox-tui-installer, as was already the case in the GUI installer. Signed-off-by: Filip Schauer --- Changes sinve v1: * Use a circle symbol to display the state. GUI: green circle -> UP GUI: hollow circle -> DOWN TUI: green circle -> UP TUI: red circle -> DOWN * Show the MAC address of each network interface in the proxmox-tui-installer Proxmox/Install/RunEnv.pm | 2 ++ debian/control | 1 + proxinstall | 4 +++- proxmox-tui-installer/src/main.rs | 28 ++++++++++++++++++---------- proxmox-tui-installer/src/setup.rs | 2 ++ 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Proxmox/Install/RunEnv.pm b/Proxmox/Install/RunEnv.pm index 9878f55..0dabe3c 100644 --- a/Proxmox/Install/RunEnv.pm +++ b/Proxmox/Install/RunEnv.pm @@ -61,6 +61,7 @@ sub query_cpu_hvm_support : prototype() { # mac => , # index => , # name => , +# state => , # addresses => [ # family => , # address => , @@ -99,6 +100,7 @@ my sub query_netdevs : prototype() { $ifs->{$name} = { index => $index, name => $name, + state => $state, mac => $mac, }; $ifs->{$name}->{addresses} = \@valid_addrs if @valid_addrs; diff --git a/debian/control b/debian/control index 3d13019..f863c91 100644 --- a/debian/control +++ b/debian/control @@ -19,6 +19,7 @@ Homepage: https://www.proxmox.com Package: proxmox-installer Architecture: any Depends: chrony, + fonts-noto-color-emoji, geoip-bin, libgtk3-perl, libgtk3-webkit2-perl, diff --git a/proxinstall b/proxinstall index d5b2565..51170cd 100755 --- a/proxinstall +++ b/proxinstall @@ -347,7 +347,9 @@ sub create_ipconf_view { my $get_device_desc = sub { my $iface = shift; - return "$iface->{name} - $iface->{mac} ($iface->{driver})"; + my $iface_state_symbol = "\N{U+25EF}"; + $iface_state_symbol = "\N{U+1F7E2}" if ($iface->{state} eq "UP"); + return "$iface_state_symbol $iface->{name} - $iface->{mac} ($iface->{driver})"; }; my $run_env = Proxmox::Install::RunEnv::get(); diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index 3f01713..7e46f33 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -548,20 +548,28 @@ fn password_dialog(siv: &mut Cursive) -> InstallerView { fn network_dialog(siv: &mut Cursive) -> InstallerView { let state = siv.user_data::().unwrap(); let options = &state.options.network; - let ifnames = state.runtime_info.network.interfaces.keys(); + + let mut management_interface_select_view = SelectView::new().popup(); + for (key, value) in state.runtime_info.network.interfaces.iter() { + let interface_state_symbol = if value.state == "UP" { + "\x1b[1;92m\u{25CF}" + } else { + "\x1b[1;31m\u{25CF}" + }; + + let interface_label = format!("{0} - {1} {2}", value.name, value.mac, interface_state_symbol); + management_interface_select_view.add_item(interface_label, key.to_string()); + } let inner = FormView::new() .child( "Management interface", - SelectView::new() - .popup() - .with_all_str(ifnames.clone()) - .selected( - ifnames - .clone() - .position(|ifname| ifname == &options.ifname) - .unwrap_or_default(), - ), + management_interface_select_view.selected( + state.runtime_info.network.interfaces.keys() + .clone() + .position(|ifname| ifname == &options.ifname) + .unwrap_or_default(), + ), ) .child( "Hostname (FQDN)", diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs index 942e319..dbff3da 100644 --- a/proxmox-tui-installer/src/setup.rs +++ b/proxmox-tui-installer/src/setup.rs @@ -443,6 +443,8 @@ pub struct Interface { pub index: usize, + pub state: String, + pub mac: String, #[serde(default)] -- 2.39.2 From f.schauer at proxmox.com Thu Oct 12 15:04:11 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Thu, 12 Oct 2023 15:04:11 +0200 Subject: [pve-devel] [PATCH installer 1/1] fix #4869: Show state in management interface ComboBox In-Reply-To: <6d373ac3-4064-0e7f-1190-8ecb6324cc60@proxmox.com> References: <20230804102646.34999-1-f.schauer@proxmox.com> <20230804102646.34999-2-f.schauer@proxmox.com> <2730c950-b0d7-45dc-af83-80ff808da9b7@proxmox.com> <4e4c00c7-5412-4bec-9952-8497f609b71d@proxmox.com> <841632e7-f011-9bb9-f29f-60761e02699d@proxmox.com> <6cfe1ce5-42e9-4965-ab61-e10315062107@proxmox.com> <6d373ac3-4064-0e7f-1190-8ecb6324cc60@proxmox.com> Message-ID: patch v2 available: https://lists.proxmox.com/pipermail/pve-devel/2023-October/059450.html On 11/10/2023 17:20, Filip Schauer wrote: > > On 11/10/2023 12:58, Thomas Lamprecht wrote: >> Am 11/10/2023 um 11:54 schrieb Filip Schauer: >>> The green circle is not displayed correctly by the PVE installer as it >>> does not have the corresponding emoji font package. However, the >>> suggested circles for DOWN are rendered correctly. >> The options I see (on top of that): >> >> - we could just install a font package that ships it, e.g., the >> ?? fonts-noto-color-emoji one, I mean 10.8 MB isn't negligible, but >> ?? neither _that_ big.. >> >> - Use ? for down and ? for up, and color them via CSS, or >> ?? whatever is the easiest here for GTK combobox entries. > > > Installing the font package for a single symbol seems a bit excessive. > However, changing the color of individual characters turns out to be > impossible with combo boxes. At least I haven't found a way to do this > with Gtk's limited CSS functionality, nor with override_color or a > custom cell renderer. > > That leaves us with the font package. > > >> >>> Alternatively we could use an arrow pointing either ? (UP) or ? (DOWN). >>> >>> https://unicode-explorer.com/c/2B06 >>> https://unicode-explorer.com/c/2B07 >>> >>> These arrows are also displayed correctly by the PVE installer. >> I cannot 100% pin it down, but I do not really like arrows for >> conveying that information even though the map to up/down directly, >> but arrows are normally used for rather different things in UI >> context (e.g., sorting, or resize handles), so IMO to overloaded >> already. > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel From t.lamprecht at proxmox.com Thu Oct 12 16:11:28 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Thu, 12 Oct 2023 16:11:28 +0200 Subject: [pve-devel] applied-series: [PATCH debcargo-conf boookworm 0/2] update nettle-sys to 2.2 In-Reply-To: <20231012083748.1751285-1-d.csapak@proxmox.com> References: <20231012083748.1751285-1-d.csapak@proxmox.com> Message-ID: <2601ccbb-0fbb-49e3-bad5-8ef30a01e0ea@proxmox.com> Am 12/10/2023 um 10:37 schrieb Dominik Csapak: > current nettle-sys 2.1 does not support our current packaged bindgen > version (0.66), an update from debian unstable (and backport) fixes that > > this packaged is used by sequoia-openpgp (which is used by > proxmox-offline-mirror) > > Dominik Csapak (2): > update nettle-sys > backport nettle-sys > > src/nettle-sys/debian/changelog | 25 +++++++++++++++++++ > src/nettle-sys/debian/copyright | 4 +-- > src/nettle-sys/debian/copyright.debcargo.hint | 12 ++++----- > .../0001-Avoid-msvc-dependencies.patch | 21 ++++------------ > .../0002-Relax-dependency-on-bindgen.patch | 16 +++++++----- > 5 files changed, 48 insertions(+), 30 deletions(-) > applied, with a bit more context added to the commit message, thanks! From c.heiss at proxmox.com Fri Oct 13 12:36:12 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 13 Oct 2023 12:36:12 +0200 Subject: [pve-devel] [PATCH v2 installer] fix #4869: Make management interface selection more verbose In-Reply-To: <20231012130208.181749-1-f.schauer@proxmox.com> References: <20231012130208.181749-1-f.schauer@proxmox.com> Message-ID: See inline comments. Also, `cargo fmt` please :^) On Thu, Oct 12, 2023 at 03:02:08PM +0200, Filip Schauer wrote: > [..] > diff --git a/proxinstall b/proxinstall > index d5b2565..51170cd 100755 > --- a/proxinstall > +++ b/proxinstall > @@ -347,7 +347,9 @@ sub create_ipconf_view { > > my $get_device_desc = sub { > my $iface = shift; > - return "$iface->{name} - $iface->{mac} ($iface->{driver})"; > + my $iface_state_symbol = "\N{U+25EF}"; > + $iface_state_symbol = "\N{U+1F7E2}" if ($iface->{state} eq "UP"); > + return "$iface_state_symbol $iface->{name} - $iface->{mac} ($iface->{driver})"; ^ Here we have e.g. " eno1 - 12:34:56:78:9a:bc (r8169)". > }; > > my $run_env = Proxmox::Install::RunEnv::get(); > diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs > index 3f01713..7e46f33 100644 > --- a/proxmox-tui-installer/src/main.rs > +++ b/proxmox-tui-installer/src/main.rs > @@ -548,20 +548,28 @@ fn password_dialog(siv: &mut Cursive) -> InstallerView { > fn network_dialog(siv: &mut Cursive) -> InstallerView { > let state = siv.user_data::().unwrap(); > let options = &state.options.network; > - let ifnames = state.runtime_info.network.interfaces.keys(); > + > + let mut management_interface_select_view = SelectView::new().popup(); > + for (key, value) in state.runtime_info.network.interfaces.iter() { > + let interface_state_symbol = if value.state == "UP" { > + "\x1b[1;92m\u{25CF}" > + } else { > + "\x1b[1;31m\u{25CF}" > + }; One thing I noticed while testing the TUI installer is the setting of the color here fsck up the colors of the other entries. Colors/effects applied using ANSI escape sequences do not reset automatically, so this must be done after the unicode symbol manually using "\x1b[1;30m". E.g. with multiple NICS, nics in UP are sometimes displayed entirely in green, in non-UP entirely red. And scrolling through the list changes that completely. [ To properly display colors in Cursive, you'd normally use a `StyledString`, but - after quickly trying it out - seems broken for SelectView (not sure why, I might investigate that some time). ] So although a bit hacky, seems to work here fine (still seems a bit broken under VGA for me, but that is just some random artifact it seems, as 30 is the correct color code in any case). > + > + let interface_label = format!("{0} - {1} {2}", value.name, value.mac, interface_state_symbol); ^ And here we have "eno1 - 12:34:56:78:9a:bc ". Just for the sake of consistency they should be the same between GUI and TUI. I would propose " - ", and just drop the driver part completely in the GUI. The latter does not provide any real value IMO, at least not I could come up with any. > + management_interface_select_view.add_item(interface_label, key.to_string()); > + } > > let inner = FormView::new() > .child( > "Management interface", > - SelectView::new() > - .popup() > - .with_all_str(ifnames.clone()) > - .selected( > - ifnames > - .clone() > - .position(|ifname| ifname == &options.ifname) > - .unwrap_or_default(), > - ), > + management_interface_select_view.selected( > + state.runtime_info.network.interfaces.keys() > + .clone() > + .position(|ifname| ifname == &options.ifname) > + .unwrap_or_default(), > + ), > ) > .child( > "Hostname (FQDN)", > diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs > index 942e319..dbff3da 100644 > --- a/proxmox-tui-installer/src/setup.rs > +++ b/proxmox-tui-installer/src/setup.rs > @@ -443,6 +443,8 @@ pub struct Interface { > > pub index: usize, > > + pub state: String, This can be an enum for cleanliness, as this field is well defined. All possible states as recognized (and thus can be put out) by iproute2 are defined here: https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/ipaddress.c?h=v6.5.0#n119 > + > pub mac: String, > > #[serde(default)] > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > From s.lendl at proxmox.com Fri Oct 13 13:03:47 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 13 Oct 2023 13:03:47 +0200 Subject: [pve-devel] [PATCH manager] SDN: Rename VNet Alias to Comment in the UI Message-ID: <20231013110347.80854-1-s.lendl@proxmox.com> Make this consistent with PVE networking which has a Comment option. Aliases used in VNet are basically comments if the id which is limited to 8 chars is too long. Signed-off-by: Stefan Lendl --- www/manager6/sdn/VnetEdit.js | 2 +- www/manager6/sdn/VnetView.js | 2 +- www/manager6/sdn/ZoneContentView.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/www/manager6/sdn/VnetEdit.js b/www/manager6/sdn/VnetEdit.js index 0f55595f..12642f85 100644 --- a/www/manager6/sdn/VnetEdit.js +++ b/www/manager6/sdn/VnetEdit.js @@ -31,7 +31,7 @@ Ext.define('PVE.sdn.VnetInputPanel', { { xtype: 'textfield', name: 'alias', - fieldLabel: gettext('Alias'), + fieldLabel: gettext('Comment'), allowBlank: true, }, { diff --git a/www/manager6/sdn/VnetView.js b/www/manager6/sdn/VnetView.js index 3fd3c916..5c153f6b 100644 --- a/www/manager6/sdn/VnetView.js +++ b/www/manager6/sdn/VnetView.js @@ -93,7 +93,7 @@ Ext.define('PVE.sdn.VnetView', { }, }, { - header: gettext('Alias'), + header: gettext('Comment'), flex: 1, dataIndex: 'alias', renderer: function(value, metaData, rec) { diff --git a/www/manager6/sdn/ZoneContentView.js b/www/manager6/sdn/ZoneContentView.js index 2a3cbf52..107be97e 100644 --- a/www/manager6/sdn/ZoneContentView.js +++ b/www/manager6/sdn/ZoneContentView.js @@ -62,7 +62,7 @@ Ext.define('PVE.sdn.ZoneContentView', { dataIndex: 'vnet', }, { - header: 'Alias', + header: 'Comment', width: 300, sortable: true, dataIndex: 'alias', -- 2.41.0 From t.lamprecht at proxmox.com Fri Oct 13 13:21:52 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 13 Oct 2023 13:21:52 +0200 Subject: [pve-devel] [PATCH manager] SDN: Rename VNet Alias to Comment in the UI In-Reply-To: <20231013110347.80854-1-s.lendl@proxmox.com> References: <20231013110347.80854-1-s.lendl@proxmox.com> Message-ID: <378f352a-48aa-44f3-af5d-6866f92a8a14@proxmox.com> Am 13/10/2023 um 13:03 schrieb Stefan Lendl: > Make this consistent with PVE networking which has a Comment option. > Aliases used in VNet are basically comments if the id which is limited > to 8 chars is too long. but comments have no effect, these have a real effect, this is set on the interface and tracked by the kernel Also, ifupdown2's interfaces names it alias as it's named alias also in the kernel, so naming this "comment" here makes it harder to make the actual connection to what this refers ? I'd like to avoid such things as it actually makes things less consistent.. From l.wagner at proxmox.com Fri Oct 13 15:33:26 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Fri, 13 Oct 2023 15:33:26 +0200 Subject: [pve-devel] [RFC] towards automated integration testing Message-ID: <1bb25817-66e7-406a-bd4b-0699de6cba31@proxmox.com> Hello, I am currently doing the groundwork that should eventually enable us to write automated integration tests for our products. Part of that endeavor will be to write a custom test runner, which will - setup a specified test environment - execute test cases in that environment - create some sort of test report What will follow is a description of how that test runner would roughly work. The main point is to get some feedback on some of the ideas/ approaches before I start with the actual implementation. Let me know what you think! ## Introduction The goal is to establish a framework that allows us to write automated integration tests for our products. These tests are intended to run in the following situations: - When new packages are uploaded to the staging repos (by triggering a test run from repoman, or similar) - Later, this tests could also be run when patch series are posted to our mailing lists. This requires a mechanism to automatically discover, fetch and build patches, which will be a separate, follow-up project. - Additionally, it should be easy to run these integration tests locally on a developer's workstation in order to write new test cases, as well as troubleshooting and debugging existing test cases. The local test environment should match the one being used for automated testing as closely as possible As a main mode of operation, the Systems under Test (SUTs) will be virtualized on top of a Proxmox VE node. This has the following benefits: - it is easy to create various test setups (fixtures), including but not limited to single Proxmox VE nodes, clusters, Backup servers and auxiliary services (e.g. an LDAP server for testing LDAP authentication) - these test setups can easily be brought to a well-defined state: cloning from a template/restoring a backup/rolling back to snapshot - it makes it easy to run the integration tests on a developers workstation in identical configuration For the sake of completeness, some of the drawbacks of not running the tests on bare-metal: - Might be unable to detect regressions that only occur on real hardware In theory, the test runner would also be able to drive tests on real hardware, but of course with some limitations (harder to have a predictable, reproducible environment, etc.) ## Terminology - Template: A backup/VM template that can be instantiated by the test runner - Test Case: Some script/executable executed by the test runner, success is determined via exit code. - Fixture: Description of a test setup (e.g. which templates are needed, additional setup steps to run, etc.) ## Approach Test writers write template, fixture, test case definition in declarative configuration files (most likely TOML). The test case references a test executable/script, which performs the actual test. The test script is executed by the test runner; the test outcome is determined by the exit code of the script. Test scripts could be written in any language, e.g. they could be Perl scripts that use the official `libpve-apiclient-perl` to test-drive the SUTs. If we notice any emerging patterns, we could write additional helper libs that reduce the amount of boilerplate in test scripts. In essence, the test runner would do the following: - Group testcases by fixture - For every fixture: - Instantiate needed templates from their backup snapshot - Start VMs - Run any specified `setup-hooks` (update system, deploy packages, etc.) - Take a snapshot, including RAM - For every testcase using that fixture: - Run testcase (execute test executable, check exit code) - Rollback to snapshot (iff `rollback = true` for that template) - destroy test instances (or at least those which are not needed by other fixtures) In the beginning, the test scripts would primarily drive the Systems under Test (SUTs) via their API. However, the system would also offer the flexibility for us to venture into the realm of automated GUI testing at some point (e.g. using selenium) - without having to change the overall test architecture. ## Mock Test Runner Config Beside the actual test scripts, test writers would write test configuration. Based on the current requirements and approach that I have chose, a example config *could* look like the one following. These would likely be split into multiple files/folders (e.g. to group test case definition and the test script logically). ```toml [template.pve-default] # Backup image to restore from, in this case this would be a previously # set up PVE installation restore = '...' # To check if node is booted successfully, also made available to hook # scripts, in case they need to SSH in to setup things. ip = "10.0.0.1" # Define credentials in separate file - most template could use a # default password/SSH key/API token etc. credentials = "default" # Update to latest packages, install test .debs # credentials are passed via env var # Maybe this could also be ansible playbooks, if the need arises. setup-hooks = [ "update.sh", ] # Take snapshot after setup-hook, roll back after each test case rollback = true [template.ldap-server] # Backup image to restore from restore = '...' credentials = "default" ip = "10.0.0.3" # No need to roll back in between test cases, there won't be any changes rollback = false # Example fixture. They can be used by multiple testcases. [fixture.pve-with-ldap-server] # Maybe one could specify additional setup-hooks here as well, in case # one wants a 'per-fixture' setup? So that we can reduce the number of # base images? templates = [ 'pve-default', 'ldap-server', ] # testcases.toml (might be split to multiple files/folders?) [testcase.test-ldap-realms] fixture = 'pve-with-ldap-server' # - return code is check to determine test case success # - stderr/stdout is captured for the final test report # - some data is passed via env var: # - name of the test case # - template configuration (IPs, credentials, etc.) # - ... test-exec = './test-ldap-realms.pl' # Consider test as failed if test script does not finish fast enough test-timeout = 60 # Additional params for the test script, allowing for parameterized # tests. # Could also turn this into an array and loop over the values, in # order to create multiple test cases from the same definition. test-params = { foo = "bar" } # Second test case, using the same fixture [testcase.test-ldap-something-else] fixture = 'pve-with-ldap-server' test-exec = './test-ldap-something-else.pl' ``` -- - Lukas From f.schauer at proxmox.com Fri Oct 13 15:50:06 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Fri, 13 Oct 2023 15:50:06 +0200 Subject: [pve-devel] [PATCH qemu-server] Fix races with suspended VMs that can wake up Message-ID: <20231013135006.96272-1-f.schauer@proxmox.com> Fix races with ACPI-suspended VMs which could wake up during migration or during a suspend-mode backup. Revert prevention, of ACPI-suspended VMs automatically resuming after migration, introduced by 7ba974a6828d. The commit introduced a potential problem that causes a suspended VM that wakes up during migration to remain paused after the migration finishes. Furthermore the commit increased the race window during the preparation of a suspend-mode backup, when a suspended VM wakes up between the vm_is_paused check in PVE::VZDump::QemuServer::prepare and PVE::VZDump::QemuServer::qga_fs_freeze. This causes the code to skip fs-freeze even if the VM has woken up, potentially leaving the file system in an inconsistent state. To prevent this, do not treat the suspended runstate as paused when migrating or archiving a VM. Signed-off-by: Filip Schauer --- PVE/API2/Qemu.pm | 4 ++-- PVE/QemuMigrate.pm | 4 +++- PVE/QemuServer.pm | 6 +++--- PVE/VZDump/QemuServer.pm | 4 +++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index c8a87f3..9b1d3be 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -3083,7 +3083,7 @@ __PACKAGE__->register_method({ # sending a graceful shutdown command to paused VMs runs into timeouts, and even worse, when # the VM gets resumed later, it still gets the request delivered and powers off - if (PVE::QemuServer::vm_is_paused($vmid)) { + if (PVE::QemuServer::vm_is_paused($vmid, 1)) { if ($param->{forceStop}) { warn "VM is paused - stop instead of shutdown\n"; $shutdown = 0; @@ -3159,7 +3159,7 @@ __PACKAGE__->register_method({ my $node = extract_param($param, 'node'); my $vmid = extract_param($param, 'vmid'); - die "VM is paused - cannot shutdown\n" if PVE::QemuServer::vm_is_paused($vmid); + die "VM is paused - cannot shutdown\n" if PVE::QemuServer::vm_is_paused($vmid, 1); die "VM $vmid not running\n" if !PVE::QemuServer::check_running($vmid); diff --git a/PVE/QemuMigrate.pm b/PVE/QemuMigrate.pm index f41c61f..111eeb0 100644 --- a/PVE/QemuMigrate.pm +++ b/PVE/QemuMigrate.pm @@ -224,7 +224,9 @@ sub prepare { } } - $self->{vm_was_paused} = 1 if PVE::QemuServer::vm_is_paused($vmid); + # Do not treat a suspended VM as paused, as it might wake up + # during migration and remain paused after migration finishes. + $self->{vm_was_paused} = 1 if PVE::QemuServer::vm_is_paused($vmid, 0); } my ($loc_res, $mapped_res, $missing_mappings_by_node) = PVE::QemuServer::check_local_resources($conf, 1); diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 2895675..2cd8948 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -8497,7 +8497,7 @@ sub complete_migration_storage { } sub vm_is_paused { - my ($vmid) = @_; + my ($vmid, $include_suspended) = @_; my $qmpstatus = eval { PVE::QemuConfig::assert_config_exists_on_node($vmid); mon_cmd($vmid, "query-status"); @@ -8505,8 +8505,8 @@ sub vm_is_paused { warn "$@\n" if $@; return $qmpstatus && ( $qmpstatus->{status} eq "paused" || - $qmpstatus->{status} eq "suspended" || - $qmpstatus->{status} eq "prelaunch" + $qmpstatus->{status} eq "prelaunch" || + ($include_suspended && $qmpstatus->{status} eq "suspended") ); } diff --git a/PVE/VZDump/QemuServer.pm b/PVE/VZDump/QemuServer.pm index b38d7e7..f811cbf 100644 --- a/PVE/VZDump/QemuServer.pm +++ b/PVE/VZDump/QemuServer.pm @@ -67,7 +67,9 @@ sub prepare { $self->{vm_was_paused} = 0; if (!PVE::QemuServer::check_running($vmid)) { $self->{vm_was_running} = 0; - } elsif (PVE::QemuServer::vm_is_paused($vmid)) { + } elsif (PVE::QemuServer::vm_is_paused($vmid, 0)) { + # Do not treat a suspended VM as paused, as it would cause us to skip + # fs-freeze even if the VM wakes up before we reach qga_fs_freeze. $self->{vm_was_paused} = 1; } -- 2.39.2 From ykonotopov at gnome.org Sun Oct 15 20:32:15 2023 From: ykonotopov at gnome.org (ykonotopov at gnome.org) Date: Sun, 15 Oct 2023 22:32:15 +0400 Subject: [PATCH v2 storage 0/1] iscsi: multiple target portals support Message-ID: <20231015183216.86220-1-ykonotopov@gnome.org> From: Yuri Konotopov Hello! Here is v2 patch that allows to specify multiple comma-separated ISCSI portals using Proxmox UI, adds support for storing discovered portals and adds ability to login to all discovered portals in case some of them are not logged yet. With that patch my Proxmox instances don't loses iscsi multipath storage in UI and successfully restores iscsi storage on reboot. Relevant bug: https://bugzilla.proxmox.com/show_bug.cgi?id=254 --- changes since v1: * added configuration file that stores discovered portals * implemented iscsi login in case there missing sessions for any of portals * style fixes * use existent `-list` property format instead of custom one * use PVE::Tools::split_list() instead of custom split function Yuri Konotopov (1): fix #254: iscsi: allow to configure multiple portals PVE/API2/Storage/Scan.pm | 2 +- PVE/Storage.pm | 10 +- PVE/Storage/ISCSIPlugin.pm | 207 ++++++++++++++++++++++++++++++++----- 3 files changed, 187 insertions(+), 32 deletions(-) -- 2.41.0 From ykonotopov at gnome.org Sun Oct 15 20:32:16 2023 From: ykonotopov at gnome.org (ykonotopov at gnome.org) Date: Sun, 15 Oct 2023 22:32:16 +0400 Subject: [PATCH v2 storage 1/1] fix #254: iscsi: allow to configure multiple portals In-Reply-To: <20231015183216.86220-1-ykonotopov@gnome.org> References: <20231015183216.86220-1-ykonotopov@gnome.org> Message-ID: <20231015183216.86220-2-ykonotopov@gnome.org> From: Yuri Konotopov Signed-off-by: Yuri Konotopov --- PVE/API2/Storage/Scan.pm | 2 +- PVE/Storage.pm | 10 +- PVE/Storage/ISCSIPlugin.pm | 207 ++++++++++++++++++++++++++++++++----- 3 files changed, 187 insertions(+), 32 deletions(-) diff --git a/PVE/API2/Storage/Scan.pm b/PVE/API2/Storage/Scan.pm index d7a8743..1f9773c 100644 --- a/PVE/API2/Storage/Scan.pm +++ b/PVE/API2/Storage/Scan.pm @@ -305,7 +305,7 @@ __PACKAGE__->register_method({ node => get_standard_option('pve-node'), portal => { description => "The iSCSI portal (IP or DNS name with optional port).", - type => 'string', format => 'pve-storage-portal-dns', + type => 'string', format => 'pve-storage-portal-dns-list', }, }, }, diff --git a/PVE/Storage.pm b/PVE/Storage.pm index cec3996..0043507 100755 --- a/PVE/Storage.pm +++ b/PVE/Storage.pm @@ -1428,12 +1428,14 @@ sub resolv_portal { sub scan_iscsi { my ($portal_in) = @_; - my $portal; - if (!($portal = resolv_portal($portal_in))) { - die "unable to parse/resolve portal address '${portal_in}'\n"; + my @portals = PVE::Tools::split_list($portal_in); + for my $portal (@portals) { + if (!resolv_portal($portal)) { + die "unable to parse/resolve portal address '${portal}'\n"; + } } - return PVE::Storage::ISCSIPlugin::iscsi_discovery($portal); + return PVE::Storage::ISCSIPlugin::iscsi_discovery(\@portals); } sub storage_default_format { diff --git a/PVE/Storage/ISCSIPlugin.pm b/PVE/Storage/ISCSIPlugin.pm index a79fcb0..6f4309f 100644 --- a/PVE/Storage/ISCSIPlugin.pm +++ b/PVE/Storage/ISCSIPlugin.pm @@ -18,6 +18,65 @@ use base qw(PVE::Storage::Plugin); my $ISCSIADM = '/usr/bin/iscsiadm'; $ISCSIADM = undef if ! -X $ISCSIADM; +my $iscsi_cfg = "/etc/pve/iscsi.cfg"; + +sub read_config { + my ($filename, $raw) = @_; + + my $cfg = {}; + + return $cfg if ! -f $iscsi_cfg; + + my $content = PVE::Tools::file_get_contents($iscsi_cfg); + return $cfg if !defined($content); + + my @lines = split /\n/, $content; + + my $target; + + for my $line (@lines) { + $line =~ s/#.*$//; + $line =~ s/^\s+//; + $line =~ s/^;.*$//; + $line =~ s/\s+$//; + next if !$line; + + $target = $1 if $line =~ m/^\[(\S+)\]$/; + if (!$target) { + warn "no target - skip: $line\n"; + next; + } + + if (!defined($cfg->{$target})) { + $cfg->{$target} = []; + } + + if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)$/) { + push @{$cfg->{$target}}, $1; + } + } + + return $cfg; +} + +sub write_config { + my ($cfg) = @_; + + my $out = ''; + + for my $target (sort keys %$cfg) { + $out .= "[$target]\n"; + for my $portal (sort @{$cfg->{$target}}) { + $out .= "$portal\n"; + } + $out .= "\n"; + } + + PVE::Tools::file_set_contents($iscsi_cfg, $out); + + return; +} + sub check_iscsi_support { my $noerr = shift; @@ -45,11 +104,10 @@ sub iscsi_session_list { eval { run_command($cmd, errmsg => 'iscsi session scan failed', outfunc => sub { my $line = shift; - - if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)(\s+\S+)?\s*$/) { - my ($session, $target) = ($1, $2); + if ($line =~ m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)(\s+\S+)?\s*$/) { + my ($session, $portal, $target) = ($1, $2, $3); # there can be several sessions per target (multipath) - push @{$res->{$target}}, $session; + push @{$res->{$target}}, [$session, $portal]; } }); }; @@ -68,42 +126,68 @@ sub iscsi_test_portal { return PVE::Network::tcp_ping($server, $port || 3260, 2); } -sub iscsi_discovery { - my ($portal) = @_; +sub iscsi_portals { + my ($target) = @_; check_iscsi_support (); - my $res = {}; - return $res if !iscsi_test_portal($portal); # fixme: raise exception here? - - my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; + my $res = []; + my $cmd = [$ISCSIADM, '--mode', 'node']; run_command($cmd, outfunc => sub { my $line = shift; if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { - my $portal = $1; - my $target = $2; - # one target can have more than one portal (multipath). - push @{$res->{$target}}, $portal; + my ($portal, $portal_target) = ($1, $2); + if ($portal_target eq $target) { + push @{$res}, $portal; + } } }); return $res; } +sub iscsi_discovery { + my ($portals) = @_; + + check_iscsi_support (); + + my $res = {}; + for my $portal ($portals->@*) { + next if !iscsi_test_portal($portal); # fixme: raise exception here? + + my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; + run_command($cmd, outfunc => sub { + my $line = shift; + + if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { + my $portal = $1; + my $target = $2; + # one target can have more than one portal (multipath). + push @{$res->{$target}}, $portal; + } + }); + + # In case of multipath we want to exit on any portal available + last; + } + + return $res; +} + sub iscsi_login { - my ($target, $portal_in) = @_; + my ($target, $portals) = @_; check_iscsi_support(); - eval { iscsi_discovery($portal_in); }; + eval { iscsi_discovery($portals); }; warn $@ if $@; run_command([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login']); } sub iscsi_logout { - my ($target, $portal) = @_; + my ($target) = @_; check_iscsi_support(); @@ -133,7 +217,7 @@ sub iscsi_session_rescan { } foreach my $session (@$session_list) { - my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session, '--rescan']; + my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session->@[0], '--rescan']; eval { run_command($cmd, outfunc => sub {}); }; warn $@ if $@; } @@ -245,8 +329,8 @@ sub properties { type => 'string', }, portal => { - description => "iSCSI portal (IP or DNS name with optional port).", - type => 'string', format => 'pve-storage-portal-dns', + description => "iSCSI portal (IP or DNS name with optional port). For multipath, multiple portals can be specified.", + type => 'string', format => 'pve-storage-portal-dns-list', }, }; } @@ -264,6 +348,33 @@ sub options { # Storage implementation +sub on_add_hook { + my ($class, $storeid, $scfg, %param) = @_; + + my @portals = PVE::Tools::split_list($scfg->{portal}); + my $target = $scfg->{target}; + + my $portal_cfg = read_config(); + $portal_cfg->{$target} = \@portals; + + write_config($portal_cfg); + + return; +} + +sub on_delete_hook { + my ($class, $storeid, $scfg) = @_; + + my $portal_cfg = read_config(); + my $target = $scfg->{target}; + + delete $portal_cfg->{$target}; + + write_config($portal_cfg); + + return; +} + sub parse_volname { my ($class, $volname) = @_; @@ -365,6 +476,21 @@ sub iscsi_session { return $cache->{iscsi_sessions}->{$target}; } +sub iscsi_session_portals { + my ($cache, $target) = @_; + + my $res = []; + my $sessions = iscsi_session($cache, $target); + + if (defined($sessions)) { + for my $session ($sessions->@*) { + push @{$res}, $session->@[1]; + } + } + + return $res; +} + sub status { my ($class, $storeid, $scfg, $cache) = @_; @@ -379,14 +505,37 @@ sub activate_storage { return if !check_iscsi_support(1); - my $session = iscsi_session($cache, $scfg->{target}); - if (!defined ($session)) { - eval { iscsi_login($scfg->{target}, $scfg->{portal}); }; + my $sessions = iscsi_session($cache, $scfg->{target}); + my $portal_cfg = read_config(); + + my @portals = PVE::Tools::split_list($scfg->{portal}); + if (defined($portal_cfg->{$scfg->{target}})) { + @portals = @{$portal_cfg->{$scfg->{target}}}; + } + + if (!defined ($sessions)) { + eval { iscsi_login($scfg->{target}, \@portals); }; warn $@ if $@; } else { + my @discovered_portals = @{iscsi_portals($scfg->{target})}; + my @session_portals = @{iscsi_session_portals($cache, $scfg->{target})}; + + for my $discovered_portal (@discovered_portals) { + if (!grep(/^\Q$discovered_portal\E$/, @session_portals)) { + eval { iscsi_login($scfg->{target}, \@discovered_portals); }; + warn $@ if $@; + last; + } + } + + if(join(",", sort(@discovered_portals)) ne join(",", sort(@portals))) { + $portal_cfg->{$scfg->{target}} = \@discovered_portals; + write_config($portal_cfg); + } + # make sure we get all devices - iscsi_session_rescan($session); + iscsi_session_rescan($sessions); } } @@ -396,15 +545,19 @@ sub deactivate_storage { return if !check_iscsi_support(1); if (defined(iscsi_session($cache, $scfg->{target}))) { - iscsi_logout($scfg->{target}, $scfg->{portal}); + iscsi_logout($scfg->{target}); } } sub check_connection { my ($class, $storeid, $scfg) = @_; - my $portal = $scfg->{portal}; - return iscsi_test_portal($portal); + for my $portal (PVE::Tools::split_list($scfg->{portal})) { + my $result = iscsi_test_portal($portal); + return $result if $result; + } + + return 0; } sub volume_resize { -- 2.41.0 From ykonotopov at gnome.org Sat Oct 14 08:54:06 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Sat, 14 Oct 2023 10:54:06 +0400 Subject: [PATCH storage 1/1] iscsi: allow to configure multiple portals In-Reply-To: <46997106-a66c-cc16-15b8-a72d2fe7dac5@proxmox.com> References: <20230816115627.59681-1-ykonotopov@gnome.org> <20230816115627.59681-2-ykonotopov@gnome.org> <46997106-a66c-cc16-15b8-a72d2fe7dac5@proxmox.com> Message-ID: 10.10.2023 16:54, Fiona Ebner ?????: > Hi, Hi, Fiona! Thanks for your review! I will send updated patch soon. > thank you for the contribution! Please prepend the commit title with > "fix #254: ". > > Am 16.08.23 um 13:56 schrieb ykonotopov at gnome.org: >> From: Yuri Konotopov >> >> Fixes: https://bugzilla.proxmox.com/show_bug.cgi?id=254 > To accept contributions, we need your Signed-off-by trailer here and you > need to agree to the Harmony CLA (assuming you haven't sent it to us > already): > https://pve.proxmox.com/wiki/Developer_Documentation#Software_License_and_Copyright Got it, will add Signed-off-by trailer. As for CLA - I signed it before sending this patch -- Best regards, Yuri Konotopov From f.schauer at proxmox.com Mon Oct 16 12:20:06 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Mon, 16 Oct 2023 12:20:06 +0200 Subject: [pve-devel] [PATCH installer] Correct DNS IP check on management interface setup Message-ID: <20231016102006.18064-1-f.schauer@proxmox.com> Signed-off-by: Filip Schauer --- proxinstall | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/proxinstall b/proxinstall index d5b2565..764187f 100755 --- a/proxinstall +++ b/proxinstall @@ -481,8 +481,8 @@ sub create_ipconf_view { $text = $ipconf_entry_dns->get_text(); my ($dns_ip, $dns_ip_version) = parse_ip_address($text); if (!defined($dns_ip) || $dns_ip_version != $ipversion) { - my $msg = defined($gateway_ip) - ? "DNS and host IP version must not differ (IPv$gateway_ip_version != IPv$ipversion)." + my $msg = defined($dns_ip) + ? "DNS and host IP version must not differ (IPv$dns_ip_version != IPv$ipversion)." : "DNS IP is not valid."; Proxmox::UI::message($msg); $ipconf_entry_dns->grab_focus(); -- 2.39.2 From s.hanreich at proxmox.com Mon Oct 16 13:20:18 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Mon, 16 Oct 2023 13:20:18 +0200 Subject: [pve-devel] [RFC] towards automated integration testing In-Reply-To: <1bb25817-66e7-406a-bd4b-0699de6cba31@proxmox.com> References: <1bb25817-66e7-406a-bd4b-0699de6cba31@proxmox.com> Message-ID: <44f89ce6-043f-7b05-75ad-ac66550eb3e8@proxmox.com> On 10/13/23 15:33, Lukas Wagner wrote: > - Additionally, it should be easy to run these integration tests locally > on a developer's workstation in order to write new test cases, as well > as troubleshooting and debugging existing test cases. The local > test environment should match the one being used for automated testing > as closely as possible This would also include sharing those fixture templates somewhere, do you already have an idea on how to accomplish this? PBS sounds like a good option for this if I'm not missing something. > As a main mode of operation, the Systems under Test (SUTs) > will be virtualized on top of a Proxmox VE node. > > This has the following benefits: > - it is easy to create various test setups (fixtures), including but not > limited to single Proxmox VE nodes, clusters, Backup servers and > auxiliary services (e.g. an LDAP server for testing LDAP > authentication) I can imagine having to setup VMs inside the Test Setup as well for doing various tests. Doing this manually every time could be quite cumbersome / hard to automate. Do you have a mechanism in mind to deploy VMs inside the test system as well? Again, PBS could be an interesting option for this imo. > In theory, the test runner would also be able to drive tests on real > hardware, but of course with some limitations (harder to have a > predictable, reproducible environment, etc.) Maybe utilizing Aaron's installer for setting up those test systems could at least produce somewhat identical setups? Although it is really hard managing systems with different storage types, network cards, ... . I've seen GitLab using tags for runners that specify certain capabilities of systems. Maybe we could also introduce something like that here for different bare-metal systems? E.g. a test case specifies it needs a system with tag `ZFS` and then you can run / skip the respective test case on that system. Managing those tags can introduce quite a lot of churn though, so I'm not sure if this would be a good idea. > The test script is executed by the test runner; the test outcome is > determined by the exit code of the script. Test scripts could be written Are you considering capturing output as well? That would make sense when using assertions at least, so in case of failures developers have a starting point for debugging. Would it make sense to allow specifying a expected exit code for tests that actually should fail - or do you consider this something that should be handled by the test script? I've refrained from talking about the toml files too much since it's probably too early to say something about that, but they look good so far from my pov. In general this sounds like quite the exciting feature and the RFC looks very promising already. Kind Regards Stefan From f.schauer at proxmox.com Mon Oct 16 13:29:55 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Mon, 16 Oct 2023 13:29:55 +0200 Subject: [pve-devel] [PATCH v2 installer] fix #4869: Make management interface selection more verbose In-Reply-To: References: <20231012130208.181749-1-f.schauer@proxmox.com> Message-ID: On 13/10/2023 12:36, Christoph Heiss wrote: > See inline comments. Also, `cargo fmt` please :^) > > On Thu, Oct 12, 2023 at 03:02:08PM +0200, Filip Schauer wrote: >> [..] >> diff --git a/proxinstall b/proxinstall >> index d5b2565..51170cd 100755 >> --- a/proxinstall >> +++ b/proxinstall >> @@ -347,7 +347,9 @@ sub create_ipconf_view { >> >> my $get_device_desc = sub { >> my $iface = shift; >> - return "$iface->{name} - $iface->{mac} ($iface->{driver})"; >> + my $iface_state_symbol = "\N{U+25EF}"; >> + $iface_state_symbol = "\N{U+1F7E2}" if ($iface->{state} eq "UP"); >> + return "$iface_state_symbol $iface->{name} - $iface->{mac} ($iface->{driver})"; > ^ Here we have e.g. " eno1 - 12:34:56:78:9a:bc (r8169)". > >> }; >> >> my $run_env = Proxmox::Install::RunEnv::get(); >> diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs >> index 3f01713..7e46f33 100644 >> --- a/proxmox-tui-installer/src/main.rs >> +++ b/proxmox-tui-installer/src/main.rs >> @@ -548,20 +548,28 @@ fn password_dialog(siv: &mut Cursive) -> InstallerView { >> fn network_dialog(siv: &mut Cursive) -> InstallerView { >> let state = siv.user_data::().unwrap(); >> let options = &state.options.network; >> - let ifnames = state.runtime_info.network.interfaces.keys(); >> + >> + let mut management_interface_select_view = SelectView::new().popup(); >> + for (key, value) in state.runtime_info.network.interfaces.iter() { >> + let interface_state_symbol = if value.state == "UP" { >> + "\x1b[1;92m\u{25CF}" >> + } else { >> + "\x1b[1;31m\u{25CF}" >> + }; > One thing I noticed while testing the TUI installer is the > setting of the color here fsck up the colors of the other entries. > Colors/effects applied using ANSI escape sequences do not reset > automatically, so this must be done after the unicode symbol manually > using "\x1b[1;30m". > > E.g. with multiple NICS, nics in UP are sometimes displayed entirely in > green, in non-UP entirely red. And scrolling through the list changes > that completely. > > [ To properly display colors in Cursive, you'd normally use a > `StyledString`, but - after quickly trying it out - seems broken for > SelectView (not sure why, I might investigate that some time). ] > > So although a bit hacky, seems to work here fine (still seems a bit > broken under VGA for me, but that is just some random artifact it seems, > as 30 is the correct color code in any case). > >> + >> + let interface_label = format!("{0} - {1} {2}", value.name, value.mac, interface_state_symbol); > ^ And here we have "eno1 - 12:34:56:78:9a:bc ". > > Just for the sake of consistency they should be the same between GUI and > TUI. > I would propose " - ", and just drop the driver part > completely in the GUI. The latter does not provide any real value IMO, > at least not I could come up with any. In the TUI installer, placing the symbol at the front changes the color of the remaining text that comes after it. Resetting the color with \x1b[1;30m is also not an option since it sets the text color to gray, which makes it inconsistent with the selectable elements and hard to read. >> + management_interface_select_view.add_item(interface_label, key.to_string()); >> + } >> >> let inner = FormView::new() >> .child( >> "Management interface", >> - SelectView::new() >> - .popup() >> - .with_all_str(ifnames.clone()) >> - .selected( >> - ifnames >> - .clone() >> - .position(|ifname| ifname == &options.ifname) >> - .unwrap_or_default(), >> - ), >> + management_interface_select_view.selected( >> + state.runtime_info.network.interfaces.keys() >> + .clone() >> + .position(|ifname| ifname == &options.ifname) >> + .unwrap_or_default(), >> + ), >> ) >> .child( >> "Hostname (FQDN)", >> diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs >> index 942e319..dbff3da 100644 >> --- a/proxmox-tui-installer/src/setup.rs >> +++ b/proxmox-tui-installer/src/setup.rs >> @@ -443,6 +443,8 @@ pub struct Interface { >> >> pub index: usize, >> >> + pub state: String, > This can be an enum for cleanliness, as this field is well defined. All > possible states as recognized (and thus can be put out) by iproute2 are > defined here: > https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/ip/ipaddress.c?h=v6.5.0#n119 > >> + >> pub mac: String, >> >> #[serde(default)] >> -- >> 2.39.2 >> >> >> >> _______________________________________________ >> pve-devel mailing list >> pve-devel at lists.proxmox.com >> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel >> >> > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > From f.ebner at proxmox.com Mon Oct 16 13:43:02 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Mon, 16 Oct 2023 13:43:02 +0200 Subject: [pve-devel] [PATCH qemu-server] Fix races with suspended VMs that can wake up In-Reply-To: <20231013135006.96272-1-f.schauer@proxmox.com> References: <20231013135006.96272-1-f.schauer@proxmox.com> Message-ID: I'd add a bit to the commit message, but changes look good to me: Am 13.10.23 um 15:50 schrieb Filip Schauer: > Fix races with ACPI-suspended VMs which could wake up during migration > or during a suspend-mode backup. > > Revert prevention, of ACPI-suspended VMs automatically resuming after > migration, introduced by 7ba974a6828d. The commit introduced a potential > problem that causes a suspended VM that wakes up during migration to > remain paused after the migration finishes. > This can be fixed once QEMU preserves the 'suspended' runstate during migration (current patch on the qemu-devel list [0]) by checking for the 'suspended' runstate on the target after migration. > Furthermore the commit increased the race window during the preparation > of a suspend-mode backup, when a suspended VM wakes up between the > vm_is_paused check in PVE::VZDump::QemuServer::prepare and > PVE::VZDump::QemuServer::qga_fs_freeze. This causes the code to skip > fs-freeze even if the VM has woken up, potentially leaving the file > system in an inconsistent state. > > To prevent this, do not treat the suspended runstate as paused when > migrating or archiving a VM. > [0]: https://lists.nongnu.org/archive/html/qemu-devel/2023-08/msg05260.html > Signed-off-by: Filip Schauer Reviewed-by: Fiona Ebner From h.duerr at proxmox.com Mon Oct 16 13:59:20 2023 From: h.duerr at proxmox.com (Hannes Duerr) Date: Mon, 16 Oct 2023 13:59:20 +0200 Subject: [pve-devel] [PATCH pve-storage] fix #1611: implement import of base-images for LVM-thin Storage Message-ID: <20231016115920.95859-1-h.duerr@proxmox.com> if a base-image is to be migrated to a lvm-thin storage, a new vm-image is allocated on the target side, then the data is written and afterwards the image is converted to a base-image Signed-off-by: Hannes Duerr --- In the bugtracker wolfgang suggested two different approaches. In my opinion this approach is the cleaner one, but please let me know what you think src/PVE/Storage/LvmThinPlugin.pm | 65 ++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/PVE/Storage/LvmThinPlugin.pm b/src/PVE/Storage/LvmThinPlugin.pm index 1d2e37c..4579d47 100644 --- a/src/PVE/Storage/LvmThinPlugin.pm +++ b/src/PVE/Storage/LvmThinPlugin.pm @@ -383,6 +383,71 @@ sub volume_has_feature { return undef; } +sub volume_import { + my ($class, $scfg, $storeid, $fh, $volname, $format, $snapshot, $base_snapshot, $with_snapshots, $allow_rename) = @_; + die "volume import format $format not available for $class\n" + if $format ne 'raw+size'; + die "cannot import volumes together with their snapshots in $class\n" + if $with_snapshots; + die "cannot import an incremental stream in $class\n" if defined($base_snapshot); + + my ($vtype, $name, $vmid, $basename, $basevmid, $isBase, $file_format) = + $class->parse_volname($volname); + die "cannot import format $format into a file of format $file_format\n" + if $file_format ne 'raw'; + + my $vg = $scfg->{vgname}; + my $lvs = PVE::Storage::LVMPlugin::lvm_list_volumes($vg); + if ($lvs->{$vg}->{$volname}) { + die "volume $vg/$volname already exists\n" if !$allow_rename; + warn "volume $vg/$volname already exists - importing with a different name\n"; + $name = undef; + } + + my ($size) = PVE::Storage::Plugin::read_common_header($fh); + $size = int($size/1024); + + # Request new vm-name which is needed for the import + if ($isBase) { + my $newvmname = $class->find_free_diskname($storeid, $scfg, $vmid); + $name = $newvmname; + $volname = $newvmname; + } + + eval { + my $allocname = $class->alloc_image($storeid, $scfg, $vmid, 'raw', $name, $size); + my $oldname = $volname; + $volname = $allocname; + if (defined($name) && $allocname ne $oldname) { + die "internal error: unexpected allocated name: '$allocname' != '$oldname'\n"; + } + my $file = $class->path($scfg, $volname, $storeid) + or die "internal error: failed to get path to newly allocated volume $volname\n"; + + $class->volume_import_write($fh, $file); + }; + if (my $err = $@) { + my $cleanup_worker = eval { $class->free_image($storeid, $scfg, $volname, 0) }; + warn $@ if $@; + + if ($cleanup_worker) { + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + $rpcenv->fork_worker('imgdel', undef, $authuser, $cleanup_worker); + } + + die $err; + } + + if ($isBase) { + my $newbasename = $class->create_base($storeid, $scfg, $volname); + $volname=$newbasename; + } + + return "$storeid:$volname"; +} + # used in LVMPlugin->volume_import sub volume_import_write { my ($class, $input_fh, $output_file) = @_; -- 2.39.2 From f.ebner at proxmox.com Mon Oct 16 15:12:24 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Mon, 16 Oct 2023 15:12:24 +0200 Subject: [pve-devel] [PATCH-SERIES qemu-server] fix #4522: vncproxy: always set environment variable for ticket Message-ID: <20231016131226.53708-1-f.ebner@proxmox.com> Since commit 2dc0eb61 ("qm: assume correct VNC setup in 'vncproxy', disallow passwordless"), 'qm vncproxy' will just fail when the LC_PVE_TICKET environment variable is not set. Fix the vncproxy API call, which previously, would only set the variable in presence of the 'websocket' parameter. Fiona Ebner (2): api: vncproxy: update description of websocket parameter fix #4522: api: vncproxy: also set environment variable for ticket without websocket PVE/API2/Qemu.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) -- 2.39.2 From f.ebner at proxmox.com Mon Oct 16 15:12:25 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Mon, 16 Oct 2023 15:12:25 +0200 Subject: [pve-devel] [PATCH qemu-server 1/2] api: vncproxy: update description of websocket parameter In-Reply-To: <20231016131226.53708-1-f.ebner@proxmox.com> References: <20231016131226.53708-1-f.ebner@proxmox.com> Message-ID: <20231016131226.53708-2-f.ebner@proxmox.com> Since commit 3e7567e0 ("do not use novnc wsproxy"), the websocket upgrade is done via the HTTP server. Signed-off-by: Fiona Ebner --- PVE/API2/Qemu.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index c8a87f3f..a31ddb81 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -2267,7 +2267,7 @@ __PACKAGE__->register_method({ websocket => { optional => 1, type => 'boolean', - description => "starts websockify instead of vncproxy", + description => "Prepare for websocket upgrade.", }, 'generate-password' => { optional => 1, -- 2.39.2 From f.ebner at proxmox.com Mon Oct 16 15:12:26 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Mon, 16 Oct 2023 15:12:26 +0200 Subject: [pve-devel] [PATCH qemu-server 2/2] fix #4522: api: vncproxy: also set environment variable for ticket without websocket In-Reply-To: <20231016131226.53708-1-f.ebner@proxmox.com> References: <20231016131226.53708-1-f.ebner@proxmox.com> Message-ID: <20231016131226.53708-3-f.ebner@proxmox.com> Since commit 2dc0eb61 ("qm: assume correct VNC setup in 'vncproxy', disallow passwordless"), 'qm vncproxy' will just fail when the LC_PVE_TICKET environment variable is not set. Since it is not only required in combination with websocket, drop that conditional. For the non-serial case, this was the last remaining effect of the 'websocket' parameter, so update the parameter description. Signed-off-by: Fiona Ebner --- PVE/API2/Qemu.pm | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index a31ddb81..9877ce24 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -2267,7 +2267,8 @@ __PACKAGE__->register_method({ websocket => { optional => 1, type => 'boolean', - description => "Prepare for websocket upgrade.", + description => "Prepare for websocket upgrade (only required when using " + ."serial terminal, otherwise upgrade is always possible).", }, 'generate-password' => { optional => 1, @@ -2365,7 +2366,7 @@ __PACKAGE__->register_method({ } else { - $ENV{LC_PVE_TICKET} = $password if $websocket; # set ticket with "qm vncproxy" + $ENV{LC_PVE_TICKET} = $password; # set ticket with "qm vncproxy" $cmd = [@$remcmd, "/usr/sbin/qm", 'vncproxy', $vmid]; -- 2.39.2 From t.lamprecht at proxmox.com Mon Oct 16 15:57:25 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Mon, 16 Oct 2023 15:57:25 +0200 Subject: [pve-devel] [RFC] towards automated integration testing In-Reply-To: <1bb25817-66e7-406a-bd4b-0699de6cba31@proxmox.com> References: <1bb25817-66e7-406a-bd4b-0699de6cba31@proxmox.com> Message-ID: A few things, most of which we talked off-list already anyway. We should eye if we can integrate existing regression testing in there too, i.e.: - The qemu autotest that Stefan Reiter started and Fiona still uses, here we should drop the in-git tracked backup that the test VM is restored from (replace with something like vmdb2 [0] managed Debian image that gets generated on demand), replace some hard coded configs with a simple config and make it public. [0]: https://vmdb2.liw.fi/ - The selenium based end-to-end tests which we also use to generate most screenshots with (they can run headless too). Here we also need a few clean-ups, but not that many, and make the repo public. Am 13/10/2023 um 15:33 schrieb Lukas Wagner:> I am currently doing the groundwork that should eventually enable us > to write automated integration tests for our products. > > Part of that endeavor will be to write a custom test runner, which will > - setup a specified test environment > - execute test cases in that environment This should be decoupled from all else, so that I can run it on any existing installation, bare-metal or not. This allows devs using it in their existing setups with almost no change required. We can then also add it easily in our existing buildbot instance relatively easily, so it would be worth doing so even if we might deprecate Buildbot in the future (for what little it can do, it could be simpler). > - create some sort of test report As Stefan mentioned, test-output can be good to have. Our buildbot instance provides that, and while I don't look at them in 99% of the builds, when I need to its worth *a lot*. > > ## Introduction > > The goal is to establish a framework that allows us to write > automated integration tests for our products. > These tests are intended to run in the following situations: > - When new packages are uploaded to the staging repos (by triggering > a test run from repoman, or similar) *debian repos, as we could also trigger some when git commits are pushed, just like we do now through Buildbot. Doing so is IMO nice as it will catch issues before a package was bumped, but is still quite a bit simpler to implement than an "apply patch from list to git repos" thing from the next point, but could still act as a preparation for that. > - Later, this tests could also be run when patch series are posted to > our mailing lists. This requires a mechanism to automatically > discover, fetch and build patches, which will be a separate, > follow-up project. > > As a main mode of operation, the Systems under Test (SUTs) > will be virtualized on top of a Proxmox VE node. For the fully-automated test system this can be OK as primary mode, as it indeed makes things like going back to an older software state much easier. But, if we decouple the test harness and running them from that more automated system, we can also run the harness periodically on our bare-metal test servers. > ## Terminology > - Template: A backup/VM template that can be instantiated by the test > runner I.e., the base of the test host? I'd call this test-host, template is a bit to overloaded/generic and might focus too much on the virtual test environment. Or is this some part that takes place in the test, i.e., a generalization of product to test and supplementary tool/app that helps on that test? Hmm, could work out ok, and we should be able to specialize stuff relatively easier later too, if wanted. > - Test Case: Some script/executable executed by the test runner, success > is determined via exit code. > - Fixture: Description of a test setup (e.g. which templates are needed, > additional setup steps to run, etc.) > > ## Approach > Test writers write template, fixture, test case definition in > declarative configuration files (most likely TOML). The test case > references a test executable/script, which performs the actual test. > > The test script is executed by the test runner; the test outcome is > determined by the exit code of the script. Test scripts could be written > in any language, e.g. they could be Perl scripts that use the official > `libpve-apiclient-perl` to test-drive the SUTs. > If we notice any emerging patterns, we could write additional helper > libs that reduce the amount of boilerplate in test scripts. > > In essence, the test runner would do the following: > - Group testcases by fixture > - For every fixture: > - Instantiate needed templates from their backup snapshot Should be optional, possible a default-on boolean option that conveys > - Start VMs Same. > - Run any specified `setup-hooks` (update system, deploy packages, > etc.) Should be as idempotent as possible. > - Take a snapshot, including RAM Should be optional (as in, don't care if it cannot be done, e.g., on bare metal). > - For every testcase using that fixture: > - Run testcase (execute test executable, check exit code) > - Rollback to snapshot (iff `rollback = true` for that template) > - destroy test instances (or at least those which are not needed by > other fixtures) Might be optional for l1 hosts, l2 test VMs might be a separate switch. > In the beginning, the test scripts would primarily drive the Systems > under Test (SUTs) via their API. However, the system would also offer > the flexibility for us to venture into the realm of automated GUI > testing at some point (e.g. using selenium) - without having to > change the overall test architecture. Our existing selenium based UI test simple use the API to create stuff that it needs, if it's not existing, and sometimes remove also some. It uses some special ranges or values to avoid most conflicts with real systems, allowing one to point it at existing (production) systems without problems. IMO this has a big value, and I actually added a bit of resiliency, as I find that having to set up clean states a bit annoying and for one of the main use cases of that tooling, creating screenshots, too sterile. But always starting out from a very clean state is IMO not only "ugly" for screenshots, but can also sometimes mas issues that test can run into on systems with a longer uptime and the "organic mess" that comes from long-term maintenance. In practice one naturally wants both, starting from a clean state and from existing one, both have their advantages and disadvantages. Like messy systems also might have more false-positives on regression tracking. > > ## Mock Test Runner Config > > Beside the actual test scripts, test writers would write test > configuration. Based on the current requirements and approach that > I have chose, a example config *could* look like the one following. > These would likely be split into multiple files/folders > (e.g. to group test case definition and the test script logically). > > ```toml > [template.pve-default] > # Backup image to restore from, in this case this would be a previously > # set up PVE installation > restore = '...' > # To check if node is booted successfully, also made available to hook > # scripts, in case they need to SSH in to setup things. > ip = "10.0.0.1" > # Define credentials in separate file - most template could use a > # default password/SSH key/API token etc. > credentials = "default" > # Update to latest packages, install test .debs > # credentials are passed via env var > # Maybe this could also be ansible playbooks, if the need arises. fwiw, one could also define a config-deployment-system, like - none (already is setup) - cloudinit - QGA but that can be added later on too. > setup-hooks = [ > "update.sh", > ] > # Take snapshot after setup-hook, roll back after each test case > rollback = true > > > [template.ldap-server] > # Backup image to restore from > restore = '...' > credentials = "default" > ip = "10.0.0.3" > # No need to roll back in between test cases, there won't be any changes > rollback = false > > > > # Example fixture. They can be used by multiple testcases. > [fixture.pve-with-ldap-server] > # Maybe one could specify additional setup-hooks here as well, in case > # one wants a 'per-fixture' setup? So that we can reduce the number of > # base images? > templates = [ > 'pve-default', > 'ldap-server', > ] > > > # testcases.toml (might be split to multiple files/folders?) maybe some sort of predicates could be also nice (even if not there from the start), like to place condition where a test is skipped if that's not met, like the existence of a ZFS-storage or something like that. While those seem like details, having a general (simple) dependency and, so to say, anti-dependency system might influence overall design more. > [testcase.test-ldap-realms] > fixture = 'pve-with-ldap-server' > > # - return code is check to determine test case success > # - stderr/stdout is captured for the final test report > # - some data is passed via env var: > # - name of the test case > # - template configuration (IPs, credentials, etc.) > # - ... > test-exec = './test-ldap-realms.pl' > # Consider test as failed if test script does not finish fast enough > test-timeout = 60 > # Additional params for the test script, allowing for parameterized > # tests. > # Could also turn this into an array and loop over the values, in > # order to create multiple test cases from the same definition. > test-params = { foo = "bar" } > > # Second test case, using the same fixture > [testcase.test-ldap-something-else] > fixture = 'pve-with-ldap-server' > test-exec = './test-ldap-something-else.pl' > > ``` > Is the order of test-cases guaranteed by toml parsing, or how are intra- fixture dependencies ensured? Anyway, the most important thing is to start out here, so I don't want to block anything on base on minorish stuff. The most important thing for me is that the following parts are decoupled and ideally shippable by a separate debian package each: - parts that manage automated testing, including how the test host base system is set up (the latter could be even its own thing) - running test itself inclusive some helper modules/scripts - the test definitions As then we can run them anywhere easily and extend, or possible even rework some parts independently, if ever needed. - Thomas From d.csapak at proxmox.com Mon Oct 16 15:58:52 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Mon, 16 Oct 2023 15:58:52 +0200 Subject: [pve-devel] [PATCH v2 storage 1/1] fix #254: iscsi: allow to configure multiple portals In-Reply-To: <20231015183216.86220-2-ykonotopov@gnome.org> References: <20231015183216.86220-1-ykonotopov@gnome.org> <20231015183216.86220-2-ykonotopov@gnome.org> Message-ID: <0314d5f0-a08b-41dc-b027-90ff8a44c041@proxmox.com> hi, a few high level comments: first: is the iscsi.cfg really necessary? couldn't we just lookup on demand like we do now? *if* we want to cache that list, we should only do this on a per node level, since there is no guarantee that this is the same for all nodes. (e.g. use /var/run/..../scsi.cache) but really i'm in favor of dropping that altogether (except you can provide a good argument why it's necessary) this would remove a lot of code from this patch second: i would favor an approach that uses an 'array' instead of the '-list' types. i'm not completely sure if one can just have that as a drop in replacement with old configs/api calls still working. if not, we could introduce a new 'portals' api parameter/storage config that is an array which could take precedence over the old 'portal' one. (that we could drop with the next major release) in that case we could even automatically convert from portal -> portals if we detect that in the api update and a few (basic) comments inline: On 10/15/23 20:32, ykonotopov at gnome.org wrote: > From: Yuri Konotopov > it would be great to have a real commit message here. best would be a short description of what the patch wants to achieve, and maybe some more tricky implementation detail that is not obvious (most of what you wrote in the cover letter would be fine, sans the changelog + mention of 'Here is v2 patch' > Signed-off-by: Yuri Konotopov > --- > PVE/API2/Storage/Scan.pm | 2 +- > PVE/Storage.pm | 10 +- > PVE/Storage/ISCSIPlugin.pm | 207 ++++++++++++++++++++++++++++++++----- > 3 files changed, 187 insertions(+), 32 deletions(-) > > diff --git a/PVE/API2/Storage/Scan.pm b/PVE/API2/Storage/Scan.pm > index d7a8743..1f9773c 100644 > --- a/PVE/API2/Storage/Scan.pm > +++ b/PVE/API2/Storage/Scan.pm > @@ -305,7 +305,7 @@ __PACKAGE__->register_method({ > node => get_standard_option('pve-node'), > portal => { > description => "The iSCSI portal (IP or DNS name with optional port).", > - type => 'string', format => 'pve-storage-portal-dns', > + type => 'string', format => 'pve-storage-portal-dns-list', > }, > }, > }, > diff --git a/PVE/Storage.pm b/PVE/Storage.pm > index cec3996..0043507 100755 > --- a/PVE/Storage.pm > +++ b/PVE/Storage.pm > @@ -1428,12 +1428,14 @@ sub resolv_portal { > sub scan_iscsi { > my ($portal_in) = @_; > > - my $portal; > - if (!($portal = resolv_portal($portal_in))) { > - die "unable to parse/resolve portal address '${portal_in}'\n"; > + my @portals = PVE::Tools::split_list($portal_in); not a big deal, but we usually pack that into a reference immediately: my $portals = [ <...>::split_list(...) ]; then you can do for my $portal (@$portals) or for my $portal ($portals->@*) and don't have to create a reference later on when passing to iscsi_discovery > + for my $portal (@portals) { > + if (!resolv_portal($portal)) { > + die "unable to parse/resolve portal address '${portal}'\n"; > + } > } > > - return PVE::Storage::ISCSIPlugin::iscsi_discovery($portal); > + return PVE::Storage::ISCSIPlugin::iscsi_discovery(\@portals); > } > > sub storage_default_format { > diff --git a/PVE/Storage/ISCSIPlugin.pm b/PVE/Storage/ISCSIPlugin.pm > index a79fcb0..6f4309f 100644 > --- a/PVE/Storage/ISCSIPlugin.pm > +++ b/PVE/Storage/ISCSIPlugin.pm > @@ -18,6 +18,65 @@ use base qw(PVE::Storage::Plugin); > my $ISCSIADM = '/usr/bin/iscsiadm'; > $ISCSIADM = undef if ! -X $ISCSIADM; > > +my $iscsi_cfg = "/etc/pve/iscsi.cfg"; > + > +sub read_config { > + my ($filename, $raw) = @_; > + > + my $cfg = {}; > + > + return $cfg if ! -f $iscsi_cfg; > + > + my $content = PVE::Tools::file_get_contents($iscsi_cfg); > + return $cfg if !defined($content); > + > + my @lines = split /\n/, $content; > + > + my $target; > + > + for my $line (@lines) { > + $line =~ s/#.*$//; > + $line =~ s/^\s+//; > + $line =~ s/^;.*$//; > + $line =~ s/\s+$//; > + next if !$line; > + > + $target = $1 if $line =~ m/^\[(\S+)\]$/; > + if (!$target) { > + warn "no target - skip: $line\n"; > + next; > + } > + > + if (!defined($cfg->{$target})) { > + $cfg->{$target} = []; > + } > + > + if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)$/) { > + push @{$cfg->{$target}}, $1; > + } > + } > + > + return $cfg; > +} > + > +sub write_config { > + my ($cfg) = @_; > + > + my $out = ''; > + > + for my $target (sort keys %$cfg) { > + $out .= "[$target]\n"; > + for my $portal (sort @{$cfg->{$target}}) { > + $out .= "$portal\n"; > + } > + $out .= "\n"; > + } > + > + PVE::Tools::file_set_contents($iscsi_cfg, $out); > + > + return; > +} didn't look too closely at read/write_config since i'm conviced that we don't actually need this > + > sub check_iscsi_support { > my $noerr = shift; > > @@ -45,11 +104,10 @@ sub iscsi_session_list { > eval { > run_command($cmd, errmsg => 'iscsi session scan failed', outfunc => sub { > my $line = shift; > - > - if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)(\s+\S+)?\s*$/) { > - my ($session, $target) = ($1, $2); > + if ($line =~ m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)(\s+\S+)?\s*$/) { at this point, i would love to have an example output line in a comment sot that we can see what we want to parse... > + my ($session, $portal, $target) = ($1, $2, $3); > # there can be several sessions per target (multipath) > - push @{$res->{$target}}, $session; > + push @{$res->{$target}}, [$session, $portal]; instead of using a list here, you could use a hash again, so that later instead of $res->{$target}->[0] you could use $res->{$target}->{session} this makes the code more readable (otherwise one always has to lookup/remember what is in ->[0]) > } > }); > }; > @@ -68,42 +126,68 @@ sub iscsi_test_portal { > return PVE::Network::tcp_ping($server, $port || 3260, 2); > } > > -sub iscsi_discovery { > - my ($portal) = @_; > +sub iscsi_portals { > + my ($target) = @_; > > check_iscsi_support (); > > - my $res = {}; > - return $res if !iscsi_test_portal($portal); # fixme: raise exception here? > - > - my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; > + my $res = []; > + my $cmd = [$ISCSIADM, '--mode', 'node']; > run_command($cmd, outfunc => sub { > my $line = shift; > > if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { is this regex... > - my $portal = $1; > - my $target = $2; > - # one target can have more than one portal (multipath). > - push @{$res->{$target}}, $portal; > + my ($portal, $portal_target) = ($1, $2); > + if ($portal_target eq $target) { > + push @{$res}, $portal; > + } > } > }); > > return $res; > } > > +sub iscsi_discovery { > + my ($portals) = @_; > + > + check_iscsi_support (); > + > + my $res = {}; > + for my $portal ($portals->@*) { > + next if !iscsi_test_portal($portal); # fixme: raise exception here? > + > + my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; > + run_command($cmd, outfunc => sub { > + my $line = shift; > + > + if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { and this the same? maybe this can be factored out? > + my $portal = $1; > + my $target = $2; > + # one target can have more than one portal (multipath). > + push @{$res->{$target}}, $portal; > + } > + }); > + > + # In case of multipath we want to exit on any portal available isn't that line contradictory to the previous comment? we gather all portals that is resolved by any (the first?) portal, but no more? if we just need one, why do we push them into an list in the first place? > + last; > + } > + > + return $res; > +} > + > sub iscsi_login { > - my ($target, $portal_in) = @_; > + my ($target, $portals) = @_; > > check_iscsi_support(); > > - eval { iscsi_discovery($portal_in); }; > + eval { iscsi_discovery($portals); }; > warn $@ if $@; > > run_command([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login']); > } > > sub iscsi_logout { > - my ($target, $portal) = @_; > + my ($target) = @_; > > check_iscsi_support(); > > @@ -133,7 +217,7 @@ sub iscsi_session_rescan { > } > > foreach my $session (@$session_list) { > - my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session, '--rescan']; > + my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session->@[0], '--rescan']; didn't know this was valid perl syntax, normally lists are accessed like this: $session->[0] (but as i said above, maybe we want to use a hash instead to have $session->{session} ?) > eval { run_command($cmd, outfunc => sub {}); }; > warn $@ if $@; > } > @@ -245,8 +329,8 @@ sub properties { > type => 'string', > }, > portal => { > - description => "iSCSI portal (IP or DNS name with optional port).", > - type => 'string', format => 'pve-storage-portal-dns', > + description => "iSCSI portal (IP or DNS name with optional port). For multipath, multiple portals can be specified.", > + type => 'string', format => 'pve-storage-portal-dns-list', > }, > }; > } > @@ -264,6 +348,33 @@ sub options { > > # Storage implementation > > +sub on_add_hook { > + my ($class, $storeid, $scfg, %param) = @_; > + > + my @portals = PVE::Tools::split_list($scfg->{portal}); > + my $target = $scfg->{target}; > + > + my $portal_cfg = read_config(); > + $portal_cfg->{$target} = \@portals; > + > + write_config($portal_cfg); > + > + return; > +} > + > +sub on_delete_hook { > + my ($class, $storeid, $scfg) = @_; > + > + my $portal_cfg = read_config(); > + my $target = $scfg->{target}; > + > + delete $portal_cfg->{$target}; > + > + write_config($portal_cfg); > + > + return; > +} > + > sub parse_volname { > my ($class, $volname) = @_; > > @@ -365,6 +476,21 @@ sub iscsi_session { > return $cache->{iscsi_sessions}->{$target}; > } > > +sub iscsi_session_portals { > + my ($cache, $target) = @_; > + > + my $res = []; > + my $sessions = iscsi_session($cache, $target); > + > + if (defined($sessions)) { > + for my $session ($sessions->@*) { > + push @{$res}, $session->@[1]; > + } > + } this could probably be push @{$res}, $_->[1] for $sessions->@* if defined($sessions); or my $res = [ map { $_->[1] } (@$sessions // ())]; (neither tested though) > + > + return $res; > +} > + > sub status { > my ($class, $storeid, $scfg, $cache) = @_; > > @@ -379,14 +505,37 @@ sub activate_storage { > > return if !check_iscsi_support(1); > > - my $session = iscsi_session($cache, $scfg->{target}); > > - if (!defined ($session)) { > - eval { iscsi_login($scfg->{target}, $scfg->{portal}); }; > + my $sessions = iscsi_session($cache, $scfg->{target}); > + my $portal_cfg = read_config(); > + > + my @portals = PVE::Tools::split_list($scfg->{portal}); > + if (defined($portal_cfg->{$scfg->{target}})) { > + @portals = @{$portal_cfg->{$scfg->{target}}}; > + } > + > + if (!defined ($sessions)) { > + eval { iscsi_login($scfg->{target}, \@portals); }; > warn $@ if $@; > } else { > + my @discovered_portals = @{iscsi_portals($scfg->{target})}; > + my @session_portals = @{iscsi_session_portals($cache, $scfg->{target})}; do i understand this right that you reuse the saved portals only for fresh sessions? since here you discover them again if there is already a session? > + > + for my $discovered_portal (@discovered_portals) { > + if (!grep(/^\Q$discovered_portal\E$/, @session_portals)) { > + eval { iscsi_login($scfg->{target}, \@discovered_portals); }; isn't that already done via the iscsi_login from above when there is no session? > + warn $@ if $@; > + last; > + } > + } > + > + if(join(",", sort(@discovered_portals)) ne join(",", sort(@portals))) { > + $portal_cfg->{$scfg->{target}} = \@discovered_portals; > + write_config($portal_cfg); > + } > + > # make sure we get all devices > - iscsi_session_rescan($session); > + iscsi_session_rescan($sessions); > } > } > > @@ -396,15 +545,19 @@ sub deactivate_storage { > return if !check_iscsi_support(1); > > if (defined(iscsi_session($cache, $scfg->{target}))) { > - iscsi_logout($scfg->{target}, $scfg->{portal}); > + iscsi_logout($scfg->{target}); > } > } > > sub check_connection { > my ($class, $storeid, $scfg) = @_; > > - my $portal = $scfg->{portal}; > - return iscsi_test_portal($portal); > + for my $portal (PVE::Tools::split_list($scfg->{portal})) { > + my $result = iscsi_test_portal($portal); > + return $result if $result; > + } > + here you don't use the cache config at all... > + return 0; > } > > sub volume_resize { From t.lamprecht at proxmox.com Mon Oct 16 16:45:07 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Mon, 16 Oct 2023 16:45:07 +0200 Subject: [pve-devel] applied-series: [PATCH-SERIES qemu-server] fix #4522: vncproxy: always set environment variable for ticket In-Reply-To: <20231016131226.53708-1-f.ebner@proxmox.com> References: <20231016131226.53708-1-f.ebner@proxmox.com> Message-ID: Am 16/10/2023 um 15:12 schrieb Fiona Ebner: > Since commit 2dc0eb61 ("qm: assume correct VNC setup in 'vncproxy', > disallow passwordless"), 'qm vncproxy' will just fail when the > LC_PVE_TICKET environment variable is not set. Fix the vncproxy API > call, which previously, would only set the variable in presence of the > 'websocket' parameter. > > > Fiona Ebner (2): > api: vncproxy: update description of websocket parameter > fix #4522: api: vncproxy: also set environment variable for ticket > without websocket > > PVE/API2/Qemu.pm | 5 +++-- > 1 file changed, 3 insertions(+), 2 deletions(-) > applied series, thanks! From t.lamprecht at proxmox.com Mon Oct 16 16:50:02 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Mon, 16 Oct 2023 16:50:02 +0200 Subject: [pve-devel] applied: [PATCH qemu-server] Fix races with suspended VMs that can wake up In-Reply-To: <20231013135006.96272-1-f.schauer@proxmox.com> References: <20231013135006.96272-1-f.schauer@proxmox.com> Message-ID: <358678da-ff09-4a7c-ad28-b39eb3b086f5@proxmox.com> Am 13/10/2023 um 15:50 schrieb Filip Schauer: > Fix races with ACPI-suspended VMs which could wake up during migration > or during a suspend-mode backup. > > Revert prevention, of ACPI-suspended VMs automatically resuming after > migration, introduced by 7ba974a6828d. The commit introduced a potential > problem that causes a suspended VM that wakes up during migration to > remain paused after the migration finishes. > > Furthermore the commit increased the race window during the preparation > of a suspend-mode backup, when a suspended VM wakes up between the > vm_is_paused check in PVE::VZDump::QemuServer::prepare and > PVE::VZDump::QemuServer::qga_fs_freeze. This causes the code to skip > fs-freeze even if the VM has woken up, potentially leaving the file > system in an inconsistent state. > > To prevent this, do not treat the suspended runstate as paused when > migrating or archiving a VM. > > Signed-off-by: Filip Schauer > --- > PVE/API2/Qemu.pm | 4 ++-- > PVE/QemuMigrate.pm | 4 +++- > PVE/QemuServer.pm | 6 +++--- > PVE/VZDump/QemuServer.pm | 4 +++- > 4 files changed, 11 insertions(+), 7 deletions(-) > > applied, with Fiona's R-b and extra info massaged into the commit message, thanks! From l.wagner at proxmox.com Mon Oct 16 17:18:54 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 16 Oct 2023 17:18:54 +0200 Subject: [pve-devel] [RFC] towards automated integration testing In-Reply-To: <44f89ce6-043f-7b05-75ad-ac66550eb3e8@proxmox.com> References: <1bb25817-66e7-406a-bd4b-0699de6cba31@proxmox.com> <44f89ce6-043f-7b05-75ad-ac66550eb3e8@proxmox.com> Message-ID: <9780afc5-6bf9-40da-8a2f-0c5e02ded605@proxmox.com> Thank you for the feedback! On 10/16/23 13:20, Stefan Hanreich wrote: > On 10/13/23 15:33, Lukas Wagner wrote: > >> - Additionally, it should be easy to run these integration tests locally >> on a developer's workstation in order to write new test cases, as well >> as troubleshooting and debugging existing test cases. The local >> test environment should match the one being used for automated testing >> as closely as possible > This would also include sharing those fixture templates somewhere, do > you already have an idea on how to accomplish this? PBS sounds like a > good option for this if I'm not missing something. > Yes, these templates could be stored on some shared storage, e.g. a PBS instance, or they could also distributed via a .deb/multiple .debs (not sure if that is a good idea, since these would become huge pretty quickly). It could also be a two-step process: Use one command to get the latest test templates, restoring them from a remote backup, converting them to a local VM template. When executing tests, the test runner could then use linked clones, speeding up the test setup time quite a bit. All in all, these templates that can be used in test fixtures should be: - easily obtainable for developers, in order to have a fully functional test setup on their workstation - easily updateable (e.g. installing the latest packages, so that the setup-hook does not need to fetch a boatload of packages every time) >> As a main mode of operation, the Systems under Test (SUTs) >> will be virtualized on top of a Proxmox VE node. >> >> This has the following benefits: >> - it is easy to create various test setups (fixtures), including but not >> limited to single Proxmox VE nodes, clusters, Backup servers and >> auxiliary services (e.g. an LDAP server for testing LDAP >> authentication) > I can imagine having to setup VMs inside the Test Setup as well for > doing various tests. Doing this manually every time could be quite > cumbersome / hard to automate. Do you have a mechanism in mind to deploy > VMs inside the test system as well? Again, PBS could be an interesting > option for this imo. > Several options come to mind. We could use a virtualized PBS instance with a datastore containing the VM backup as part of the fixture. We could use some external backup store (so the same 'source' as for the templates themselves) - however that means that the systems under test must have network access to that. We could also think about using iPXE to boot test VMs, with the boot image either be provided by some template from the fixture, or by some external server. For both approaches, the 'as part of the fixture' approaches seem a bit nicer, as they are more self-contained. Also, the vmbd2 thingy that thomas mentioned might be interesting for this - i've only glanced at it so far though. As of now it seems that this question will not influence the design of the test runner much, so it can probably be postponed to a later stage. >> In theory, the test runner would also be able to drive tests on real >> hardware, but of course with some limitations (harder to have a >> predictable, reproducible environment, etc.) > > Maybe utilizing Aaron's installer for setting up those test systems > could at least produce somewhat identical setups? Although it is really > hard managing systems with different storage types, network cards, ... . In general my biggest concern with 'bare-metal' tests - and to precise, that does not really have anything to do with being 'bare-metal', more about testing on something that is harder roll back into a clean state that can be used for the next test execution, is that I'm afraid that a setup like this could become quite brittle and a maintenance burden. At some point, a test execution might leave something in an unclean state (e.g. due to a crashed test or missing something while cleanup), tripping up the following test job. As an example from personal experience: One test run might test new packages which introduce a new flag in a configuration file. If that flag is not cleanup up afterwards, another test job testing other packages might fail because it now has to deal with an 'unknown' configuration key. Maybe ZFS snapshots could help with that, but I'm not sure how that would work in practice (e.g. due to the kernel being stored on the EFI partition). The automated installer *could* certainly help here - however, right now I don't want to extend the scope of this project too much. Also, there is also the question if the installation should be refreshed after every single test run, increasing the test cycle time/resource consumption quite a bit? Or only if 'something' breaks? That being said, it might also make sense to be able to run the tests (or more likely, a subset of them, since some will inherently require a fixture) against an arbitrary PVE instance that is under full control of a developer (e.g. a development VM, or, if feeling adventurous, the workstation itself). If this is possible, then these tests could the fastest way to get feedback while developing, since there is no need to instantiate a template, update, deploy, etc. In this case, the test runner's job would only be to run the test scripts, without managing fixtures/etc, and then reporting the results back to the developer. Essentially, as Thomas already mentioned, one approach to do this would be to decouple the 'fixture setup' and 'test case execution' part as much as possible. How that will look in practice will be part of further research. > I've seen GitLab using tags for runners that specify certain > capabilities of systems. Maybe we could also introduce something like > that here for different bare-metal systems? E.g. a test case specifies > it needs a system with tag `ZFS` and then you can run / skip the > respective test case on that system. Managing those tags can introduce > quite a lot of churn though, so I'm not sure if this would be a good idea. > I have thought about a tag system as well - not necessarily for test runners, but for test cases. E.g. you could tag tests for the authentication system with 'auth' - because at least for the local development cycle it might not make much sense to run tests for clusters, ceph, etc. while working on the authentication system. The 'tags' to be executed might then be simply passed to the test runner. These tags could also be used to mark the subset of 'simple' test cases that don't need a special test fixture, as described above... This could also be extended to a full 'predicate-like' system as Thomas described. >> The test script is executed by the test runner; the test outcome is >> determined by the exit code of the script. Test scripts could be written > Are you considering capturing output as well? That would make sense when > using assertions at least, so in case of failures developers have a > starting point for debugging. Yup, I'd capture stdout/stderr from all test executables/scripts and include it in the final test report. Test output is indeed very useful when determining *why* something went wrong. > > Would it make sense to allow specifying a expected exit code for tests > that actually should fail - or do you consider this something that > should be handled by the test script? I guess that's a matter of taste. Personally I'd keep the contract between test runner and test script simple and say 0 == success, everything else is a failure. If there are any test cases that expect a failure of some API call, then the script should 'translate' the exit code. If we discover that specifying an expected exit actually makes things easier for us, then adding it should be rather trivial - and easier than ripping it out the other way round. > I've refrained from talking about the toml files too much since it's > probably too early to say something about that, but they look good so > far from my pov. > > In general this sounds like quite the exciting feature and the RFC looks > very promising already. Thanks for your feedback! > > Kind Regards > Stefan -- - Lukas From l.wagner at proxmox.com Mon Oct 16 17:33:06 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Mon, 16 Oct 2023 17:33:06 +0200 Subject: [pve-devel] [RFC] towards automated integration testing In-Reply-To: References: <1bb25817-66e7-406a-bd4b-0699de6cba31@proxmox.com> Message-ID: <832bc039-57d0-4d27-ac48-721c5c82af83@proxmox.com> Thanks for the summary from our discussion and the additional feedback! On 10/16/23 15:57, Thomas Lamprecht wrote: >> - create some sort of test report > > As Stefan mentioned, test-output can be good to have. Our buildbot > instance provides that, and while I don't look at them in 99% of the > builds, when I need to its worth *a lot*. > Agreed, test output is always valuable and will definitely captured. >> >> ## Introduction >> >> The goal is to establish a framework that allows us to write >> automated integration tests for our products. >> These tests are intended to run in the following situations: >> - When new packages are uploaded to the staging repos (by triggering >> a test run from repoman, or similar) > > *debian repos, as we could also trigger some when git commits are > pushed, just like we do now through Buildbot. Doing so is IMO nice as it > will catch issues before a package was bumped, but is still quite a bit > simpler to implement than an "apply patch from list to git repos" thing > from the next point, but could still act as a preparation for that. > >> - Later, this tests could also be run when patch series are posted to >> our mailing lists. This requires a mechanism to automatically >> discover, fetch and build patches, which will be a separate, >> follow-up project. > >> >> As a main mode of operation, the Systems under Test (SUTs) >> will be virtualized on top of a Proxmox VE node. > > For the fully-automated test system this can be OK as primary mode, as > it indeed makes things like going back to an older software state much > easier. > > But, if we decouple the test harness and running them from that more > automated system, we can also run the harness periodically on our > bare-metal test servers. > >> ## Terminology >> - Template: A backup/VM template that can be instantiated by the test >> runner > > I.e., the base of the test host? I'd call this test-host, template is a > bit to overloaded/generic and might focus too much on the virtual test > environment. True, 'template' is a bit overloaded. > > Or is this some part that takes place in the test, i.e., a > generalization of product to test and supplementary tool/app that helps > on that test? It was intended to be a 'general VM/CT base thingy' that can be instantiated and managed by the test runner, so either a PVE/PBS/PMG base installation, or some auxiliary resource, e.g. a Debian VM with an already-set-up LDAP server. I'll see if I can find good terms with the newly added focus on bare-metal testing / the decoupling between environment setup and test execution. > Is the order of test-cases guaranteed by toml parsing, or how are intra- > fixture dependencies ensured? > Good point. With rollbacks in between test cases it probably does not matter much, but on 'real hardware' with no rollback this could definitely be a concern. A super simple thing that could just work fine is ordering test execution by testcase-names, sorted alphabetically. Ideally you'd write test cases that do not depend on each other any way, and *if* you ever find yourself in the situation where you *need* some ordering, you could just encode the order in the test-case name by adding an integer prefix - similar how you would name config files in /etc/sysctl.d/*, for instance. -- - Lukas From ykonotopov at gnome.org Mon Oct 16 18:08:40 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Mon, 16 Oct 2023 20:08:40 +0400 Subject: [PATCH v2 storage 1/1] fix #254: iscsi: allow to configure multiple portals In-Reply-To: <0314d5f0-a08b-41dc-b027-90ff8a44c041@proxmox.com> References: <20231015183216.86220-1-ykonotopov@gnome.org> <20231015183216.86220-2-ykonotopov@gnome.org> <0314d5f0-a08b-41dc-b027-90ff8a44c041@proxmox.com> Message-ID: 16.10.2023 17:58, Dominik Csapak ?????: > hi, Hi, Dominik! Thanks for your review. I will try to address all issues. Some comments are below. > > a few high level comments: > > first: > > is the iscsi.cfg really necessary? couldn't we just lookup on demand > like we do now? Until now I thought it is. For some reason I thought that open-iscsi node list is not persistent between reboots. However it's indeed can be done without custom cache file. I will rewrite that part. Thanks! > second: > > i would favor an approach that uses an 'array' instead of the '-list' > types. > i'm not completely sure if one can just have that as a drop in > replacement > with old configs/api calls still working. if not, we could introduce > a new 'portals' api parameter/storage config that is an array which could > take precedence over the old 'portal' one. (that we could drop with the > next major release) in that case we could even automatically convert from > portal -> portals if we detect that in the api update I'm not familiar with Proxmox formats but will try to investigate it. > > and a few (basic) comments inline: > > On 10/15/23 20:32, ykonotopov at gnome.org wrote: >> From: Yuri Konotopov >> > > it would be great to have a real commit message here. best would be a > short description > of what the patch wants to achieve, and maybe some more tricky > implementation detail > that is not obvious (most of what you wrote in the cover letter would > be fine, sans > the changelog + mention of 'Here is v2 patch' I will add it. Git's send-email interface is not something I work often :-) > >> Signed-off-by: Yuri Konotopov >> --- >> ? PVE/API2/Storage/Scan.pm?? |?? 2 +- >> ? PVE/Storage.pm???????????? |? 10 +- >> ? PVE/Storage/ISCSIPlugin.pm | 207 ++++++++++++++++++++++++++++++++----- >> ? 3 files changed, 187 insertions(+), 32 deletions(-) >> >> diff --git a/PVE/API2/Storage/Scan.pm b/PVE/API2/Storage/Scan.pm >> index d7a8743..1f9773c 100644 >> --- a/PVE/API2/Storage/Scan.pm >> +++ b/PVE/API2/Storage/Scan.pm >> @@ -305,7 +305,7 @@ __PACKAGE__->register_method({ >> ????????? node => get_standard_option('pve-node'), >> ????????? portal => { >> ????????? description => "The iSCSI portal (IP or DNS name with >> optional port).", >> -??????? type => 'string', format => 'pve-storage-portal-dns', >> +??????? type => 'string', format => 'pve-storage-portal-dns-list', >> ????????? }, >> ????? }, >> ????? }, >> diff --git a/PVE/Storage.pm b/PVE/Storage.pm >> index cec3996..0043507 100755 >> --- a/PVE/Storage.pm >> +++ b/PVE/Storage.pm >> @@ -1428,12 +1428,14 @@ sub resolv_portal { >> ? sub scan_iscsi { >> ????? my ($portal_in) = @_; >> ? -??? my $portal; >> -??? if (!($portal = resolv_portal($portal_in))) { >> -??? die "unable to parse/resolve portal address '${portal_in}'\n"; >> +??? my @portals = PVE::Tools::split_list($portal_in); > > not a big deal, but we usually pack that into a reference immediately: > > my $portals = [ <...>::split_list(...) ]; > > then you can do > for my $portal (@$portals) > or > for my $portal ($portals->@*) > > and don't have to create a reference later on when passing to > iscsi_discovery > >> +??? for my $portal (@portals) { >> +??? if (!resolv_portal($portal)) { >> +??????? die "unable to parse/resolve portal address '${portal}'\n"; >> +??? } >> ????? } >> ? -??? return PVE::Storage::ISCSIPlugin::iscsi_discovery($portal); >> +??? return PVE::Storage::ISCSIPlugin::iscsi_discovery(\@portals); >> ? } >> ? ? sub storage_default_format { >> diff --git a/PVE/Storage/ISCSIPlugin.pm b/PVE/Storage/ISCSIPlugin.pm >> index a79fcb0..6f4309f 100644 >> --- a/PVE/Storage/ISCSIPlugin.pm >> +++ b/PVE/Storage/ISCSIPlugin.pm >> @@ -18,6 +18,65 @@ use base qw(PVE::Storage::Plugin); >> ? my $ISCSIADM = '/usr/bin/iscsiadm'; >> ? $ISCSIADM = undef if ! -X $ISCSIADM; >> ? +my $iscsi_cfg = "/etc/pve/iscsi.cfg"; >> + >> +sub read_config { >> +??? my ($filename, $raw) = @_; >> + >> +??? my $cfg = {}; >> + >> +??? return $cfg if ! -f $iscsi_cfg; >> + >> +??? my $content = PVE::Tools::file_get_contents($iscsi_cfg); >> +??? return $cfg if !defined($content); >> + >> +??? my @lines = split /\n/, $content; >> + >> +??? my $target; >> + >> +??? for my $line (@lines) { >> +??? $line =~ s/#.*$//; >> +??? $line =~ s/^\s+//; >> +??? $line =~ s/^;.*$//; >> +??? $line =~ s/\s+$//; >> +??? next if !$line; >> + >> +??? $target = $1 if $line =~ m/^\[(\S+)\]$/; >> +??? if (!$target) { >> +??????? warn "no target - skip: $line\n"; >> +??????? next; >> +??? } >> + >> +??? if (!defined($cfg->{$target})) { >> +??????? $cfg->{$target} = []; >> +??? } >> + >> +??? if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)$/) { >> +??????? push @{$cfg->{$target}}, $1; >> +??? } >> +??? } >> + >> +??? return $cfg; >> +} >> + >> +sub write_config { >> +??? my ($cfg) = @_; >> + >> +??? my $out = ''; >> + >> +??? for my $target (sort keys %$cfg) { >> +??? $out .= "[$target]\n"; >> +??? for my $portal (sort @{$cfg->{$target}}) { >> +??????? $out .= "$portal\n"; >> +??? } >> +??? $out .= "\n"; >> +??? } >> + >> +??? PVE::Tools::file_set_contents($iscsi_cfg, $out); >> + >> +??? return; >> +} > > didn't look too closely at read/write_config since i'm conviced that > we don't > actually need this > >> + >> ? sub check_iscsi_support { >> ????? my $noerr = shift; >> ? @@ -45,11 +104,10 @@ sub iscsi_session_list { >> ????? eval { >> ????? run_command($cmd, errmsg => 'iscsi session scan failed', >> outfunc => sub { >> ????????? my $line = shift; >> - >> -??????? if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)(\s+\S+)?\s*$/) { >> -??????? my ($session, $target) = ($1, $2); >> +??????? if ($line =~ >> m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)(\s+\S+)?\s*$/) >> { > > at this point, i would love to have an example output line in a > comment sot that we can see what > we want to parse... > >> +??????? my ($session, $portal, $target) = ($1, $2, $3); >> ????????? # there can be several sessions per target (multipath) >> -??????? push @{$res->{$target}}, $session; >> +??????? push @{$res->{$target}}, [$session, $portal]; > > instead of using a list here, you could use a hash again, so that later > instead of $res->{$target}->[0] you could use $res->{$target}->{session} > this makes the code more readable (otherwise one always has to > lookup/remember what is > in ->[0]) > >> ????????? } >> ????? }); >> ????? }; >> @@ -68,42 +126,68 @@ sub iscsi_test_portal { >> ????? return PVE::Network::tcp_ping($server, $port || 3260, 2); >> ? } >> ? -sub iscsi_discovery { >> -??? my ($portal) = @_; >> +sub iscsi_portals { >> +??? my ($target) = @_; >> ? ????? check_iscsi_support (); >> ? -??? my $res = {}; >> -??? return $res if !iscsi_test_portal($portal); # fixme: raise >> exception here? >> - >> -??? my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', >> 'sendtargets', '--portal', $portal]; >> +??? my $res = []; >> +??? my $cmd = [$ISCSIADM, '--mode', 'node']; >> ????? run_command($cmd, outfunc => sub { >> ????? my $line = shift; >> ? ????? if ($line =~ >> m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { > > is this regex... > >> -??????? my $portal = $1; >> -??????? my $target = $2; >> -??????? # one target can have more than one portal (multipath). >> -??????? push @{$res->{$target}}, $portal; >> +??????? my ($portal, $portal_target) = ($1, $2); >> +??????? if ($portal_target eq $target) { >> +??????? push @{$res}, $portal; >> +??????? } >> ????? } >> ????? }); >> ? ????? return $res; >> ? } >> ? +sub iscsi_discovery { >> +??? my ($portals) = @_; >> + >> +??? check_iscsi_support (); >> + >> +??? my $res = {}; >> +??? for my $portal ($portals->@*) { >> +??? next if !iscsi_test_portal($portal); # fixme: raise exception here? >> + >> +??? my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', >> 'sendtargets', '--portal', $portal]; >> +??? run_command($cmd, outfunc => sub { >> +??????? my $line = shift; >> + >> +??????? if ($line =~ >> m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { > > and this the same? maybe this can be factored out? Yes, the output of commands is same, but commands are different (one is online and another is offline). > >> +??????? my $portal = $1; >> +??????? my $target = $2; >> +??????? # one target can have more than one portal (multipath). >> +??????? push @{$res->{$target}}, $portal; >> +??????? } >> +??? }); >> + >> +??? # In case of multipath we want to exit on any portal available > > isn't that line contradictory to the previous comment? > we gather all portals that is resolved by any (the first?) portal, but > no more? No, there is no contradiction here. There may be multiple portals for single target, but any portal is suitable for discovery. > if we just need one, why do we push them into an list in the first place? It looks like the return value of iscsi_discovery is not used. So we can drop $res completely and regex too. > >> +??? last; >> +??? } >> + >> +??? return $res; >> +} >> + >> ? sub iscsi_login { >> -??? my ($target, $portal_in) = @_; >> +??? my ($target, $portals) = @_; >> ? ????? check_iscsi_support(); >> ? -??? eval { iscsi_discovery($portal_in); }; >> +??? eval { iscsi_discovery($portals); }; >> ????? warn $@ if $@; >> ? ????? run_command([$ISCSIADM, '--mode', 'node', '--targetname',? >> $target, '--login']); >> ? } >> ? ? sub iscsi_logout { >> -??? my ($target, $portal) = @_; >> +??? my ($target) = @_; >> ? ????? check_iscsi_support(); >> ? @@ -133,7 +217,7 @@ sub iscsi_session_rescan { >> ????? } >> ? ????? foreach my $session (@$session_list) { >> -??? my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session, >> '--rescan']; >> +??? my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', >> $session->@[0], '--rescan']; > > didn't know this was valid perl syntax, normally lists are accessed > like this: > > $session->[0] > > (but as i said above, maybe we want to use a hash instead to have > $session->{session} ?) > >> ????? eval { run_command($cmd, outfunc => sub {}); }; >> ????? warn $@ if $@; >> ????? } >> @@ -245,8 +329,8 @@ sub properties { >> ????????? type => 'string', >> ????? }, >> ????? portal => { >> -??????? description => "iSCSI portal (IP or DNS name with optional >> port).", >> -??????? type => 'string', format => 'pve-storage-portal-dns', >> +??????? description => "iSCSI portal (IP or DNS name with optional >> port). For multipath, multiple portals can be specified.", >> +??????? type => 'string', format => 'pve-storage-portal-dns-list', >> ????? }, >> ????? }; >> ? } >> @@ -264,6 +348,33 @@ sub options { >> ? ? # Storage implementation >> ? +sub on_add_hook { >> +??? my ($class, $storeid, $scfg, %param) = @_; >> + >> +??? my @portals = PVE::Tools::split_list($scfg->{portal}); >> +??? my $target = $scfg->{target}; >> + >> +??? my $portal_cfg = read_config(); >> +??? $portal_cfg->{$target} = \@portals; >> + >> +??? write_config($portal_cfg); >> + >> +??? return; >> +} >> + >> +sub on_delete_hook { >> +??? my ($class, $storeid, $scfg) = @_; >> + >> +??? my $portal_cfg = read_config(); >> +??? my $target = $scfg->{target}; >> + >> +??? delete $portal_cfg->{$target}; >> + >> +??? write_config($portal_cfg); >> + >> +??? return; >> +} >> + >> ? sub parse_volname { >> ????? my ($class, $volname) = @_; >> ? @@ -365,6 +476,21 @@ sub iscsi_session { >> ????? return $cache->{iscsi_sessions}->{$target}; >> ? } >> ? +sub iscsi_session_portals { >> +??? my ($cache, $target) = @_; >> + >> +??? my $res = []; >> +??? my $sessions = iscsi_session($cache, $target); >> + >> +??? if (defined($sessions)) { >> +??? for my $session ($sessions->@*) { >> +??????? push @{$res}, $session->@[1]; >> +??? } >> +??? } > > this could probably be > > push @{$res}, $_->[1] for $sessions->@* if defined($sessions); > or > my $res = [ map { $_->[1] } (@$sessions // ())]; > > > (neither tested though) > >> + >> +??? return $res; >> +} >> + >> ? sub status { >> ????? my ($class, $storeid, $scfg, $cache) = @_; >> ? @@ -379,14 +505,37 @@ sub activate_storage { >> ? ????? return if !check_iscsi_support(1); >> ? -??? my $session = iscsi_session($cache, $scfg->{target}); >> ? -??? if (!defined ($session)) { >> -??? eval { iscsi_login($scfg->{target}, $scfg->{portal}); }; >> +??? my $sessions = iscsi_session($cache, $scfg->{target}); >> +??? my $portal_cfg = read_config(); >> + >> +??? my @portals = PVE::Tools::split_list($scfg->{portal}); >> +??? if (defined($portal_cfg->{$scfg->{target}})) { >> +??? @portals = @{$portal_cfg->{$scfg->{target}}}; >> +??? } >> + >> +??? if (!defined ($sessions)) { >> +??? eval { iscsi_login($scfg->{target}, \@portals); }; >> ????? warn $@ if $@; >> ????? } else { >> +??? my @discovered_portals = @{iscsi_portals($scfg->{target})}; >> +??? my @session_portals = @{iscsi_session_portals($cache, >> $scfg->{target})}; > > do i understand this right that you reuse the saved portals only for > fresh > sessions? since here you discover them again if there is already a > session? The iscsi_portrals function is not actually discover but rather retrieval of last discovered portals (during iscsi_discover). Before I thought that we can extract discovered portals from open-iscsi only when we have active session, but we can do it without sessions too. > >> + >> +??? for my $discovered_portal (@discovered_portals) { >> +??????? if (!grep(/^\Q$discovered_portal\E$/, @session_portals)) { >> +??????? eval { iscsi_login($scfg->{target}, \@discovered_portals); }; > > isn't that already done via the iscsi_login from above when there is > no session? In case some portals were unavailable during iscsi_login we will not have all sessions. So we need to check that and login again when sessions are missing. Otherwise we will miss multipath. > > >> +??????? warn $@ if $@; >> +??????? last; >> +??????? } >> +??? } >> + >> +??? if(join(",", sort(@discovered_portals)) ne join(",", >> sort(@portals))) { >> +??????? $portal_cfg->{$scfg->{target}} = \@discovered_portals; >> +??????? write_config($portal_cfg); >> +??? } >> + >> ????? # make sure we get all devices >> -??? iscsi_session_rescan($session); >> +??? iscsi_session_rescan($sessions); >> ????? } >> ? } >> ? @@ -396,15 +545,19 @@ sub deactivate_storage { >> ????? return if !check_iscsi_support(1); >> ? ????? if (defined(iscsi_session($cache, $scfg->{target}))) { >> -??? iscsi_logout($scfg->{target}, $scfg->{portal}); >> +??? iscsi_logout($scfg->{target}); >> ????? } >> ? } >> ? ? sub check_connection { >> ????? my ($class, $storeid, $scfg) = @_; >> ? -??? my $portal = $scfg->{portal}; >> -??? return iscsi_test_portal($portal); >> +??? for my $portal (PVE::Tools::split_list($scfg->{portal})) { >> +??? my $result = iscsi_test_portal($portal); >> +??? return $result if $result; >> +??? } >> + > > here you don't use the cache config at all... > >> +??? return 0; >> ? } >> ? ? sub volume_resize { > > -- Best regards, Yuri Konotopov From s.sterz at proxmox.com Mon Oct 16 18:28:49 2023 From: s.sterz at proxmox.com (Stefan Sterz) Date: Mon, 16 Oct 2023 18:28:49 +0200 Subject: [pve-devel] [PATCH widget-toolkit] dark-mode: set intentionally black icons to `$icon-color` Message-ID: <20231016162848.2546638-1-s.sterz@proxmox.com> some icons intentionally use black as their color in the light theme. this includes the little pencil and check mark icon in the acme overview. change their color to the regular dark-mode icon-color. for this to work the filter inversion needed for some other icons needs to be remove too. Signed-off-by: Stefan Sterz --- src/proxmox-dark/scss/other/_icons.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/proxmox-dark/scss/other/_icons.scss b/src/proxmox-dark/scss/other/_icons.scss index d4dc316..c045cf4 100644 --- a/src/proxmox-dark/scss/other/_icons.scss +++ b/src/proxmox-dark/scss/other/_icons.scss @@ -104,6 +104,9 @@ } // pbs show task log in longest task list column +.fa.black, +.fa.black::after, +.fa.black::before, .x-action-col-icon.fa-chevron-right::before { filter: none; } @@ -222,6 +225,12 @@ } } +// set icon color of intentional black icons (e.g.: pencil icon for +// quickly changing the ACME account) +.fa.black { + color: $icon-color; +} + // The usage icons dynamically displaying how full a storage is .usage-wrapper { border: 1px solid $icon-color; -- 2.39.2 From t.lamprecht at proxmox.com Tue Oct 17 08:35:58 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 17 Oct 2023 08:35:58 +0200 Subject: [pve-devel] [RFC] towards automated integration testing In-Reply-To: <832bc039-57d0-4d27-ac48-721c5c82af83@proxmox.com> References: <1bb25817-66e7-406a-bd4b-0699de6cba31@proxmox.com> <832bc039-57d0-4d27-ac48-721c5c82af83@proxmox.com> Message-ID: <125370ed-9b55-42d2-b544-28683a17be79@proxmox.com> Am 16/10/2023 um 17:33 schrieb Lukas Wagner: >> Or is this some part that takes place in the test, i.e., a >> generalization of product to test and supplementary tool/app that helps >> on that test? > > It was intended to be a 'general VM/CT base thingy' that can be > instantiated and managed by the test runner, so either a PVE/PBS/PMG > base installation, or some auxiliary resource, e.g. a Debian VM with > an already-set-up LDAP server. > > I'll see if I can find good terms with the newly added focus on > bare-metal testing / the decoupling between environment setup and test > execution. Hmm, yeah OK, having some additional info on top of "template" like e.g., "system-template", or "app-template", could be already slightly better then. While slightly details, IMO still important for overall future direction, I'd possibly split "restore" into "source-type" and "source", where the "source-type" can be e.g., "disk-image" for a qcow2 or the like to work directly on, or "backup-image" for your backup restore process, or some type for bootstrap tools like debootstrap or the VM specific vmdb2. Also having re-use configurable, i.e., if the app-template-instance is destroyed after some test run is done. For that, writing a simple info about mapping instantiated templates to other identifiers (VMID, IP, ...) in e.g. /var/cache/ (or some XDG_ directory to cater also to any users running this as non-root). Again, can be classified as details, but IMO important for the direction this is going, and not too much work, so should be at least on the radar. >> Is the order of test-cases guaranteed by toml parsing, or how are intra- >> fixture dependencies ensured? >> > > Good point. With rollbacks in between test cases it probably does not > matter much, but on 'real hardware' with no rollback this could > definitely be a concern. > A super simple thing that could just work fine is ordering test > execution by testcase-names, sorted alphabetically. Ideally you'd write > test cases that do not depend on each other any way, and *if* you ever > find yourself in the situation where you *need* some ordering, you > could> just encode the order in the test-case name by adding an integer > prefix> - similar how you would name config files in /etc/sysctl.d/*, > for instance. While it can be OK to leave that for later, encoding such things in names is IMO brittle and hard to manage if more than a handful of tests, and we hopefully got lots more ;-) >From top of my head I'd rather do some attribute based dependency annotation, so that one can depend on single tests, or whole fixture on others single tests or whole fixture. From f.ebner at proxmox.com Tue Oct 17 09:07:24 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 17 Oct 2023 09:07:24 +0200 Subject: [pve-devel] [PATCH v2 storage 1/1] fix #254: iscsi: allow to configure multiple portals In-Reply-To: References: <20231015183216.86220-1-ykonotopov@gnome.org> <20231015183216.86220-2-ykonotopov@gnome.org> <0314d5f0-a08b-41dc-b027-90ff8a44c041@proxmox.com> Message-ID: <3f89e1fd-f899-4e60-bb2b-7be077af4ade@proxmox.com> Am 16.10.23 um 18:08 schrieb Yuri Konotopov: > > It looks like the return value of iscsi_discovery is not used. So we can > drop > > $res completely and regex too. > It is used by the scan/iscsi API endpoint via scan_iscsi() in Storage.pm. Best Regards, Fiona From ykonotopov at gnome.org Tue Oct 17 09:25:09 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Tue, 17 Oct 2023 11:25:09 +0400 Subject: [PATCH v2 storage 1/1] fix #254: iscsi: allow to configure multiple portals In-Reply-To: <3f89e1fd-f899-4e60-bb2b-7be077af4ade@proxmox.com> References: <20231015183216.86220-1-ykonotopov@gnome.org> <20231015183216.86220-2-ykonotopov@gnome.org> <0314d5f0-a08b-41dc-b027-90ff8a44c041@proxmox.com> <3f89e1fd-f899-4e60-bb2b-7be077af4ade@proxmox.com> Message-ID: <09b9ae8b-a05c-42cc-b6af-b5ebd6c5ae8f@gnome.org> 17.10.2023 11:07, Fiona Ebner ?????: > Am 16.10.23 um 18:08 schrieb Yuri Konotopov: >> It looks like the return value of iscsi_discovery is not used. So we can >> drop >> >> $res completely and regex too. >> > It is used by the scan/iscsi API endpoint via scan_iscsi() in Storage.pm. Thanks. I found this too. As for regexes - I will move them to variable. > > Best Regards, > Fiona > -- Best regards, Yuri Konotopov From l.wagner at proxmox.com Tue Oct 17 09:27:50 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Tue, 17 Oct 2023 09:27:50 +0200 Subject: [pve-devel] [PATCH v3 many 0/7] notifications: add SMTP endpoint In-Reply-To: <20230918111443.465970-1-l.wagner@proxmox.com> References: <20230918111443.465970-1-l.wagner@proxmox.com> Message-ID: <70abe760-c79f-4108-893f-8093bc43a278@proxmox.com> Ping - would be great to get some reviews on this to get this merged for the next release. On 9/18/23 13:14, Lukas Wagner wrote: > This patch series adds support for a new notification endpoint type, > smtp. As the name suggests, this new endpoint allows PVE to talk > to SMTP server directly, without using the system's MTA (postfix). > -- - Lukas From l.wagner at proxmox.com Tue Oct 17 09:28:22 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Tue, 17 Oct 2023 09:28:22 +0200 Subject: [pve-devel] [PATCH v2 many 00/11] notifications: feed system mails into proxmox_notify In-Reply-To: <20231002080624.198759-1-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> Message-ID: <17a0be7e-84bc-45c7-877d-27cb0c30d095@proxmox.com> Ping - would be great to get some reviews on this to get this merged for the next release. On 10/2/23 10:06, Lukas Wagner wrote: > The aim of this patch series is to adapt `proxmox-mail-forward` > so that it forwards emails that were sent to the local root user > through the `proxmox_notify` crate. > -- - Lukas From ykonotopov at gnome.org Tue Oct 17 09:30:37 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Tue, 17 Oct 2023 11:30:37 +0400 Subject: [PATCH v2 storage 1/1] fix #254: iscsi: allow to configure multiple portals In-Reply-To: <0314d5f0-a08b-41dc-b027-90ff8a44c041@proxmox.com> References: <20231015183216.86220-1-ykonotopov@gnome.org> <20231015183216.86220-2-ykonotopov@gnome.org> <0314d5f0-a08b-41dc-b027-90ff8a44c041@proxmox.com> Message-ID: <3e515ecb-a391-4fdc-90fd-aa2bb7422738@gnome.org> 16.10.2023 17:58, Dominik Csapak ?????: > > i would favor an approach that uses an 'array' instead of the '-list' > types. > While working more on this patch I came to conclusion we don't need to modify portal property format at all (nor -list, nor -array). What we need for proper multipath support is to use discovered portals when any are available. So initial portal is only needed to find proper target and discover all portals. I will sent v3 patch today. -- Best regards, Yuri Konotopov From t.lamprecht at proxmox.com Tue Oct 17 09:34:56 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 17 Oct 2023 09:34:56 +0200 Subject: [pve-devel] [RFC] towards automated integration testing In-Reply-To: <9780afc5-6bf9-40da-8a2f-0c5e02ded605@proxmox.com> References: <1bb25817-66e7-406a-bd4b-0699de6cba31@proxmox.com> <44f89ce6-043f-7b05-75ad-ac66550eb3e8@proxmox.com> <9780afc5-6bf9-40da-8a2f-0c5e02ded605@proxmox.com> Message-ID: <347d64bd-486b-4078-b396-ff75dbd38996@proxmox.com> Am 16/10/2023 um 17:18 schrieb Lukas Wagner: > On 10/16/23 13:20, Stefan Hanreich wrote: >> I can imagine having to setup VMs inside the Test Setup as well for >> doing various tests. Doing this manually every time could be quite >> cumbersome / hard to automate. Do you have a mechanism in mind to >> deploy VMs inside the test system as well? Again, PBS could be an >> interesting option for this imo. >> > Several options come to mind. We could use a virtualized PBS instance > with a datastore containing the VM backup as part of the fixture. We > could use some external backup store (so the same 'source' as for the > templates themselves) - however that means that the systems under test > must have network access to that. We could also think about using > iPXE to boot test VMs, with the boot image either be provided by some > template from the fixture, or by some external server. For both > approaches, the 'as part of the fixture' approaches seem a bit nicer, > as they are more self-contained. What about the following approach: The test state that they need one or more VMs with certain properties, i.e., something like "none" (don't care), "ostype=win*", "memory>=10G" or the like (can start out easy w.r.t to supported comparison features, as long the base system is there it can be extended relatively easily later on). Then, on a run of a test first all those asset-dependencies are collected. Then they can be, depending on further config, get newly created or selected from existing candidates on the target test-host system. In general the test-system can add a specific tag (like "test-asset") to such virtual guests by default, and also add that as implicit property condition (if no explicit tag-condition is already present) for when searching for existing assets, this way one can either re-use guests, be it because they exist due to running on a bare-metal system, that won't get rolled back, or even in some virtual system that gets rolled back to a state that already has to virtual-guest test-assets configured and thus can also reduce the time required to set up a clean environment by a lot, benefiting both use cases. Extra config, and/or command line, knobs can then force re-creation of all, or some asses of, a test, or the base search path for images, here it's probably enough to have some simpler definitively wanted ones to provide the core-infra for how to add others, maybe more complex knobs in the future more easily (creating new things is IMO always harder than extending existing ones, at least if non-trivial). > Also, the vmbd2 thingy that thomas mentioned might be interesting for Because I stumbled upon it today, systemd's mkosi tool could be also interesting here: https://github.com/systemd/mkosi https://github.com/systemd/mkosi/blob/main/mkosi/resources/mkosi.md > this - i've only glanced at it so far though. > > As of now it seems that this question will not influence the design of > the test runner much, so it can probably be postponed to a later > stage. Not of the runner itself, but all set up stuff for it, so I'd at least try to keep it in mind ? above features might not be that much work, but would create lots of flexibility to allow devs using it more easily for declarative reproduction tries of bugs too. At least I see it a big mental roadblock if I have to set up specific environments for using such tools, and cannot just re-use my existing ones 1:1. > >>> In theory, the test runner would also be able to drive tests on real >>> hardware, but of course with some limitations (harder to have a >>> predictable, reproducible environment, etc.) >> >> Maybe utilizing Aaron's installer for setting up those test systems >> could at least produce somewhat identical setups? Although it is >> really hard managing systems with different storage types, network >> cards, ... . > > In general my biggest concern with 'bare-metal' tests - and to > precise, that does not really have anything to do with being > 'bare-metal', more about testing on something that is harder roll back > into a clean state that can be used for the next test execution, is > that I'm afraid that a setup like this could become quite brittle and > a maintenance burden I don't see that as issue, just as two separate thing, one is regression testing in clean states where we can turn up reporting of test-failures to the max and the other is integration testing where we don't report widely but only allow some way to see list of issues for admins to decide. Bugs in the test system or configuration issue breaking idempotency assumptions can then be fixed, other issues that are not visible in those clean-room tests can become visible, I see no reason why both cannot co-exist and have equivalent priority/focus. New tests can be checked for basic idempotency by running them twice, with the second run not doing any rollback. >> I've seen GitLab using tags for runners that specify certain >> capabilities of systems. Maybe we could also introduce something like >> that here for different bare-metal systems? E.g. a test case >> specifies it needs a system with tag `ZFS` and then you can run / >> skip the respective test case on that system. Managing those tags can >> introduce quite a lot of churn though, so I'm not sure if this would >> be a good idea. > > I have thought about a tag system as well - not necessarily for test > runners, but for test cases. E.g. you could tag tests for the > authentication system with 'auth' - because at least for the local > development cycle it might not make much sense to run tests for > clusters, ceph, etc. while working on the authentication system. Yes, I thought about something like that too, a known set of tags (i.e., centrally managed set and bail, or at least warn if test uses unknown one) ? having test runs be filtered by their use classes, like "migration" or "windows" or your "auth" example would be definitively nice. >>> The test script is executed by the test runner; the test outcome is >>> determined by the exit code of the script. Test scripts could be >>> written >> Are you considering capturing output as well? That would make sense >> when using assertions at least, so in case of failures developers >> have a starting point for debugging. > Yup, I'd capture stdout/stderr from all test executables/scripts and > include it in the final test report. I guess there would be a (optional) notification to a set of addresses, passed to the test system via CLI/Config by the tester (human on manual tests or derived from changes and maintainers for automated tests), and that would only have a summary and link/point to the full report that provides the longer outputs of test harness and possibly system logs. > Test output is indeed very useful when determining *why* something > went wrong. Journalctl of all nodes that took part of a test might be useful too. >> Would it make sense to allow specifying a expected exit code for >> tests that actually should fail - or do you consider this something >> that should be handled by the test script? > > I guess that's a matter of taste. Personally I'd keep the contract > between test runner and test script simple and say 0 == success, > everything else is a failure. If there are any test cases that expect > a failure of some API call, then the script should 'translate' the > exit code. W.r.t. exit code I find that fine, but maybe we want to allow passing a more formal result text back, but we always can extend this by just using some special files that the test script writes to, or something like that, in the future, here starting out with simply checking exit code seems fine enough to me. From f.ebner at proxmox.com Tue Oct 17 14:10:07 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 17 Oct 2023 14:10:07 +0200 Subject: [pve-devel] [PATCH v3 qemu 2/7] buildsys: fixup submodule target In-Reply-To: <20231017121012.132636-1-f.ebner@proxmox.com> References: <20231017121012.132636-1-f.ebner@proxmox.com> Message-ID: <20231017121012.132636-3-f.ebner@proxmox.com> It's not enough to initialize the submodules anymore, as some got replaced by wrap files, see QEMU commit 2019cabfee ("meson: subprojects: replace submodules with wrap files"). Download the subprojects during initialization of the QEMU submodule, so building (without the automagical --enable-download) can succeeed afterwards. Signed-off-by: Fiona Ebner --- No changes in v3. Makefile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6c62c78..e389a9c 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,10 @@ all: $(DEBS) .PHONY: submodule submodule: - test -f "$(SRCDIR)/configure" || git submodule update --init --recursive +ifeq ($(shell test -f "$(SRCDIR)/configure" && echo 1 || echo 0), 0) + git submodule update --init --recursive + cd $(SRCDIR); meson subprojects download +endif PC_BIOS_FW_PURGE_LIST_IN = \ hppa-firmware.img \ -- 2.39.2 From f.ebner at proxmox.com Tue Oct 17 14:10:05 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 17 Oct 2023 14:10:05 +0200 Subject: [pve-devel] [PATCH-SERIES v3 qemu] update to QEMU 8.1.2 Message-ID: <20231017121012.132636-1-f.ebner@proxmox.com> Patch changes: For backup, opening the backup dump block driver needed to be adapted, because of coroutine context changes. Block graph locking was disabled, because of deadlocks. Snapshot code has a huge performance regression which required a workaround. Meta-changes: Use --disable-download options to avoid automatic downloads during build, but require the user to do so once themselves. Also done when initializing the submodule in the Makefile Switch back to using QEMU's keycodemapdb instead of splitting it out in our build-dir. Wasn't updated since QEMU 6.0 anymore and the reason for the split is not known. If anything pops up, we can re-do it and document the reason this time. Versioned Breaks for qemu-server required, because an upstream change would prevent VM boot with most configurations (including default one). Changes in v3: * Rebase on QEMU 8.1.2 which includes a few more TCG and migration-related fixes and... * ...remove previously picked-up patches that are included there. Changes in v2: * Add stable fix for guest-triggerable SCSI crash. * Make reverting graph locking complete. Fiona Ebner (7): d/rules: use disable-download option instead of git-submodules=ignore buildsys: fixup submodule target buildsys: use QEMU's keycodemapdb again update submodule and patches to QEMU 8.1.2 add patch to disable graph locking add patch to avoid huge snapshot performance regression d/control: add versioned Breaks for qemu-server <= 8.0.6 Makefile | 20 +- debian/control | 1 + ...d-support-for-sync-bitmap-mode-never.patch | 124 +- ...-support-for-conditional-and-always-.patch | 10 +- ...check-for-bitmap-mode-without-bitmap.patch | 4 +- ...-to-bdrv_dirty_bitmap_merge_internal.patch | 6 +- .../0006-mirror-move-some-checks-to-qmp.patch | 8 +- ...race-with-clients-disconnecting-earl.patch | 22 +- ...as-Internal-cdbs-have-16-byte-length.patch | 10 +- ...ial-deadlock-when-draining-during-tr.patch | 10 +- ...irty-bitmap-fix-loading-bitmap-when.patch} | 2 +- ...hen-getting-cursor-without-a-console.patch | 36 - ...el-async-DMA-operation-before-reset.patch} | 4 +- ...-memory-prevent-dma-reentracy-issues.patch | 130 - ...t-graph-lock-Disable-locking-for-now.patch | 140 + ...le-reentrancy-detection-for-script-R.patch | 39 - ...-disable-reentrancy-detection-for-io.patch | 37 - ...-workaround-snapshot-performance-reg.patch | 57 + ...sable-reentrancy-detection-for-iomem.patch | 35 - ...le-reentrancy-detection-for-apic-msi.patch | 36 - .../extra/0011-vhost-fix-the-fd-leak.patch | 29 - ...k-file-change-locking-default-to-off.patch | 6 +- ...he-CPU-model-to-kvm64-32-instead-of-.patch | 4 +- ...erfs-no-default-logfile-if-daemonize.patch | 4 +- ...PVE-Up-glusterfs-allow-partial-reads.patch | 2 +- ...return-success-on-info-without-snaps.patch | 4 +- ...dd-add-osize-and-read-from-to-stdin-.patch | 12 +- ...E-Up-qemu-img-dd-add-isize-parameter.patch | 14 +- ...PVE-Up-qemu-img-dd-add-n-skip_create.patch | 10 +- ...-add-l-option-for-loading-a-snapshot.patch | 14 +- ...virtio-balloon-improve-query-balloon.patch | 12 +- .../0014-PVE-qapi-modify-query-machines.patch | 12 +- .../0015-PVE-qapi-modify-spice-query.patch | 4 +- ...nnel-implementation-for-savevm-async.patch | 8 +- ...async-for-background-state-snapshots.patch | 58 +- ...add-optional-buffer-size-to-QEMUFile.patch | 44 +- ...add-the-zeroinit-block-driver-filter.patch | 10 +- ...-Add-dummy-id-command-line-parameter.patch | 10 +- ...le-posix-make-locking-optiono-on-cre.patch | 18 +- ...3-PVE-monitor-disable-oob-capability.patch | 4 +- ...sed-balloon-qemu-4-0-config-size-fal.patch | 4 +- ...E-Allow-version-code-in-machine-type.patch | 22 +- ...VE-Backup-add-vma-backup-format-code.patch | 22 +- ...-Backup-add-backup-dump-block-driver.patch | 4 +- ...ckup-Proxmox-backup-patches-for-QEMU.patch | 127 +- ...estore-new-command-to-restore-from-p.patch | 4 +- ...k-driver-to-map-backup-archives-into.patch | 54 +- ...ct-stderr-to-journal-when-daemonized.patch | 12 +- ...igrate-dirty-bitmap-state-via-savevm.patch | 23 +- ...dirty-bitmap-migrate-other-bitmaps-e.patch | 4 +- ...all-back-to-open-iscsi-initiatorname.patch | 4 +- ...PVE-block-stream-increase-chunk-size.patch | 2 +- ...accept-NULL-qiov-in-bdrv_pad_request.patch | 14 +- .../0039-block-add-alloc-track-driver.patch | 2 +- ...apshots-hold-the-BQL-during-setup-ca.patch | 24 +- ...vm-async-don-t-hold-BQL-during-setup.patch | 4 +- debian/patches/series | 13 +- debian/rules | 2 +- keycodemapdb/LICENSE.BSD | 27 - keycodemapdb/LICENSE.GPL2 | 339 --- keycodemapdb/README | 114 - keycodemapdb/data/README | 89 - keycodemapdb/data/keymaps.csv | 539 ---- keycodemapdb/meson.build | 1 - keycodemapdb/tests/.gitignore | 11 - keycodemapdb/tests/Makefile | 150 -- keycodemapdb/tests/javascript | 53 - keycodemapdb/tests/python2 | 3 - keycodemapdb/tests/python3 | 3 - keycodemapdb/tests/stdc++.cc | 40 - keycodemapdb/tests/stdc.c | 64 - keycodemapdb/tests/test.py | 30 - keycodemapdb/thirdparty/LICENSE-argparse.txt | 20 - keycodemapdb/thirdparty/__init__.py | 0 keycodemapdb/thirdparty/argparse.py | 2392 ----------------- keycodemapdb/tools/keymap-gen | 1147 -------- qemu | 2 +- 77 files changed, 617 insertions(+), 5758 deletions(-) rename debian/patches/extra/{0010-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch => 0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch} (98%) delete mode 100644 debian/patches/extra/0004-ui-return-NULL-when-getting-cursor-without-a-console.patch rename debian/patches/extra/{0012-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch => 0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch} (97%) delete mode 100644 debian/patches/extra/0005-memory-prevent-dma-reentracy-issues.patch create mode 100644 debian/patches/extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch delete mode 100644 debian/patches/extra/0006-lsi53c895a-disable-reentrancy-detection-for-script-R.patch delete mode 100644 debian/patches/extra/0007-bcm2835_property-disable-reentrancy-detection-for-io.patch create mode 100644 debian/patches/extra/0007-migration-states-workaround-snapshot-performance-reg.patch delete mode 100644 debian/patches/extra/0008-raven-disable-reentrancy-detection-for-iomem.patch delete mode 100644 debian/patches/extra/0009-apic-disable-reentrancy-detection-for-apic-msi.patch delete mode 100644 debian/patches/extra/0011-vhost-fix-the-fd-leak.patch delete mode 100644 keycodemapdb/LICENSE.BSD delete mode 100644 keycodemapdb/LICENSE.GPL2 delete mode 100644 keycodemapdb/README delete mode 100644 keycodemapdb/data/README delete mode 100644 keycodemapdb/data/keymaps.csv delete mode 100644 keycodemapdb/meson.build delete mode 100644 keycodemapdb/tests/.gitignore delete mode 100644 keycodemapdb/tests/Makefile delete mode 100755 keycodemapdb/tests/javascript delete mode 100755 keycodemapdb/tests/python2 delete mode 100755 keycodemapdb/tests/python3 delete mode 100644 keycodemapdb/tests/stdc++.cc delete mode 100644 keycodemapdb/tests/stdc.c delete mode 100644 keycodemapdb/tests/test.py delete mode 100644 keycodemapdb/thirdparty/LICENSE-argparse.txt delete mode 100644 keycodemapdb/thirdparty/__init__.py delete mode 100644 keycodemapdb/thirdparty/argparse.py delete mode 100755 keycodemapdb/tools/keymap-gen -- 2.39.2 From f.ebner at proxmox.com Tue Oct 17 14:10:06 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 17 Oct 2023 14:10:06 +0200 Subject: [pve-devel] [PATCH v3 qemu 1/7] d/rules: use disable-download option instead of git-submodules=ignore In-Reply-To: <20231017121012.132636-1-f.ebner@proxmox.com> References: <20231017121012.132636-1-f.ebner@proxmox.com> Message-ID: <20231017121012.132636-2-f.ebner@proxmox.com> See the following QEMU commits for reference: 0c5f3dcbb2 ("configure: add --enable-pypi and --disable-pypi") ac4ccac740 ("configure: rename --enable-pypi to --enable-download, control subprojects too") 6f3ae23b29 ("configure: remove --with-git-submodules=") removed The last one removed the option and the closest thing to git-submodule=ignore is using disable-download. Which will then just verify that the submodules are present. Building now will require running either * Running 'meson subprojects download' in the qemu submodule first. * Using --enable-download, but then the submodules would be downloaded for each build (if not already downloaded in the submodule first) and it's just a bit too surprising if downloads happen during build. The disable-download option will also disable automatic downloading of missing Python modules from PyPI. Hopefully, it's enough to add them as Debian build dependencies when required. Signed-off-by: Fiona Ebner --- No changes in v3. debian/rules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/debian/rules b/debian/rules index 4d06697..51f56c5 100755 --- a/debian/rules +++ b/debian/rules @@ -38,7 +38,7 @@ endif # guest-agent is only required for guest systems ./configure \ - --with-git-submodules=ignore \ + --disable-download \ --docdir=/usr/share/doc/pve-qemu-kvm \ --localstatedir=/var \ --prefix=/usr \ -- 2.39.2 From f.ebner at proxmox.com Tue Oct 17 14:10:12 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 17 Oct 2023 14:10:12 +0200 Subject: [pve-devel] [PATCH v3 qemu 7/7] d/control: add versioned Breaks for qemu-server <= 8.0.6 In-Reply-To: <20231017121012.132636-1-f.ebner@proxmox.com> References: <20231017121012.132636-1-f.ebner@proxmox.com> Message-ID: <20231017121012.132636-8-f.ebner@proxmox.com> Upstream QEMU commit 4271f40383 ("virtio-net: correctly report maximum tx_queue_size value") made setting an invalid tx_queue_size for a non-vDPA/vhost-user net device a hard error. Now, qemu-server before commit 089aed81 ("cfg2cmd: netdev: fix value for tx_queue_size") did just that, so the newer QEMU version would break start-up for most VMs (a default vNIC configuration would be affected). Signed-off-by: Fiona Ebner --- No changes in v3. debian/control | 1 + 1 file changed, 1 insertion(+) diff --git a/debian/control b/debian/control index 8328cd4..7e6cd6b 100644 --- a/debian/control +++ b/debian/control @@ -79,6 +79,7 @@ Replaces: pve-kvm, qemu-system-arm, qemu-system-x86, qemu-utils, +Breaks: qemu-server (<= 8.0.6) Description: Full virtualization on x86 hardware Using KVM, one can run multiple virtual PCs, each running unmodified Linux or Windows images. Each virtual machine has private virtualized hardware: a -- 2.39.2 From f.ebner at proxmox.com Tue Oct 17 14:10:11 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 17 Oct 2023 14:10:11 +0200 Subject: [pve-devel] [PATCH v3 qemu 6/7] add patch to avoid huge snapshot performance regression In-Reply-To: <20231017121012.132636-1-f.ebner@proxmox.com> References: <20231017121012.132636-1-f.ebner@proxmox.com> Message-ID: <20231017121012.132636-7-f.ebner@proxmox.com> Taking a snapshot became prohibitively slow because of the migration_transferred_bytes() call in migration_rate_exceeded() [0]. This also applied to the async snapshot taking in Proxmox VE, so work around the issue until it is fixed upstream. [0]: https://gitlab.com/qemu-project/qemu/-/issues/1821 Signed-off-by: Fiona Ebner --- No changes in v3. ...-workaround-snapshot-performance-reg.patch | 57 +++++++++++++++++++ debian/patches/series | 1 + 2 files changed, 58 insertions(+) create mode 100644 debian/patches/extra/0007-migration-states-workaround-snapshot-performance-reg.patch diff --git a/debian/patches/extra/0007-migration-states-workaround-snapshot-performance-reg.patch b/debian/patches/extra/0007-migration-states-workaround-snapshot-performance-reg.patch new file mode 100644 index 0000000..8031837 --- /dev/null +++ b/debian/patches/extra/0007-migration-states-workaround-snapshot-performance-reg.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Fiona Ebner +Date: Thu, 28 Sep 2023 11:19:14 +0200 +Subject: [PATCH] migration states: workaround snapshot performance regression + +Commit 813cd616 ("migration: Use migration_transferred_bytes() to +calculate rate_limit") introduced a prohibitive performance regression +when taking a snapshot [0]. The reason turns out to be the flushing +done by migration_transferred_bytes() + +Just use a _noflush version of the relevant function as a workaround +until upstream fixes the issue. This is inspired by a not-applied +upstream series [1], but doing the very minimum to avoid the +regression. + +[0]: https://gitlab.com/qemu-project/qemu/-/issues/1821 +[1]: https://lists.nongnu.org/archive/html/qemu-devel/2023-05/msg07708.html + +Signed-off-by: Fiona Ebner +--- + migration/migration-stats.c | 16 +++++++++++++++- + 1 file changed, 15 insertions(+), 1 deletion(-) + +diff --git a/migration/migration-stats.c b/migration/migration-stats.c +index 095d6d75bb..8073c8ebaa 100644 +--- a/migration/migration-stats.c ++++ b/migration/migration-stats.c +@@ -18,6 +18,20 @@ + + MigrationAtomicStats mig_stats; + ++/* ++ * Same as migration_transferred_bytes below, but using the _noflush ++ * variant of qemu_file_transferred() to avoid a performance ++ * regression in migration_rate_exceeded(). ++ */ ++static uint64_t migration_transferred_bytes_noflush(QEMUFile *f) ++{ ++ uint64_t multifd = stat64_get(&mig_stats.multifd_bytes); ++ uint64_t qemu_file = qemu_file_transferred_noflush(f); ++ ++ trace_migration_transferred_bytes(qemu_file, multifd); ++ return qemu_file + multifd; ++} ++ + bool migration_rate_exceeded(QEMUFile *f) + { + if (qemu_file_get_error(f)) { +@@ -25,7 +39,7 @@ bool migration_rate_exceeded(QEMUFile *f) + } + + uint64_t rate_limit_start = stat64_get(&mig_stats.rate_limit_start); +- uint64_t rate_limit_current = migration_transferred_bytes(f); ++ uint64_t rate_limit_current = migration_transferred_bytes_noflush(f); + uint64_t rate_limit_used = rate_limit_current - rate_limit_start; + uint64_t rate_limit_max = stat64_get(&mig_stats.rate_limit_max); + diff --git a/debian/patches/series b/debian/patches/series index 6d681da..c27c245 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -4,6 +4,7 @@ extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch +extra/0007-migration-states-workaround-snapshot-performance-reg.patch bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch -- 2.39.2 From f.ebner at proxmox.com Tue Oct 17 14:10:10 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 17 Oct 2023 14:10:10 +0200 Subject: [pve-devel] [PATCH v3 qemu 5/7] add patch to disable graph locking In-Reply-To: <20231017121012.132636-1-f.ebner@proxmox.com> References: <20231017121012.132636-1-f.ebner@proxmox.com> Message-ID: <20231017121012.132636-6-f.ebner@proxmox.com> There are still some issues with graph locking, e.g. deadlocks during backup canceling [0] and initial attempts to fix it didn't work [1]. Because the AioContext locks still exist, it should still be safe to disable graph locking. [0]: https://lists.nongnu.org/archive/html/qemu-devel/2023-09/msg00729.html [1]: https://lists.nongnu.org/archive/html/qemu-devel/2023-09/msg06905.html Signed-off-by: Fiona Ebner --- No changes in v3. ...t-graph-lock-Disable-locking-for-now.patch | 140 ++++++++++++++++++ debian/patches/series | 1 + 2 files changed, 141 insertions(+) create mode 100644 debian/patches/extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch diff --git a/debian/patches/extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch b/debian/patches/extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch new file mode 100644 index 0000000..f0648d2 --- /dev/null +++ b/debian/patches/extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch @@ -0,0 +1,140 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Fiona Ebner +Date: Thu, 28 Sep 2023 10:07:03 +0200 +Subject: [PATCH] Revert "Revert "graph-lock: Disable locking for now"" + +This reverts commit 3cce22defb4b0e47cf135444e30cc673cff5ebad. + +There are still some issues with graph locking, e.g. deadlocks during +backup canceling [0]. Because the AioContext locks still exist, it +should be safe to disable locking again. + +From the original 80fc5d2600 ("graph-lock: Disable locking for now"): + +> We don't currently rely on graph locking yet. It is supposed to replace +> the AioContext lock eventually to enable multiqueue support, but as long +> as we still have the AioContext lock, it is sufficient without the graph +> lock. Once the AioContext lock goes away, the deadlock doesn't exist any +> more either and this commit can be reverted. (Of course, it can also be +> reverted while the AioContext lock still exists if the callers have been +> fixed.) + +[0]: https://lists.nongnu.org/archive/html/qemu-devel/2023-09/msg00729.html + +Signed-off-by: Fiona Ebner +--- + block/graph-lock.c | 24 ++++++++++++++++++++++++ + 1 file changed, 24 insertions(+) + +diff --git a/block/graph-lock.c b/block/graph-lock.c +index 5e66f01ae8..5c2873262a 100644 +--- a/block/graph-lock.c ++++ b/block/graph-lock.c +@@ -30,8 +30,10 @@ BdrvGraphLock graph_lock; + /* Protects the list of aiocontext and orphaned_reader_count */ + static QemuMutex aio_context_list_lock; + ++#if 0 + /* Written and read with atomic operations. */ + static int has_writer; ++#endif + + /* + * A reader coroutine could move from an AioContext to another. +@@ -88,6 +90,7 @@ void unregister_aiocontext(AioContext *ctx) + g_free(ctx->bdrv_graph); + } + ++#if 0 + static uint32_t reader_count(void) + { + BdrvGraphRWlock *brdv_graph; +@@ -105,12 +108,19 @@ static uint32_t reader_count(void) + assert((int32_t)rd >= 0); + return rd; + } ++#endif + + void bdrv_graph_wrlock(BlockDriverState *bs) + { ++#if 0 + AioContext *ctx = NULL; + + GLOBAL_STATE_CODE(); ++ /* ++ * TODO Some callers hold an AioContext lock when this is called, which ++ * causes deadlocks. Reenable once the AioContext locking is cleaned up (or ++ * AioContext locks are gone). ++ */ + assert(!qatomic_read(&has_writer)); + + /* +@@ -158,11 +168,13 @@ void bdrv_graph_wrlock(BlockDriverState *bs) + if (ctx) { + aio_context_acquire(bdrv_get_aio_context(bs)); + } ++#endif + } + + void bdrv_graph_wrunlock(void) + { + GLOBAL_STATE_CODE(); ++#if 0 + QEMU_LOCK_GUARD(&aio_context_list_lock); + assert(qatomic_read(&has_writer)); + +@@ -174,10 +186,13 @@ void bdrv_graph_wrunlock(void) + + /* Wake up all coroutine that are waiting to read the graph */ + qemu_co_enter_all(&reader_queue, &aio_context_list_lock); ++#endif + } + + void coroutine_fn bdrv_graph_co_rdlock(void) + { ++ /* TODO Reenable when wrlock is reenabled */ ++#if 0 + BdrvGraphRWlock *bdrv_graph; + bdrv_graph = qemu_get_current_aio_context()->bdrv_graph; + +@@ -237,10 +252,12 @@ void coroutine_fn bdrv_graph_co_rdlock(void) + qemu_co_queue_wait(&reader_queue, &aio_context_list_lock); + } + } ++#endif + } + + void coroutine_fn bdrv_graph_co_rdunlock(void) + { ++#if 0 + BdrvGraphRWlock *bdrv_graph; + bdrv_graph = qemu_get_current_aio_context()->bdrv_graph; + +@@ -258,6 +275,7 @@ void coroutine_fn bdrv_graph_co_rdunlock(void) + if (qatomic_read(&has_writer)) { + aio_wait_kick(); + } ++#endif + } + + void bdrv_graph_rdlock_main_loop(void) +@@ -275,13 +293,19 @@ void bdrv_graph_rdunlock_main_loop(void) + void assert_bdrv_graph_readable(void) + { + /* reader_count() is slow due to aio_context_list_lock lock contention */ ++ /* TODO Reenable when wrlock is reenabled */ ++#if 0 + #ifdef CONFIG_DEBUG_GRAPH_LOCK + assert(qemu_in_main_thread() || reader_count()); + #endif ++#endif + } + + void assert_bdrv_graph_writable(void) + { + assert(qemu_in_main_thread()); ++ /* TODO Reenable when wrlock is reenabled */ ++#if 0 + assert(qatomic_read(&has_writer)); ++#endif + } diff --git a/debian/patches/series b/debian/patches/series index 01d4d3c..6d681da 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -3,6 +3,7 @@ extra/0002-scsi-megasas-Internal-cdbs-have-16-byte-length.patch extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch +extra/0006-Revert-Revert-graph-lock-Disable-locking-for-now.patch bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch -- 2.39.2 From f.ebner at proxmox.com Tue Oct 17 14:11:57 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 17 Oct 2023 14:11:57 +0200 Subject: [pve-devel] [PATCH-SERIES v2 qemu] update to QEMU 8.1.1 In-Reply-To: <20231006110148.154914-1-f.ebner@proxmox.com> References: <20231006110148.154914-1-f.ebner@proxmox.com> Message-ID: <1efd6d5b-e049-43b3-84a4-88ce9d1e3256@proxmox.com> Superseded by [PATCH-SERIES v3 qemu] update to QEMU 8.1.2: https://lists.proxmox.com/pipermail/pve-devel/2023-October/059485.html From l.wagner at proxmox.com Tue Oct 17 14:33:20 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Tue, 17 Oct 2023 14:33:20 +0200 Subject: [pve-devel] [RFC] towards automated integration testing In-Reply-To: <125370ed-9b55-42d2-b544-28683a17be79@proxmox.com> References: <1bb25817-66e7-406a-bd4b-0699de6cba31@proxmox.com> <832bc039-57d0-4d27-ac48-721c5c82af83@proxmox.com> <125370ed-9b55-42d2-b544-28683a17be79@proxmox.com> Message-ID: <9406389e-da89-475e-b8ad-e37efb72df10@proxmox.com> On 10/17/23 08:35, Thomas Lamprecht wrote: >>> Is the order of test-cases guaranteed by toml parsing, or how are intra- >>> fixture dependencies ensured? >>> >> >> Good point. With rollbacks in between test cases it probably does not >> matter much, but on 'real hardware' with no rollback this could >> definitely be a concern. >> A super simple thing that could just work fine is ordering test >> execution by testcase-names, sorted alphabetically. Ideally you'd write >> test cases that do not depend on each other any way, and *if* you ever >> find yourself in the situation where you *need* some ordering, you >> could> just encode the order in the test-case name by adding an integer >> prefix> - similar how you would name config files in /etc/sysctl.d/*, >> for instance. > > > While it can be OK to leave that for later, encoding such things > in names is IMO brittle and hard to manage if more than a handful > of tests, and we hopefully got lots more ;-) > > > From top of my head I'd rather do some attribute based dependency > annotation, so that one can depend on single tests, or whole fixture > on others single tests or whole fixture. > The more thought I spend on it, the more I believe that inter-testcase deps should be avoided as much as possible. In unit testing, (hidden) dependencies between tests are in my experience the no. 1 cause of flaky tests, and I see no reason why this would not also apply for end-to-end integration testing. I'd suggest to only allow test cases to depend on fixtures. The fixtures themselves could have setup/teardown hooks that allow setting up and cleaning up a test scenario. If needed, we could also have something like 'fixture inheritance', where a fixture can 'extend' another, supplying additional setup/teardown. Example: the 'outermost' or 'parent' fixture might define that we want a 'basic PVE installation' with the latest .debs deployed, while another fixture that inherits from that one might set up a storage of a certain type, useful for all tests that require specific that type of storage. On the other hand, instead of inheritance, a 'role/trait'-based system might also work (composition >>> inheritance, after all) - and maybe that also aligns better with the 'properties' mentioned in your other mail (I mean this here: "ostype=win*", "memory>=10G"). This is essentially a very similar pattern as in numerous other testing frameworks (xUnit, pytest, etc.); I think it makes sense to build upon this battle-proven approach. Regarding execution order, I'd now even suggest the polar opposite of my prior idea. Instead of enforcing some execution order, we could also actively shuffle execution order from run to run, at least for tests using the same fixture. The seed used for the RNG should be put into the test report and could also be provided via a flag to the test runner, in case we need to repeat a specific test sequence . In that way, the runner would actively help us to hunt down hidden inter-TC deps, making our test suite hopefully less brittle and more robust in the long term. Any way, lots of details to figure out. Thanks again for your input. -- - Lukas From c.ebner at proxmox.com Tue Oct 17 14:38:27 2023 From: c.ebner at proxmox.com (Christian Ebner) Date: Tue, 17 Oct 2023 14:38:27 +0200 Subject: [pve-devel] [RFC pve-container] backup: do not delete not backed-up mps on restore Message-ID: <20231017123827.313014-1-c.ebner@proxmox.com> The current behaviour of the restore is to recreate all backed up mountpoints and remove all previous ones, causing potential data loss on restore when the mountpoint was not included in the backup and the user not aware of this behaviour. By checking the mountpoint configuration from the backup, only recreate the disks which are included in the backup and add them as mountpoints. Leave all other mountpoints untouched and attach them as unused disks as final step of the restore. To facilitate selective restore from PBS backups, split the currently single root pxar archive into a pxar archive for each individual mountpoint, while remaining backwards compatible. Signed-off-by: Christian Ebner --- I'm sending this as RFC since backwards compatibility for restore still suffers from the fact, that mountpoints are now expected to have their corresponding pxar archive for PBS backups, all previous backups however do not follow this scheme. For now, this is handled by treating restore errors of these archives as soft errors, meaning restore will continue. This is not ideal and I am very much open for suggestions on how this might be handled better. src/PVE/API2/LXC.pm | 26 ++++++++++++---- src/PVE/LXC/Create.pm | 72 +++++++++++++++++++++++++++++++++++-------- src/PVE/VZDump/LXC.pm | 10 +++--- 3 files changed, 85 insertions(+), 23 deletions(-) diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm index 28d14de..3c7b22d 100644 --- a/src/PVE/API2/LXC.pm +++ b/src/PVE/API2/LXC.pm @@ -381,8 +381,10 @@ __PACKAGE__->register_method({ my $vollist = []; eval { my $orig_mp_param; # only used if $restore + my $clear_mps; if ($restore) { die "can't overwrite running container\n" if PVE::LXC::check_running($vmid); + if ($archive ne '-') { my $orig_conf; print "recovering backed-up configuration from '$archive'\n"; @@ -424,6 +426,14 @@ __PACKAGE__->register_method({ if !defined($mp_param->{rootfs}); PVE::LXC::Config->foreach_volume($mp_param, sub { my ($ms, $mountpoint) = @_; + if ($ms eq 'rootfs' || $mountpoint->{backup}) { + # backup conf contains the mp, clear for retsore + $clear_mps->{$ms} = $mountpoint; + } else { + # do not add as mp, will be attach as unused at the end + delete $mp_param->{$ms}; + return; + } my $type = $mountpoint->{type}; if ($type eq 'volume') { die "unable to detect disk size - please specify $ms (size)\n" @@ -459,18 +469,19 @@ __PACKAGE__->register_method({ $vollist = PVE::LXC::create_disks($storage_cfg, $vmid, $mp_param, $conf); - # we always have the 'create' lock so check for more than 1 entry - if (scalar(keys %$old_conf) > 1) { - # destroy old container volumes - PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $old_conf, { lock => 'create' }); - } + # Delete old mountpoints which are restored from backup. + PVE::LXC::Config->foreach_volume($old_conf, sub { + my ($name, $mountpoint, undef) = @_; + return if !defined($clear_mps->{$name}); + PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $mountpoint->{volume}); + }); eval { my $rootdir = PVE::LXC::mount_all($vmid, $storage_cfg, $conf, 1); $bwlimit = PVE::Storage::get_bandwidth_limit('restore', [keys %used_storages], $bwlimit); print "restoring '$archive' now..\n" if $restore && $archive ne '-'; - PVE::LXC::Create::restore_archive($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit); + PVE::LXC::Create::restore_archive($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit, $orig_mp_param); if ($restore) { print "merging backed-up and given configuration..\n"; @@ -501,6 +512,9 @@ __PACKAGE__->register_method({ $conf->{template} = 1; } PVE::LXC::Config->write_config($vmid, $conf); + + # Attach all additionally found mountpoints as unused disks. + PVE::LXC::rescan($vmid, 1, 0); }; if (my $err = $@) { PVE::LXC::destroy_disks($storage_cfg, $vollist); diff --git a/src/PVE/LXC/Create.pm b/src/PVE/LXC/Create.pm index f4c3220..74d7954 100644 --- a/src/PVE/LXC/Create.pm +++ b/src/PVE/LXC/Create.pm @@ -83,27 +83,33 @@ sub detect_architecture { } sub restore_archive { - my ($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit) = @_; + my ($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $orig_mp_param) = @_; + my $orig_mps; + PVE::LXC::Config->foreach_volume($orig_mp_param, sub { + my ($name, $vol, undef) = @_; + $orig_mps->{$name} = $vol; + }); my ($storeid, $volname) = PVE::Storage::parse_volume_id($archive, 1); if (defined($storeid)) { my $scfg = PVE::Storage::storage_check_enabled($storage_cfg, $storeid); if ($scfg->{type} eq 'pbs') { - return restore_proxmox_backup_archive($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit); + return restore_proxmox_backup_archive( + $storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $orig_mps); } } $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $archive) if $archive ne '-'; - restore_tar_archive($archive, $rootdir, $conf, $no_unpack_error, $bwlimit); + restore_tar_archive($archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $orig_mps); } sub restore_proxmox_backup_archive { - my ($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit) = @_; + my ($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $orig_mps) = @_; my ($storeid, $volname) = PVE::Storage::parse_volume_id($archive); my $scfg = PVE::Storage::storage_config($storage_cfg, $storeid); - my ($vtype, $name, undef, undef, undef, undef, $format) = + my ($vtype, $snapshot, undef, undef, undef, undef, $format) = PVE::Storage::parse_volname($storage_cfg, $archive); die "got unexpected vtype '$vtype'\n" if $vtype ne 'backup'; @@ -114,14 +120,47 @@ sub restore_proxmox_backup_archive { my $userns_cmd = PVE::LXC::userns_command($id_map); my $cmd = "restore"; - my $param = [$name, "root.pxar", $rootdir, '--allow-existing-dirs']; + PVE::LXC::Config->foreach_volume($conf, sub { + my ($name, $vol, undef) = @_; - if ($no_unpack_error) { - push(@$param, '--ignore-extract-device-errors'); - } + my $orig_mp = $orig_mps->{$name}; + if (!defined($orig_mp)) { + print "'$name': mp not present in original backup.\n"; + return; + } - PVE::Storage::PBSPlugin::run_raw_client_cmd( - $scfg, $storeid, $cmd, $param, userns_cmd => $userns_cmd); + if ($name ne 'rootfs' && (!defined($orig_mp->{backup}) || !$orig_mp->{backup})) { + print "'$name': mp not included in original backup.\n"; + return; + } + + $name = 'root' if $name eq 'rootfs'; + my $target = "$rootdir/.$vol->{mp}"; + if ($orig_mp->{mp} ne $vol->{mp}) { + print "'$name': path differs from backed-up path, restore to backed-up path.\n"; + print " If this is not intended, change the path after restore.\n"; + $target = "$rootdir/.$orig_mp->{mp}"; + } + + my $param = [$snapshot, "$name.pxar", $target, '--allow-existing-dirs']; + + if ($no_unpack_error) { + push(@$param, '--ignore-extract-device-errors'); + } + + eval { + # This will fail for backups created without mp archive splitting + PVE::Storage::PBSPlugin::run_raw_client_cmd( + $scfg, $storeid, $cmd, $param, userns_cmd => $userns_cmd); + }; + my $err = $@; + if ($err) { + # Only handle root restore failure as hard error + die $err if $name eq 'root'; + print "extracting moutpoint '$name' failed:\n$err"; + print "backup created with older version?\n"; + } + }); # if arch is set, we do not try to autodetect it return if defined($conf->{arch}); @@ -130,11 +169,20 @@ sub restore_proxmox_backup_archive { } sub restore_tar_archive { - my ($archive, $rootdir, $conf, $no_unpack_error, $bwlimit) = @_; + my ($archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $orig_mps) = @_; my ($id_map, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf); my $userns_cmd = PVE::LXC::userns_command($id_map); + PVE::LXC::Config->foreach_volume($conf, sub { + my ($name, $vol, undef) = @_; + my $orig_mp = $orig_mps->{$name}; + if (defined($orig_mp->{mp}) && $orig_mp->{mp} ne $vol->{mp}) { + print "'$name': path differs from backed-up path, restore to backed-up path.\n"; + print " If this is not intended, change the path after restore.\n"; + } + }); + my $archive_fh; my $tar_input = '<&STDIN'; my @compression_opt; diff --git a/src/PVE/VZDump/LXC.pm b/src/PVE/VZDump/LXC.pm index c68a06f..0d411b8 100644 --- a/src/PVE/VZDump/LXC.pm +++ b/src/PVE/VZDump/LXC.pm @@ -381,11 +381,11 @@ sub archive { push @$param, "fw.conf:$fw_conf"; } - my $rootdir = $snapdir; - push @$param, "root.pxar:$rootdir"; - - foreach my $disk (@sources) { - push @$param, '--include-dev', "$snapdir/$disk"; + foreach my $disk (@$disks) { + my $name = $disk->{name}; + # Needed for backwards compatibility with previous backups + $name = 'root' if $name eq 'rootfs'; + push @$param, "$name.pxar:$snapdir/.$disk->{mp}"; } push @$param, '--skip-lost-and-found' if $userns_cmd; -- 2.39.2 From s.hanreich at proxmox.com Tue Oct 17 15:54:59 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 15:54:59 +0200 Subject: [pve-devel] [WIP v2 pve-network 02/10] subnets: vnets: preparations for DHCP plugins In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: <20231017135507.2220948-3-s.hanreich@proxmox.com> Add the option to retrieve the running configuration instead of only the pending configuration via the config methods. Refactor methods using the running config to utilize the new parameter. Additionally include the id for subnets, since it is needed later by some of the DHCP plugins. Signed-off-by: Stefan Hanreich --- src/PVE/Network/SDN/Subnets.pm | 25 +++++++++++++------------ src/PVE/Network/SDN/Vnets.pm | 27 +++++++++++++-------------- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/PVE/Network/SDN/Subnets.pm b/src/PVE/Network/SDN/Subnets.pm index 6bb42e5..f654d3a 100644 --- a/src/PVE/Network/SDN/Subnets.pm +++ b/src/PVE/Network/SDN/Subnets.pm @@ -23,7 +23,9 @@ sub sdn_subnets_config { my $scfg = $cfg->{ids}->{$id}; die "sdn subnet '$id' does not exist\n" if (!$noerr && !$scfg); - if($scfg) { + if ($scfg) { + $scfg->{id} = $id; + my ($zone, $network, $mask) = split(/-/, $id); $scfg->{cidr} = "$network/$mask"; $scfg->{zone} = $zone; @@ -35,7 +37,14 @@ sub sdn_subnets_config { } sub config { - my $config = cfs_read_file("sdn/subnets.cfg"); + my ($running) = @_; + + if ($running) { + my $cfg = PVE::Network::SDN::running_config(); + return $cfg->{subnets}; + } + + return cfs_read_file("sdn/subnets.cfg"); } sub write_config { @@ -61,16 +70,8 @@ sub complete_sdn_subnet { sub get_subnet { my ($subnetid, $running) = @_; - my $cfg = {}; - if($running) { - my $cfg = PVE::Network::SDN::running_config(); - $cfg = $cfg->{subnets}; - } else { - $cfg = PVE::Network::SDN::Subnets::config(); - } - - my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $subnetid, 1); - return $subnet; + my $cfg = PVE::Network::SDN::Subnets::config($running); + return PVE::Network::SDN::Subnets::sdn_subnets_config($cfg, $subnetid, 1); } sub find_ip_subnet { diff --git a/src/PVE/Network/SDN/Vnets.pm b/src/PVE/Network/SDN/Vnets.pm index 1106c9f..39bdda0 100644 --- a/src/PVE/Network/SDN/Vnets.pm +++ b/src/PVE/Network/SDN/Vnets.pm @@ -26,6 +26,13 @@ sub sdn_vnets_config { } sub config { + my ($running) = @_; + + if ($running) { + my $cfg = PVE::Network::SDN::running_config(); + return $cfg->{vnets}; + } + return cfs_read_file("sdn/vnets.cfg"); } @@ -54,31 +61,23 @@ sub get_vnet { return if !$vnetid; - my $scfg = {}; - if($running) { - my $cfg = PVE::Network::SDN::running_config(); - $scfg = $cfg->{vnets}; - } else { - $scfg = PVE::Network::SDN::Vnets::config(); - } - - my $vnet = PVE::Network::SDN::Vnets::sdn_vnets_config($scfg, $vnetid, 1); - - return $vnet; + my $cfg = PVE::Network::SDN::Vnets::config($running); + return PVE::Network::SDN::Vnets::sdn_vnets_config($cfg, $vnetid, 1); } sub get_subnets { - my ($vnetid) = @_; + my ($vnetid, $running) = @_; my $subnets = undef; - my $subnets_cfg = PVE::Network::SDN::Subnets::config(); + my $subnets_cfg = PVE::Network::SDN::Subnets::config($running); + foreach my $subnetid (sort keys %{$subnets_cfg->{ids}}) { my $subnet = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets_cfg, $subnetid); next if !$subnet->{vnet} || ($vnetid && $subnet->{vnet} ne $vnetid); $subnets->{$subnetid} = $subnet; } - return $subnets; + return $subnets; } sub get_subnet_from_vnet_cidr { -- 2.39.2 From s.hanreich at proxmox.com Tue Oct 17 15:55:05 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 15:55:05 +0200 Subject: [pve-devel] [WIP v2 pve-manager 08/10] sdn: regenerate DHCP config on reload In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: <20231017135507.2220948-9-s.hanreich@proxmox.com> Signed-off-by: Stefan Hanreich --- PVE/API2/Network.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/PVE/API2/Network.pm b/PVE/API2/Network.pm index 00d964a79..f39f04f52 100644 --- a/PVE/API2/Network.pm +++ b/PVE/API2/Network.pm @@ -660,6 +660,7 @@ __PACKAGE__->register_method({ if ($have_sdn) { PVE::Network::SDN::generate_zone_config(); + PVE::Network::SDN::generate_dhcp_config(); } my $err = sub { -- 2.39.2 From s.hanreich at proxmox.com Tue Oct 17 15:55:03 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 15:55:03 +0200 Subject: [pve-devel] [WIP v2 pve-network 06/10] ipam: Add helper methods for DHCP to PVE IPAM In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: <20231017135507.2220948-7-s.hanreich@proxmox.com> Those methods are used by the DHCP plugins to attain the next free IP address for a given DHCP range, as well as delete all entries with a certain MAC address. Signed-off-by: Stefan Hanreich --- src/PVE/Network/SDN/Ipams/PVEPlugin.pm | 64 ++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm index 3e8ffc5..fcc8282 100644 --- a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm +++ b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm @@ -156,6 +156,70 @@ sub add_next_freeip { return "$freeip/$mask"; } +sub add_dhcp_ip { + my ($class, $subnet, $dhcp_range, $data) = @_; + + my $cidr = $subnet->{cidr}; + my $zone = $subnet->{zone}; + + cfs_lock_file($ipamdb_file, undef, sub { + my $db = read_db(); + + my $dbzone = $db->{zones}->{$zone}; + die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone; + + my $dbsubnet = $dbzone->{subnets}->{$cidr}; + die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet; + + my $ip = new Net::IP ("$dhcp_range->{'start-address'} - $dhcp_range->{'end-address'}") + or die "Invalid IP address(es) in DHCP Range!\n"; + + do { + my $ip_address = $ip->ip(); + if (!$dbsubnet->{ips}->{$ip_address}) { + $dbsubnet->{ips}->{$ip_address} = $data; + write_db($db); + + return $ip_address; + } + } while (++$ip); + + die "No free IP left in DHCP Range $dhcp_range->{'start-address'}:$dhcp_range->{'end-address'}}\n"; + }); +} + +sub del_dhcp_ip { + my ($class, $subnet, $mac) = @_; + + my $cidr = $subnet->{cidr}; + my $zone = $subnet->{zone}; + + my $returned_ip = undef; + + cfs_lock_file($ipamdb_file, undef, sub { + my $db = read_db(); + + die "zone $zone don't exist in ipam db" if !$db->{zones}->{$zone}; + my $dbzone = $db->{zones}->{$zone}; + + die "subnet $cidr don't exist in ipam db" if !$dbzone->{subnets}->{$cidr}; + my $dbsubnet = $dbzone->{subnets}->{$cidr}; + + foreach my $ip_address (keys %{$dbsubnet->{ips}}) { + my $data = $dbsubnet->{ips}->{$ip_address}; + next if !$data->{mac} || $data->{mac} ne $mac; + + delete $dbsubnet->{ips}->{$ip_address}; + write_db($db); + + $returned_ip = $ip_address; + } + }); + die "$@" if $@; + + return $returned_ip; +} + sub del_ip { my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_; -- 2.39.2 From s.hanreich at proxmox.com Tue Oct 17 15:55:06 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 15:55:06 +0200 Subject: [pve-devel] [WIP v2 qemu-server 09/10] sdn: dhcp: add DHCP setup to vm-network-scripts In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: <20231017135507.2220948-10-s.hanreich@proxmox.com> When setting up the bridge for the VMs, also set up the DHCP mappings in the respective DHCP plugins if the VM has interfaces on SDN networks that utilize DHCP. Also remove the mapping in the VM cleanup function, so the mappings also get removed when stopping the VM forcefully. Signed-off-by: Stefan Hanreich --- PVE/QemuServer.pm | 14 ++++++++++++++ vm-network-scripts/pve-bridge | 3 +++ vm-network-scripts/pve-bridgedown | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 2cd8948..6c1e463 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -6098,6 +6098,18 @@ sub cleanup_pci_devices { PVE::QemuServer::PCI::remove_pci_reservation($vmid); } +sub cleanup_sdn_dhcp { + my ($vmid, $conf) = @_; + + for my $k (keys %$conf) { + next if $k !~ /^net(\d+)/; + my $netconf = $conf->{$k}; + my $net = PVE::QemuServer::parse_net($netconf); + + PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{macaddr}); + } +} + sub vm_stop_cleanup { my ($storecfg, $vmid, $conf, $keepActive, $apply_pending_changes) = @_; @@ -6131,6 +6143,8 @@ sub vm_stop_cleanup { cleanup_pci_devices($vmid, $conf); + cleanup_sdn_dhcp($vmid, $conf); + vmconfig_apply_pending($vmid, $conf, $storecfg) if $apply_pending_changes; }; warn $@ if $@; # avoid errors - just warn diff --git a/vm-network-scripts/pve-bridge b/vm-network-scripts/pve-bridge index d37ce33..5c8acdf 100755 --- a/vm-network-scripts/pve-bridge +++ b/vm-network-scripts/pve-bridge @@ -10,6 +10,7 @@ use PVE::Network; my $have_sdn; eval { require PVE::Network::SDN::Zones; + require PVE::Network::SDN::Dhcp; $have_sdn = 1; }; @@ -44,6 +45,8 @@ my $net = PVE::QemuServer::parse_net($netconf); die "unable to parse network config '$netid'\n" if !$net; if ($have_sdn) { + PVE::Network::SDN::Dhcp::add_mapping($vmid, $net->{bridge}, $net->{macaddr}); + PVE::Network::SDN::Zones::tap_create($iface, $net->{bridge}); PVE::Network::SDN::Zones::tap_plug($iface, $net->{bridge}, $net->{tag}, $net->{firewall}, $net->{trunks}, $net->{rate}); } else { diff --git a/vm-network-scripts/pve-bridgedown b/vm-network-scripts/pve-bridgedown index d18d88f..a220660 100755 --- a/vm-network-scripts/pve-bridgedown +++ b/vm-network-scripts/pve-bridgedown @@ -4,6 +4,13 @@ use strict; use warnings; use PVE::Network; +my $have_sdn; +eval { + require PVE::Network::SDN::Zones; + require PVE::Network::SDN::Dhcp; + $have_sdn = 1; +}; + my $iface = shift; die "no interface specified\n" if !$iface; @@ -11,6 +18,18 @@ die "no interface specified\n" if !$iface; die "got strange interface name '$iface'\n" if $iface !~ m/^tap(\d+)i(\d+)$/; +my $vmid = $1; +my $netid = "net$2"; + +my $conf = PVE::QemuConfig->load_config($vmid); + +my $netconf = $conf->{$netid}; +my $net = PVE::QemuServer::parse_net($netconf); + +if ($have_sdn) { + PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{macaddr}); +} + PVE::Network::tap_unplug($iface); exit 0; -- 2.39.2 From s.hanreich at proxmox.com Tue Oct 17 15:55:02 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 15:55:02 +0200 Subject: [pve-devel] [WIP v2 pve-network 05/10] dhcp: add DHCP plugin for dnsmasq In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: <20231017135507.2220948-6-s.hanreich@proxmox.com> The plugin generates several dnsmasq configuration files from the SDN configuration. /etc/default/dnsmasq. This file specifies the configuration directory for the dnsmasq instance (/etc/dnsmasq.d/). It also sets the configuration file to /dev/null so the default configuration from the package has no influence on the dnsmasq configuration. /etc/dnsmasq.d//00-default.conf The default configration does several things: * disable DNS functionality. * make dnsmasq listen only on the interfaces where it should provide DHCP (contrary to the default configuration, which listens on all interfaces). This is particularly important when running multiple instances of dnsmasq. * Set the lease file to /var/lib/misc/dnsmasq..leases * add some security-related settings * add some platform-specific settings * set dnsmasq to only hand out IP addresses to known hosts /etc/dnsmasq.d//10-.conf This file contains the subnet specific settings, which usually includes: * Listen address * Default gateway (for IPv4) * DNS server /etc/dnsmasq.d//10--ranges.conf This file contains the DHCP ranges configured for the subnet . /etc/dnsmasq.d//ethers This file contains the MAC address to IP mappings for all VMs and CTs that should be used by dnsmasq DHCP. There is no need to split them on a subnet level, since the MAC <-> IP mappings should always be unique. Currently regenerating and reloading dnsmasq is very sledgehammery. It deletes all existing subnet configuration files and disables + stops all currently running dnsmasq instances. Then it enables + starts all dnsmasq instances that have been recreated. I intend to improve this behaviour in the future either by getting access to the old configuration or using systemd targets. Adding new MAC address mappings is done with a simple reload that does not disrupt the dnsmasq daemon in any way. This plugin currently only works for simple Zones with subnets that have a gateway configured, since I use the gateway as listening address for dnsmasq. Signed-off-by: Stefan Hanreich --- debian/control | 1 + src/PVE/Network/SDN/Dhcp/Dnsmasq.pm | 186 ++++++++++++++++++++++++++++ src/PVE/Network/SDN/Dhcp/Makefile | 2 +- 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm diff --git a/debian/control b/debian/control index 8b720c3..4424096 100644 --- a/debian/control +++ b/debian/control @@ -24,6 +24,7 @@ Depends: libpve-common-perl (>= 5.0-45), ${misc:Depends}, ${perl:Depends}, Recommends: frr-pythontools (>= 8.5.1~), ifupdown2 +Suggests: dnsmasq Description: Proxmox VE's SDN (Software Defined Network) stack This package contains the Software Defined Network (tech preview) for Proxmox VE. diff --git a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm new file mode 100644 index 0000000..af109b8 --- /dev/null +++ b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm @@ -0,0 +1,186 @@ +package PVE::Network::SDN::Dhcp::Dnsmasq; + +use strict; +use warnings; + +use base qw(PVE::Network::SDN::Dhcp::Plugin); + +use Net::IP qw(:PROC); +use PVE::Tools qw(file_set_contents run_command lock_file); + +use File::Copy; + +my $DNSMASQ_CONFIG_ROOT = '/etc/dnsmasq.d'; +my $DNSMASQ_DEFAULT_ROOT = '/etc/default'; +my $DNSMASQ_LEASE_ROOT = '/var/lib/misc'; + +sub type { + return 'dnsmasq'; +} + +sub del_ip_mapping { + my ($class, $dhcp_config, $mac) = @_; + + my $ethers_file = "$DNSMASQ_CONFIG_ROOT/$dhcp_config->{id}/ethers"; + my $ethers_tmp_file = "$ethers_file.tmp"; + + my $removeFn = sub { + open(my $in, '<', $ethers_file) or die "Could not open file '$ethers_file' $!\n"; + open(my $out, '>', $ethers_tmp_file) or die "Could not open file '$ethers_tmp_file' $!\n"; + + while (my $line = <$in>) { + next if $line =~ m/^$mac/; + print $out $line; + } + + close $in; + close $out; + + move $ethers_tmp_file, $ethers_file; + + chmod 0644, $ethers_file; + }; + + PVE::Tools::lock_file($ethers_file, 10, $removeFn); + + if ($@) { + warn "Unable to remove $mac from the dnsmasq configuration: $@\n"; + return; + } + + my $service_name = "dnsmasq\@$dhcp_config->{id}"; + PVE::Tools::run_command(['systemctl', 'reload', $service_name]); +} + +sub add_ip_mapping { + my ($class, $dhcp_config, $mac, $ip) = @_; + my $ethers_file = "$DNSMASQ_CONFIG_ROOT/$dhcp_config->{id}/ethers"; + + my $appendFn = sub { + open(my $fh, '>>', $ethers_file) or die "Could not open file '$ethers_file' $!\n"; + print $fh "$mac,$ip\n"; + close $fh; + }; + + PVE::Tools::lock_file($ethers_file, 10, $appendFn); + + if ($@) { + warn "Unable to add $mac/$ip to the dnsmasq configuration: $@\n"; + return; + } + + my $service_name = "dnsmasq\@$dhcp_config->{id}"; + PVE::Tools::run_command(['systemctl', 'reload', $service_name]); +} + +sub configure_subnet { + my ($class, $dhcp_config, $subnet_config) = @_; + + die "No gateway defined for subnet $subnet_config->{id}" + if !$subnet_config->{gateway}; + + my $tag = $subnet_config->{id}; + + my @dnsmasq_config = ( + "listen-address=$subnet_config->{gateway}", + ); + + my $option_string; + if (ip_is_ipv6($subnet_config->{network})) { + $option_string = 'option6'; + push @dnsmasq_config, "enable-ra"; + } else { + $option_string = 'option'; + push @dnsmasq_config, "dhcp-option=tag:$tag,$option_string:router,$subnet_config->{gateway}"; + } + + push @dnsmasq_config, "dhcp-option=tag:$tag,$option_string:dns-server,$subnet_config->{'dhcp-dns-server'}" + if $subnet_config->{'dhcp-dns-server'}; + + PVE::Tools::file_set_contents( + "$DNSMASQ_CONFIG_ROOT/$dhcp_config->{id}/10-$subnet_config->{id}.conf", + join("\n", @dnsmasq_config) . "\n" + ); +} + +sub configure_range { + my ($class, $dhcp_config, $subnet_config, $range_config) = @_; + + my $range_file = "$DNSMASQ_CONFIG_ROOT/$dhcp_config->{id}/10-$subnet_config->{id}.ranges.conf", + my $tag = $subnet_config->{id}; + + open(my $fh, '>>', $range_file) or die "Could not open file '$range_file' $!\n"; + print $fh "dhcp-range=set:$tag,$range_config->{'start-address'},$range_config->{'end-address'}\n"; + close $fh; +} + +sub before_configure { + my ($class, $dhcp_config) = @_; + + my $config_directory = "$DNSMASQ_CONFIG_ROOT/$dhcp_config->{id}"; + + mkdir($config_directory, 755) if !-d $config_directory; + + my $default_config = <{id}", + $default_config + ); + + my $default_dnsmasq_config = <{id}.leases +dhcp-hostsfile=$config_directory/ethers +dhcp-ignore=tag:!known + +# Send an empty WPAD option. This may be REQUIRED to get windows 7 to behave. +dhcp-option=252,"\\n" + +# Send microsoft-specific option to tell windows to release the DHCP lease +# when it shuts down. Note the "i" flag, to tell dnsmasq to send the +# value as a four-byte integer - that's what microsoft wants. +dhcp-option=vendor:MSFT,2,1i + +# If a DHCP client claims that its name is "wpad", ignore that. +# This fixes a security hole. see CERT Vulnerability VU#598349 +dhcp-name-match=set:wpad-ignore,wpad +dhcp-ignore-names=tag:wpad-ignore +CFG + + PVE::Tools::file_set_contents( + "$config_directory/00-default.conf", + $default_dnsmasq_config + ); + + unlink glob "$config_directory/10-*.conf"; +} + +sub after_configure { + my ($class, $dhcp_config) = @_; + + my $service_name = "dnsmasq\@$dhcp_config->{id}"; + + PVE::Tools::run_command(['systemctl', 'enable', $service_name]); + PVE::Tools::run_command(['systemctl', 'restart', $service_name]); +} + +sub before_regenerate { + my ($class) = @_; + + PVE::Tools::run_command(['systemctl', 'stop', "dnsmasq@*"]); + PVE::Tools::run_command(['systemctl', 'disable', 'dnsmasq@']); +} + +sub after_regenerate { + my ($class) = @_; + # noop +} + +1; diff --git a/src/PVE/Network/SDN/Dhcp/Makefile b/src/PVE/Network/SDN/Dhcp/Makefile index 1e9b6d3..6546513 100644 --- a/src/PVE/Network/SDN/Dhcp/Makefile +++ b/src/PVE/Network/SDN/Dhcp/Makefile @@ -1,4 +1,4 @@ -SOURCES=Plugin.pm +SOURCES=Plugin.pm Dnsmasq.pm PERL5DIR=${DESTDIR}/usr/share/perl5 -- 2.39.2 From s.hanreich at proxmox.com Tue Oct 17 15:54:58 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 15:54:58 +0200 Subject: [pve-devel] [WIP v2 pve-cluster 01/10] cluster files: add dhcp.cfg In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: <20231017135507.2220948-2-s.hanreich@proxmox.com> Signed-off-by: Stefan Hanreich --- src/PVE/Cluster.pm | 1 + src/pmxcfs/status.c | 1 + 2 files changed, 2 insertions(+) diff --git a/src/PVE/Cluster.pm b/src/PVE/Cluster.pm index cfa2583..aac4574 100644 --- a/src/PVE/Cluster.pm +++ b/src/PVE/Cluster.pm @@ -78,6 +78,7 @@ my $observed = { 'sdn/subnets.cfg' => 1, 'sdn/ipams.cfg' => 1, 'sdn/dns.cfg' => 1, + 'sdn/dhcp.cfg' => 1, 'sdn/.running-config' => 1, 'virtual-guest/cpu-models.conf' => 1, 'mapping/pci.cfg' => 1, diff --git a/src/pmxcfs/status.c b/src/pmxcfs/status.c index c8094ac..4993fc1 100644 --- a/src/pmxcfs/status.c +++ b/src/pmxcfs/status.c @@ -107,6 +107,7 @@ static memdb_change_t memdb_change_array[] = { { .path = "sdn/subnets.cfg" }, { .path = "sdn/ipams.cfg" }, { .path = "sdn/dns.cfg" }, + { .path = "sdn/dhcp.cfg" }, { .path = "sdn/.running-config" }, { .path = "virtual-guest/cpu-models.conf" }, { .path = "firewall/cluster.fw" }, -- 2.39.2 From s.hanreich at proxmox.com Tue Oct 17 15:55:01 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 15:55:01 +0200 Subject: [pve-devel] [WIP v2 pve-network 04/10] dhcp: subnet: add DHCP options to subnet configuration In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: <20231017135507.2220948-5-s.hanreich@proxmox.com> Parse the dhcp-ranges when getting the configuration via the Subnet class. Signed-off-by: Stefan Hanreich --- src/PVE/Network/SDN/SubnetPlugin.pm | 32 +++++++++++++++++++++++++++++ src/PVE/Network/SDN/Subnets.pm | 18 ++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/PVE/Network/SDN/SubnetPlugin.pm b/src/PVE/Network/SDN/SubnetPlugin.pm index 15b370f..47b8406 100644 --- a/src/PVE/Network/SDN/SubnetPlugin.pm +++ b/src/PVE/Network/SDN/SubnetPlugin.pm @@ -61,6 +61,23 @@ sub private { return $defaultData; } +my $dhcp_range_fmt = { + server => { + type => 'pve-configid', + description => 'ID of the DHCP server responsible for managing this range', + }, + 'start-address' => { + type => 'ip', + description => 'Start address for the DHCP IP range', + }, + 'end-address' => { + type => 'ip', + description => 'End address for the DHCP IP range', + }, +}; + +PVE::JSONSchema::register_format('pve-sdn-dhcp-range', $dhcp_range_fmt); + sub properties { return { vnet => { @@ -84,6 +101,19 @@ sub properties { type => 'string', format => 'dns-name', description => "dns domain zone prefix ex: 'adm' -> .adm.mydomain.com", }, + 'dhcp-range' => { + type => 'array', + description => 'A list of DHCP ranges for this subnet', + items => { + type => 'string', + format => 'pve-sdn-dhcp-range', + } + }, + 'dhcp-dns-server' => { + type => 'ip', + description => 'IP address for the DNS server', + optional => 1, + }, }; } @@ -94,6 +124,8 @@ sub options { # routes => { optional => 1 }, snat => { optional => 1 }, dnszoneprefix => { optional => 1 }, + 'dhcp-range' => { optional => 1 }, + 'dhcp-dns-server' => { optional => 1 }, }; } diff --git a/src/PVE/Network/SDN/Subnets.pm b/src/PVE/Network/SDN/Subnets.pm index f654d3a..dd9e697 100644 --- a/src/PVE/Network/SDN/Subnets.pm +++ b/src/PVE/Network/SDN/Subnets.pm @@ -8,6 +8,7 @@ use Net::IP; use NetAddr::IP qw(:lower); use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); +use PVE::JSONSchema qw(parse_property_string); use PVE::Network::SDN::Dns; use PVE::Network::SDN::Ipams; @@ -31,6 +32,23 @@ sub sdn_subnets_config { $scfg->{zone} = $zone; $scfg->{network} = $network; $scfg->{mask} = $mask; + + if ($scfg->{'dhcp-range'}) { + my @dhcp_ranges; + + foreach my $element (@{$scfg->{'dhcp-range'}}) { + my $dhcp_range = eval { parse_property_string('pve-sdn-dhcp-range', $element) }; + + if ($@ || !$dhcp_range) { + warn "Unable to parse dhcp-range string: $element\n"; + warn "$@\n" if $@; + next; + } + + push @dhcp_ranges, $dhcp_range; + } + $scfg->{'dhcp-range'} = \@dhcp_ranges; + } } return $scfg; -- 2.39.2 From s.hanreich at proxmox.com Tue Oct 17 15:54:57 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 15:54:57 +0200 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN Message-ID: <20231017135507.2220948-1-s.hanreich@proxmox.com> This is a WIP patch series, since I will be gone for 3 weeks and wanted to share my current progress with the DHCP support for SDN. This patch series adds support for automatically deploying dnsmasq as a DHCP server to a simple SDN Zone. While certainly not 100% polished on some ends (looking at restarting systemd services in particular), the general idea behind the mechanism shows. I wanted to gather some feedback on how I approached designing the plugins and the config regeneration process before comitting to this design by creating an API and UI around it. You need to install dnsmasq (and disable it afterwards): apt install dnsmasq && systemctl disable --now dnsmasq You can use the following example configuration for deploying a DHCP server in a SDN subnet: /etc/pve/sdn/dhcp.cfg: dnsmasq: nat /etc/pve/sdn/zones.cfg: simple: DHCPNAT ipam pve /etc/pve/sdn/vnets.cfg: vnet: dhcpnat zone DHCPNAT /etc/pve/sdn/subnets.cfg: subnet: DHCPNAT-10.1.0.0-16 vnet dhcpnat dhcp-dns-server 10.1.0.1 dhcp-range server=nat,start-address=10.1.0.100,end-address=10.1.0.200 gateway 10.1.0.1 snat 1 Then apply the SDN configuration: pvesh set /cluster/sdn You need to apply the SDN configuration once after adding the dhcp-range lines to the configuration, since the running configuration is used for managing DHCP. It will not work otherwise! For testing it can be helpful to monitor the following files (e.g. with watch) to find out what is happening * /etc/dnsmasq.d//ethers (on each node) * /etc/pve/priv/ipam.db Changes from v1 -> v2: * added hooks for handling DHCP when starting / stopping / .. VMs and CTs * Get an IP from IPAM and register that IP in the DHCP server (pve only for now) * remove lease-time, since it is now infinite and managed by the VM lifecycle * add hooks for setting & deleting DHCP mappings to DHCP plugins * modified interface of the abstract class to reflect new requirements * added helpers in existing SDN classes * simplified DHCP configuration settings pve-cluster: Stefan Hanreich (1): cluster files: add dhcp.cfg src/PVE/Cluster.pm | 1 + src/pmxcfs/status.c | 1 + 2 files changed, 2 insertions(+) pve-network: Stefan Hanreich (6): subnets: vnets: preparations for DHCP plugins dhcp: add abstract class for DHCP plugins dhcp: subnet: add DHCP options to subnet configuration dhcp: add DHCP plugin for dnsmasq ipam: Add helper methods for DHCP to PVE IPAM dhcp: regenerate config for DHCP servers on reload debian/control | 1 + src/PVE/Network/SDN.pm | 11 +- src/PVE/Network/SDN/Dhcp.pm | 192 +++++++++++++++++++++++++ src/PVE/Network/SDN/Dhcp/Dnsmasq.pm | 186 ++++++++++++++++++++++++ src/PVE/Network/SDN/Dhcp/Makefile | 8 ++ src/PVE/Network/SDN/Dhcp/Plugin.pm | 83 +++++++++++ src/PVE/Network/SDN/Ipams/PVEPlugin.pm | 64 +++++++++ src/PVE/Network/SDN/Makefile | 3 +- src/PVE/Network/SDN/SubnetPlugin.pm | 32 +++++ src/PVE/Network/SDN/Subnets.pm | 43 ++++-- src/PVE/Network/SDN/Vnets.pm | 27 ++-- 11 files changed, 622 insertions(+), 28 deletions(-) create mode 100644 src/PVE/Network/SDN/Dhcp.pm create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm pve-manager: Stefan Hanreich (1): sdn: regenerate DHCP config on reload PVE/API2/Network.pm | 1 + 1 file changed, 1 insertion(+) qemu-server: Stefan Hanreich (1): sdn: dhcp: add DHCP setup to vm-network-scripts PVE/QemuServer.pm | 14 ++++++++++++++ vm-network-scripts/pve-bridge | 3 +++ vm-network-scripts/pve-bridgedown | 19 +++++++++++++++++++ 3 files changed, 36 insertions(+) pve-container: Stefan Hanreich (1): sdn: dhcp: setup DHCP mappings in LXC hooks src/PVE/LXC.pm | 10 ++++++++++ src/lxc-pve-poststop-hook | 1 + src/lxc-pve-prestart-hook | 9 +++++++++ 3 files changed, 20 insertions(+) Summary over all repositories: 20 files changed, 681 insertions(+), 28 deletions(-) -- murpp v0.4.0 From s.hanreich at proxmox.com Tue Oct 17 15:55:00 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 15:55:00 +0200 Subject: [pve-devel] [WIP v2 pve-network 03/10] dhcp: add abstract class for DHCP plugins In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: <20231017135507.2220948-4-s.hanreich@proxmox.com> This abstract class provides several hooks that should be called during the config generation process, they expose the functionality for the different configuration tasks required from the DHCP plugins. add_ip_mapping Adds a mapping from MAC address to an IP for a given DHCP server. The DHCP server will then always assign the given IP address to the MAC. del_ip_mapping Deletes all mappings for a given MAC address for a given DHCP server. before_regenerate Should be called before the plugin does any configuration tasks. The main usage for this hook is tearing down old instances. after_regenerate Should be called after the plugin has finished generating any configuration. The main usage for this hook is to perform cleanup and restart / reload services. before_configure Should be called before creating the configuration for a specific DHCP instance, as defined in the dhcp.cfg. This can be used for performing instance-specific setup. after_configure Should be called after the configuration for a specific DHCP instance, as defined in the dhcp.cfg. This will mainly be used for enabling and restarting / reloading a specific instance of a DHCP server. configure_subnet This function configures the settings for a specific subnet (that can contain multiple DHCP ranges). This sets global settings for a specific subnet such as DNS server or gateway. configure_range This configures a DHCP range that is available for a given Subnet. Signed-off-by: Stefan Hanreich --- src/PVE/Network/SDN/Dhcp/Makefile | 8 +++ src/PVE/Network/SDN/Dhcp/Plugin.pm | 83 ++++++++++++++++++++++++++++++ src/PVE/Network/SDN/Makefile | 1 + 3 files changed, 92 insertions(+) create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm diff --git a/src/PVE/Network/SDN/Dhcp/Makefile b/src/PVE/Network/SDN/Dhcp/Makefile new file mode 100644 index 0000000..1e9b6d3 --- /dev/null +++ b/src/PVE/Network/SDN/Dhcp/Makefile @@ -0,0 +1,8 @@ +SOURCES=Plugin.pm + + +PERL5DIR=${DESTDIR}/usr/share/perl5 + +.PHONY: install +install: + for i in ${SOURCES}; do install -D -m 0644 $$i ${PERL5DIR}/PVE/Network/SDN/Dhcp/$$i; done diff --git a/src/PVE/Network/SDN/Dhcp/Plugin.pm b/src/PVE/Network/SDN/Dhcp/Plugin.pm new file mode 100644 index 0000000..75979e8 --- /dev/null +++ b/src/PVE/Network/SDN/Dhcp/Plugin.pm @@ -0,0 +1,83 @@ +package PVE::Network::SDN::Dhcp::Plugin; + +use strict; +use warnings; + +use PVE::Cluster; +use PVE::JSONSchema qw(get_standard_option); + +use base qw(PVE::SectionConfig); + +PVE::Cluster::cfs_register_file('sdn/dhcp.cfg', + sub { __PACKAGE__->parse_config(@_); }, + sub { __PACKAGE__->write_config(@_); }, +); + +my $defaultData = { + propertyList => { + type => { + description => "Plugin type.", + format => 'pve-configid', + type => 'string', + }, + node => { + type => 'array', + description => 'A list of nodes where this DHCP server should be deployed', + items => get_standard_option('pve-node'), + }, + }, +}; + +sub private { + return $defaultData; +} + +sub options { + return { + node => { + optional => 1, + }, + }; +} + +sub add_ip_mapping { + my ($class, $dhcp_config, $mac, $ip) = @_; + die 'implement in sub class'; +} + +sub del_ip_mapping { + my ($class, $dhcp_config, $mac) = @_; + die 'implement in sub class'; +} + +sub configure_range { + my ($class, $dhcp_config, $subnet_config, $range_config) = @_; + die 'implement in sub class'; +} + +sub configure_subnet { + my ($class, $dhcp_config, $subnet_config) = @_; + die 'implement in sub class'; +} + +sub before_configure { + my ($class, $dhcp_config) = @_; + die 'implement in sub class'; +} + +sub after_configure { + my ($class, $dhcp_config) = @_; + die 'implement in sub class'; +} + +sub before_regenerate { + my ($class) = @_; + die 'implement in sub class'; +} + +sub after_regenerate { + my ($class, $dhcp_config) = @_; + die 'implement in sub class'; +} + +1; diff --git a/src/PVE/Network/SDN/Makefile b/src/PVE/Network/SDN/Makefile index 92cfcd0..848f7d4 100644 --- a/src/PVE/Network/SDN/Makefile +++ b/src/PVE/Network/SDN/Makefile @@ -10,4 +10,5 @@ install: make -C Zones install make -C Ipams install make -C Dns install + make -C Dhcp install -- 2.39.2 From s.hanreich at proxmox.com Tue Oct 17 15:55:07 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 15:55:07 +0200 Subject: [pve-devel] [WIP v2 pve-container 10/10] sdn: dhcp: setup DHCP mappings in LXC hooks In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: <20231017135507.2220948-11-s.hanreich@proxmox.com> Setup DHCP mappings if a container has interfaces on a SDN network managed via DHCP. Additionally remove the mapping in the stop_cleanup function so the mapping gets removed when forcefully stopping the container. Signed-off-by: Stefan Hanreich --- src/PVE/LXC.pm | 10 ++++++++++ src/lxc-pve-poststop-hook | 1 + src/lxc-pve-prestart-hook | 9 +++++++++ 3 files changed, 20 insertions(+) diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index c9b5ba7..bd8eb63 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -916,6 +916,14 @@ sub vm_stop_cleanup { eval { my $vollist = PVE::LXC::Config->get_vm_volumes($conf); PVE::Storage::deactivate_volumes($storage_cfg, $vollist); + + for my $k (keys %$conf) { + next if $k !~ /^net(\d+)/; + my $net = PVE::LXC::Config->parse_lxc_network($conf->{$k}); + next if $net->{type} ne 'veth'; + + PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{hwaddr}); + } }; warn $@ if $@; # avoid errors - just warn } @@ -1012,6 +1020,8 @@ sub hotplug_net { my $eth = $newnet->{name}; if ($have_sdn) { + PVE::Network::SDN::Dhcp::add_mapping($vmid, $newnet->{bridge}, $newnet->{macaddr}); + PVE::Network::SDN::Zones::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr}); } else { PVE::Network::veth_create($veth, $vethpeer, $newnet->{bridge}, $newnet->{hwaddr}); diff --git a/src/lxc-pve-poststop-hook b/src/lxc-pve-poststop-hook index 2fe97ec..e7d46c7 100755 --- a/src/lxc-pve-poststop-hook +++ b/src/lxc-pve-poststop-hook @@ -12,6 +12,7 @@ use PVE::LXC::Config; use PVE::LXC::Tools; use PVE::LXC; use PVE::Network; +use PVE::Network::SDN::Dhcp; use PVE::RESTEnvironment; use PVE::Storage; use PVE::Tools; diff --git a/src/lxc-pve-prestart-hook b/src/lxc-pve-prestart-hook index 936d0bf..5b2484e 100755 --- a/src/lxc-pve-prestart-hook +++ b/src/lxc-pve-prestart-hook @@ -15,6 +15,7 @@ use PVE::LXC::Config; use PVE::LXC::Setup; use PVE::LXC::Tools; use PVE::LXC; +use PVE::Network::SDN::Dhcp; use PVE::RESTEnvironment; use PVE::SafeSyslog; use PVE::Storage; @@ -140,6 +141,14 @@ PVE::LXC::Tools::lxc_hook('pre-start', 'lxc', sub { } PVE::Tools::file_set_contents($devlist_file, $devlist); } + + for my $k (keys %$conf) { + next if $k !~ /^net(\d+)/; + my $net = PVE::LXC::Config->parse_lxc_network($conf->{$k}); + next if $net->{type} ne 'veth'; + + PVE::Network::SDN::Dhcp::add_mapping($vmid, $net->{bridge}, $net->{hwaddr}); + } }); # Leftover cgroups prevent lxc from starting without any useful information -- 2.39.2 From s.hanreich at proxmox.com Tue Oct 17 15:55:04 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 15:55:04 +0200 Subject: [pve-devel] [WIP v2 pve-network 07/10] dhcp: regenerate config for DHCP servers on reload In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: <20231017135507.2220948-8-s.hanreich@proxmox.com> Regenerate the configuration files for the different DHCP server plugins when applying SDN settings by calling the respective hooks of the plugin responsible for configuring a DHCP instance. Signed-off-by: Stefan Hanreich --- src/PVE/Network/SDN.pm | 11 +- src/PVE/Network/SDN/Dhcp.pm | 192 +++++++++++++++++++++++++++++++++++ src/PVE/Network/SDN/Makefile | 2 +- 3 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 src/PVE/Network/SDN/Dhcp.pm diff --git a/src/PVE/Network/SDN.pm b/src/PVE/Network/SDN.pm index 057034f..5c059bc 100644 --- a/src/PVE/Network/SDN.pm +++ b/src/PVE/Network/SDN.pm @@ -12,6 +12,7 @@ use PVE::Network::SDN::Vnets; use PVE::Network::SDN::Zones; use PVE::Network::SDN::Controllers; use PVE::Network::SDN::Subnets; +use PVE::Network::SDN::Dhcp; use PVE::Tools qw(extract_param dir_glob_regex run_command); use PVE::Cluster qw(cfs_read_file cfs_write_file cfs_lock_file); @@ -149,13 +150,15 @@ sub commit_config { my $zones_cfg = PVE::Network::SDN::Zones::config(); my $controllers_cfg = PVE::Network::SDN::Controllers::config(); my $subnets_cfg = PVE::Network::SDN::Subnets::config(); + my $dhcp_cfg = PVE::Network::SDN::Dhcp::config(); my $vnets = { ids => $vnets_cfg->{ids} }; my $zones = { ids => $zones_cfg->{ids} }; my $controllers = { ids => $controllers_cfg->{ids} }; my $subnets = { ids => $subnets_cfg->{ids} }; + my $dhcp = { ids => $dhcp_cfg->{ids} }; - $cfg = { version => $version, vnets => $vnets, zones => $zones, controllers => $controllers, subnets => $subnets }; + $cfg = { version => $version, vnets => $vnets, zones => $zones, controllers => $controllers, subnets => $subnets, dhcps => $dhcp }; cfs_write_file($running_cfg, $cfg); } @@ -231,6 +234,12 @@ sub generate_controller_config { PVE::Network::SDN::Controllers::reload_controller() if $reload; } +sub generate_dhcp_config { + my ($reload) = @_; + + PVE::Network::SDN::Dhcp::regenerate_config($reload); +} + sub encode_value { my ($type, $key, $value) = @_; diff --git a/src/PVE/Network/SDN/Dhcp.pm b/src/PVE/Network/SDN/Dhcp.pm new file mode 100644 index 0000000..b92c73a --- /dev/null +++ b/src/PVE/Network/SDN/Dhcp.pm @@ -0,0 +1,192 @@ +package PVE::Network::SDN::Dhcp; + +use strict; +use warnings; + +use PVE::Cluster qw(cfs_read_file); + +use PVE::Network::SDN; +use PVE::Network::SDN::Ipams::Plugin; +use PVE::Network::SDN::SubnetPlugin; +use PVE::Network::SDN::Dhcp qw(config); +use PVE::Network::SDN::Subnets qw(sdn_subnets_config config); +use PVE::Network::SDN::Dhcp::Plugin; +use PVE::Network::SDN::Dhcp::Dnsmasq; + +use PVE::INotify qw(nodename); + +PVE::Network::SDN::Dhcp::Plugin->init(); + +PVE::Network::SDN::Dhcp::Dnsmasq->register(); +PVE::Network::SDN::Dhcp::Dnsmasq->init(); + +sub config { + my ($running) = @_; + + if ($running) { + my $cfg = PVE::Network::SDN::running_config(); + return $cfg->{dhcps}; + } + + return cfs_read_file('sdn/dhcp.cfg'); +} + +sub sdn_dhcps_config { + my ($cfg, $id, $noerr) = @_; + + die "No DHCP ID specified!\n" if !$id; + + my $dhcp_config = $cfg->{ids}->{$id}; + die "SDN DHCP '$id' does not exist!\n" if (!$noerr && !$dhcp_config); + + if ($dhcp_config) { + $dhcp_config->{id} = $id; + } + + return $dhcp_config; +} + +sub get_dhcp { + my ($dhcp_id, $running) = @_; + + return if !$dhcp_id; + + my $cfg = PVE::Network::SDN::Dhcp::config($running); + return PVE::Network::SDN::Dhcp::sdn_dhcps_config($cfg, $dhcp_id, 1); +} + +sub add_mapping { + my ($vmid, $vnet, $mac) = @_; + + my $vnet_config = PVE::Network::SDN::Vnets::get_vnet($vnet, 1); + return if !$vnet_config; + + my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnet, 1); + + for my $subnet_id (keys %{$subnets}) { + my $subnet_config = $subnets->{$subnet_id}; + + next if !$subnet_config->{'dhcp-range'}; + + foreach my $dhcp_range (@{$subnet_config->{'dhcp-range'}}) { + my $dhcp_config = PVE::Network::SDN::Dhcp::get_dhcp($dhcp_range->{server}); + + if (!$dhcp_config) { + warn "Cannot find configuration for DHCP server $dhcp_range->{server}"; + next; + } + + my $ipam_plugin = PVE::Network::SDN::Ipams::Plugin->lookup('pve'); + + my $data = { + vmid => $vmid, + mac => $mac, + }; + + my $ip = $ipam_plugin->add_dhcp_ip($subnet_config, $dhcp_range, $data); + + next if !$ip; + + my $dhcp_plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($dhcp_config->{type}); + $dhcp_plugin->add_ip_mapping($dhcp_config, $mac, $ip); + + return $ip; + } + } +} + +sub remove_mapping { + my ($vnet, $mac) = @_; + + my $vnet_config = PVE::Network::SDN::Vnets::get_vnet($vnet, 1); + return if !$vnet_config; + + my $subnets = PVE::Network::SDN::Vnets::get_subnets($vnet, 1); + + for my $subnet_id (keys %{$subnets}) { + my $subnet_config = $subnets->{$subnet_id}; + next if !$subnet_config->{'dhcp-range'}; + + my $ipam_plugin = PVE::Network::SDN::Ipams::Plugin->lookup('pve'); + $ipam_plugin->del_dhcp_ip($subnet_config, $mac); + + foreach my $dhcp_range (@{$subnet_config->{'dhcp-range'}}) { + my $dhcp_config = PVE::Network::SDN::Dhcp::get_dhcp($dhcp_range->{server}); + + if (!$dhcp_config) { + warn "Cannot find configuration for DHCP server $dhcp_range->{server}"; + next; + } + + my $dhcp_plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($dhcp_config->{type}); + $dhcp_plugin->del_ip_mapping($dhcp_config, $mac); + } + } +} + +sub regenerate_config { + my ($reload) = @_; + + my $dhcps = PVE::Network::SDN::Dhcp::config(); + my $subnets = PVE::Network::SDN::Subnets::config(); + + my $plugins = PVE::Network::SDN::Dhcp::Plugin->lookup_types(); + + my $nodename = PVE::INotify::nodename(); + + foreach my $plugin_name (@$plugins) { + my $plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($plugin_name); + + eval { $plugin->before_regenerate() }; + die "Could not run before_regenerate for DHCP plugin $plugin_name $@\n" if $@; + } + + foreach my $dhcp_id (keys %{$dhcps->{ids}}) { + my $dhcp_config = PVE::Network::SDN::Dhcp::sdn_dhcps_config($dhcps, $dhcp_id); + my $plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($dhcp_config->{type}); + + eval { $plugin->before_configure($dhcp_config) }; + die "Could not run before_configure for DHCP server $dhcp_id $@\n" if $@; + } + + foreach my $subnet_id (keys %{$subnets->{ids}}) { + my $subnet_config = PVE::Network::SDN::Subnets::sdn_subnets_config($subnets, $subnet_id); + next if !$subnet_config->{'dhcp-range'}; + + my @configured_servers = (); + + foreach my $dhcp_range (@{$subnet_config->{'dhcp-range'}}) { + my $dhcp_config = PVE::Network::SDN::Dhcp::sdn_dhcps_config($dhcps, $dhcp_range->{server}); + my $plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($dhcp_config->{type}); + + next if $dhcp_config->{node} && !grep(/^$nodename$/, @{$dhcp_config->{node}}); + + if (!grep(/^$subnet_id$/, @configured_servers)) { + eval { $plugin->configure_subnet($dhcp_config, $subnet_config) }; + warn "Could not configure Subnet $subnet_id: $@\n" if $@; + + push @configured_servers, $subnet_id; + } + + eval { $plugin->configure_range($dhcp_config, $subnet_config, $dhcp_range) }; + warn "Could not configure DHCP range for $subnet_id: $@\n" if $@; + } + } + + foreach my $dhcp_id (keys %{$dhcps->{ids}}) { + my $dhcp_config = PVE::Network::SDN::Dhcp::sdn_dhcps_config($dhcps, $dhcp_id); + my $plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($dhcp_config->{type}); + + eval { $plugin->after_configure($dhcp_config) }; + warn "Could not run after_configure for DHCP server $dhcp_id $@\n" if $@; + } + + foreach my $plugin_name (@$plugins) { + my $plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($plugin_name); + + eval { $plugin->after_regenerate() }; + warn "Could not run after_regenerate for DHCP plugin $plugin_name $@\n" if $@; + } +} + +1; diff --git a/src/PVE/Network/SDN/Makefile b/src/PVE/Network/SDN/Makefile index 848f7d4..3e6e5fb 100644 --- a/src/PVE/Network/SDN/Makefile +++ b/src/PVE/Network/SDN/Makefile @@ -1,4 +1,4 @@ -SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm Dns.pm +SOURCES=Vnets.pm VnetPlugin.pm Zones.pm Controllers.pm Subnets.pm SubnetPlugin.pm Ipams.pm Dns.pm Dhcp.pm PERL5DIR=${DESTDIR}/usr/share/perl5 -- 2.39.2 From m.sandoval at proxmox.com Tue Oct 17 15:57:08 2023 From: m.sandoval at proxmox.com (Maximiliano Sandoval R) Date: Tue, 17 Oct 2023 15:57:08 +0200 Subject: [pve-devel] [PATCH installer v2] fix #4869: Show state in management interface ComboBox Message-ID: <20231017135708.128807-1-m.sandoval@proxmox.com> From: Maximiliano Sandoval This is a continuation of https://lists.proxmox.com/pipermail/pve-devel/2023-August/058639.html. Signed-off-by: Maximiliano Sandoval R --- proxinstall | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/proxinstall b/proxinstall index d5b2565..d1f8ae2 100755 --- a/proxinstall +++ b/proxinstall @@ -341,10 +341,20 @@ sub create_ipconf_view { my ($cidr_box, $ipconf_entry_addr, $ipconf_entry_mask) = create_cidr_inputs($cidr); - my $device_cb = Gtk3::ComboBoxText->new(); + my $device_model = Gtk3::ListStore->new('Glib::String', 'Glib::String'); + my $device_cb = Gtk3::ComboBox->new_with_model($device_model); $device_cb->set_active(0); $device_cb->set_visible(1); + my $icon_cell = Gtk3::CellRendererText->new(); + $device_cb->pack_start($icon_cell, 0); + $device_cb->add_attribute($icon_cell, 'text', 0); + $icon_cell->set_property('foreground', 'green'); + + my $cell = Gtk3::CellRendererText->new(); + $device_cb->pack_start($cell, 0); + $device_cb->add_attribute($cell, 'text', 1); + my $get_device_desc = sub { my $iface = shift; return "$iface->{name} - $iface->{mac} ($iface->{driver})"; @@ -374,7 +384,16 @@ sub create_ipconf_view { my $i = 0; for my $index (sort keys $ipconf->{ifaces}->%*) { my $iface = $ipconf->{ifaces}->{$index}; - $device_cb->append_text($get_device_desc->($iface)); + my $iter = $device_model->append(); + my $symbol; + { + use utf8; + $symbol = "$iface->{state}" eq "UP" ? '?' : ' '; + } + $device_model->set($iter, + 0 => $symbol, + 1 => $get_device_desc->($iface), + ); $device_active_map->{$i} = $index; $device_active_reverse_map->{$iface->{name}} = $i; if ($ipconf_first_view && $index == $ipconf->{default}) { -- 2.39.2 From f.ebner at proxmox.com Tue Oct 17 14:10:09 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 17 Oct 2023 14:10:09 +0200 Subject: [pve-devel] [PATCH v3 qemu 4/7] update submodule and patches to QEMU 8.1.2 In-Reply-To: <20231017121012.132636-1-f.ebner@proxmox.com> References: <20231017121012.132636-1-f.ebner@proxmox.com> Message-ID: <20231017121012.132636-5-f.ebner@proxmox.com> Bigger notable changes: * Commit 1a30b0f5d7 ("block: .bdrv_open is non-coroutine and unlocked") broke the PVE backup patches, in particular setting up the backup dump block driver, because bdrv_new_open_driver() cannot be called from a coroutine. To fix it, bdrv_co_open() is used instead, and while it's a much more involved function, the result should be essentially the same. The only difference I noticed is that the BDRV_O_ALLOW_RDWR flag is also set in the resulting bds (block driver state), but that shouldn't hurt. Smaller notable changes: * aio_set_fd_handler() dropped its 'is_external' parameter stating that all callers now pass false in 60f782b6b7 ("aio: remove aio_disable_external() API"). The calls in the PVE patches also passed false, so just drop the parameter too. * global_state_store() does not have a return value anymore, so the user in the PVE savevm-async patch was adapted. For context, see c33f1829f8 ("migration: never fail in global_state_store()"). * Renames affecting the PVE savevm-async patch: migrate_use_block() -> migrate_block() and ram_counters -> mig_stats 9d4b1e5f22 ("migration: Move migrate_use_block() to options.c") aff3f6606d ("migration: Rename ram_counters to mig_stats") Signed-off-by: Fiona Ebner --- Changes in v3: * Rebase on QEMU 8.1.2. ...d-support-for-sync-bitmap-mode-never.patch | 124 ++++++++--------- ...-support-for-conditional-and-always-.patch | 10 +- ...check-for-bitmap-mode-without-bitmap.patch | 4 +- ...-to-bdrv_dirty_bitmap_merge_internal.patch | 6 +- .../0006-mirror-move-some-checks-to-qmp.patch | 8 +- ...race-with-clients-disconnecting-earl.patch | 22 +-- ...as-Internal-cdbs-have-16-byte-length.patch | 10 +- ...ial-deadlock-when-draining-during-tr.patch | 10 +- ...irty-bitmap-fix-loading-bitmap-when.patch} | 2 +- ...hen-getting-cursor-without-a-console.patch | 36 ----- ...el-async-DMA-operation-before-reset.patch} | 4 +- ...-memory-prevent-dma-reentracy-issues.patch | 130 ------------------ ...le-reentrancy-detection-for-script-R.patch | 39 ------ ...-disable-reentrancy-detection-for-io.patch | 37 ----- ...sable-reentrancy-detection-for-iomem.patch | 35 ----- ...le-reentrancy-detection-for-apic-msi.patch | 36 ----- .../extra/0011-vhost-fix-the-fd-leak.patch | 29 ---- ...k-file-change-locking-default-to-off.patch | 6 +- ...he-CPU-model-to-kvm64-32-instead-of-.patch | 4 +- ...erfs-no-default-logfile-if-daemonize.patch | 4 +- ...PVE-Up-glusterfs-allow-partial-reads.patch | 2 +- ...return-success-on-info-without-snaps.patch | 4 +- ...dd-add-osize-and-read-from-to-stdin-.patch | 12 +- ...E-Up-qemu-img-dd-add-isize-parameter.patch | 14 +- ...PVE-Up-qemu-img-dd-add-n-skip_create.patch | 10 +- ...-add-l-option-for-loading-a-snapshot.patch | 14 +- ...virtio-balloon-improve-query-balloon.patch | 12 +- .../0014-PVE-qapi-modify-query-machines.patch | 12 +- .../0015-PVE-qapi-modify-spice-query.patch | 4 +- ...nnel-implementation-for-savevm-async.patch | 8 +- ...async-for-background-state-snapshots.patch | 58 ++++---- ...add-optional-buffer-size-to-QEMUFile.patch | 44 +++--- ...add-the-zeroinit-block-driver-filter.patch | 10 +- ...-Add-dummy-id-command-line-parameter.patch | 10 +- ...le-posix-make-locking-optiono-on-cre.patch | 18 +-- ...3-PVE-monitor-disable-oob-capability.patch | 4 +- ...sed-balloon-qemu-4-0-config-size-fal.patch | 4 +- ...E-Allow-version-code-in-machine-type.patch | 22 +-- ...VE-Backup-add-vma-backup-format-code.patch | 22 +-- ...-Backup-add-backup-dump-block-driver.patch | 4 +- ...ckup-Proxmox-backup-patches-for-QEMU.patch | 127 ++++++++++++----- ...estore-new-command-to-restore-from-p.patch | 4 +- ...k-driver-to-map-backup-archives-into.patch | 54 ++++---- ...ct-stderr-to-journal-when-daemonized.patch | 12 +- ...igrate-dirty-bitmap-state-via-savevm.patch | 23 ++-- ...dirty-bitmap-migrate-other-bitmaps-e.patch | 4 +- ...all-back-to-open-iscsi-initiatorname.patch | 4 +- ...PVE-block-stream-increase-chunk-size.patch | 2 +- ...accept-NULL-qiov-in-bdrv_pad_request.patch | 14 +- .../0039-block-add-alloc-track-driver.patch | 2 +- ...apshots-hold-the-BQL-during-setup-ca.patch | 24 ++-- ...vm-async-don-t-hold-BQL-during-setup.patch | 4 +- debian/patches/series | 11 +- qemu | 2 +- 54 files changed, 411 insertions(+), 720 deletions(-) rename debian/patches/extra/{0010-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch => 0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch} (98%) delete mode 100644 debian/patches/extra/0004-ui-return-NULL-when-getting-cursor-without-a-console.patch rename debian/patches/extra/{0012-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch => 0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch} (97%) delete mode 100644 debian/patches/extra/0005-memory-prevent-dma-reentracy-issues.patch delete mode 100644 debian/patches/extra/0006-lsi53c895a-disable-reentrancy-detection-for-script-R.patch delete mode 100644 debian/patches/extra/0007-bcm2835_property-disable-reentrancy-detection-for-io.patch delete mode 100644 debian/patches/extra/0008-raven-disable-reentrancy-detection-for-iomem.patch delete mode 100644 debian/patches/extra/0009-apic-disable-reentrancy-detection-for-apic-msi.patch delete mode 100644 debian/patches/extra/0011-vhost-fix-the-fd-leak.patch diff --git a/debian/patches/bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch b/debian/patches/bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch index 4d9b8b9..c0cb23f 100644 --- a/debian/patches/bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch +++ b/debian/patches/bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch @@ -27,18 +27,18 @@ Signed-off-by: Ma Haocong Signed-off-by: John Snow Signed-off-by: Fabian Gr?nbichler Signed-off-by: Thomas Lamprecht -[FE: rebased for 8.0] +[FE: rebased for 8.1.1] Signed-off-by: Fiona Ebner --- block/mirror.c | 98 +++++++++++++++++++++----- blockdev.c | 38 +++++++++- include/block/block_int-global-state.h | 4 +- - qapi/block-core.json | 29 ++++++-- + qapi/block-core.json | 25 ++++++- tests/unit/test-block-iothread.c | 4 +- - 5 files changed, 144 insertions(+), 29 deletions(-) + 5 files changed, 142 insertions(+), 27 deletions(-) diff --git a/block/mirror.c b/block/mirror.c -index 663e2b7002..9099c75992 100644 +index d3cacd1708..1ff42c8af1 100644 --- a/block/mirror.c +++ b/block/mirror.c @@ -51,7 +51,7 @@ typedef struct MirrorBlockJob { @@ -59,7 +59,7 @@ index 663e2b7002..9099c75992 100644 BdrvDirtyBitmap *dirty_bitmap; BdrvDirtyBitmapIter *dbi; uint8_t *buf; -@@ -703,7 +705,8 @@ static int mirror_exit_common(Job *job) +@@ -705,7 +707,8 @@ static int mirror_exit_common(Job *job) bdrv_child_refresh_perms(mirror_top_bs, mirror_top_bs->backing, &error_abort); if (!abort && s->backing_mode == MIRROR_SOURCE_BACKING_CHAIN) { @@ -69,7 +69,7 @@ index 663e2b7002..9099c75992 100644 BlockDriverState *unfiltered_target = bdrv_skip_filters(target_bs); if (bdrv_cow_bs(unfiltered_target) != backing) { -@@ -801,6 +804,16 @@ static void mirror_abort(Job *job) +@@ -809,6 +812,16 @@ static void mirror_abort(Job *job) assert(ret == 0); } @@ -86,7 +86,7 @@ index 663e2b7002..9099c75992 100644 static void coroutine_fn mirror_throttle(MirrorBlockJob *s) { int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); -@@ -987,7 +1000,8 @@ static int coroutine_fn mirror_run(Job *job, Error **errp) +@@ -997,7 +1010,8 @@ static int coroutine_fn mirror_run(Job *job, Error **errp) mirror_free_init(s); s->last_pause_ns = qemu_clock_get_ns(QEMU_CLOCK_REALTIME); @@ -96,7 +96,7 @@ index 663e2b7002..9099c75992 100644 ret = mirror_dirty_init(s); if (ret < 0 || job_is_cancelled(&s->common.job)) { goto immediate_exit; -@@ -1240,6 +1254,7 @@ static const BlockJobDriver mirror_job_driver = { +@@ -1251,6 +1265,7 @@ static const BlockJobDriver mirror_job_driver = { .run = mirror_run, .prepare = mirror_prepare, .abort = mirror_abort, @@ -104,7 +104,7 @@ index 663e2b7002..9099c75992 100644 .pause = mirror_pause, .complete = mirror_complete, .cancel = mirror_cancel, -@@ -1256,6 +1271,7 @@ static const BlockJobDriver commit_active_job_driver = { +@@ -1267,6 +1282,7 @@ static const BlockJobDriver commit_active_job_driver = { .run = mirror_run, .prepare = mirror_prepare, .abort = mirror_abort, @@ -112,7 +112,7 @@ index 663e2b7002..9099c75992 100644 .pause = mirror_pause, .complete = mirror_complete, .cancel = commit_active_cancel, -@@ -1647,7 +1663,10 @@ static BlockJob *mirror_start_job( +@@ -1658,7 +1674,10 @@ static BlockJob *mirror_start_job( BlockCompletionFunc *cb, void *opaque, const BlockJobDriver *driver, @@ -124,7 +124,7 @@ index 663e2b7002..9099c75992 100644 bool auto_complete, const char *filter_node_name, bool is_mirror, MirrorCopyMode copy_mode, Error **errp) -@@ -1659,10 +1678,39 @@ static BlockJob *mirror_start_job( +@@ -1670,10 +1689,39 @@ static BlockJob *mirror_start_job( uint64_t target_perms, target_shared_perms; int ret; @@ -166,7 +166,7 @@ index 663e2b7002..9099c75992 100644 assert(is_power_of_2(granularity)); if (buf_size < 0) { -@@ -1793,7 +1841,9 @@ static BlockJob *mirror_start_job( +@@ -1804,7 +1852,9 @@ static BlockJob *mirror_start_job( s->replaces = g_strdup(replaces); s->on_source_error = on_source_error; s->on_target_error = on_target_error; @@ -177,7 +177,7 @@ index 663e2b7002..9099c75992 100644 s->backing_mode = backing_mode; s->zero_target = zero_target; s->copy_mode = copy_mode; -@@ -1814,6 +1864,18 @@ static BlockJob *mirror_start_job( +@@ -1825,6 +1875,18 @@ static BlockJob *mirror_start_job( bdrv_disable_dirty_bitmap(s->dirty_bitmap); } @@ -196,7 +196,7 @@ index 663e2b7002..9099c75992 100644 ret = block_job_add_bdrv(&s->common, "source", bs, 0, BLK_PERM_WRITE_UNCHANGED | BLK_PERM_WRITE | BLK_PERM_CONSISTENT_READ, -@@ -1891,6 +1953,9 @@ fail: +@@ -1902,6 +1964,9 @@ fail: if (s->dirty_bitmap) { bdrv_release_dirty_bitmap(s->dirty_bitmap); } @@ -206,7 +206,7 @@ index 663e2b7002..9099c75992 100644 job_early_fail(&s->common.job); } -@@ -1908,31 +1973,25 @@ void mirror_start(const char *job_id, BlockDriverState *bs, +@@ -1919,31 +1984,25 @@ void mirror_start(const char *job_id, BlockDriverState *bs, BlockDriverState *target, const char *replaces, int creation_flags, int64_t speed, uint32_t granularity, int64_t buf_size, @@ -243,7 +243,7 @@ index 663e2b7002..9099c75992 100644 } BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, -@@ -1959,7 +2018,8 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, +@@ -1970,7 +2029,8 @@ BlockJob *commit_active_start(const char *job_id, BlockDriverState *bs, job_id, bs, creation_flags, base, NULL, speed, 0, 0, MIRROR_LEAVE_BACKING_CHAIN, false, on_error, on_error, true, cb, opaque, @@ -254,10 +254,10 @@ index 663e2b7002..9099c75992 100644 errp); if (!job) { diff --git a/blockdev.c b/blockdev.c -index e464daea58..50e4a9c682 100644 +index e6eba61484..a8b1fd2a73 100644 --- a/blockdev.c +++ b/blockdev.c -@@ -2942,6 +2942,9 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, +@@ -2848,6 +2848,9 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, BlockDriverState *target, const char *replaces, enum MirrorSyncMode sync, @@ -267,15 +267,15 @@ index e464daea58..50e4a9c682 100644 BlockMirrorBackingMode backing_mode, bool zero_target, bool has_speed, int64_t speed, -@@ -2960,6 +2963,7 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, +@@ -2866,6 +2869,7 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, { BlockDriverState *unfiltered_bs; int job_flags = JOB_DEFAULT; + BdrvDirtyBitmap *bitmap = NULL; - if (!has_speed) { - speed = 0; -@@ -3011,6 +3015,29 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, + GLOBAL_STATE_CODE(); + GRAPH_RDLOCK_GUARD_MAINLOOP(); +@@ -2920,6 +2924,29 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, sync = MIRROR_SYNC_MODE_FULL; } @@ -305,7 +305,7 @@ index e464daea58..50e4a9c682 100644 if (!replaces) { /* We want to mirror from @bs, but keep implicit filters on top */ unfiltered_bs = bdrv_skip_implicit_filters(bs); -@@ -3056,8 +3083,8 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, +@@ -2965,8 +2992,8 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, * and will allow to check whether the node still exist at mirror completion */ mirror_start(job_id, bs, target, @@ -316,7 +316,7 @@ index e464daea58..50e4a9c682 100644 on_source_error, on_target_error, unmap, filter_node_name, copy_mode, errp); } -@@ -3202,6 +3229,8 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp) +@@ -3114,6 +3141,8 @@ void qmp_drive_mirror(DriveMirror *arg, Error **errp) blockdev_mirror_common(arg->job_id, bs, target_bs, arg->replaces, arg->sync, @@ -325,7 +325,7 @@ index e464daea58..50e4a9c682 100644 backing_mode, zero_target, arg->has_speed, arg->speed, arg->has_granularity, arg->granularity, -@@ -3223,6 +3252,8 @@ void qmp_blockdev_mirror(const char *job_id, +@@ -3135,6 +3164,8 @@ void qmp_blockdev_mirror(const char *job_id, const char *device, const char *target, const char *replaces, MirrorSyncMode sync, @@ -334,7 +334,7 @@ index e464daea58..50e4a9c682 100644 bool has_speed, int64_t speed, bool has_granularity, uint32_t granularity, bool has_buf_size, int64_t buf_size, -@@ -3271,7 +3302,8 @@ void qmp_blockdev_mirror(const char *job_id, +@@ -3183,7 +3214,8 @@ void qmp_blockdev_mirror(const char *job_id, } blockdev_mirror_common(job_id, bs, target_bs, @@ -345,7 +345,7 @@ index e464daea58..50e4a9c682 100644 has_granularity, granularity, has_buf_size, buf_size, diff --git a/include/block/block_int-global-state.h b/include/block/block_int-global-state.h -index 902406eb99..d559be928c 100644 +index da5fb31089..32f0f9858a 100644 --- a/include/block/block_int-global-state.h +++ b/include/block/block_int-global-state.h @@ -152,7 +152,9 @@ void mirror_start(const char *job_id, BlockDriverState *bs, @@ -360,31 +360,26 @@ index 902406eb99..d559be928c 100644 BlockdevOnError on_source_error, BlockdevOnError on_target_error, diff --git a/qapi/block-core.json b/qapi/block-core.json -index c05ad0c07e..3c945c1f93 100644 +index 2b1d493d6e..903392cb8f 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json -@@ -2095,10 +2095,19 @@ - # (all the disk, only the sectors allocated in the topmost image, or - # only new I/O). +@@ -2145,6 +2145,15 @@ + # destination (all the disk, only the sectors allocated in the + # topmost image, or only new I/O). # -+# @bitmap: The name of a bitmap to use for sync=bitmap mode. This argument must -+# be present for bitmap mode and absent otherwise. The bitmap's -+# granularity is used instead of @granularity (since 4.1). ++# @bitmap: The name of a bitmap to use for sync=bitmap mode. This ++# argument must be present for bitmap mode and absent otherwise. ++# The bitmap's granularity is used instead of @granularity (Since ++# 4.1). +# -+# @bitmap-mode: Specifies the type of data the bitmap should contain after -+# the operation concludes. Must be present if sync is "bitmap". -+# Must NOT be present otherwise. (Since 4.1) ++# @bitmap-mode: Specifies the type of data the bitmap should contain ++# after the operation concludes. Must be present if sync is ++# "bitmap". Must NOT be present otherwise. (Since 4.1) +# - # @granularity: granularity of the dirty bitmap, default is 64K - # if the image format doesn't have clusters, 4K if the clusters - # are smaller than that, else the cluster size. Must be a --# power of 2 between 512 and 64M (since 1.4). -+# power of 2 between 512 and 64M. Must not be specified if -+# @bitmap is present (since 1.4). - # - # @buf-size: maximum amount of data in flight from source to - # target (since 1.4). -@@ -2138,7 +2147,9 @@ + # @granularity: granularity of the dirty bitmap, default is 64K if the + # image format doesn't have clusters, 4K if the clusters are + # smaller than that, else the cluster size. Must be a power of 2 +@@ -2187,7 +2196,9 @@ { 'struct': 'DriveMirror', 'data': { '*job-id': 'str', 'device': 'str', 'target': 'str', '*format': 'str', '*node-name': 'str', '*replaces': 'str', @@ -395,28 +390,23 @@ index c05ad0c07e..3c945c1f93 100644 '*speed': 'int', '*granularity': 'uint32', '*buf-size': 'int', '*on-source-error': 'BlockdevOnError', '*on-target-error': 'BlockdevOnError', -@@ -2417,10 +2428,19 @@ - # (all the disk, only the sectors allocated in the topmost image, or - # only new I/O). +@@ -2471,6 +2482,15 @@ + # destination (all the disk, only the sectors allocated in the + # topmost image, or only new I/O). # -+# @bitmap: The name of a bitmap to use for sync=bitmap mode. This argument must -+# be present for bitmap mode and absent otherwise. The bitmap's -+# granularity is used instead of @granularity (since 4.1). ++# @bitmap: The name of a bitmap to use for sync=bitmap mode. This ++# argument must be present for bitmap mode and absent otherwise. ++# The bitmap's granularity is used instead of @granularity (since ++# 4.1). +# -+# @bitmap-mode: Specifies the type of data the bitmap should contain after -+# the operation concludes. Must be present if sync is "bitmap". -+# Must NOT be present otherwise. (Since 4.1) ++# @bitmap-mode: Specifies the type of data the bitmap should contain ++# after the operation concludes. Must be present if sync is ++# "bitmap". Must NOT be present otherwise. (Since 4.1) +# - # @granularity: granularity of the dirty bitmap, default is 64K - # if the image format doesn't have clusters, 4K if the clusters - # are smaller than that, else the cluster size. Must be a --# power of 2 between 512 and 64M -+# power of 2 between 512 and 64M . Must not be specified if -+# @bitmap is present. - # - # @buf-size: maximum amount of data in flight from source to - # target -@@ -2470,7 +2490,8 @@ + # @granularity: granularity of the dirty bitmap, default is 64K if the + # image format doesn't have clusters, 4K if the clusters are + # smaller than that, else the cluster size. Must be a power of 2 +@@ -2521,7 +2541,8 @@ { 'command': 'blockdev-mirror', 'data': { '*job-id': 'str', 'device': 'str', 'target': 'str', '*replaces': 'str', @@ -427,7 +417,7 @@ index c05ad0c07e..3c945c1f93 100644 '*buf-size': 'int', '*on-source-error': 'BlockdevOnError', '*on-target-error': 'BlockdevOnError', diff --git a/tests/unit/test-block-iothread.c b/tests/unit/test-block-iothread.c -index 3a5e1eb2c4..c1ecc49073 100644 +index d727a5fee8..8a34aa2328 100644 --- a/tests/unit/test-block-iothread.c +++ b/tests/unit/test-block-iothread.c @@ -757,8 +757,8 @@ static void test_propagate_mirror(void) diff --git a/debian/patches/bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch b/debian/patches/bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch index b0ade68..a4a5a0b 100644 --- a/debian/patches/bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch +++ b/debian/patches/bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch @@ -24,10 +24,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/block/mirror.c b/block/mirror.c -index 9099c75992..e2ff42067b 100644 +index 1ff42c8af1..11b8a8e959 100644 --- a/block/mirror.c +++ b/block/mirror.c -@@ -680,8 +680,6 @@ static int mirror_exit_common(Job *job) +@@ -682,8 +682,6 @@ static int mirror_exit_common(Job *job) bdrv_unfreeze_backing_chain(mirror_top_bs, target_bs); } @@ -36,7 +36,7 @@ index 9099c75992..e2ff42067b 100644 /* Make sure that the source BDS doesn't go away during bdrv_replace_node, * before we can call bdrv_drained_end */ bdrv_ref(src); -@@ -782,6 +780,18 @@ static int mirror_exit_common(Job *job) +@@ -788,6 +786,18 @@ static int mirror_exit_common(Job *job) block_job_remove_all_bdrv(bjob); bdrv_replace_node(mirror_top_bs, mirror_top_bs->backing->bs, &error_abort); @@ -55,7 +55,7 @@ index 9099c75992..e2ff42067b 100644 bs_opaque->job = NULL; bdrv_drained_end(src); -@@ -1688,10 +1698,6 @@ static BlockJob *mirror_start_job( +@@ -1699,10 +1709,6 @@ static BlockJob *mirror_start_job( " sync mode", MirrorSyncMode_str(sync_mode)); return NULL; @@ -66,7 +66,7 @@ index 9099c75992..e2ff42067b 100644 } } else if (bitmap) { error_setg(errp, -@@ -1708,6 +1714,12 @@ static BlockJob *mirror_start_job( +@@ -1719,6 +1725,12 @@ static BlockJob *mirror_start_job( return NULL; } granularity = bdrv_dirty_bitmap_granularity(bitmap); diff --git a/debian/patches/bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch b/debian/patches/bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch index 302bbc5..4546b78 100644 --- a/debian/patches/bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch +++ b/debian/patches/bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch @@ -16,10 +16,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 3 insertions(+) diff --git a/blockdev.c b/blockdev.c -index 50e4a9c682..e6b2b1e338 100644 +index a8b1fd2a73..83d5cc1e49 100644 --- a/blockdev.c +++ b/blockdev.c -@@ -3036,6 +3036,9 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, +@@ -2945,6 +2945,9 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, if (bdrv_dirty_bitmap_check(bitmap, BDRV_BITMAP_ALLOW_RO, errp)) { return; } diff --git a/debian/patches/bitmap-mirror/0004-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch b/debian/patches/bitmap-mirror/0004-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch index b954682..93a1524 100644 --- a/debian/patches/bitmap-mirror/0004-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch +++ b/debian/patches/bitmap-mirror/0004-mirror-switch-to-bdrv_dirty_bitmap_merge_internal.patch @@ -16,10 +16,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/block/mirror.c b/block/mirror.c -index e2ff42067b..f42953837b 100644 +index 11b8a8e959..00f2665ca4 100644 --- a/block/mirror.c +++ b/block/mirror.c -@@ -786,8 +786,8 @@ static int mirror_exit_common(Job *job) +@@ -792,8 +792,8 @@ static int mirror_exit_common(Job *job) job->ret == 0 && ret == 0)) { /* Success; synchronize copy back to sync. */ bdrv_clear_dirty_bitmap(s->sync_bitmap, NULL); @@ -30,7 +30,7 @@ index e2ff42067b..f42953837b 100644 } } bdrv_release_dirty_bitmap(s->dirty_bitmap); -@@ -1881,11 +1881,8 @@ static BlockJob *mirror_start_job( +@@ -1892,11 +1892,8 @@ static BlockJob *mirror_start_job( } if (s->sync_mode == MIRROR_SYNC_MODE_BITMAP) { diff --git a/debian/patches/bitmap-mirror/0006-mirror-move-some-checks-to-qmp.patch b/debian/patches/bitmap-mirror/0006-mirror-move-some-checks-to-qmp.patch index 5298342..9a3108f 100644 --- a/debian/patches/bitmap-mirror/0006-mirror-move-some-checks-to-qmp.patch +++ b/debian/patches/bitmap-mirror/0006-mirror-move-some-checks-to-qmp.patch @@ -21,10 +21,10 @@ Signed-off-by: Fiona Ebner 3 files changed, 70 insertions(+), 59 deletions(-) diff --git a/block/mirror.c b/block/mirror.c -index f42953837b..8f79efaa87 100644 +index 00f2665ca4..60cf574de5 100644 --- a/block/mirror.c +++ b/block/mirror.c -@@ -1688,31 +1688,13 @@ static BlockJob *mirror_start_job( +@@ -1699,31 +1699,13 @@ static BlockJob *mirror_start_job( uint64_t target_perms, target_shared_perms; int ret; @@ -62,10 +62,10 @@ index f42953837b..8f79efaa87 100644 if (bitmap_mode != BITMAP_SYNC_MODE_NEVER) { diff --git a/blockdev.c b/blockdev.c -index e6b2b1e338..bdae211a54 100644 +index 83d5cc1e49..060d86a65f 100644 --- a/blockdev.c +++ b/blockdev.c -@@ -3015,7 +3015,36 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, +@@ -2924,7 +2924,36 @@ static void blockdev_mirror_common(const char *job_id, BlockDriverState *bs, sync = MIRROR_SYNC_MODE_FULL; } diff --git a/debian/patches/extra/0001-monitor-qmp-fix-race-with-clients-disconnecting-earl.patch b/debian/patches/extra/0001-monitor-qmp-fix-race-with-clients-disconnecting-earl.patch index a7fe592..5ed0d76 100644 --- a/debian/patches/extra/0001-monitor-qmp-fix-race-with-clients-disconnecting-earl.patch +++ b/debian/patches/extra/0001-monitor-qmp-fix-race-with-clients-disconnecting-earl.patch @@ -48,7 +48,7 @@ Signed-off-by: Thomas Lamprecht 6 files changed, 59 insertions(+), 5 deletions(-) diff --git a/include/monitor/monitor.h b/include/monitor/monitor.h -index 033390f699..ad35d4fea8 100644 +index 965f5d5450..e04bd059b6 100644 --- a/include/monitor/monitor.h +++ b/include/monitor/monitor.h @@ -16,6 +16,7 @@ extern QemuOptsList qemu_mon_opts; @@ -60,10 +60,10 @@ index 033390f699..ad35d4fea8 100644 void monitor_init_globals(void); void monitor_init_globals_core(void); diff --git a/monitor/monitor-internal.h b/monitor/monitor-internal.h -index 53e3808054..a19f8cbc2b 100644 +index 252de85681..8db28f9272 100644 --- a/monitor/monitor-internal.h +++ b/monitor/monitor-internal.h -@@ -152,6 +152,13 @@ typedef struct { +@@ -151,6 +151,13 @@ typedef struct { QemuMutex qmp_queue_lock; /* Input queue that holds all the parsed QMP requests */ GQueue *qmp_requests; @@ -78,10 +78,10 @@ index 53e3808054..a19f8cbc2b 100644 /** diff --git a/monitor/monitor.c b/monitor/monitor.c -index 8dc96f6af9..f3c38cb714 100644 +index dc352f9e9d..56e1307014 100644 --- a/monitor/monitor.c +++ b/monitor/monitor.c -@@ -135,6 +135,21 @@ bool monitor_cur_is_qmp(void) +@@ -117,6 +117,21 @@ bool monitor_cur_is_qmp(void) return cur_mon && monitor_is_qmp(cur_mon); } @@ -104,10 +104,10 @@ index 8dc96f6af9..f3c38cb714 100644 * Is @mon is using readline? * Note: not all HMP monitors use readline, e.g., gdbserver has a diff --git a/monitor/qmp.c b/monitor/qmp.c -index 092c527b6f..6b8cfcf6d8 100644 +index 6eee450fe4..c15bf1e1fc 100644 --- a/monitor/qmp.c +++ b/monitor/qmp.c -@@ -141,6 +141,8 @@ static void monitor_qmp_dispatch(MonitorQMP *mon, QObject *req) +@@ -165,6 +165,8 @@ static void monitor_qmp_dispatch(MonitorQMP *mon, QObject *req) QDict *rsp; QDict *error; @@ -116,7 +116,7 @@ index 092c527b6f..6b8cfcf6d8 100644 rsp = qmp_dispatch(mon->commands, req, qmp_oob_enabled(mon), &mon->common); -@@ -156,7 +158,17 @@ static void monitor_qmp_dispatch(MonitorQMP *mon, QObject *req) +@@ -180,7 +182,17 @@ static void monitor_qmp_dispatch(MonitorQMP *mon, QObject *req) } } @@ -135,7 +135,7 @@ index 092c527b6f..6b8cfcf6d8 100644 qobject_unref(rsp); } -@@ -444,6 +456,7 @@ static void monitor_qmp_event(void *opaque, QEMUChrEvent event) +@@ -478,6 +490,7 @@ static void monitor_qmp_event(void *opaque, QEMUChrEvent event) switch (event) { case CHR_EVENT_OPENED: @@ -144,7 +144,7 @@ index 092c527b6f..6b8cfcf6d8 100644 monitor_qmp_caps_reset(mon); data = qmp_greeting(mon); diff --git a/qapi/qmp-dispatch.c b/qapi/qmp-dispatch.c -index 0990873ec8..e605003771 100644 +index 555528b6bb..3baa508b4b 100644 --- a/qapi/qmp-dispatch.c +++ b/qapi/qmp-dispatch.c @@ -117,16 +117,28 @@ typedef struct QmpDispatchBH { @@ -180,7 +180,7 @@ index 0990873ec8..e605003771 100644 aio_co_wake(data->co); } -@@ -231,6 +243,7 @@ QDict *qmp_dispatch(const QmpCommandList *cmds, QObject *request, +@@ -231,6 +243,7 @@ QDict *coroutine_mixed_fn qmp_dispatch(const QmpCommandList *cmds, QObject *requ .ret = &ret, .errp = &err, .co = qemu_coroutine_self(), diff --git a/debian/patches/extra/0002-scsi-megasas-Internal-cdbs-have-16-byte-length.patch b/debian/patches/extra/0002-scsi-megasas-Internal-cdbs-have-16-byte-length.patch index 20ec053..328f6fb 100644 --- a/debian/patches/extra/0002-scsi-megasas-Internal-cdbs-have-16-byte-length.patch +++ b/debian/patches/extra/0002-scsi-megasas-Internal-cdbs-have-16-byte-length.patch @@ -22,10 +22,10 @@ Signed-off-by: Fiona Ebner 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/hw/scsi/megasas.c b/hw/scsi/megasas.c -index 9cbbb16121..d624866bb6 100644 +index 32c70c9e99..984b6a3145 100644 --- a/hw/scsi/megasas.c +++ b/hw/scsi/megasas.c -@@ -1780,7 +1780,7 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) +@@ -1781,7 +1781,7 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) uint8_t cdb[16]; int len; struct SCSIDevice *sdev = NULL; @@ -34,7 +34,7 @@ index 9cbbb16121..d624866bb6 100644 lba_count = le32_to_cpu(cmd->frame->io.header.data_len); lba_start_lo = le32_to_cpu(cmd->frame->io.lba_lo); -@@ -1789,7 +1789,6 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) +@@ -1790,7 +1790,6 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) target_id = cmd->frame->header.target_id; lun_id = cmd->frame->header.lun_id; @@ -42,7 +42,7 @@ index 9cbbb16121..d624866bb6 100644 if (target_id < MFI_MAX_LD && lun_id == 0) { sdev = scsi_device_find(&s->bus, 0, target_id, lun_id); -@@ -1804,15 +1803,6 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) +@@ -1805,15 +1804,6 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) return MFI_STAT_DEVICE_NOT_FOUND; } @@ -58,7 +58,7 @@ index 9cbbb16121..d624866bb6 100644 cmd->iov_size = lba_count * sdev->blocksize; if (megasas_map_sgl(s, cmd, &cmd->frame->io.sgl)) { megasas_write_sense(cmd, SENSE_CODE(TARGET_FAILURE)); -@@ -1823,7 +1813,7 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) +@@ -1824,7 +1814,7 @@ static int megasas_handle_io(MegasasState *s, MegasasCmd *cmd, int frame_cmd) megasas_encode_lba(cdb, lba_start, lba_count, is_write); cmd->req = scsi_req_new(sdev, cmd->index, diff --git a/debian/patches/extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch b/debian/patches/extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch index 89491a5..018f0c9 100644 --- a/debian/patches/extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch +++ b/debian/patches/extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch @@ -55,7 +55,7 @@ Signed-off-by: Fiona Ebner 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/hw/ide/core.c b/hw/ide/core.c -index 45d14a25e9..08e1f0c3d7 100644 +index 07971c0218..6a74afe564 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c @@ -444,7 +444,7 @@ static void ide_trim_bh_cb(void *opaque) @@ -76,8 +76,8 @@ index 45d14a25e9..08e1f0c3d7 100644 replay_bh_schedule_event(iocb->bh); } } -@@ -515,9 +517,6 @@ BlockAIOCB *ide_issue_trim( - IDEState *s = opaque; +@@ -516,9 +518,6 @@ BlockAIOCB *ide_issue_trim( + IDEDevice *dev = s->unit ? s->bus->slave : s->bus->master; TrimAIOCB *iocb; - /* Paired with a decrement in ide_trim_bh_cb() */ @@ -85,8 +85,8 @@ index 45d14a25e9..08e1f0c3d7 100644 - iocb = blk_aio_get(&trim_aiocb_info, s->blk, cb, cb_opaque); iocb->s = s; - iocb->bh = qemu_bh_new(ide_trim_bh_cb, iocb); -@@ -740,8 +739,9 @@ void ide_cancel_dma_sync(IDEState *s) + iocb->bh = qemu_bh_new_guarded(ide_trim_bh_cb, iocb, +@@ -742,8 +741,9 @@ void ide_cancel_dma_sync(IDEState *s) */ if (s->bus->dma->aiocb) { trace_ide_cancel_dma_sync_remaining(); diff --git a/debian/patches/extra/0010-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch b/debian/patches/extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch similarity index 98% rename from debian/patches/extra/0010-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch rename to debian/patches/extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch index bb01ced..4dae6ca 100644 --- a/debian/patches/extra/0010-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch +++ b/debian/patches/extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch @@ -20,7 +20,7 @@ Signed-off-by: Fiona Ebner 1 file changed, 6 insertions(+) diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c -index fe73aa94b1..7eaf498439 100644 +index 032fc5f405..e1ae3b7316 100644 --- a/migration/block-dirty-bitmap.c +++ b/migration/block-dirty-bitmap.c @@ -805,8 +805,11 @@ static int dirty_bitmap_load_start(QEMUFile *f, DBMLoadState *s) diff --git a/debian/patches/extra/0004-ui-return-NULL-when-getting-cursor-without-a-console.patch b/debian/patches/extra/0004-ui-return-NULL-when-getting-cursor-without-a-console.patch deleted file mode 100644 index 0b8d2c0..0000000 --- a/debian/patches/extra/0004-ui-return-NULL-when-getting-cursor-without-a-console.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= -Date: Fri, 28 Apr 2023 19:48:06 +0400 -Subject: [PATCH] ui: return NULL when getting cursor without a console -MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: 8bit - -VNC may try to get the current cursor even when there are no consoles -and crashes. Simple reproducer is qemu with -nodefaults. - -Fixes: (again) -https://gitlab.com/qemu-project/qemu/-/issues/1548 - -Fixes: commit 385ac97f8 ("ui: keep current cursor with QemuConsole") -Signed-off-by: Marc-Andr? Lureau -Reviewed-by: Philippe Mathieu-Daud? -(picked up from https://lists.nongnu.org/archive/html/qemu-devel/2023-04/msg05598.html) -Signed-off-by: Fiona Ebner ---- - ui/console.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/ui/console.c b/ui/console.c -index e173731e20..7461446e71 100644 ---- a/ui/console.c -+++ b/ui/console.c -@@ -2306,7 +2306,7 @@ QEMUCursor *qemu_console_get_cursor(QemuConsole *con) - if (con == NULL) { - con = active_console; - } -- return con->cursor; -+ return con ? con->cursor : NULL; - } - - bool qemu_console_is_visible(QemuConsole *con) diff --git a/debian/patches/extra/0012-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch b/debian/patches/extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch similarity index 97% rename from debian/patches/extra/0012-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch rename to debian/patches/extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch index d11ae00..ef1a649 100644 --- a/debian/patches/extra/0012-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch +++ b/debian/patches/extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch @@ -68,10 +68,10 @@ Signed-off-by: Fiona Ebner 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/hw/ide/core.c b/hw/ide/core.c -index 08e1f0c3d7..148fccdef2 100644 +index 6a74afe564..289347af58 100644 --- a/hw/ide/core.c +++ b/hw/ide/core.c -@@ -2513,19 +2513,19 @@ static void ide_dummy_transfer_stop(IDEState *s) +@@ -2515,19 +2515,19 @@ static void ide_dummy_transfer_stop(IDEState *s) void ide_bus_reset(IDEBus *bus) { diff --git a/debian/patches/extra/0005-memory-prevent-dma-reentracy-issues.patch b/debian/patches/extra/0005-memory-prevent-dma-reentracy-issues.patch deleted file mode 100644 index c9d0cd5..0000000 --- a/debian/patches/extra/0005-memory-prevent-dma-reentracy-issues.patch +++ /dev/null @@ -1,130 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alexander Bulekov -Date: Thu, 27 Apr 2023 17:10:06 -0400 -Subject: [PATCH] memory: prevent dma-reentracy issues - -Add a flag to the DeviceState, when a device is engaged in PIO/MMIO/DMA. -This flag is set/checked prior to calling a device's MemoryRegion -handlers, and set when device code initiates DMA. The purpose of this -flag is to prevent two types of DMA-based reentrancy issues: - -1.) mmio -> dma -> mmio case -2.) bh -> dma write -> mmio case - -These issues have led to problems such as stack-exhaustion and -use-after-frees. - -Summary of the problem from Peter Maydell: -https://lore.kernel.org/qemu-devel/CAFEAcA_23vc7hE3iaM-JVA6W38LK4hJoWae5KcknhPRD5fPBZA at mail.gmail.com - -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/62 -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/540 -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/541 -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/556 -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/557 -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/827 -Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1282 -Resolves: CVE-2023-0330 - -Signed-off-by: Alexander Bulekov -Reviewed-by: Thomas Huth -Message-Id: <20230427211013.2994127-2-alxndr at bu.edu> -[thuth: Replace warn_report() with warn_report_once()] -Signed-off-by: Thomas Huth -(cherry-picked from commit a2e1753b8054344f32cf94f31c6399a58794a380) -Signed-off-by: Fiona Ebner ---- - include/exec/memory.h | 5 +++++ - include/hw/qdev-core.h | 7 +++++++ - softmmu/memory.c | 16 ++++++++++++++++ - 3 files changed, 28 insertions(+) - -diff --git a/include/exec/memory.h b/include/exec/memory.h -index 15ade918ba..e45ce6061f 100644 ---- a/include/exec/memory.h -+++ b/include/exec/memory.h -@@ -767,6 +767,8 @@ struct MemoryRegion { - bool is_iommu; - RAMBlock *ram_block; - Object *owner; -+ /* owner as TYPE_DEVICE. Used for re-entrancy checks in MR access hotpath */ -+ DeviceState *dev; - - const MemoryRegionOps *ops; - void *opaque; -@@ -791,6 +793,9 @@ struct MemoryRegion { - unsigned ioeventfd_nb; - MemoryRegionIoeventfd *ioeventfds; - RamDiscardManager *rdm; /* Only for RAM */ -+ -+ /* For devices designed to perform re-entrant IO into their own IO MRs */ -+ bool disable_reentrancy_guard; - }; - - struct IOMMUMemoryRegion { -diff --git a/include/hw/qdev-core.h b/include/hw/qdev-core.h -index bd50ad5ee1..7623703943 100644 ---- a/include/hw/qdev-core.h -+++ b/include/hw/qdev-core.h -@@ -162,6 +162,10 @@ struct NamedClockList { - QLIST_ENTRY(NamedClockList) node; - }; - -+typedef struct { -+ bool engaged_in_io; -+} MemReentrancyGuard; -+ - /** - * DeviceState: - * @realized: Indicates whether the device has been fully constructed. -@@ -194,6 +198,9 @@ struct DeviceState { - int alias_required_for_version; - ResettableState reset; - GSList *unplug_blockers; -+ -+ /* Is the device currently in mmio/pio/dma? Used to prevent re-entrancy */ -+ MemReentrancyGuard mem_reentrancy_guard; - }; - - struct DeviceListener { -diff --git a/softmmu/memory.c b/softmmu/memory.c -index b1a6cae6f5..b7b3386e9d 100644 ---- a/softmmu/memory.c -+++ b/softmmu/memory.c -@@ -542,6 +542,18 @@ static MemTxResult access_with_adjusted_size(hwaddr addr, - access_size_max = 4; - } - -+ /* Do not allow more than one simultaneous access to a device's IO Regions */ -+ if (mr->dev && !mr->disable_reentrancy_guard && -+ !mr->ram_device && !mr->ram && !mr->rom_device && !mr->readonly) { -+ if (mr->dev->mem_reentrancy_guard.engaged_in_io) { -+ warn_report_once("Blocked re-entrant IO on MemoryRegion: " -+ "%s at addr: 0x%" HWADDR_PRIX, -+ memory_region_name(mr), addr); -+ return MEMTX_ACCESS_ERROR; -+ } -+ mr->dev->mem_reentrancy_guard.engaged_in_io = true; -+ } -+ - /* FIXME: support unaligned access? */ - access_size = MAX(MIN(size, access_size_max), access_size_min); - access_mask = MAKE_64BIT_MASK(0, access_size * 8); -@@ -556,6 +568,9 @@ static MemTxResult access_with_adjusted_size(hwaddr addr, - access_mask, attrs); - } - } -+ if (mr->dev) { -+ mr->dev->mem_reentrancy_guard.engaged_in_io = false; -+ } - return r; - } - -@@ -1170,6 +1185,7 @@ static void memory_region_do_init(MemoryRegion *mr, - } - mr->name = g_strdup(name); - mr->owner = owner; -+ mr->dev = (DeviceState *) object_dynamic_cast(mr->owner, TYPE_DEVICE); - mr->ram_block = NULL; - - if (name) { diff --git a/debian/patches/extra/0006-lsi53c895a-disable-reentrancy-detection-for-script-R.patch b/debian/patches/extra/0006-lsi53c895a-disable-reentrancy-detection-for-script-R.patch deleted file mode 100644 index 96d254c..0000000 --- a/debian/patches/extra/0006-lsi53c895a-disable-reentrancy-detection-for-script-R.patch +++ /dev/null @@ -1,39 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alexander Bulekov -Date: Thu, 27 Apr 2023 17:10:10 -0400 -Subject: [PATCH] lsi53c895a: disable reentrancy detection for script RAM - -As the code is designed to use the memory APIs to access the script ram, -disable reentrancy checks for the pseudo-RAM ram_io MemoryRegion. - -In the future, ram_io may be converted from an IO to a proper RAM MemoryRegion. - -Reported-by: Fiona Ebner -Signed-off-by: Alexander Bulekov -Reviewed-by: Thomas Huth -Reviewed-by: Darren Kenny -Message-Id: <20230427211013.2994127-6-alxndr at bu.edu> -Signed-off-by: Thomas Huth -(cherry-picked from commit bfd6e7ae6a72b84e2eb9574f56e6ec037f05182c) -Signed-off-by: Fiona Ebner ---- - hw/scsi/lsi53c895a.c | 6 ++++++ - 1 file changed, 6 insertions(+) - -diff --git a/hw/scsi/lsi53c895a.c b/hw/scsi/lsi53c895a.c -index bbf32d3f73..17af67935f 100644 ---- a/hw/scsi/lsi53c895a.c -+++ b/hw/scsi/lsi53c895a.c -@@ -2313,6 +2313,12 @@ static void lsi_scsi_realize(PCIDevice *dev, Error **errp) - memory_region_init_io(&s->io_io, OBJECT(s), &lsi_io_ops, s, - "lsi-io", 256); - -+ /* -+ * Since we use the address-space API to interact with ram_io, disable the -+ * re-entrancy guard. -+ */ -+ s->ram_io.disable_reentrancy_guard = true; -+ - address_space_init(&s->pci_io_as, pci_address_space_io(dev), "lsi-pci-io"); - qdev_init_gpio_out(d, &s->ext_irq, 1); - diff --git a/debian/patches/extra/0007-bcm2835_property-disable-reentrancy-detection-for-io.patch b/debian/patches/extra/0007-bcm2835_property-disable-reentrancy-detection-for-io.patch deleted file mode 100644 index 6ec9d03..0000000 --- a/debian/patches/extra/0007-bcm2835_property-disable-reentrancy-detection-for-io.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alexander Bulekov -Date: Thu, 27 Apr 2023 17:10:11 -0400 -Subject: [PATCH] bcm2835_property: disable reentrancy detection for iomem - -As the code is designed for re-entrant calls from bcm2835_property to -bcm2835_mbox and back into bcm2835_property, mark iomem as -reentrancy-safe. - -Signed-off-by: Alexander Bulekov -Reviewed-by: Thomas Huth -Message-Id: <20230427211013.2994127-7-alxndr at bu.edu> -Signed-off-by: Thomas Huth -(cherry-picked from commit 985c4a4e547afb9573b6bd6843d20eb2c3d1d1cd) -Signed-off-by: Fiona Ebner ---- - hw/misc/bcm2835_property.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/hw/misc/bcm2835_property.c b/hw/misc/bcm2835_property.c -index 890ae7bae5..de056ea2df 100644 ---- a/hw/misc/bcm2835_property.c -+++ b/hw/misc/bcm2835_property.c -@@ -382,6 +382,13 @@ static void bcm2835_property_init(Object *obj) - - memory_region_init_io(&s->iomem, OBJECT(s), &bcm2835_property_ops, s, - TYPE_BCM2835_PROPERTY, 0x10); -+ -+ /* -+ * bcm2835_property_ops call into bcm2835_mbox, which in-turn reads from -+ * iomem. As such, mark iomem as re-entracy safe. -+ */ -+ s->iomem.disable_reentrancy_guard = true; -+ - sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); - sysbus_init_irq(SYS_BUS_DEVICE(s), &s->mbox_irq); - } diff --git a/debian/patches/extra/0008-raven-disable-reentrancy-detection-for-iomem.patch b/debian/patches/extra/0008-raven-disable-reentrancy-detection-for-iomem.patch deleted file mode 100644 index bea68d4..0000000 --- a/debian/patches/extra/0008-raven-disable-reentrancy-detection-for-iomem.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alexander Bulekov -Date: Thu, 27 Apr 2023 17:10:12 -0400 -Subject: [PATCH] raven: disable reentrancy detection for iomem - -As the code is designed for re-entrant calls from raven_io_ops to -pci-conf, mark raven_io_ops as reentrancy-safe. - -Signed-off-by: Alexander Bulekov -Message-Id: <20230427211013.2994127-8-alxndr at bu.edu> -Signed-off-by: Thomas Huth -(cherry-picked from commit 6dad5a6810d9c60ca320d01276f6133bbcfa1fc7) -Signed-off-by: Fiona Ebner ---- - hw/pci-host/raven.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/hw/pci-host/raven.c b/hw/pci-host/raven.c -index 072ffe3c5e..9a11ac4b2b 100644 ---- a/hw/pci-host/raven.c -+++ b/hw/pci-host/raven.c -@@ -294,6 +294,13 @@ static void raven_pcihost_initfn(Object *obj) - memory_region_init(&s->pci_memory, obj, "pci-memory", 0x3f000000); - address_space_init(&s->pci_io_as, &s->pci_io, "raven-io"); - -+ /* -+ * Raven's raven_io_ops use the address-space API to access pci-conf-idx -+ * (which is also owned by the raven device). As such, mark the -+ * pci_io_non_contiguous as re-entrancy safe. -+ */ -+ s->pci_io_non_contiguous.disable_reentrancy_guard = true; -+ - /* CPU address space */ - memory_region_add_subregion(address_space_mem, PCI_IO_BASE_ADDR, - &s->pci_io); diff --git a/debian/patches/extra/0009-apic-disable-reentrancy-detection-for-apic-msi.patch b/debian/patches/extra/0009-apic-disable-reentrancy-detection-for-apic-msi.patch deleted file mode 100644 index 154cc36..0000000 --- a/debian/patches/extra/0009-apic-disable-reentrancy-detection-for-apic-msi.patch +++ /dev/null @@ -1,36 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Alexander Bulekov -Date: Thu, 27 Apr 2023 17:10:13 -0400 -Subject: [PATCH] apic: disable reentrancy detection for apic-msi - -As the code is designed for re-entrant calls to apic-msi, mark apic-msi -as reentrancy-safe. - -Signed-off-by: Alexander Bulekov -Reviewed-by: Darren Kenny -Message-Id: <20230427211013.2994127-9-alxndr at bu.edu> -Signed-off-by: Thomas Huth -(cherry-picked from commit 50795ee051a342c681a9b45671c552fbd6274db8) -Signed-off-by: Fiona Ebner ---- - hw/intc/apic.c | 7 +++++++ - 1 file changed, 7 insertions(+) - -diff --git a/hw/intc/apic.c b/hw/intc/apic.c -index 20b5a94073..ac3d47d231 100644 ---- a/hw/intc/apic.c -+++ b/hw/intc/apic.c -@@ -885,6 +885,13 @@ static void apic_realize(DeviceState *dev, Error **errp) - memory_region_init_io(&s->io_memory, OBJECT(s), &apic_io_ops, s, "apic-msi", - APIC_SPACE_SIZE); - -+ /* -+ * apic-msi's apic_mem_write can call into ioapic_eoi_broadcast, which can -+ * write back to apic-msi. As such mark the apic-msi region re-entrancy -+ * safe. -+ */ -+ s->io_memory.disable_reentrancy_guard = true; -+ - s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, apic_timer, s); - local_apics[s->id] = s; - diff --git a/debian/patches/extra/0011-vhost-fix-the-fd-leak.patch b/debian/patches/extra/0011-vhost-fix-the-fd-leak.patch deleted file mode 100644 index 31392fb..0000000 --- a/debian/patches/extra/0011-vhost-fix-the-fd-leak.patch +++ /dev/null @@ -1,29 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Li Feng -Date: Mon, 31 Jul 2023 20:10:06 +0800 -Subject: [PATCH] vhost: fix the fd leak - -When the vhost-user reconnect to the backend, the notifer should be -cleanup. Otherwise, the fd resource will be exhausted. - -Fixes: f9a09ca3ea ("vhost: add support for configure interrupt") - -Signed-off-by: Li Feng -Reviewed-by: Raphael Norwitz ---- - hw/virtio/vhost.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/hw/virtio/vhost.c b/hw/virtio/vhost.c -index a266396576..8e3311781f 100644 ---- a/hw/virtio/vhost.c -+++ b/hw/virtio/vhost.c -@@ -2034,6 +2034,8 @@ void vhost_dev_stop(struct vhost_dev *hdev, VirtIODevice *vdev, bool vrings) - event_notifier_test_and_clear( - &hdev->vqs[VHOST_QUEUE_NUM_CONFIG_INR].masked_config_notifier); - event_notifier_test_and_clear(&vdev->config_notifier); -+ event_notifier_cleanup( -+ &hdev->vqs[VHOST_QUEUE_NUM_CONFIG_INR].masked_config_notifier); - - trace_vhost_dev_stop(hdev, vdev->name, vrings); - diff --git a/debian/patches/pve/0001-PVE-Config-block-file-change-locking-default-to-off.patch b/debian/patches/pve/0001-PVE-Config-block-file-change-locking-default-to-off.patch index 4926a64..3d8785c 100644 --- a/debian/patches/pve/0001-PVE-Config-block-file-change-locking-default-to-off.patch +++ b/debian/patches/pve/0001-PVE-Config-block-file-change-locking-default-to-off.patch @@ -14,10 +14,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/block/file-posix.c b/block/file-posix.c -index c2dee3f056..9681bd0434 100644 +index aa89789737..0db366a851 100644 --- a/block/file-posix.c +++ b/block/file-posix.c -@@ -553,7 +553,7 @@ static QemuOptsList raw_runtime_opts = { +@@ -564,7 +564,7 @@ static QemuOptsList raw_runtime_opts = { { .name = "locking", .type = QEMU_OPT_STRING, @@ -26,7 +26,7 @@ index c2dee3f056..9681bd0434 100644 }, { .name = "pr-manager", -@@ -653,7 +653,7 @@ static int raw_open_common(BlockDriverState *bs, QDict *options, +@@ -664,7 +664,7 @@ static int raw_open_common(BlockDriverState *bs, QDict *options, s->use_lock = false; break; case ON_OFF_AUTO_AUTO: diff --git a/debian/patches/pve/0003-PVE-Config-set-the-CPU-model-to-kvm64-32-instead-of-.patch b/debian/patches/pve/0003-PVE-Config-set-the-CPU-model-to-kvm64-32-instead-of-.patch index 2827fa4..297e250 100644 --- a/debian/patches/pve/0003-PVE-Config-set-the-CPU-model-to-kvm64-32-instead-of-.patch +++ b/debian/patches/pve/0003-PVE-Config-set-the-CPU-model-to-kvm64-32-instead-of-.patch @@ -10,10 +10,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/target/i386/cpu.h b/target/i386/cpu.h -index d243e290d3..3489b05ec4 100644 +index e0771a1043..1018ccc0b8 100644 --- a/target/i386/cpu.h +++ b/target/i386/cpu.h -@@ -2203,9 +2203,9 @@ uint64_t cpu_get_tsc(CPUX86State *env); +@@ -2243,9 +2243,9 @@ uint64_t cpu_get_tsc(CPUX86State *env); #define CPU_RESOLVING_TYPE TYPE_X86_CPU #ifdef TARGET_X86_64 diff --git a/debian/patches/pve/0005-PVE-Config-glusterfs-no-default-logfile-if-daemonize.patch b/debian/patches/pve/0005-PVE-Config-glusterfs-no-default-logfile-if-daemonize.patch index 6f3afdb..947fc90 100644 --- a/debian/patches/pve/0005-PVE-Config-glusterfs-no-default-logfile-if-daemonize.patch +++ b/debian/patches/pve/0005-PVE-Config-glusterfs-no-default-logfile-if-daemonize.patch @@ -9,7 +9,7 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/block/gluster.c b/block/gluster.c -index 185a83e5e5..f11a40aa9e 100644 +index ad5fadbe79..d0011085c4 100644 --- a/block/gluster.c +++ b/block/gluster.c @@ -43,7 +43,7 @@ @@ -24,7 +24,7 @@ index 185a83e5e5..f11a40aa9e 100644 @@ -425,6 +425,7 @@ static struct glfs *qemu_gluster_glfs_init(BlockdevOptionsGluster *gconf, int old_errno; SocketAddressList *server; - unsigned long long port; + uint64_t port; + const char *logfile; glfs = glfs_find_preopened(gconf->volume); diff --git a/debian/patches/pve/0007-PVE-Up-glusterfs-allow-partial-reads.patch b/debian/patches/pve/0007-PVE-Up-glusterfs-allow-partial-reads.patch index 122a07d..c4e6729 100644 --- a/debian/patches/pve/0007-PVE-Up-glusterfs-allow-partial-reads.patch +++ b/debian/patches/pve/0007-PVE-Up-glusterfs-allow-partial-reads.patch @@ -16,7 +16,7 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/block/gluster.c b/block/gluster.c -index f11a40aa9e..6756e6b886 100644 +index d0011085c4..2df3d6e35d 100644 --- a/block/gluster.c +++ b/block/gluster.c @@ -58,6 +58,7 @@ typedef struct GlusterAIOCB { diff --git a/debian/patches/pve/0008-PVE-Up-qemu-img-return-success-on-info-without-snaps.patch b/debian/patches/pve/0008-PVE-Up-qemu-img-return-success-on-info-without-snaps.patch index feb1ef3..fb505e5 100644 --- a/debian/patches/pve/0008-PVE-Up-qemu-img-return-success-on-info-without-snaps.patch +++ b/debian/patches/pve/0008-PVE-Up-qemu-img-return-success-on-info-without-snaps.patch @@ -9,10 +9,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qemu-img.c b/qemu-img.c -index 9aeac69fa6..0919fac1f1 100644 +index 27f48051b0..bb287d8538 100644 --- a/qemu-img.c +++ b/qemu-img.c -@@ -3059,7 +3059,8 @@ static int img_info(int argc, char **argv) +@@ -3062,7 +3062,8 @@ static int img_info(int argc, char **argv) list = collect_image_info_list(image_opts, filename, fmt, chain, force_share); if (!list) { diff --git a/debian/patches/pve/0009-PVE-Up-qemu-img-dd-add-osize-and-read-from-to-stdin-.patch b/debian/patches/pve/0009-PVE-Up-qemu-img-dd-add-osize-and-read-from-to-stdin-.patch index ffc30d0..5b88664 100644 --- a/debian/patches/pve/0009-PVE-Up-qemu-img-dd-add-osize-and-read-from-to-stdin-.patch +++ b/debian/patches/pve/0009-PVE-Up-qemu-img-dd-add-osize-and-read-from-to-stdin-.patch @@ -54,10 +54,10 @@ index 1b1dab5b17..d1616c045a 100644 DEF("info", img_info, diff --git a/qemu-img.c b/qemu-img.c -index 0919fac1f1..c584de648c 100644 +index bb287d8538..09c0340d16 100644 --- a/qemu-img.c +++ b/qemu-img.c -@@ -4885,10 +4885,12 @@ static int img_bitmap(int argc, char **argv) +@@ -4888,10 +4888,12 @@ static int img_bitmap(int argc, char **argv) #define C_IF 04 #define C_OF 010 #define C_SKIP 020 @@ -70,7 +70,7 @@ index 0919fac1f1..c584de648c 100644 }; struct DdIo { -@@ -4964,6 +4966,19 @@ static int img_dd_skip(const char *arg, +@@ -4967,6 +4969,19 @@ static int img_dd_skip(const char *arg, return 0; } @@ -90,7 +90,7 @@ index 0919fac1f1..c584de648c 100644 static int img_dd(int argc, char **argv) { int ret = 0; -@@ -5004,6 +5019,7 @@ static int img_dd(int argc, char **argv) +@@ -5007,6 +5022,7 @@ static int img_dd(int argc, char **argv) { "if", img_dd_if, C_IF }, { "of", img_dd_of, C_OF }, { "skip", img_dd_skip, C_SKIP }, @@ -98,7 +98,7 @@ index 0919fac1f1..c584de648c 100644 { NULL, NULL, 0 } }; const struct option long_options[] = { -@@ -5079,91 +5095,112 @@ static int img_dd(int argc, char **argv) +@@ -5082,91 +5098,112 @@ static int img_dd(int argc, char **argv) arg = NULL; } @@ -275,7 +275,7 @@ index 0919fac1f1..c584de648c 100644 } if (dd.flags & C_SKIP && (in.offset > INT64_MAX / in.bsz || -@@ -5180,20 +5217,43 @@ static int img_dd(int argc, char **argv) +@@ -5183,20 +5220,43 @@ static int img_dd(int argc, char **argv) in.buf = g_new(uint8_t, in.bsz); for (out_pos = 0; in_pos < size; ) { diff --git a/debian/patches/pve/0010-PVE-Up-qemu-img-dd-add-isize-parameter.patch b/debian/patches/pve/0010-PVE-Up-qemu-img-dd-add-isize-parameter.patch index af21381..0325fe9 100644 --- a/debian/patches/pve/0010-PVE-Up-qemu-img-dd-add-isize-parameter.patch +++ b/debian/patches/pve/0010-PVE-Up-qemu-img-dd-add-isize-parameter.patch @@ -16,10 +16,10 @@ Signed-off-by: Fiona Ebner 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/qemu-img.c b/qemu-img.c -index c584de648c..a57ceeddfe 100644 +index 09c0340d16..556535d9d5 100644 --- a/qemu-img.c +++ b/qemu-img.c -@@ -4886,11 +4886,13 @@ static int img_bitmap(int argc, char **argv) +@@ -4889,11 +4889,13 @@ static int img_bitmap(int argc, char **argv) #define C_OF 010 #define C_SKIP 020 #define C_OSIZE 040 @@ -33,7 +33,7 @@ index c584de648c..a57ceeddfe 100644 }; struct DdIo { -@@ -4979,6 +4981,19 @@ static int img_dd_osize(const char *arg, +@@ -4982,6 +4984,19 @@ static int img_dd_osize(const char *arg, return 0; } @@ -53,7 +53,7 @@ index c584de648c..a57ceeddfe 100644 static int img_dd(int argc, char **argv) { int ret = 0; -@@ -4993,12 +5008,14 @@ static int img_dd(int argc, char **argv) +@@ -4996,12 +5011,14 @@ static int img_dd(int argc, char **argv) int c, i; const char *out_fmt = "raw"; const char *fmt = NULL; @@ -69,7 +69,7 @@ index c584de648c..a57ceeddfe 100644 }; struct DdIo in = { .bsz = 512, /* Block size is by default 512 bytes */ -@@ -5020,6 +5037,7 @@ static int img_dd(int argc, char **argv) +@@ -5023,6 +5040,7 @@ static int img_dd(int argc, char **argv) { "of", img_dd_of, C_OF }, { "skip", img_dd_skip, C_SKIP }, { "osize", img_dd_osize, C_OSIZE }, @@ -77,7 +77,7 @@ index c584de648c..a57ceeddfe 100644 { NULL, NULL, 0 } }; const struct option long_options[] = { -@@ -5216,9 +5234,10 @@ static int img_dd(int argc, char **argv) +@@ -5219,9 +5237,10 @@ static int img_dd(int argc, char **argv) in.buf = g_new(uint8_t, in.bsz); @@ -90,7 +90,7 @@ index c584de648c..a57ceeddfe 100644 if (blk1) { in_ret = blk_pread(blk1, in_pos, bytes, in.buf, 0); if (in_ret == 0) { -@@ -5227,6 +5246,9 @@ static int img_dd(int argc, char **argv) +@@ -5230,6 +5249,9 @@ static int img_dd(int argc, char **argv) } else { in_ret = read(STDIN_FILENO, in.buf, bytes); if (in_ret == 0) { diff --git a/debian/patches/pve/0011-PVE-Up-qemu-img-dd-add-n-skip_create.patch b/debian/patches/pve/0011-PVE-Up-qemu-img-dd-add-n-skip_create.patch index 376aa67..5cca59a 100644 --- a/debian/patches/pve/0011-PVE-Up-qemu-img-dd-add-n-skip_create.patch +++ b/debian/patches/pve/0011-PVE-Up-qemu-img-dd-add-n-skip_create.patch @@ -65,10 +65,10 @@ index d1616c045a..b5b0bb4467 100644 DEF("info", img_info, diff --git a/qemu-img.c b/qemu-img.c -index a57ceeddfe..06d814e39c 100644 +index 556535d9d5..289c78febb 100644 --- a/qemu-img.c +++ b/qemu-img.c -@@ -5010,7 +5010,7 @@ static int img_dd(int argc, char **argv) +@@ -5013,7 +5013,7 @@ static int img_dd(int argc, char **argv) const char *fmt = NULL; int64_t size = 0, readsize = 0; int64_t out_pos, in_pos; @@ -77,7 +77,7 @@ index a57ceeddfe..06d814e39c 100644 struct DdInfo dd = { .flags = 0, .count = 0, -@@ -5048,7 +5048,7 @@ static int img_dd(int argc, char **argv) +@@ -5051,7 +5051,7 @@ static int img_dd(int argc, char **argv) { 0, 0, 0, 0 } }; @@ -86,7 +86,7 @@ index a57ceeddfe..06d814e39c 100644 if (c == EOF) { break; } -@@ -5068,6 +5068,9 @@ static int img_dd(int argc, char **argv) +@@ -5071,6 +5071,9 @@ static int img_dd(int argc, char **argv) case 'h': help(); break; @@ -96,7 +96,7 @@ index a57ceeddfe..06d814e39c 100644 case 'U': force_share = true; break; -@@ -5198,13 +5201,15 @@ static int img_dd(int argc, char **argv) +@@ -5201,13 +5204,15 @@ static int img_dd(int argc, char **argv) size - in.bsz * in.offset, &error_abort); } diff --git a/debian/patches/pve/0012-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch b/debian/patches/pve/0012-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch index debfc54..d649d24 100644 --- a/debian/patches/pve/0012-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch +++ b/debian/patches/pve/0012-qemu-img-dd-add-l-option-for-loading-a-snapshot.patch @@ -46,10 +46,10 @@ index b5b0bb4467..36f97e1f19 100644 DEF("info", img_info, diff --git a/qemu-img.c b/qemu-img.c -index 06d814e39c..e2c06c496d 100644 +index 289c78febb..da543d05cb 100644 --- a/qemu-img.c +++ b/qemu-img.c -@@ -5002,6 +5002,7 @@ static int img_dd(int argc, char **argv) +@@ -5005,6 +5005,7 @@ static int img_dd(int argc, char **argv) BlockDriver *drv = NULL, *proto_drv = NULL; BlockBackend *blk1 = NULL, *blk2 = NULL; QemuOpts *opts = NULL; @@ -57,7 +57,7 @@ index 06d814e39c..e2c06c496d 100644 QemuOptsList *create_opts = NULL; Error *local_err = NULL; bool image_opts = false; -@@ -5011,6 +5012,7 @@ static int img_dd(int argc, char **argv) +@@ -5014,6 +5015,7 @@ static int img_dd(int argc, char **argv) int64_t size = 0, readsize = 0; int64_t out_pos, in_pos; bool force_share = false, skip_create = false; @@ -65,7 +65,7 @@ index 06d814e39c..e2c06c496d 100644 struct DdInfo dd = { .flags = 0, .count = 0, -@@ -5048,7 +5050,7 @@ static int img_dd(int argc, char **argv) +@@ -5051,7 +5053,7 @@ static int img_dd(int argc, char **argv) { 0, 0, 0, 0 } }; @@ -74,7 +74,7 @@ index 06d814e39c..e2c06c496d 100644 if (c == EOF) { break; } -@@ -5071,6 +5073,19 @@ static int img_dd(int argc, char **argv) +@@ -5074,6 +5076,19 @@ static int img_dd(int argc, char **argv) case 'n': skip_create = true; break; @@ -94,7 +94,7 @@ index 06d814e39c..e2c06c496d 100644 case 'U': force_share = true; break; -@@ -5130,11 +5145,24 @@ static int img_dd(int argc, char **argv) +@@ -5133,11 +5148,24 @@ static int img_dd(int argc, char **argv) if (dd.flags & C_IF) { blk1 = img_open(image_opts, in.filename, fmt, 0, false, false, force_share); @@ -120,7 +120,7 @@ index 06d814e39c..e2c06c496d 100644 } if (dd.flags & C_OSIZE) { -@@ -5289,6 +5317,7 @@ static int img_dd(int argc, char **argv) +@@ -5292,6 +5320,7 @@ static int img_dd(int argc, char **argv) out: g_free(arg); qemu_opts_del(opts); diff --git a/debian/patches/pve/0013-PVE-virtio-balloon-improve-query-balloon.patch b/debian/patches/pve/0013-PVE-virtio-balloon-improve-query-balloon.patch index b465314..2c1524f 100644 --- a/debian/patches/pve/0013-PVE-virtio-balloon-improve-query-balloon.patch +++ b/debian/patches/pve/0013-PVE-virtio-balloon-improve-query-balloon.patch @@ -59,10 +59,10 @@ index c3e55ef9e9..0e32e6201f 100644 qapi_free_BalloonInfo(info); } diff --git a/hw/virtio/virtio-balloon.c b/hw/virtio/virtio-balloon.c -index 746f07c4d2..a41854b902 100644 +index d004cf29d2..2660ed520b 100644 --- a/hw/virtio/virtio-balloon.c +++ b/hw/virtio/virtio-balloon.c -@@ -804,8 +804,37 @@ static uint64_t virtio_balloon_get_features(VirtIODevice *vdev, uint64_t f, +@@ -782,8 +782,37 @@ static uint64_t virtio_balloon_get_features(VirtIODevice *vdev, uint64_t f, static void virtio_balloon_stat(void *opaque, BalloonInfo *info) { VirtIOBalloon *dev = opaque; @@ -103,12 +103,12 @@ index 746f07c4d2..a41854b902 100644 static void virtio_balloon_to_target(void *opaque, ram_addr_t target) diff --git a/qapi/machine.json b/qapi/machine.json -index 604b686e59..15f5f86683 100644 +index a08b6576ca..5c9a4d55f4 100644 --- a/qapi/machine.json +++ b/qapi/machine.json -@@ -1056,9 +1056,29 @@ - # @actual: the logical size of the VM in bytes - # Formula used: logical_vm_size = vm_ram_size - balloon_size +@@ -1063,9 +1063,29 @@ + # @actual: the logical size of the VM in bytes Formula used: + # logical_vm_size = vm_ram_size - balloon_size # +# @last_update: time when stats got updated from guest +# diff --git a/debian/patches/pve/0014-PVE-qapi-modify-query-machines.patch b/debian/patches/pve/0014-PVE-qapi-modify-query-machines.patch index e40d67f..ab331f3 100644 --- a/debian/patches/pve/0014-PVE-qapi-modify-query-machines.patch +++ b/debian/patches/pve/0014-PVE-qapi-modify-query-machines.patch @@ -13,10 +13,10 @@ Signed-off-by: Dietmar Maurer 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/hw/core/machine-qmp-cmds.c b/hw/core/machine-qmp-cmds.c -index b98ff15089..24595f618c 100644 +index 3860a50c3b..40821e2317 100644 --- a/hw/core/machine-qmp-cmds.c +++ b/hw/core/machine-qmp-cmds.c -@@ -103,6 +103,12 @@ MachineInfoList *qmp_query_machines(Error **errp) +@@ -91,6 +91,12 @@ MachineInfoList *qmp_query_machines(Error **errp) info->numa_mem_supported = mc->numa_mem_supported; info->deprecated = !!mc->deprecation_reason; info->acpi = !!object_class_property_find(OBJECT_CLASS(mc), "acpi"); @@ -30,19 +30,19 @@ index b98ff15089..24595f618c 100644 info->default_cpu_type = g_strdup(mc->default_cpu_type); } diff --git a/qapi/machine.json b/qapi/machine.json -index 15f5f86683..c904280085 100644 +index 5c9a4d55f4..fbb61f18e4 100644 --- a/qapi/machine.json +++ b/qapi/machine.json -@@ -138,6 +138,8 @@ +@@ -139,6 +139,8 @@ # # @is-default: whether the machine is default # +# @is-current: whether this machine is currently used +# # @cpu-max: maximum number of CPUs supported by the machine type - # (since 1.5) + # (since 1.5) # -@@ -161,7 +163,7 @@ +@@ -163,7 +165,7 @@ ## { 'struct': 'MachineInfo', 'data': { 'name': 'str', '*alias': 'str', diff --git a/debian/patches/pve/0015-PVE-qapi-modify-spice-query.patch b/debian/patches/pve/0015-PVE-qapi-modify-spice-query.patch index df551da..26c6840 100644 --- a/debian/patches/pve/0015-PVE-qapi-modify-spice-query.patch +++ b/debian/patches/pve/0015-PVE-qapi-modify-spice-query.patch @@ -14,10 +14,10 @@ Signed-off-by: Fiona Ebner 2 files changed, 7 insertions(+) diff --git a/qapi/ui.json b/qapi/ui.json -index 98322342f7..316d4dc933 100644 +index 006616aa77..dfd1d3e36b 100644 --- a/qapi/ui.json +++ b/qapi/ui.json -@@ -310,11 +310,14 @@ +@@ -317,11 +317,14 @@ # # @channels: a list of @SpiceChannel for each active spice channel # diff --git a/debian/patches/pve/0016-PVE-add-IOChannel-implementation-for-savevm-async.patch b/debian/patches/pve/0016-PVE-add-IOChannel-implementation-for-savevm-async.patch index ce12543..40c9b32 100644 --- a/debian/patches/pve/0016-PVE-add-IOChannel-implementation-for-savevm-async.patch +++ b/debian/patches/pve/0016-PVE-add-IOChannel-implementation-for-savevm-async.patch @@ -269,14 +269,14 @@ index 0000000000..17ae2cb261 + +#endif /* QIO_CHANNEL_SAVEVM_ASYNC_H */ diff --git a/migration/meson.build b/migration/meson.build -index 0d1bb9f96e..8a142fc7a9 100644 +index 1ae28523a1..37ddcb5d60 100644 --- a/migration/meson.build +++ b/migration/meson.build -@@ -13,6 +13,7 @@ softmmu_ss.add(files( +@@ -13,6 +13,7 @@ system_ss.add(files( 'block-dirty-bitmap.c', 'channel.c', 'channel-block.c', + 'channel-savevm-async.c', - 'colo-failover.c', - 'colo.c', + 'dirtyrate.c', 'exec.c', + 'fd.c', diff --git a/debian/patches/pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch b/debian/patches/pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch index 0c98142..976f73f 100644 --- a/debian/patches/pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch +++ b/debian/patches/pve/0017-PVE-add-savevm-async-for-background-state-snapshots.patch @@ -35,20 +35,20 @@ Signed-off-by: Fiona Ebner include/migration/snapshot.h | 2 + include/monitor/hmp.h | 3 + migration/meson.build | 1 + - migration/savevm-async.c | 533 +++++++++++++++++++++++++++++++++++ + migration/savevm-async.c | 531 +++++++++++++++++++++++++++++++++++ monitor/hmp-cmds.c | 38 +++ qapi/migration.json | 34 +++ qapi/misc.json | 16 ++ qemu-options.hx | 12 + softmmu/vl.c | 10 + - 11 files changed, 679 insertions(+) + 11 files changed, 677 insertions(+) create mode 100644 migration/savevm-async.c diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx -index 47d63d26db..a166bff3d5 100644 +index f5b37eb74a..10fdd822e0 100644 --- a/hmp-commands-info.hx +++ b/hmp-commands-info.hx -@@ -540,6 +540,19 @@ SRST +@@ -525,6 +525,19 @@ SRST Show current migration parameters. ERST @@ -69,10 +69,10 @@ index 47d63d26db..a166bff3d5 100644 .name = "balloon", .args_type = "", diff --git a/hmp-commands.hx b/hmp-commands.hx -index bb85ee1d26..d9f9f42d11 100644 +index 2cbd0f77a0..e352f86872 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx -@@ -1846,3 +1846,20 @@ SRST +@@ -1865,3 +1865,20 @@ SRST List event channels in the guest ERST #endif @@ -105,7 +105,7 @@ index e72083b117..c846d37806 100644 + #endif diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h -index fdb69b7f9c..fdf6b45fb8 100644 +index 13f9a2dedb..7a7def7530 100644 --- a/include/monitor/hmp.h +++ b/include/monitor/hmp.h @@ -28,6 +28,7 @@ void hmp_info_status(Monitor *mon, const QDict *qdict); @@ -126,11 +126,11 @@ index fdb69b7f9c..fdf6b45fb8 100644 void coroutine_fn hmp_screendump(Monitor *mon, const QDict *qdict); void hmp_chardev_add(Monitor *mon, const QDict *qdict); diff --git a/migration/meson.build b/migration/meson.build -index 8a142fc7a9..a7824b5266 100644 +index 37ddcb5d60..07f6057acc 100644 --- a/migration/meson.build +++ b/migration/meson.build -@@ -25,6 +25,7 @@ softmmu_ss.add(files( - 'multifd-zlib.c', +@@ -26,6 +26,7 @@ system_ss.add(files( + 'options.c', 'postcopy-ram.c', 'savevm.c', + 'savevm-async.c', @@ -139,13 +139,15 @@ index 8a142fc7a9..a7824b5266 100644 'threadinfo.c', diff --git a/migration/savevm-async.c b/migration/savevm-async.c new file mode 100644 -index 0000000000..aa2017d496 +index 0000000000..e9fc18fb10 --- /dev/null +++ b/migration/savevm-async.c -@@ -0,0 +1,533 @@ +@@ -0,0 +1,531 @@ +#include "qemu/osdep.h" +#include "migration/channel-savevm-async.h" +#include "migration/migration.h" ++#include "migration/migration-stats.h" ++#include "migration/options.h" +#include "migration/savevm.h" +#include "migration/snapshot.h" +#include "migration/global_state.h" @@ -420,11 +422,7 @@ index 0000000000..aa2017d496 + DPRINTF("savevm iterate pending size %lu ret %d\n", pending_size, ret); + } else { + qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL); -+ ret = global_state_store(); -+ if (ret) { -+ save_snapshot_error("global_state_store error %d", ret); -+ break; -+ } ++ global_state_store(); + + DPRINTF("savevm iterate complete\n"); + break; @@ -485,7 +483,7 @@ index 0000000000..aa2017d496 + return; + } + -+ if (migrate_use_block()) { ++ if (migrate_block()) { + error_set(errp, ERROR_CLASS_GENERIC_ERROR, + "Block migration and snapshots are incompatible"); + return; @@ -538,7 +536,7 @@ index 0000000000..aa2017d496 + * here (blocking main thread, from QMP) to avoid race conditions. + */ + migrate_init(ms); -+ memset(&ram_counters, 0, sizeof(ram_counters)); ++ memset(&mig_stats, 0, sizeof(mig_stats)); + memset(&compression_counters, 0, sizeof(compression_counters)); + ms->to_dst_file = snap_state.file; + @@ -730,12 +728,12 @@ index 6c559b48c8..91be698308 100644 + } +} diff --git a/qapi/migration.json b/qapi/migration.json -index c84fa10e86..1702b92553 100644 +index 8843e74b59..aca0ca1ac1 100644 --- a/qapi/migration.json +++ b/qapi/migration.json -@@ -261,6 +261,40 @@ - '*compression': 'CompressionStats', - '*socket-address': ['SocketAddress'] } } +@@ -291,6 +291,40 @@ + '*dirty-limit-throttle-time-per-round': 'uint64', + '*dirty-limit-ring-full-time': 'uint64'} } +## +# @SaveVMInfo: @@ -775,10 +773,10 @@ index c84fa10e86..1702b92553 100644 # @query-migrate: # diff --git a/qapi/misc.json b/qapi/misc.json -index 6ddd16ea28..e5681ae8a2 100644 +index cda2effa81..94a58bb0bf 100644 --- a/qapi/misc.json +++ b/qapi/misc.json -@@ -469,6 +469,22 @@ +@@ -456,6 +456,22 @@ ## { 'command': 'query-fdsets', 'returns': ['FdsetInfo'] } @@ -802,10 +800,10 @@ index 6ddd16ea28..e5681ae8a2 100644 # @CommandLineParameterType: # diff --git a/qemu-options.hx b/qemu-options.hx -index fdddfab6ff..fdd551c2bb 100644 +index b56f6b2fb2..c8c78c92d4 100644 --- a/qemu-options.hx +++ b/qemu-options.hx -@@ -4398,6 +4398,18 @@ SRST +@@ -4479,6 +4479,18 @@ SRST Start right away with a saved state (``loadvm`` in monitor) ERST @@ -825,7 +823,7 @@ index fdddfab6ff..fdd551c2bb 100644 DEF("daemonize", 0, QEMU_OPTION_daemonize, \ "-daemonize daemonize QEMU after initializing\n", QEMU_ARCH_ALL) diff --git a/softmmu/vl.c b/softmmu/vl.c -index ea20b23e4c..0eabc71b68 100644 +index b0b96f67fa..f3251de3e7 100644 --- a/softmmu/vl.c +++ b/softmmu/vl.c @@ -164,6 +164,7 @@ static const char *accelerators; @@ -836,7 +834,7 @@ index ea20b23e4c..0eabc71b68 100644 static QTAILQ_HEAD(, ObjectOption) object_opts = QTAILQ_HEAD_INITIALIZER(object_opts); static QTAILQ_HEAD(, DeviceOption) device_opts = QTAILQ_HEAD_INITIALIZER(device_opts); static int display_remote; -@@ -2612,6 +2613,12 @@ void qmp_x_exit_preconfig(Error **errp) +@@ -2643,6 +2644,12 @@ void qmp_x_exit_preconfig(Error **errp) if (loadvm) { load_snapshot(loadvm, NULL, false, NULL, &error_fatal); @@ -849,7 +847,7 @@ index ea20b23e4c..0eabc71b68 100644 } if (replay_mode != REPLAY_MODE_NONE) { replay_vmstate_init(); -@@ -3159,6 +3166,9 @@ void qemu_init(int argc, char **argv) +@@ -3190,6 +3197,9 @@ void qemu_init(int argc, char **argv) case QEMU_OPTION_loadvm: loadvm = optarg; break; diff --git a/debian/patches/pve/0018-PVE-add-optional-buffer-size-to-QEMUFile.patch b/debian/patches/pve/0018-PVE-add-optional-buffer-size-to-QEMUFile.patch index f5f6f7b..c946137 100644 --- a/debian/patches/pve/0018-PVE-add-optional-buffer-size-to-QEMUFile.patch +++ b/debian/patches/pve/0018-PVE-add-optional-buffer-size-to-QEMUFile.patch @@ -19,11 +19,11 @@ Signed-off-by: Fiona Ebner 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/migration/qemu-file.c b/migration/qemu-file.c -index 102ab3b439..5ced17aba4 100644 +index 19c33c9985..e9ffff0f0a 100644 --- a/migration/qemu-file.c +++ b/migration/qemu-file.c -@@ -31,8 +31,8 @@ - #include "trace.h" +@@ -33,8 +33,8 @@ + #include "options.h" #include "qapi/error.h" -#define IO_BUF_SIZE 32768 @@ -33,7 +33,7 @@ index 102ab3b439..5ced17aba4 100644 struct QEMUFile { const QEMUFileHooks *hooks; -@@ -55,7 +55,8 @@ struct QEMUFile { +@@ -46,7 +46,8 @@ struct QEMUFile { int buf_index; int buf_size; /* 0 when writing */ @@ -43,8 +43,8 @@ index 102ab3b439..5ced17aba4 100644 DECLARE_BITMAP(may_free, MAX_IOV_SIZE); struct iovec iov[MAX_IOV_SIZE]; -@@ -127,7 +128,9 @@ bool qemu_file_mode_is_not_valid(const char *mode) - return false; +@@ -100,7 +101,9 @@ int qemu_file_shutdown(QEMUFile *f) + return 0; } -static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable) @@ -54,7 +54,7 @@ index 102ab3b439..5ced17aba4 100644 { QEMUFile *f; -@@ -136,6 +139,8 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable) +@@ -109,6 +112,8 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable) object_ref(ioc); f->ioc = ioc; f->is_writable = is_writable; @@ -63,7 +63,7 @@ index 102ab3b439..5ced17aba4 100644 return f; } -@@ -146,17 +151,27 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable) +@@ -119,17 +124,27 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable) */ QEMUFile *qemu_file_get_return_path(QEMUFile *f) { @@ -94,7 +94,7 @@ index 102ab3b439..5ced17aba4 100644 } void qemu_file_set_hooks(QEMUFile *f, const QEMUFileHooks *hooks) -@@ -414,7 +429,7 @@ static ssize_t qemu_fill_buffer(QEMUFile *f) +@@ -375,7 +390,7 @@ static ssize_t coroutine_mixed_fn qemu_fill_buffer(QEMUFile *f) do { len = qio_channel_read(f->ioc, (char *)f->buf + pending, @@ -103,7 +103,7 @@ index 102ab3b439..5ced17aba4 100644 &local_error); if (len == QIO_CHANNEL_ERR_BLOCK) { if (qemu_in_coroutine()) { -@@ -464,6 +479,8 @@ int qemu_fclose(QEMUFile *f) +@@ -425,6 +440,8 @@ int qemu_fclose(QEMUFile *f) } g_clear_pointer(&f->ioc, object_unref); @@ -112,7 +112,7 @@ index 102ab3b439..5ced17aba4 100644 /* If any error was spotted before closing, we should report it * instead of the close() return value. */ -@@ -518,7 +535,7 @@ static void add_buf_to_iovec(QEMUFile *f, size_t len) +@@ -479,7 +496,7 @@ static void add_buf_to_iovec(QEMUFile *f, size_t len) { if (!add_to_iovec(f, f->buf + f->buf_index, len, false)) { f->buf_index += len; @@ -121,7 +121,7 @@ index 102ab3b439..5ced17aba4 100644 qemu_fflush(f); } } -@@ -544,7 +561,7 @@ void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, size_t size) +@@ -504,7 +521,7 @@ void qemu_put_buffer(QEMUFile *f, const uint8_t *buf, size_t size) } while (size > 0) { @@ -130,7 +130,7 @@ index 102ab3b439..5ced17aba4 100644 if (l > size) { l = size; } -@@ -591,8 +608,8 @@ size_t qemu_peek_buffer(QEMUFile *f, uint8_t **buf, size_t size, size_t offset) +@@ -549,8 +566,8 @@ size_t coroutine_mixed_fn qemu_peek_buffer(QEMUFile *f, uint8_t **buf, size_t si size_t index; assert(!qemu_file_is_writable(f)); @@ -141,7 +141,7 @@ index 102ab3b439..5ced17aba4 100644 /* The 1st byte to read from */ index = f->buf_index + offset; -@@ -642,7 +659,7 @@ size_t qemu_get_buffer(QEMUFile *f, uint8_t *buf, size_t size) +@@ -600,7 +617,7 @@ size_t coroutine_mixed_fn qemu_get_buffer(QEMUFile *f, uint8_t *buf, size_t size size_t res; uint8_t *src; @@ -150,16 +150,16 @@ index 102ab3b439..5ced17aba4 100644 if (res == 0) { return done; } -@@ -676,7 +693,7 @@ size_t qemu_get_buffer(QEMUFile *f, uint8_t *buf, size_t size) +@@ -634,7 +651,7 @@ size_t coroutine_mixed_fn qemu_get_buffer(QEMUFile *f, uint8_t *buf, size_t size */ - size_t qemu_get_buffer_in_place(QEMUFile *f, uint8_t **buf, size_t size) + size_t coroutine_mixed_fn qemu_get_buffer_in_place(QEMUFile *f, uint8_t **buf, size_t size) { - if (size < IO_BUF_SIZE) { + if (size < f->buf_allocated_size) { size_t res; uint8_t *src = NULL; -@@ -701,7 +718,7 @@ int qemu_peek_byte(QEMUFile *f, int offset) +@@ -659,7 +676,7 @@ int coroutine_mixed_fn qemu_peek_byte(QEMUFile *f, int offset) int index = f->buf_index + offset; assert(!qemu_file_is_writable(f)); @@ -168,7 +168,7 @@ index 102ab3b439..5ced17aba4 100644 if (index >= f->buf_size) { qemu_fill_buffer(f); -@@ -853,7 +870,7 @@ static int qemu_compress_data(z_stream *stream, uint8_t *dest, size_t dest_len, +@@ -777,7 +794,7 @@ static int qemu_compress_data(z_stream *stream, uint8_t *dest, size_t dest_len, ssize_t qemu_put_compression_data(QEMUFile *f, z_stream *stream, const uint8_t *p, size_t size) { @@ -178,7 +178,7 @@ index 102ab3b439..5ced17aba4 100644 if (blen < compressBound(size)) { return -1; diff --git a/migration/qemu-file.h b/migration/qemu-file.h -index 9d0155a2a1..cc06240e8d 100644 +index 47015f5201..1312b7c903 100644 --- a/migration/qemu-file.h +++ b/migration/qemu-file.h @@ -63,7 +63,9 @@ typedef struct QEMUFileHooks { @@ -192,10 +192,10 @@ index 9d0155a2a1..cc06240e8d 100644 int qemu_fclose(QEMUFile *f); diff --git a/migration/savevm-async.c b/migration/savevm-async.c -index aa2017d496..b97f2c4f14 100644 +index e9fc18fb10..80624fada8 100644 --- a/migration/savevm-async.c +++ b/migration/savevm-async.c -@@ -380,7 +380,7 @@ void qmp_savevm_start(const char *statefile, Error **errp) +@@ -378,7 +378,7 @@ void qmp_savevm_start(const char *statefile, Error **errp) QIOChannel *ioc = QIO_CHANNEL(qio_channel_savevm_async_new(snap_state.target, &snap_state.bs_pos)); @@ -204,7 +204,7 @@ index aa2017d496..b97f2c4f14 100644 if (!snap_state.file) { error_set(errp, ERROR_CLASS_GENERIC_ERROR, "failed to open '%s'", statefile); -@@ -498,7 +498,8 @@ int load_snapshot_from_blockdev(const char *filename, Error **errp) +@@ -496,7 +496,8 @@ int load_snapshot_from_blockdev(const char *filename, Error **errp) blk_op_block_all(be, blocker); /* restore the VM state */ diff --git a/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch b/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch index 39264ee..1cb8166 100644 --- a/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch +++ b/debian/patches/pve/0019-PVE-block-add-the-zeroinit-block-driver-filter.patch @@ -13,17 +13,17 @@ Signed-off-by: Fiona Ebner create mode 100644 block/zeroinit.c diff --git a/block/meson.build b/block/meson.build -index 382bec0e7d..253fe49fa2 100644 +index 529fc172c6..1833c71ce9 100644 --- a/block/meson.build +++ b/block/meson.build -@@ -44,6 +44,7 @@ block_ss.add(files( - 'vmdk.c', - 'vpc.c', +@@ -40,6 +40,7 @@ block_ss.add(files( + 'throttle-groups.c', + 'throttle.c', 'write-threshold.c', + 'zeroinit.c', ), zstd, zlib, gnutls) - softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) + system_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) diff --git a/block/zeroinit.c b/block/zeroinit.c new file mode 100644 index 0000000000..1257342724 diff --git a/debian/patches/pve/0020-PVE-Add-dummy-id-command-line-parameter.patch b/debian/patches/pve/0020-PVE-Add-dummy-id-command-line-parameter.patch index 2c44e34..5327c11 100644 --- a/debian/patches/pve/0020-PVE-Add-dummy-id-command-line-parameter.patch +++ b/debian/patches/pve/0020-PVE-Add-dummy-id-command-line-parameter.patch @@ -14,10 +14,10 @@ Signed-off-by: Thomas Lamprecht 2 files changed, 11 insertions(+) diff --git a/qemu-options.hx b/qemu-options.hx -index fdd551c2bb..4eb43b7bc5 100644 +index c8c78c92d4..20ca2cdba7 100644 --- a/qemu-options.hx +++ b/qemu-options.hx -@@ -1162,6 +1162,9 @@ legacy PC, they are not recommended for modern configurations. +@@ -1197,6 +1197,9 @@ legacy PC, they are not recommended for modern configurations. ERST @@ -28,10 +28,10 @@ index fdd551c2bb..4eb43b7bc5 100644 "-fda/-fdb file use 'file' as floppy disk 0/1 image\n", QEMU_ARCH_ALL) DEF("fdb", HAS_ARG, QEMU_OPTION_fdb, "", QEMU_ARCH_ALL) diff --git a/softmmu/vl.c b/softmmu/vl.c -index 0eabc71b68..323f6a23d4 100644 +index f3251de3e7..1b63ffd33d 100644 --- a/softmmu/vl.c +++ b/softmmu/vl.c -@@ -2648,6 +2648,7 @@ void qemu_init(int argc, char **argv) +@@ -2679,6 +2679,7 @@ void qemu_init(int argc, char **argv) MachineClass *machine_class; bool userconfig = true; FILE *vmstate_dump_file = NULL; @@ -39,7 +39,7 @@ index 0eabc71b68..323f6a23d4 100644 qemu_add_opts(&qemu_drive_opts); qemu_add_drive_opts(&qemu_legacy_drive_opts); -@@ -3271,6 +3272,13 @@ void qemu_init(int argc, char **argv) +@@ -3302,6 +3303,13 @@ void qemu_init(int argc, char **argv) machine_parse_property_opt(qemu_find_opts("smp-opts"), "smp", optarg); break; diff --git a/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch b/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch index e9e9f12..766c4f9 100644 --- a/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch +++ b/debian/patches/pve/0022-PVE-Up-Config-file-posix-make-locking-optiono-on-cre.patch @@ -13,10 +13,10 @@ Signed-off-by: Thomas Lamprecht 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/block/file-posix.c b/block/file-posix.c -index 9681bd0434..044890822d 100644 +index 0db366a851..46f1ee38ae 100644 --- a/block/file-posix.c +++ b/block/file-posix.c -@@ -2483,6 +2483,7 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) +@@ -2870,6 +2870,7 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) int fd; uint64_t perm, shared; int result = 0; @@ -24,7 +24,7 @@ index 9681bd0434..044890822d 100644 /* Validate options and set default values */ assert(options->driver == BLOCKDEV_DRIVER_FILE); -@@ -2523,19 +2524,22 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) +@@ -2910,19 +2911,22 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) perm = BLK_PERM_WRITE | BLK_PERM_RESIZE; shared = BLK_PERM_ALL & ~BLK_PERM_RESIZE; @@ -59,7 +59,7 @@ index 9681bd0434..044890822d 100644 } /* Clear the file by truncating it to 0 */ -@@ -2589,13 +2593,15 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) +@@ -2976,13 +2980,15 @@ raw_co_create(BlockdevCreateOptions *options, Error **errp) } out_unlock: @@ -82,7 +82,7 @@ index 9681bd0434..044890822d 100644 } out_close: -@@ -2619,6 +2625,7 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, +@@ -3006,6 +3012,7 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, PreallocMode prealloc; char *buf = NULL; Error *local_err = NULL; @@ -90,7 +90,7 @@ index 9681bd0434..044890822d 100644 /* Skip file: protocol prefix */ strstart(filename, "file:", &filename); -@@ -2641,6 +2648,18 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, +@@ -3028,6 +3035,18 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, return -EINVAL; } @@ -109,7 +109,7 @@ index 9681bd0434..044890822d 100644 options = (BlockdevCreateOptions) { .driver = BLOCKDEV_DRIVER_FILE, .u.file = { -@@ -2652,6 +2671,8 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, +@@ -3039,6 +3058,8 @@ raw_co_create_opts(BlockDriver *drv, const char *filename, .nocow = nocow, .has_extent_size_hint = has_extent_size_hint, .extent_size_hint = extent_size_hint, @@ -119,10 +119,10 @@ index 9681bd0434..044890822d 100644 }; return raw_co_create(&options, errp); diff --git a/qapi/block-core.json b/qapi/block-core.json -index 3c945c1f93..542add004b 100644 +index 903392cb8f..125aa89858 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json -@@ -4740,7 +4740,8 @@ +@@ -4876,7 +4876,8 @@ 'size': 'size', '*preallocation': 'PreallocMode', '*nocow': 'bool', diff --git a/debian/patches/pve/0023-PVE-monitor-disable-oob-capability.patch b/debian/patches/pve/0023-PVE-monitor-disable-oob-capability.patch index 9abde33..e2f16af 100644 --- a/debian/patches/pve/0023-PVE-monitor-disable-oob-capability.patch +++ b/debian/patches/pve/0023-PVE-monitor-disable-oob-capability.patch @@ -18,10 +18,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/monitor/qmp.c b/monitor/qmp.c -index 6b8cfcf6d8..3ec67e32d3 100644 +index c15bf1e1fc..04fe25c62c 100644 --- a/monitor/qmp.c +++ b/monitor/qmp.c -@@ -519,8 +519,7 @@ void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp) +@@ -553,8 +553,7 @@ void monitor_init_qmp(Chardev *chr, bool pretty, Error **errp) qemu_chr_fe_set_echo(&mon->common.chr, true); /* Note: we run QMP monitor in I/O thread when @chr supports that */ diff --git a/debian/patches/pve/0024-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch b/debian/patches/pve/0024-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch index 79008ee..277fa3f 100644 --- a/debian/patches/pve/0024-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch +++ b/debian/patches/pve/0024-PVE-Compat-4.0-used-balloon-qemu-4-0-config-size-fal.patch @@ -26,10 +26,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/hw/core/machine.c b/hw/core/machine.c -index 2f6ccf5623..a5927e92f1 100644 +index f0d35c6401..1427983543 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c -@@ -142,7 +142,8 @@ GlobalProperty hw_compat_4_0[] = { +@@ -148,7 +148,8 @@ GlobalProperty hw_compat_4_0[] = { { "virtio-vga", "edid", "false" }, { "virtio-gpu-device", "edid", "false" }, { "virtio-device", "use-started", "false" }, diff --git a/debian/patches/pve/0025-PVE-Allow-version-code-in-machine-type.patch b/debian/patches/pve/0025-PVE-Allow-version-code-in-machine-type.patch index d88d1d0..5ec00c1 100644 --- a/debian/patches/pve/0025-PVE-Allow-version-code-in-machine-type.patch +++ b/debian/patches/pve/0025-PVE-Allow-version-code-in-machine-type.patch @@ -21,10 +21,10 @@ Signed-off-by: Fiona Ebner 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/hw/core/machine-qmp-cmds.c b/hw/core/machine-qmp-cmds.c -index 24595f618c..ee9cb0cd04 100644 +index 40821e2317..ee93ddd69a 100644 --- a/hw/core/machine-qmp-cmds.c +++ b/hw/core/machine-qmp-cmds.c -@@ -107,6 +107,11 @@ MachineInfoList *qmp_query_machines(Error **errp) +@@ -95,6 +95,11 @@ MachineInfoList *qmp_query_machines(Error **errp) if (strcmp(mc->name, MACHINE_GET_CLASS(current_machine)->name) == 0) { info->has_is_current = true; info->is_current = true; @@ -37,10 +37,10 @@ index 24595f618c..ee9cb0cd04 100644 if (mc->default_cpu_type) { diff --git a/include/hw/boards.h b/include/hw/boards.h -index 6fbbfd56c8..61a526e97d 100644 +index ed83360198..f8b88cd86a 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h -@@ -232,6 +232,8 @@ struct MachineClass { +@@ -235,6 +235,8 @@ struct MachineClass { const char *desc; const char *deprecation_reason; @@ -50,10 +50,10 @@ index 6fbbfd56c8..61a526e97d 100644 void (*reset)(MachineState *state, ShutdownCause reason); void (*wakeup)(MachineState *state); diff --git a/qapi/machine.json b/qapi/machine.json -index c904280085..47f3facdb2 100644 +index fbb61f18e4..7da3c519ba 100644 --- a/qapi/machine.json +++ b/qapi/machine.json -@@ -159,6 +159,8 @@ +@@ -161,6 +161,8 @@ # # @acpi: machine type supports ACPI (since 8.0) # @@ -62,7 +62,7 @@ index c904280085..47f3facdb2 100644 # Since: 1.2 ## { 'struct': 'MachineInfo', -@@ -166,7 +168,7 @@ +@@ -168,7 +170,7 @@ '*is-default': 'bool', '*is-current': 'bool', 'cpu-max': 'int', 'hotpluggable-cpus': 'bool', 'numa-mem-supported': 'bool', 'deprecated': 'bool', '*default-cpu-type': 'str', @@ -72,10 +72,10 @@ index c904280085..47f3facdb2 100644 ## # @query-machines: diff --git a/softmmu/vl.c b/softmmu/vl.c -index 323f6a23d4..25abdc9da7 100644 +index 1b63ffd33d..20ba2c5c87 100644 --- a/softmmu/vl.c +++ b/softmmu/vl.c -@@ -1578,6 +1578,7 @@ static const QEMUOption *lookup_opt(int argc, char **argv, +@@ -1597,6 +1597,7 @@ static const QEMUOption *lookup_opt(int argc, char **argv, static MachineClass *select_machine(QDict *qdict, Error **errp) { const char *optarg = qdict_get_try_str(qdict, "type"); @@ -83,7 +83,7 @@ index 323f6a23d4..25abdc9da7 100644 GSList *machines = object_class_get_list(TYPE_MACHINE, false); MachineClass *machine_class; Error *local_err = NULL; -@@ -1595,6 +1596,11 @@ static MachineClass *select_machine(QDict *qdict, Error **errp) +@@ -1614,6 +1615,11 @@ static MachineClass *select_machine(QDict *qdict, Error **errp) } } @@ -95,7 +95,7 @@ index 323f6a23d4..25abdc9da7 100644 g_slist_free(machines); if (local_err) { error_append_hint(&local_err, "Use -machine help to list supported machines\n"); -@@ -3213,12 +3219,31 @@ void qemu_init(int argc, char **argv) +@@ -3244,12 +3250,31 @@ void qemu_init(int argc, char **argv) case QEMU_OPTION_machine: { bool help; diff --git a/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch b/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch index 857d22d..f42a06f 100644 --- a/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch +++ b/debian/patches/pve/0027-PVE-Backup-add-vma-backup-format-code.patch @@ -26,23 +26,23 @@ Signed-off-by: Fiona Ebner create mode 100644 vma.h diff --git a/block/meson.build b/block/meson.build -index 253fe49fa2..744b698a82 100644 +index 1833c71ce9..59b71ba9f3 100644 --- a/block/meson.build +++ b/block/meson.build -@@ -47,6 +47,8 @@ block_ss.add(files( +@@ -43,6 +43,8 @@ block_ss.add(files( 'zeroinit.c', ), zstd, zlib, gnutls) +block_ss.add(files('../vma-writer.c'), libuuid) + - softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) - softmmu_ss.add(files('block-ram-registrar.c')) + system_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) + system_ss.add(files('block-ram-registrar.c')) diff --git a/meson.build b/meson.build -index 30447cfaef..38a4e2bcef 100644 +index a9c4f28247..cd95530d3b 100644 --- a/meson.build +++ b/meson.build -@@ -1527,6 +1527,8 @@ keyutils = dependency('libkeyutils', required: false, +@@ -1778,6 +1778,8 @@ endif has_gettid = cc.has_function('gettid') @@ -51,7 +51,7 @@ index 30447cfaef..38a4e2bcef 100644 # libselinux selinux = dependency('libselinux', required: get_option('selinux'), -@@ -3650,6 +3652,9 @@ if have_tools +@@ -3908,6 +3910,9 @@ if have_tools dependencies: [blockdev, qemuutil, gnutls, selinux], install: true) @@ -936,7 +936,7 @@ index 0000000000..81a891c6b1 + diff --git a/vma-writer.c b/vma-writer.c new file mode 100644 -index 0000000000..ac7da237d0 +index 0000000000..6b7af81cae --- /dev/null +++ b/vma-writer.c @@ -0,0 +1,793 @@ @@ -1146,10 +1146,10 @@ index 0000000000..ac7da237d0 +{ + assert(qemu_in_coroutine()); + AioContext *ctx = qemu_get_current_aio_context(); -+ aio_set_fd_handler(ctx, fd, false, NULL, (IOHandler *)qemu_coroutine_enter, -+ NULL, NULL, qemu_coroutine_self()); ++ aio_set_fd_handler(ctx, fd, NULL, (IOHandler *)qemu_coroutine_enter, NULL, ++ NULL, qemu_coroutine_self()); + qemu_coroutine_yield(); -+ aio_set_fd_handler(ctx, fd, false, NULL, NULL, NULL, NULL, NULL); ++ aio_set_fd_handler(ctx, fd, NULL, NULL, NULL, NULL, NULL); +} + +static ssize_t coroutine_fn diff --git a/debian/patches/pve/0028-PVE-Backup-add-backup-dump-block-driver.patch b/debian/patches/pve/0028-PVE-Backup-add-backup-dump-block-driver.patch index cc4a679..0d7fd96 100644 --- a/debian/patches/pve/0028-PVE-Backup-add-backup-dump-block-driver.patch +++ b/debian/patches/pve/0028-PVE-Backup-add-backup-dump-block-driver.patch @@ -243,7 +243,7 @@ index 39410dcf8d..af87fa6aa9 100644 if (perf->max_chunk && perf->max_chunk < cluster_size) { error_setg(errp, "Required max-chunk (%" PRIi64 ") is less than backup " diff --git a/block/meson.build b/block/meson.build -index 744b698a82..f580f95395 100644 +index 59b71ba9f3..6fde9f7dcd 100644 --- a/block/meson.build +++ b/block/meson.build @@ -4,6 +4,7 @@ block_ss.add(files( @@ -255,7 +255,7 @@ index 744b698a82..f580f95395 100644 'blkdebug.c', 'blklogwrites.c', diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h -index f01bb8b617..d7ffd1826e 100644 +index 74195c3004..0f2e1817ad 100644 --- a/include/block/block_int-common.h +++ b/include/block/block_int-common.h @@ -26,6 +26,7 @@ diff --git a/debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch b/debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch index 7a4881b..b32c995 100644 --- a/debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch +++ b/debian/patches/pve/0030-PVE-Backup-Proxmox-backup-patches-for-QEMU.patch @@ -84,30 +84,68 @@ Signed-off-by: Wolfgang Bumiller create jobs in a drained section] Signed-off-by: Fiona Ebner --- - block/meson.build | 5 + - block/monitor/block-hmp-cmds.c | 39 ++ - blockdev.c | 1 + - hmp-commands-info.hx | 14 + - hmp-commands.hx | 29 + - include/monitor/hmp.h | 3 + - meson.build | 1 + - monitor/hmp-cmds.c | 72 +++ - proxmox-backup-client.c | 146 +++++ - proxmox-backup-client.h | 60 ++ - pve-backup.c | 1067 ++++++++++++++++++++++++++++++++ - qapi/block-core.json | 229 +++++++ - qapi/common.json | 13 + - qapi/machine.json | 15 +- - 14 files changed, 1681 insertions(+), 13 deletions(-) + block/backup-dump.c | 10 +- + block/meson.build | 5 + + block/monitor/block-hmp-cmds.c | 39 ++ + blockdev.c | 1 + + hmp-commands-info.hx | 14 + + hmp-commands.hx | 29 + + include/block/block_int-common.h | 2 +- + include/monitor/hmp.h | 3 + + meson.build | 1 + + monitor/hmp-cmds.c | 72 ++ + proxmox-backup-client.c | 146 ++++ + proxmox-backup-client.h | 60 ++ + pve-backup.c | 1067 ++++++++++++++++++++++++++++++ + qapi/block-core.json | 229 +++++++ + qapi/common.json | 14 + + qapi/machine.json | 16 +- + 16 files changed, 1690 insertions(+), 18 deletions(-) create mode 100644 proxmox-backup-client.c create mode 100644 proxmox-backup-client.h create mode 100644 pve-backup.c +diff --git a/block/backup-dump.c b/block/backup-dump.c +index 232a094426..e46abf1070 100644 +--- a/block/backup-dump.c ++++ b/block/backup-dump.c +@@ -9,6 +9,8 @@ + */ + + #include "qemu/osdep.h" ++ ++#include "qapi/qmp/qdict.h" + #include "qom/object_interfaces.h" + #include "block/block_int.h" + +@@ -141,7 +143,7 @@ static void bdrv_backup_dump_init(void) + block_init(bdrv_backup_dump_init); + + +-BlockDriverState *bdrv_backup_dump_create( ++BlockDriverState *coroutine_fn bdrv_co_backup_dump_create( + int dump_cb_block_size, + uint64_t byte_size, + BackupDumpFunc *dump_cb, +@@ -149,9 +151,11 @@ BlockDriverState *bdrv_backup_dump_create( + Error **errp) + { + BDRVBackupDumpState *state; +- BlockDriverState *bs = bdrv_new_open_driver( +- &bdrv_backup_dump_drive, NULL, BDRV_O_RDWR, errp); + ++ QDict *options = qdict_new(); ++ qdict_put_str(options, "driver", "backup-dump-drive"); ++ ++ BlockDriverState *bs = bdrv_co_open(NULL, NULL, options, BDRV_O_RDWR, errp); + if (!bs) { + return NULL; + } diff --git a/block/meson.build b/block/meson.build -index f580f95395..5bcebb934b 100644 +index 6fde9f7dcd..6d468f89e5 100644 --- a/block/meson.build +++ b/block/meson.build -@@ -49,6 +49,11 @@ block_ss.add(files( +@@ -45,6 +45,11 @@ block_ss.add(files( ), zstd, zlib, gnutls) block_ss.add(files('../vma-writer.c'), libuuid) @@ -117,8 +155,8 @@ index f580f95395..5bcebb934b 100644 +), libproxmox_backup_qemu) + - softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) - softmmu_ss.add(files('block-ram-registrar.c')) + system_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) + system_ss.add(files('block-ram-registrar.c')) diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c index ca2599de44..6efe28cef5 100644 --- a/block/monitor/block-hmp-cmds.c @@ -167,7 +205,7 @@ index ca2599de44..6efe28cef5 100644 + hmp_handle_error(mon, error); +} diff --git a/blockdev.c b/blockdev.c -index bdae211a54..315a27fc09 100644 +index 060d86a65f..79c3575612 100644 --- a/blockdev.c +++ b/blockdev.c @@ -37,6 +37,7 @@ @@ -179,10 +217,10 @@ index bdae211a54..315a27fc09 100644 #include "monitor/monitor.h" #include "qemu/error-report.h" diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx -index a166bff3d5..4b75966c2e 100644 +index 10fdd822e0..15937793c1 100644 --- a/hmp-commands-info.hx +++ b/hmp-commands-info.hx -@@ -486,6 +486,20 @@ SRST +@@ -471,6 +471,20 @@ SRST Show the current VM UUID. ERST @@ -204,7 +242,7 @@ index a166bff3d5..4b75966c2e 100644 { .name = "usernet", diff --git a/hmp-commands.hx b/hmp-commands.hx -index d9f9f42d11..ddb9678dc3 100644 +index e352f86872..0c8b6725fb 100644 --- a/hmp-commands.hx +++ b/hmp-commands.hx @@ -101,6 +101,35 @@ ERST @@ -243,8 +281,21 @@ index d9f9f42d11..ddb9678dc3 100644 ERST { +diff --git a/include/block/block_int-common.h b/include/block/block_int-common.h +index 0f2e1817ad..0a0339eee4 100644 +--- a/include/block/block_int-common.h ++++ b/include/block/block_int-common.h +@@ -63,7 +63,7 @@ + + typedef int BackupDumpFunc(void *opaque, uint64_t offset, uint64_t bytes, const void *buf); + +-BlockDriverState *bdrv_backup_dump_create( ++BlockDriverState *coroutine_fn bdrv_co_backup_dump_create( + int dump_cb_block_size, + uint64_t byte_size, + BackupDumpFunc *dump_cb, diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h -index fdf6b45fb8..e01b2201d8 100644 +index 7a7def7530..cba7afe70c 100644 --- a/include/monitor/hmp.h +++ b/include/monitor/hmp.h @@ -32,6 +32,7 @@ void hmp_info_savevm(Monitor *mon, const QDict *qdict); @@ -265,10 +316,10 @@ index fdf6b45fb8..e01b2201d8 100644 void hmp_device_add(Monitor *mon, const QDict *qdict); void hmp_device_del(Monitor *mon, const QDict *qdict); diff --git a/meson.build b/meson.build -index 38a4e2bcef..443b3238f9 100644 +index cd95530d3b..d53976d621 100644 --- a/meson.build +++ b/meson.build -@@ -1528,6 +1528,7 @@ keyutils = dependency('libkeyutils', required: false, +@@ -1779,6 +1779,7 @@ endif has_gettid = cc.has_function('gettid') libuuid = cc.find_library('uuid', required: true) @@ -586,7 +637,7 @@ index 0000000000..8cbf645b2c +#endif /* PROXMOX_BACKUP_CLIENT_H */ diff --git a/pve-backup.c b/pve-backup.c new file mode 100644 -index 0000000000..21441b2f97 +index 0000000000..d84d807654 --- /dev/null +++ b/pve-backup.c @@ -0,0 +1,1067 @@ @@ -1394,7 +1445,7 @@ index 0000000000..21441b2f97 + goto err_mutex; + } + -+ if (!(di->target = bdrv_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, errp))) { ++ if (!(di->target = bdrv_co_backup_dump_create(dump_cb_block_size, di->size, pvebackup_co_dump_pbs_cb, di, errp))) { + goto err_mutex; + } + @@ -1422,7 +1473,7 @@ index 0000000000..21441b2f97 + PVEBackupDevInfo *di = (PVEBackupDevInfo *)l->data; + l = g_list_next(l); + -+ if (!(di->target = bdrv_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, errp))) { ++ if (!(di->target = bdrv_co_backup_dump_create(VMA_CLUSTER_SIZE, di->size, pvebackup_co_dump_vma_cb, di, errp))) { + goto err_mutex; + } + @@ -1658,10 +1709,10 @@ index 0000000000..21441b2f97 + return ret; +} diff --git a/qapi/block-core.json b/qapi/block-core.json -index 542add004b..985859ddee 100644 +index 125aa89858..331c8336d1 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json -@@ -835,6 +835,235 @@ +@@ -839,6 +839,235 @@ { 'command': 'query-block', 'returns': ['BlockInfo'], 'allow-preconfig': true } @@ -1898,10 +1949,10 @@ index 542add004b..985859ddee 100644 # @BlockDeviceTimedStats: # diff --git a/qapi/common.json b/qapi/common.json -index 356db3f670..aae8a3b682 100644 +index 6fed9cde1a..630a2a8f9a 100644 --- a/qapi/common.json +++ b/qapi/common.json -@@ -206,3 +206,16 @@ +@@ -207,3 +207,17 @@ ## { 'struct': 'HumanReadableText', 'data': { 'human-readable-text': 'str' } } @@ -1915,11 +1966,12 @@ index 356db3f670..aae8a3b682 100644 +# +# Since: 0.14.0 +# -+# Notes: If no UUID was specified for the guest, a null UUID is returned. ++# Notes: If no UUID was specified for the guest, a null UUID is ++# returned. +## +{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} } diff --git a/qapi/machine.json b/qapi/machine.json -index 47f3facdb2..46760978ae 100644 +index 7da3c519ba..888457f810 100644 --- a/qapi/machine.json +++ b/qapi/machine.json @@ -4,6 +4,8 @@ @@ -1931,7 +1983,7 @@ index 47f3facdb2..46760978ae 100644 ## # = Machines ## -@@ -228,19 +230,6 @@ +@@ -230,20 +232,6 @@ ## { 'command': 'query-target', 'returns': 'TargetInfo' } @@ -1944,7 +1996,8 @@ index 47f3facdb2..46760978ae 100644 -# -# Since: 0.14 -# --# Notes: If no UUID was specified for the guest, a null UUID is returned. +-# Notes: If no UUID was specified for the guest, a null UUID is +-# returned. -## -{ 'struct': 'UuidInfo', 'data': {'UUID': 'str'} } - diff --git a/debian/patches/pve/0031-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch b/debian/patches/pve/0031-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch index da798b9..2d35795 100644 --- a/debian/patches/pve/0031-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch +++ b/debian/patches/pve/0031-PVE-Backup-pbs-restore-new-command-to-restore-from-p.patch @@ -14,10 +14,10 @@ Signed-off-by: Wolfgang Bumiller create mode 100644 pbs-restore.c diff --git a/meson.build b/meson.build -index 443b3238f9..32ab849ce6 100644 +index d53976d621..c3330310d9 100644 --- a/meson.build +++ b/meson.build -@@ -3656,6 +3656,10 @@ if have_tools +@@ -3914,6 +3914,10 @@ if have_tools vma = executable('vma', files('vma.c', 'vma-reader.c') + genh, dependencies: [authz, block, crypto, io, qom], install: true) diff --git a/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch b/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch index 102cfcf..5657376 100644 --- a/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch +++ b/debian/patches/pve/0032-PVE-Add-PBS-block-driver-to-map-backup-archives-into.patch @@ -24,10 +24,10 @@ Signed-off-by: Fiona Ebner create mode 100644 block/pbs.c diff --git a/block/meson.build b/block/meson.build -index 5bcebb934b..eece0d5743 100644 +index 6d468f89e5..becc99ac4e 100644 --- a/block/meson.build +++ b/block/meson.build -@@ -54,6 +54,9 @@ block_ss.add(files( +@@ -50,6 +50,9 @@ block_ss.add(files( '../pve-backup.c', ), libproxmox_backup_qemu) @@ -35,8 +35,8 @@ index 5bcebb934b..eece0d5743 100644 +block_ss.add(when: 'CONFIG_PBS_BDRV', if_true: libproxmox_backup_qemu) + - softmmu_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) - softmmu_ss.add(files('block-ram-registrar.c')) + system_ss.add(when: 'CONFIG_TCG', if_true: files('blkreplay.c')) + system_ss.add(files('block-ram-registrar.c')) diff --git a/block/pbs.c b/block/pbs.c new file mode 100644 index 0000000000..a2211e0f3b @@ -349,40 +349,40 @@ index 0000000000..a2211e0f3b + +block_init(bdrv_pbs_init); diff --git a/configure b/configure -index a62a3e6be9..1ac0feb46b 100755 +index 133f4e3235..f5a830c1f3 100755 --- a/configure +++ b/configure -@@ -288,6 +288,7 @@ linux_user="" +@@ -256,6 +256,7 @@ qemu_suffix="qemu" + softmmu="yes" + linux_user="" bsd_user="" - pie="" - coroutine="" +pbs_bdrv="yes" plugins="$default_feature" - meson="" ninja="" -@@ -873,6 +874,10 @@ for opt do + python= +@@ -809,6 +810,10 @@ for opt do ;; - --with-coroutine=*) coroutine="$optarg" + --enable-download) download="enabled"; git_submodules_action=update; ;; + --disable-pbs-bdrv) pbs_bdrv="no" + ;; + --enable-pbs-bdrv) pbs_bdrv="yes" + ;; - --with-git=*) git="$optarg" - ;; - --with-git-submodules=*) -@@ -1049,6 +1054,7 @@ cat << EOF - debug-info debugging information - safe-stack SafeStack Stack Smash Protection. Depends on - clang/llvm and requires coroutine backend ucontext. + --enable-plugins) if test "$mingw32" = "yes"; then + error_exit "TCG plugins not currently supported on Windows platforms" + else +@@ -959,6 +964,7 @@ cat << EOF + bsd-user all BSD usermode emulation targets + pie Position Independent Executables + debug-tcg TCG debugging (default is disabled) + pbs-bdrv Proxmox backup server read-only block driver support NOTE: The object files are built at the place where configure is launched EOF -@@ -2386,6 +2392,9 @@ echo "TARGET_DIRS=$target_list" >> $config_host_mak - if test "$modules" = "yes"; then - echo "CONFIG_MODULES=y" >> $config_host_mak +@@ -1744,6 +1750,9 @@ if test "$solaris" = "yes" ; then fi + echo "SRC_PATH=$source_path" >> $config_host_mak + echo "TARGET_DIRS=$target_list" >> $config_host_mak +if test "$pbs_bdrv" = "yes" ; then + echo "CONFIG_PBS_BDRV=y" >> $config_host_mak +fi @@ -390,10 +390,10 @@ index a62a3e6be9..1ac0feb46b 100755 # XXX: suppress that if [ "$bsd" = "yes" ] ; then diff --git a/meson.build b/meson.build -index 32ab849ce6..69afe3441b 100644 +index c3330310d9..cbfc9a43fb 100644 --- a/meson.build +++ b/meson.build -@@ -4041,7 +4041,7 @@ summary_info += {'bzip2 support': libbzip2} +@@ -4319,7 +4319,7 @@ summary_info += {'bzip2 support': libbzip2} summary_info += {'lzfse support': liblzfse} summary_info += {'zstd support': zstd} summary_info += {'NUMA host support': numa} @@ -403,10 +403,10 @@ index 32ab849ce6..69afe3441b 100644 summary_info += {'libdaxctl support': libdaxctl} summary_info += {'libudev': libudev} diff --git a/qapi/block-core.json b/qapi/block-core.json -index 985859ddee..d601fb4ab2 100644 +index 331c8336d1..a818d5f90f 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json -@@ -3304,6 +3304,7 @@ +@@ -3396,6 +3396,7 @@ 'parallels', 'preallocate', 'qcow', 'qcow2', 'qed', 'quorum', 'raw', 'rbd', { 'name': 'replication', 'if': 'CONFIG_REPLICATION' }, @@ -414,7 +414,7 @@ index 985859ddee..d601fb4ab2 100644 'ssh', 'throttle', 'vdi', 'vhdx', { 'name': 'virtio-blk-vfio-pci', 'if': 'CONFIG_BLKIO' }, { 'name': 'virtio-blk-vhost-user', 'if': 'CONFIG_BLKIO' }, -@@ -3380,6 +3381,17 @@ +@@ -3482,6 +3483,17 @@ { 'struct': 'BlockdevOptionsNull', 'data': { '*size': 'int', '*latency-ns': 'uint64', '*read-zeroes': 'bool' } } @@ -432,7 +432,7 @@ index 985859ddee..d601fb4ab2 100644 ## # @BlockdevOptionsNVMe: # -@@ -4753,6 +4765,7 @@ +@@ -4886,6 +4898,7 @@ 'nfs': 'BlockdevOptionsNfs', 'null-aio': 'BlockdevOptionsNull', 'null-co': 'BlockdevOptionsNull', diff --git a/debian/patches/pve/0033-PVE-redirect-stderr-to-journal-when-daemonized.patch b/debian/patches/pve/0033-PVE-redirect-stderr-to-journal-when-daemonized.patch index 147951c..2a5b43e 100644 --- a/debian/patches/pve/0033-PVE-redirect-stderr-to-journal-when-daemonized.patch +++ b/debian/patches/pve/0033-PVE-redirect-stderr-to-journal-when-daemonized.patch @@ -14,10 +14,10 @@ Signed-off-by: Thomas Lamprecht 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/meson.build b/meson.build -index 69afe3441b..b2e9b2aec7 100644 +index cbfc9a43fb..8206270272 100644 --- a/meson.build +++ b/meson.build -@@ -1528,6 +1528,7 @@ keyutils = dependency('libkeyutils', required: false, +@@ -1779,6 +1779,7 @@ endif has_gettid = cc.has_function('gettid') libuuid = cc.find_library('uuid', required: true) @@ -25,16 +25,16 @@ index 69afe3441b..b2e9b2aec7 100644 libproxmox_backup_qemu = cc.find_library('proxmox_backup_qemu', required: true) # libselinux -@@ -3144,6 +3145,7 @@ if have_block +@@ -3406,6 +3407,7 @@ if have_block # os-posix.c contains POSIX-specific functions used by qemu-storage-daemon, # os-win32.c does not blockdev_ss.add(when: 'CONFIG_POSIX', if_true: files('os-posix.c')) + blockdev_ss.add(when: 'CONFIG_POSIX', if_true: libsystemd) - softmmu_ss.add(when: 'CONFIG_WIN32', if_true: [files('os-win32.c')]) + system_ss.add(when: 'CONFIG_WIN32', if_true: [files('os-win32.c')]) endif diff --git a/os-posix.c b/os-posix.c -index 90ea71725f..33745a8c22 100644 +index cfcb96533c..fb2ad87009 100644 --- a/os-posix.c +++ b/os-posix.c @@ -28,6 +28,8 @@ @@ -46,7 +46,7 @@ index 90ea71725f..33745a8c22 100644 /* Needed early for CONFIG_BSD etc. */ #include "net/slirp.h" -@@ -301,9 +303,10 @@ void os_setup_post(void) +@@ -310,9 +312,10 @@ void os_setup_post(void) dup2(fd, 0); dup2(fd, 1); diff --git a/debian/patches/pve/0034-PVE-Migrate-dirty-bitmap-state-via-savevm.patch b/debian/patches/pve/0034-PVE-Migrate-dirty-bitmap-state-via-savevm.patch index 6da71b7..f6cd3c3 100644 --- a/debian/patches/pve/0034-PVE-Migrate-dirty-bitmap-state-via-savevm.patch +++ b/debian/patches/pve/0034-PVE-Migrate-dirty-bitmap-state-via-savevm.patch @@ -26,7 +26,7 @@ Signed-off-by: Fiona Ebner create mode 100644 migration/pbs-state.c diff --git a/include/migration/misc.h b/include/migration/misc.h -index 8b49841016..78f63ca400 100644 +index 7dcc0b5c2c..4c940b2475 100644 --- a/include/migration/misc.h +++ b/include/migration/misc.h @@ -77,4 +77,7 @@ bool migration_in_bg_snapshot(void); @@ -38,25 +38,24 @@ index 8b49841016..78f63ca400 100644 + #endif diff --git a/migration/meson.build b/migration/meson.build -index a7824b5266..de6a271b58 100644 +index 07f6057acc..343994d891 100644 --- a/migration/meson.build +++ b/migration/meson.build -@@ -6,8 +6,10 @@ migration_files = files( +@@ -7,7 +7,9 @@ migration_files = files( 'vmstate.c', 'qemu-file.c', 'yank_functions.c', + 'pbs-state.c', ) - softmmu_ss.add(migration_files) -+softmmu_ss.add(libproxmox_backup_qemu) ++system_ss.add(libproxmox_backup_qemu) - softmmu_ss.add(files( + system_ss.add(files( 'block-dirty-bitmap.c', diff --git a/migration/migration.c b/migration/migration.c -index 99f86bd6c2..db229e72c9 100644 +index 7a4c8beb5d..0a955a2a18 100644 --- a/migration/migration.c +++ b/migration/migration.c -@@ -245,6 +245,7 @@ void migration_object_init(void) +@@ -162,6 +162,7 @@ void migration_object_init(void) blk_mig_init(); ram_mig_init(); dirty_bitmap_mig_init(); @@ -175,7 +174,7 @@ index 0000000000..887e998b9e + NULL); +} diff --git a/pve-backup.c b/pve-backup.c -index 21441b2f97..5e5c37e06d 100644 +index d84d807654..9c8b88d075 100644 --- a/pve-backup.c +++ b/pve-backup.c @@ -1060,6 +1060,7 @@ ProxmoxSupportStatus *qmp_query_proxmox_support(Error **errp) @@ -187,10 +186,10 @@ index 21441b2f97..5e5c37e06d 100644 ret->pbs_masterkey = true; ret->backup_max_workers = true; diff --git a/qapi/block-core.json b/qapi/block-core.json -index d601fb4ab2..16be1e02ec 100644 +index a818d5f90f..48eb47c6ea 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json -@@ -987,6 +987,11 @@ +@@ -991,6 +991,11 @@ # @pbs-dirty-bitmap-savevm: True if 'dirty-bitmaps' migration capability can # safely be set for savevm-async. # @@ -202,7 +201,7 @@ index d601fb4ab2..16be1e02ec 100644 # @pbs-masterkey: True if the QMP backup call supports the 'master_keyfile' # parameter. # -@@ -997,6 +1002,7 @@ +@@ -1001,6 +1006,7 @@ 'data': { 'pbs-dirty-bitmap': 'bool', 'query-bitmap-info': 'bool', 'pbs-dirty-bitmap-savevm': 'bool', diff --git a/debian/patches/pve/0035-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch b/debian/patches/pve/0035-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch index bd721fc..75f5c28 100644 --- a/debian/patches/pve/0035-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch +++ b/debian/patches/pve/0035-migration-block-dirty-bitmap-migrate-other-bitmaps-e.patch @@ -19,10 +19,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c -index 7eaf498439..509f3df0a6 100644 +index e1ae3b7316..285dd1d148 100644 --- a/migration/block-dirty-bitmap.c +++ b/migration/block-dirty-bitmap.c -@@ -539,7 +539,7 @@ static int add_bitmaps_to_list(DBMSaveState *s, BlockDriverState *bs, +@@ -540,7 +540,7 @@ static int add_bitmaps_to_list(DBMSaveState *s, BlockDriverState *bs, if (bdrv_dirty_bitmap_check(bitmap, BDRV_BITMAP_DEFAULT, &local_err)) { error_report_err(local_err); diff --git a/debian/patches/pve/0036-PVE-fall-back-to-open-iscsi-initiatorname.patch b/debian/patches/pve/0036-PVE-fall-back-to-open-iscsi-initiatorname.patch index 0e50c93..f9fee14 100644 --- a/debian/patches/pve/0036-PVE-fall-back-to-open-iscsi-initiatorname.patch +++ b/debian/patches/pve/0036-PVE-fall-back-to-open-iscsi-initiatorname.patch @@ -21,10 +21,10 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 30 insertions(+) diff --git a/block/iscsi.c b/block/iscsi.c -index 9fc0bed90b..1d40933165 100644 +index 34f97ab646..398782963d 100644 --- a/block/iscsi.c +++ b/block/iscsi.c -@@ -1392,12 +1392,42 @@ static char *get_initiator_name(QemuOpts *opts) +@@ -1391,12 +1391,42 @@ static char *get_initiator_name(QemuOpts *opts) const char *name; char *iscsi_name; UuidInfo *uuid_info; diff --git a/debian/patches/pve/0037-PVE-block-stream-increase-chunk-size.patch b/debian/patches/pve/0037-PVE-block-stream-increase-chunk-size.patch index 5707bee..28dd8d1 100644 --- a/debian/patches/pve/0037-PVE-block-stream-increase-chunk-size.patch +++ b/debian/patches/pve/0037-PVE-block-stream-increase-chunk-size.patch @@ -11,7 +11,7 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/block/stream.c b/block/stream.c -index 7f9e1ecdbb..6a29d80398 100644 +index e522bbdec5..afed72db55 100644 --- a/block/stream.c +++ b/block/stream.c @@ -27,7 +27,7 @@ enum { diff --git a/debian/patches/pve/0038-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch b/debian/patches/pve/0038-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch index d1bd74d..0e43de5 100644 --- a/debian/patches/pve/0038-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch +++ b/debian/patches/pve/0038-block-io-accept-NULL-qiov-in-bdrv_pad_request.patch @@ -17,17 +17,17 @@ Signed-off-by: Thomas Lamprecht 1 file changed, 4 insertions(+) diff --git a/block/io.c b/block/io.c -index 2e267a85ab..449a44bf20 100644 +index 055fcf7438..63f7b3ad3e 100644 --- a/block/io.c +++ b/block/io.c -@@ -1576,6 +1576,10 @@ static int bdrv_pad_request(BlockDriverState *bs, - { - int ret; +@@ -1710,6 +1710,10 @@ static int bdrv_pad_request(BlockDriverState *bs, + int sliced_niov; + size_t sliced_head, sliced_tail; + if (!qiov) { + return 0; + } + - bdrv_check_qiov_request(*offset, *bytes, *qiov, *qiov_offset, &error_abort); - - if (!bdrv_init_padding(bs, *offset, *bytes, pad)) { + /* Should have been checked by the caller already */ + ret = bdrv_check_request32(*offset, *bytes, *qiov, *qiov_offset); + if (ret < 0) { diff --git a/debian/patches/pve/0039-block-add-alloc-track-driver.patch b/debian/patches/pve/0039-block-add-alloc-track-driver.patch index 823be87..ea5f105 100644 --- a/debian/patches/pve/0039-block-add-alloc-track-driver.patch +++ b/debian/patches/pve/0039-block-add-alloc-track-driver.patch @@ -393,7 +393,7 @@ index 0000000000..b75d7c6460 + +block_init(bdrv_alloc_track_init); diff --git a/block/meson.build b/block/meson.build -index eece0d5743..8a68162cc0 100644 +index becc99ac4e..0a69836593 100644 --- a/block/meson.build +++ b/block/meson.build @@ -2,6 +2,7 @@ block_ss.add(genh) diff --git a/debian/patches/pve/0044-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch b/debian/patches/pve/0044-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch index 04ef6cb..a7f6e4d 100644 --- a/debian/patches/pve/0044-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch +++ b/debian/patches/pve/0044-migration-for-snapshots-hold-the-BQL-during-setup-ca.patch @@ -52,7 +52,7 @@ Signed-off-by: Fiona Ebner 5 files changed, 38 insertions(+), 12 deletions(-) diff --git a/include/migration/register.h b/include/migration/register.h -index a8dfd8fefd..fa9b0b0f10 100644 +index 90914f32f5..c728fd9120 100644 --- a/include/migration/register.h +++ b/include/migration/register.h @@ -43,9 +43,9 @@ typedef struct SaveVMHandlers { @@ -67,10 +67,10 @@ index a8dfd8fefd..fa9b0b0f10 100644 * must_precopy: * - must be migrated in precopy or in stopped state diff --git a/migration/block-dirty-bitmap.c b/migration/block-dirty-bitmap.c -index 509f3df0a6..42dc4a8d61 100644 +index 285dd1d148..f7ee5a74d9 100644 --- a/migration/block-dirty-bitmap.c +++ b/migration/block-dirty-bitmap.c -@@ -1220,10 +1220,17 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque) +@@ -1219,10 +1219,17 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque) { DBMSaveState *s = &((DBMState *)opaque)->save; SaveBitmapState *dbms = NULL; @@ -90,7 +90,7 @@ index 509f3df0a6..42dc4a8d61 100644 return -1; } -@@ -1231,7 +1238,9 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque) +@@ -1230,7 +1237,9 @@ static int dirty_bitmap_save_setup(QEMUFile *f, void *opaque) send_bitmap_start(f, s, dbms); } qemu_put_bitmap_flags(f, DIRTY_BITMAP_MIG_FLAG_EOS); @@ -102,10 +102,10 @@ index 509f3df0a6..42dc4a8d61 100644 } diff --git a/migration/block.c b/migration/block.c -index b2497bbd32..c9d55be642 100644 +index 86c2256a2b..8423e0c9f9 100644 --- a/migration/block.c +++ b/migration/block.c -@@ -716,21 +716,30 @@ static void block_migration_cleanup(void *opaque) +@@ -725,21 +725,30 @@ static void block_migration_cleanup(void *opaque) static int block_save_setup(QEMUFile *f, void *opaque) { int ret; @@ -140,10 +140,10 @@ index b2497bbd32..c9d55be642 100644 if (ret) { return ret; diff --git a/migration/ram.c b/migration/ram.c -index 79d881f735..0ecbbc3202 100644 +index 9040d66e61..01532c9fc9 100644 --- a/migration/ram.c +++ b/migration/ram.c -@@ -3117,8 +3117,16 @@ static void migration_bitmap_clear_discarded_pages(RAMState *rs) +@@ -2895,8 +2895,16 @@ static void migration_bitmap_clear_discarded_pages(RAMState *rs) static void ram_init_bitmaps(RAMState *rs) { @@ -162,7 +162,7 @@ index 79d881f735..0ecbbc3202 100644 qemu_mutex_lock_ramlist(); WITH_RCU_READ_LOCK_GUARD() { -@@ -3130,7 +3138,9 @@ static void ram_init_bitmaps(RAMState *rs) +@@ -2908,7 +2916,9 @@ static void ram_init_bitmaps(RAMState *rs) } } qemu_mutex_unlock_ramlist(); @@ -174,11 +174,11 @@ index 79d881f735..0ecbbc3202 100644 /* * After an eventual first bitmap sync, fixup the initial bitmap diff --git a/migration/savevm.c b/migration/savevm.c -index aa54a67fda..fc6a82a555 100644 +index a2cb8855e2..ea8b30a630 100644 --- a/migration/savevm.c +++ b/migration/savevm.c -@@ -1621,10 +1621,8 @@ static int qemu_savevm_state(QEMUFile *f, Error **errp) - memset(&compression_counters, 0, sizeof(compression_counters)); +@@ -1625,10 +1625,8 @@ static int qemu_savevm_state(QEMUFile *f, Error **errp) + reset_vfio_bytes_transferred(); ms->to_dst_file = f; - qemu_mutex_unlock_iothread(); diff --git a/debian/patches/pve/0045-savevm-async-don-t-hold-BQL-during-setup.patch b/debian/patches/pve/0045-savevm-async-don-t-hold-BQL-during-setup.patch index f5fa200..3ff0bf7 100644 --- a/debian/patches/pve/0045-savevm-async-don-t-hold-BQL-during-setup.patch +++ b/debian/patches/pve/0045-savevm-async-don-t-hold-BQL-during-setup.patch @@ -13,10 +13,10 @@ Signed-off-by: Fiona Ebner 1 file changed, 2 deletions(-) diff --git a/migration/savevm-async.c b/migration/savevm-async.c -index b97f2c4f14..87ea0573d3 100644 +index 80624fada8..b1d85a4b41 100644 --- a/migration/savevm-async.c +++ b/migration/savevm-async.c -@@ -403,10 +403,8 @@ void qmp_savevm_start(const char *statefile, Error **errp) +@@ -401,10 +401,8 @@ void qmp_savevm_start(const char *statefile, Error **errp) snap_state.state = SAVE_STATE_ACTIVE; snap_state.finalize_bh = qemu_bh_new(process_savevm_finalize, &snap_state); snap_state.co = qemu_coroutine_create(&process_savevm_co, NULL); diff --git a/debian/patches/series b/debian/patches/series index 996fa1f..01d4d3c 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -1,15 +1,8 @@ extra/0001-monitor-qmp-fix-race-with-clients-disconnecting-earl.patch extra/0002-scsi-megasas-Internal-cdbs-have-16-byte-length.patch extra/0003-ide-avoid-potential-deadlock-when-draining-during-tr.patch -extra/0004-ui-return-NULL-when-getting-cursor-without-a-console.patch -extra/0005-memory-prevent-dma-reentracy-issues.patch -extra/0006-lsi53c895a-disable-reentrancy-detection-for-script-R.patch -extra/0007-bcm2835_property-disable-reentrancy-detection-for-io.patch -extra/0008-raven-disable-reentrancy-detection-for-iomem.patch -extra/0009-apic-disable-reentrancy-detection-for-apic-msi.patch -extra/0010-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch -extra/0011-vhost-fix-the-fd-leak.patch -extra/0012-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch +extra/0004-migration-block-dirty-bitmap-fix-loading-bitmap-when.patch +extra/0005-hw-ide-reset-cancel-async-DMA-operation-before-reset.patch bitmap-mirror/0001-drive-mirror-add-support-for-sync-bitmap-mode-never.patch bitmap-mirror/0002-drive-mirror-add-support-for-conditional-and-always-.patch bitmap-mirror/0003-mirror-add-check-for-bitmap-mode-without-bitmap.patch diff --git a/qemu b/qemu index f7f686b..78385bc 160000 --- a/qemu +++ b/qemu @@ -1 +1 @@ -Subproject commit f7f686b61cf7ee142c9264d2e04ac2c6a96d37f8 +Subproject commit 78385bc738108a9b5b20e639520dc60425ca2a5a -- 2.39.2 From f.ebner at proxmox.com Tue Oct 17 14:10:08 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 17 Oct 2023 14:10:08 +0200 Subject: [pve-devel] [PATCH v3 qemu 3/7] buildsys: use QEMU's keycodemapdb again In-Reply-To: <20231017121012.132636-1-f.ebner@proxmox.com> References: <20231017121012.132636-1-f.ebner@proxmox.com> Message-ID: <20231017121012.132636-4-f.ebner@proxmox.com> instead of the split-out version that was last updated for QEMU 6.0. This reverts the relevant part of 6838f03 ("bump version to 2.11.1-1") which doesn't state a reason why the splitting was done. If something breaks, we can still re-do it and document the reason this time. Alternatively, it would be necessary to adapt the paths, because keycodemapdb lives in subprojects/ rather than ui/ since QEMU commit c53648abba ("meson: use subproject for keycodemapdb"). Signed-off-by: Fiona Ebner --- No changes in v3. Makefile | 15 +- keycodemapdb/LICENSE.BSD | 27 - keycodemapdb/LICENSE.GPL2 | 339 --- keycodemapdb/README | 114 - keycodemapdb/data/README | 89 - keycodemapdb/data/keymaps.csv | 539 ---- keycodemapdb/meson.build | 1 - keycodemapdb/tests/.gitignore | 11 - keycodemapdb/tests/Makefile | 150 -- keycodemapdb/tests/javascript | 53 - keycodemapdb/tests/python2 | 3 - keycodemapdb/tests/python3 | 3 - keycodemapdb/tests/stdc++.cc | 40 - keycodemapdb/tests/stdc.c | 64 - keycodemapdb/tests/test.py | 30 - keycodemapdb/thirdparty/LICENSE-argparse.txt | 20 - keycodemapdb/thirdparty/__init__.py | 0 keycodemapdb/thirdparty/argparse.py | 2392 ------------------ keycodemapdb/tools/keymap-gen | 1147 --------- 19 files changed, 1 insertion(+), 5036 deletions(-) delete mode 100644 keycodemapdb/LICENSE.BSD delete mode 100644 keycodemapdb/LICENSE.GPL2 delete mode 100644 keycodemapdb/README delete mode 100644 keycodemapdb/data/README delete mode 100644 keycodemapdb/data/keymaps.csv delete mode 100644 keycodemapdb/meson.build delete mode 100644 keycodemapdb/tests/.gitignore delete mode 100644 keycodemapdb/tests/Makefile delete mode 100755 keycodemapdb/tests/javascript delete mode 100755 keycodemapdb/tests/python2 delete mode 100755 keycodemapdb/tests/python3 delete mode 100644 keycodemapdb/tests/stdc++.cc delete mode 100644 keycodemapdb/tests/stdc.c delete mode 100644 keycodemapdb/tests/test.py delete mode 100644 keycodemapdb/thirdparty/LICENSE-argparse.txt delete mode 100644 keycodemapdb/thirdparty/__init__.py delete mode 100644 keycodemapdb/thirdparty/argparse.py delete mode 100755 keycodemapdb/tools/keymap-gen diff --git a/Makefile b/Makefile index e389a9c..cad130e 100644 --- a/Makefile +++ b/Makefile @@ -39,16 +39,14 @@ PC_BIOS_FW_PURGE_LIST_IN = \ BLOB_PURGE_SED_CMDS = $(foreach FILE,$(PC_BIOS_FW_PURGE_LIST_IN),-e "/$(FILE)/d") BLOB_PURGE_FILTER = $(foreach FILE,$(PC_BIOS_FW_PURGE_LIST_IN),-e "$(FILE)") -$(BUILDDIR): keycodemapdb | submodule +$(BUILDDIR): submodule # check if qemu/ was used for a build # if so, please run 'make distclean' in the submodule and try again test ! -f $(SRCDIR)/build/config.status rm -rf $@.tmp $@ cp -a $(SRCDIR) $@.tmp cp -a debian $@.tmp/debian - rm -rf $@.tmp/ui/keycodemapdb rm -rf $@.tmp/roms/edk2 # packaged separately - cp -a keycodemapdb $@.tmp/ui/ find $@.tmp/pc-bios -type f | grep $(BLOB_PURGE_FILTER) | xargs rm -f sed -i $(BLOB_PURGE_SED_CMDS) $@.tmp/pc-bios/meson.build echo "git clone git://git.proxmox.com/git/pve-qemu.git\\ngit checkout $(GITVERSION)" > $@.tmp/debian/SOURCE @@ -76,17 +74,6 @@ dsc: $(DSC): $(ORIG_SRC_TAR) $(BUILDDIR) cd $(BUILDDIR); dpkg-buildpackage -S -us -uc -d -.PHONY: update -update: - cd $(SRCDIR) && git submodule deinit ui/keycodemapdb || true - rm -rf $(SRCDIR)/ui/keycodemapdb - mkdir $(SRCDIR)/ui/keycodemapdb - cd $(SRCDIR) && git submodule update --init ui/keycodemapdb - rm -rf keycodemapdb - mkdir keycodemapdb - cp -R $(SRCDIR)/ui/keycodemapdb/* keycodemapdb/ - git add keycodemapdb - .PHONY: upload upload: UPLOAD_DIST ?= $(DEB_DISTRIBUTION) upload: $(DEBS) diff --git a/keycodemapdb/LICENSE.BSD b/keycodemapdb/LICENSE.BSD deleted file mode 100644 index ec1a29d..0000000 --- a/keycodemapdb/LICENSE.BSD +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) Individual contributors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of PyCA Cryptography nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/keycodemapdb/LICENSE.GPL2 b/keycodemapdb/LICENSE.GPL2 deleted file mode 100644 index d511905..0000000 --- a/keycodemapdb/LICENSE.GPL2 +++ /dev/null @@ -1,339 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 - - Copyright (C) 1989, 1991 Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -License is intended to guarantee your freedom to share and change free -software--to make sure the software is free for all its users. This -General Public License applies to most of the Free Software -Foundation's software and to any other program whose authors commit to -using it. (Some other Free Software Foundation software is covered by -the GNU Lesser General Public License instead.) You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -this service if you wish), that you receive source code or can get it -if you want it, that you can change the software or use pieces of it -in new free programs; and that you know you can do these things. - - To protect your rights, we need to make restrictions that forbid -anyone to deny you these rights or to ask you to surrender the rights. -These restrictions translate to certain responsibilities for you if you -distribute copies of the software, or if you modify it. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must give the recipients all the rights that -you have. You must make sure that they, too, receive or can get the -source code. And you must show them these terms so they know their -rights. - - We protect your rights with two steps: (1) copyright the software, and -(2) offer you this license which gives you legal permission to copy, -distribute and/or modify the software. - - Also, for each author's protection and ours, we want to make certain -that everyone understands that there is no warranty for this free -software. If the software is modified by someone else and passed on, we -want its recipients to know that what they have is not the original, so -that any problems introduced by others will not reflect on the original -authors' reputations. - - Finally, any free program is threatened constantly by software -patents. We wish to avoid the danger that redistributors of a free -program will individually obtain patent licenses, in effect making the -program proprietary. To prevent this, we have made it clear that any -patent must be licensed for everyone's free use or not licensed at all. - - The precise terms and conditions for copying, distribution and -modification follow. - - GNU GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License applies to any program or other work which contains -a notice placed by the copyright holder saying it may be distributed -under the terms of this General Public License. The "Program", below, -refers to any such program or work, and a "work based on the Program" -means either the Program or any derivative work under copyright law: -that is to say, a work containing the Program or a portion of it, -either verbatim or with modifications and/or translated into another -language. (Hereinafter, translation is included without limitation in -the term "modification".) Each licensee is addressed as "you". - -Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running the Program is not restricted, and the output from the Program -is covered only if its contents constitute a work based on the -Program (independent of having been made by running the Program). -Whether that is true depends on what the Program does. - - 1. You may copy and distribute verbatim copies of the Program's -source code as you receive it, in any medium, provided that you -conspicuously and appropriately publish on each copy an appropriate -copyright notice and disclaimer of warranty; keep intact all the -notices that refer to this License and to the absence of any warranty; -and give any other recipients of the Program a copy of this License -along with the Program. - -You may charge a fee for the physical act of transferring a copy, and -you may at your option offer warranty protection in exchange for a fee. - - 2. You may modify your copy or copies of the Program or any portion -of it, thus forming a work based on the Program, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) You must cause the modified files to carry prominent notices - stating that you changed the files and the date of any change. - - b) You must cause any work that you distribute or publish, that in - whole or in part contains or is derived from the Program or any - part thereof, to be licensed as a whole at no charge to all third - parties under the terms of this License. - - c) If the modified program normally reads commands interactively - when run, you must cause it, when started running for such - interactive use in the most ordinary way, to print or display an - announcement including an appropriate copyright notice and a - notice that there is no warranty (or else, saying that you provide - a warranty) and that users may redistribute the program under - these conditions, and telling the user how to view a copy of this - License. (Exception: if the Program itself is interactive but - does not normally print such an announcement, your work based on - the Program is not required to print an announcement.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Program, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Program, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Program. - -In addition, mere aggregation of another work not based on the Program -with the Program (or with a work based on the Program) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may copy and distribute the Program (or a work based on it, -under Section 2) in object code or executable form under the terms of -Sections 1 and 2 above provided that you also do one of the following: - - a) Accompany it with the complete corresponding machine-readable - source code, which must be distributed under the terms of Sections - 1 and 2 above on a medium customarily used for software interchange; or, - - b) Accompany it with a written offer, valid for at least three - years, to give any third party, for a charge no more than your - cost of physically performing source distribution, a complete - machine-readable copy of the corresponding source code, to be - distributed under the terms of Sections 1 and 2 above on a medium - customarily used for software interchange; or, - - c) Accompany it with the information you received as to the offer - to distribute corresponding source code. (This alternative is - allowed only for noncommercial distribution and only if you - received the program in object code or executable form with such - an offer, in accord with Subsection b above.) - -The source code for a work means the preferred form of the work for -making modifications to it. For an executable work, complete source -code means all the source code for all modules it contains, plus any -associated interface definition files, plus the scripts used to -control compilation and installation of the executable. However, as a -special exception, the source code distributed need not include -anything that is normally distributed (in either source or binary -form) with the major components (compiler, kernel, and so on) of the -operating system on which the executable runs, unless that component -itself accompanies the executable. - -If distribution of executable or object code is made by offering -access to copy from a designated place, then offering equivalent -access to copy the source code from the same place counts as -distribution of the source code, even though third parties are not -compelled to copy the source along with the object code. - - 4. You may not copy, modify, sublicense, or distribute the Program -except as expressly provided under this License. Any attempt -otherwise to copy, modify, sublicense or distribute the Program is -void, and will automatically terminate your rights under this License. -However, parties who have received copies, or rights, from you under -this License will not have their licenses terminated so long as such -parties remain in full compliance. - - 5. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Program or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Program (or any work based on the -Program), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Program or works based on it. - - 6. Each time you redistribute the Program (or any work based on the -Program), the recipient automatically receives a license from the -original licensor to copy, distribute or modify the Program subject to -these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties to -this License. - - 7. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Program at all. For example, if a patent -license would not permit royalty-free redistribution of the Program by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Program. - -If any portion of this section is held invalid or unenforceable under -any particular circumstance, the balance of the section is intended to -apply and the section as a whole is intended to apply in other -circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system, which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 8. If the distribution and/or use of the Program is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Program under this License -may add an explicit geographical distribution limitation excluding -those countries, so that distribution is permitted only in or among -countries not thus excluded. In such case, this License incorporates -the limitation as if written in the body of this License. - - 9. The Free Software Foundation may publish revised and/or new versions -of the General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - -Each version is given a distinguishing version number. If the Program -specifies a version number of this License which applies to it and "any -later version", you have the option of following the terms and conditions -either of that version or of any later version published by the Free -Software Foundation. If the Program does not specify a version number of -this License, you may choose any version ever published by the Free Software -Foundation. - - 10. If you wish to incorporate parts of the Program into other free -programs whose distribution conditions are different, write to the author -to ask for permission. For software which is copyrighted by the Free -Software Foundation, write to the Free Software Foundation; we sometimes -make exceptions for this. Our decision will be guided by the two goals -of preserving the free status of all derivatives of our free software and -of promoting the sharing and reuse of software generally. - - NO WARRANTY - - 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY -FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN -OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES -PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED -OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS -TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE -PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, -REPAIR OR CORRECTION. - - 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR -REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, -INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING -OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED -TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY -YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER -PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE -POSSIBILITY OF SUCH DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License along - with this program; if not, write to the Free Software Foundation, Inc., - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - -Also add information on how to contact you by electronic and paper mail. - -If the program is interactive, make it output a short notice like this -when it starts in an interactive mode: - - Gnomovision version 69, Copyright (C) year name of author - Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, the commands you use may -be called something other than `show w' and `show c'; they could even be -mouse-clicks or menu items--whatever suits your program. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the program, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the program - `Gnomovision' (which makes passes at compilers) written by James Hacker. - - , 1 April 1989 - Ty Coon, President of Vice - -This General Public License does not permit incorporating your program into -proprietary programs. If your program is a subroutine library, you may -consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. diff --git a/keycodemapdb/README b/keycodemapdb/README deleted file mode 100644 index a3c3c9f..0000000 --- a/keycodemapdb/README +++ /dev/null @@ -1,114 +0,0 @@ - Key code / scan code / key symbol mapping database - ================================================== - -This module provides a database that maps between different -key code / scan code / key symbol sets: - - - Linux evdev - - OS-X - - AT Set 1 - - AT Set 2 - - AT Set 3 - - XT - - Linux XT KBD driver - - USB HID - - Win32 - - XWin XT - - XKBD XT - - Xorg Evdev - - Xorg KBD - - Xorg OS-X - - XOrg Cygwin - - RFB - -Licensing ---------- - -The contents of this package are dual licensed under the terms of: - - - GNU General Public License (version 2 or later) - - 3-clause BSD License - -The output files generated by keymap-gen may be distributed & used under -the terms of either of the above licenses. - -Data formats ------------- - -The following output formats are possible - - - Code map - - An array mapping between key code sets values - - Indexes in the array are values from the source code set. - Entries in the array are values from the target code set - - - - Code table - - An array listing all values in a key code set - - Indexes in the array are simply a numeric counter - Entries in the array are values from the key code set - - The size of the array matches the total number of entries in - the keycode database. - - - - Name map - - An array mapping between key code sets values and names - - Indexes in the array are values from the source code set - Entries in the array are names from the target code set - - - - Name table - - An array listing all names in a key code set - - Indexes in the array are simply a numeric counter - Entries in the array are values from the key code set - - The size of the array matches the total number of entries in - the keycode database. - - -Output languages ----------------- - -The tool is capable of generating data tables for the following -programming languages / environments - - - Standard C - - GLib2 (standard C, but with GLib2 data types) - - Python - - Perl - - -Usage ------ - -Map values from AT Set 1 to USB HID, generating tables for the -C programming language - - $ keymap-gen --lang stdc code-map data/keymaps.csv atset1 usb - -Generate a tables of names for Linux key codes, OS-X key codes, -in python - equivalent array indexes map between the two sets. -A variable name override is used - - $ keymap-gen --varname linux_keycodes --lang stdc \ - code-table data/keymaps.csv linux - $ keymap-gen --varname osx_keycodes --lang stdc \ - code-table data/keymaps.csv os-x - -Generate a mapping from XOrg XWin values to Win32 names - - $ keymap-gen --lang perl name-map data/keymaps.csv xorgxwin win32 - -Generate a table of names for Linux key codes in Perl - - $ keymap-gen --lang perl name-table data/keymaps.csv linux - diff --git a/keycodemapdb/data/README b/keycodemapdb/data/README deleted file mode 100644 index 6b56534..0000000 --- a/keycodemapdb/data/README +++ /dev/null @@ -1,89 +0,0 @@ -This directory contains the raw data for mapping between different -keyboard codes. Naming if often based on the US keyboard layout, but -does not indicate the symbol actually generated by the key. - -The columns currently in this data set are: - -Linux ------ - -Name and value of the hardware independent keycodes used by the linux -kernel and exposed through the input subsystem. - -References: linux/input.h - -macOS ------ - -Low level key codes as exposed by Mac OS X/macOS. - -References: Carbon/HIToolbox/Events.h - -PC scan code sets ------------------ - -Scan codes for the three orignal PC keyboard generations: - - Set 1: XT - Set 2: AT - Set 3: PS/2 - -The sets include codes for modern keys as well and not just the keys -present on those original keyboards. - -References: linux/drivers/input/keyboard/atkbd.c - -USB HID -------- - -Codes as specified by the HID profile in USB. - -References: linux/drivers/hid/usbhid/usbkbd.c - -Windows Virtual-key codes -------------------------- - -The low level, hardware independent "VKEYs" exposed by Windows. - -References: mingw32/winuser.h - -XWin XT -------- - -X11 keycodes generated by the XWin server. Based on the XT scan code -set. - -References: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h} - -Xfree86 KBD XT --------------- - -X11 keycodes generated by the Xfree86 keyboard drivers. Based on the XT -scan code set. - -References: xf86-input-keyboard/src/at_scancode.c - -X11 keysyms ------------ - -Corresponding X11 keysym value(s) for a US keyboard layout. - -WARNING: These columns represent symbols, not physical keys, and should - be used with extreme care. - -References: http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h - -HTML KeyboardEvent.code ------------------------ - -Key codes seen in the KeyboardEvent.code attribute as part of the -UI Events specification. - -References: https://www.w3.org/TR/uievents-code/ - -XKEYBOARD key names -------------------- - -Hardware independent key names as used in the XKEYBOARD extension. - -References: /usr/share/X11/xkb/keycodes/ diff --git a/keycodemapdb/data/keymaps.csv b/keycodemapdb/data/keymaps.csv deleted file mode 100644 index 6b1631e..0000000 --- a/keycodemapdb/data/keymaps.csv +++ /dev/null @@ -1,539 +0,0 @@ -"Linux Name","Linux Keycode","OS-X Name","OS-X Keycode","AT set1 keycode","AT set2 keycode","AT set3 keycode","USB Keycodes","Win32 Name","Win32 Keycode","Xwin XT","Xfree86 KBD XT","X11 keysym name","X11 keysym","HTML code","XKB key name","QEMU QKeyCode","Sun KBD","Apple ADB" -KEY_RESERVED,0,,0xff,,,,,,,,,,,,,unmapped,,0xff -KEY_ESC,1,Escape,0x35,0x01,0x76,0x08,41,VK_ESCAPE,0x1b,1,1,XK_Escape,0xff1b,Escape,ESC,esc,0x1d,0x35 -KEY_1,2,ANSI_1,0x12,0x02,0x16,0x16,30,VK_1,0x31,2,2,XK_1,0x0031,Digit1,AE01,1,0x1e,0x12 -KEY_1,2,ANSI_1,0x12,0x02,0x16,0x16,30,VK_1,0x31,2,2,XK_exclam,0x0021,Digit1,AE01,1,0x1e,0x12 -KEY_2,3,ANSI_2,0x13,0x03,0x1e,0x1e,31,VK_2,0x32,3,3,XK_2,0x0032,Digit2,AE02,2,0x1f,0x13 -KEY_2,3,ANSI_2,0x13,0x03,0x1e,0x1e,31,VK_2,0x32,3,3,XK_at,0x0040,Digit2,AE02,2,0x1f,0x13 -KEY_3,4,ANSI_3,0x14,0x04,0x26,0x26,32,VK_3,0x33,4,4,XK_3,0x0033,Digit3,AE03,3,0x20,0x14 -KEY_3,4,ANSI_3,0x14,0x04,0x26,0x26,32,VK_3,0x33,4,4,XK_numbersign,0x0023,Digit3,AE03,3,0x20,0x14 -KEY_4,5,ANSI_4,0x15,0x05,0x25,0x25,33,VK_4,0x34,5,5,XK_4,0x0034,Digit4,AE04,4,0x21,0x15 -KEY_4,5,ANSI_4,0x15,0x05,0x25,0x25,33,VK_4,0x34,5,5,XK_dollar,0x0024,Digit4,AE04,4,0x21,0x15 -KEY_5,6,ANSI_5,0x17,0x06,0x2e,0x2e,34,VK_5,0x35,6,6,XK_5,0x0035,Digit5,AE05,5,0x22,0x17 -KEY_5,6,ANSI_5,0x17,0x06,0x2e,0x2e,34,VK_5,0x35,6,6,XK_percent,0x0025,Digit5,AE05,5,0x22,0x17 -KEY_6,7,ANSI_6,0x16,0x07,0x36,0x36,35,VK_6,0x36,7,7,XK_6,0x0036,Digit6,AE06,6,0x23,0x16 -KEY_6,7,ANSI_6,0x16,0x07,0x36,0x36,35,VK_6,0x36,7,7,XK_asciicircum,0x005e,Digit6,AE06,6,0x23,0x16 -KEY_7,8,ANSI_7,0x1a,0x08,0x3d,0x3d,36,VK_7,0x37,8,8,XK_7,0x0037,Digit7,AE07,7,0x24,0x1a -KEY_7,8,ANSI_7,0x1a,0x08,0x3d,0x3d,36,VK_7,0x37,8,8,XK_ampersand,0x0026,Digit7,AE07,7,0x24,0x1a -KEY_8,9,ANSI_8,0x1c,0x09,0x3e,0x3e,37,VK_8,0x38,9,9,XK_8,0x0038,Digit8,AE08,8,0x25,0x1c -KEY_8,9,ANSI_8,0x1c,0x09,0x3e,0x3e,37,VK_8,0x38,9,9,XK_asterisk,0x002a,Digit8,AE08,8,0x25,0x1c -KEY_9,10,ANSI_9,0x19,0x0a,0x46,0x46,38,VK_9,0x39,10,10,XK_9,0x0039,Digit9,AE09,9,0x26,0x19 -KEY_9,10,ANSI_9,0x19,0x0a,0x46,0x46,38,VK_9,0x39,10,10,XK_parenleft,0x0028,Digit9,AE09,9,0x26,0x19 -KEY_0,11,ANSI_0,0x1d,0x0b,0x45,0x45,39,VK_0,0x30,11,11,XK_0,0x0030,Digit0,AE10,0,0x27,0x1d -KEY_0,11,ANSI_0,0x1d,0x0b,0x45,0x45,39,VK_0,0x30,11,11,XK_parenright,0x0029,Digit0,AE10,0,0x27,0x1d -KEY_MINUS,12,ANSI_Minus,0x1b,0x0c,0x4e,0x4e,45,VK_OEM_MINUS,0xbd,12,12,XK_minus,0x002d,Minus,AE11,minus,0x28,0x1b -KEY_MINUS,12,ANSI_Minus,0x1b,0x0c,0x4e,0x4e,45,VK_OEM_MINUS,0xbd,12,12,XK_underscore,0x005f,Minus,AE11,minus,0x28,0x1b -KEY_EQUAL,13,ANSI_Equal,0x18,0x0d,0x55,0x55,46,VK_OEM_PLUS,0xbb,13,13,XK_equal,0x003d,Equal,AE12,equal,0x29,0x18 -KEY_EQUAL,13,ANSI_Equal,0x18,0x0d,0x55,0x55,46,VK_OEM_PLUS,0xbb,13,13,XK_plus,0x002b,Equal,AE12,equal,0x29,0x18 -KEY_BACKSPACE,14,Delete,0x33,0x0e,0x66,0x66,42,VK_BACK,0x08,14,14,XK_BackSpace,0xff08,Backspace,BKSP,backspace,0x2b,0x33 -KEY_TAB,15,Tab,0x30,0x0f,0x0d,0x0d,43,VK_TAB,0x09,15,15,XK_Tab,0xff09,Tab,TAB,tab,0x35,0x30 -KEY_Q,16,ANSI_Q,0xc,0x10,0x15,0x15,20,VK_Q,0x51,16,16,XK_Q,0x0051,KeyQ,AD01,q,0x36,0xc -KEY_Q,16,ANSI_Q,0xc,0x10,0x15,0x15,20,VK_Q,0x51,16,16,XK_q,0x0071,KeyQ,AD01,q,0x36,0xc -KEY_W,17,ANSI_W,0xd,0x11,0x1d,0x1d,26,VK_W,0x57,17,17,XK_W,0x0057,KeyW,AD02,w,0x37,0xd -KEY_W,17,ANSI_W,0xd,0x11,0x1d,0x1d,26,VK_W,0x57,17,17,XK_w,0x0077,KeyW,AD02,w,0x37,0xd -KEY_E,18,ANSI_E,0xe,0x12,0x24,0x24,8,VK_E,0x45,18,18,XK_E,0x0045,KeyE,AD03,e,0x38,0xe -KEY_E,18,ANSI_E,0xe,0x12,0x24,0x24,8,VK_E,0x45,18,18,XK_e,0x0065,KeyE,AD03,e,0x38,0xe -KEY_R,19,ANSI_R,0xf,0x13,0x2d,0x2d,21,VK_R,0x52,19,19,XK_R,0x0052,KeyR,AD04,r,0x39,0xf -KEY_R,19,ANSI_R,0xf,0x13,0x2d,0x2d,21,VK_R,0x52,19,19,XK_r,0x0072,KeyR,AD04,r,0x39,0xf -KEY_T,20,ANSI_T,0x11,0x14,0x2c,0x2c,23,VK_T,0x54,20,20,XK_T,0x0054,KeyT,AD05,t,0x3a,0x11 -KEY_T,20,ANSI_T,0x11,0x14,0x2c,0x2c,23,VK_T,0x54,20,20,XK_t,0x0074,KeyT,AD05,t,0x3a,0x11 -KEY_Y,21,ANSI_Y,0x10,0x15,0x35,0x35,28,VK_Y,0x59,21,21,XK_Y,0x0059,KeyY,AD06,y,0x3b,0x10 -KEY_Y,21,ANSI_Y,0x10,0x15,0x35,0x35,28,VK_Y,0x59,21,21,XK_y,0x0079,KeyY,AD06,y,0x3b,0x10 -KEY_U,22,ANSI_U,0x20,0x16,0x3c,0x3c,24,VK_U,0x55,22,22,XK_U,0x0055,KeyU,AD07,u,0x3c,0x20 -KEY_U,22,ANSI_U,0x20,0x16,0x3c,0x3c,24,VK_U,0x55,22,22,XK_u,0x0075,KeyU,AD07,u,0x3c,0x20 -KEY_I,23,ANSI_I,0x22,0x17,0x43,0x43,12,VK_I,0x49,23,23,XK_I,0x0049,KeyI,AD08,i,0x3d,0x22 -KEY_I,23,ANSI_I,0x22,0x17,0x43,0x43,12,VK_I,0x49,23,23,XK_i,0x0069,KeyI,AD08,i,0x3d,0x22 -KEY_O,24,ANSI_O,0x1f,0x18,0x44,0x44,18,VK_O,0x4f,24,24,XK_O,0x004f,KeyO,AD09,o,0x3e,0x1f -KEY_O,24,ANSI_O,0x1f,0x18,0x44,0x44,18,VK_O,0x4f,24,24,XK_o,0x006f,KeyO,AD09,o,0x3e,0x1f -KEY_P,25,ANSI_P,0x23,0x19,0x4d,0x4d,19,VK_P,0x50,25,25,XK_P,0x0050,KeyP,AD10,p,0x3f,0x23 -KEY_P,25,ANSI_P,0x23,0x19,0x4d,0x4d,19,VK_P,0x50,25,25,XK_p,0x0070,KeyP,AD10,p,0x3f,0x23 -KEY_LEFTBRACE,26,ANSI_LeftBracket,0x21,0x1a,0x54,0x54,47,VK_OEM_4,0xdb,26,26,XK_bracketleft,0x005b,BracketLeft,AD11,bracket_left,0x40,0x21 -KEY_LEFTBRACE,26,ANSI_LeftBracket,0x21,0x1a,0x54,0x54,47,VK_OEM_4,0xdb,26,26,XK_braceleft,0x007b,BracketLeft,AD11,bracket_left,0x40,0x21 -KEY_RIGHTBRACE,27,ANSI_RightBracket,0x1e,0x1b,0x5b,0x5b,48,VK_OEM_6,0xdd,27,27,XK_bracketright,0x005d,BracketRight,AD12,bracket_right,0x41,0x1e -KEY_RIGHTBRACE,27,ANSI_RightBracket,0x1e,0x1b,0x5b,0x5b,48,VK_OEM_6,0xdd,27,27,XK_braceright,0x007d,BracketRight,AD12,bracket_right,0x41,0x1e -KEY_ENTER,28,Return,0x24,0x1c,0x5a,0x5a,40,VK_RETURN,0x0d,28,28,XK_Return,0xff0d,Enter,RTRN,ret,0x59,0x24 -KEY_LEFTCTRL,29,Control,0x3b,0x1d,0x14,0x11,224,VK_LCONTROL,0xa2,29,29,XK_Control_L,0xffe3,ControlLeft,LCTL,ctrl,0x4c,0x36 -KEY_LEFTCTRL,29,Control,0x3b,0x1d,0x14,0x11,224,VK_CONTROL,0x11,29,29,XK_Control_L,0xffe3,ControlLeft,LCTL,ctrl,0x4c,0x36 -KEY_A,30,ANSI_A,0x0,0x1e,0x1c,0x1c,4,VK_A,0x41,30,30,XK_A,0x0041,KeyA,AC01,a,0x4d,0x0 -KEY_A,30,ANSI_A,0x0,0x1e,0x1c,0x1c,4,VK_A,0x41,30,30,XK_a,0x0061,KeyA,AC01,a,0x4d,0x0 -KEY_S,31,ANSI_S,0x1,0x1f,0x1b,0x1b,22,VK_S,0x53,31,31,XK_S,0x0053,KeyS,AC02,s,0x4e,0x1 -KEY_S,31,ANSI_S,0x1,0x1f,0x1b,0x1b,22,VK_S,0x53,31,31,XK_s,0x0073,KeyS,AC02,s,0x4e,0x1 -KEY_D,32,ANSI_D,0x2,0x20,0x23,0x23,7,VK_D,0x44,32,32,XK_D,0x0044,KeyD,AC03,d,0x4f,0x2 -KEY_D,32,ANSI_D,0x2,0x20,0x23,0x23,7,VK_D,0x44,32,32,XK_d,0x0064,KeyD,AC03,d,0x4f,0x2 -KEY_F,33,ANSI_F,0x3,0x21,0x2b,0x2b,9,VK_F,0x46,33,33,XK_F,0x0046,KeyF,AC04,f,0x50,0x3 -KEY_F,33,ANSI_F,0x3,0x21,0x2b,0x2b,9,VK_F,0x46,33,33,XK_f,0x0066,KeyF,AC04,f,0x50,0x3 -KEY_G,34,ANSI_G,0x5,0x22,0x34,0x34,10,VK_G,0x47,34,34,XK_G,0x0047,KeyG,AC05,g,0x51,0x5 -KEY_G,34,ANSI_G,0x5,0x22,0x34,0x34,10,VK_G,0x47,34,34,XK_g,0x0067,KeyG,AC05,g,0x51,0x5 -KEY_H,35,ANSI_H,0x4,0x23,0x33,0x33,11,VK_H,0x48,35,35,XK_H,0x0048,KeyH,AC06,h,0x52,0x4 -KEY_H,35,ANSI_H,0x4,0x23,0x33,0x33,11,VK_H,0x48,35,35,XK_h,0x0068,KeyH,AC06,h,0x52,0x4 -KEY_J,36,ANSI_J,0x26,0x24,0x3b,0x3b,13,VK_J,0x4a,36,36,XK_J,0x004a,KeyJ,AC07,j,0x53,0x26 -KEY_J,36,ANSI_J,0x26,0x24,0x3b,0x3b,13,VK_J,0x4a,36,36,XK_j,0x006a,KeyJ,AC07,j,0x53,0x26 -KEY_K,37,ANSI_K,0x28,0x25,0x42,0x42,14,VK_K,0x4b,37,37,XK_K,0x004b,KeyK,AC08,k,0x54,0x28 -KEY_K,37,ANSI_K,0x28,0x25,0x42,0x42,14,VK_K,0x4b,37,37,XK_k,0x006b,KeyK,AC08,k,0x54,0x28 -KEY_L,38,ANSI_L,0x25,0x26,0x4b,0x4b,15,VK_L,0x4c,38,38,XK_L,0x004c,KeyL,AC09,l,0x55,0x25 -KEY_L,38,ANSI_L,0x25,0x26,0x4b,0x4b,15,VK_L,0x4c,38,38,XK_l,0x006c,KeyL,AC09,l,0x55,0x25 -KEY_SEMICOLON,39,ANSI_Semicolon,0x29,0x27,0x4c,0x4c,51,VK_OEM_1,0xba,39,39,XK_semicolon,0x003b,Semicolon,AC10,semicolon,0x56,0x29 -KEY_SEMICOLON,39,ANSI_Semicolon,0x29,0x27,0x4c,0x4c,51,VK_OEM_1,0xba,39,39,XK_colon,0x003a,Semicolon,AC10,semicolon,0x56,0x29 -KEY_APOSTROPHE,40,ANSI_Quote,0x27,0x28,0x52,0x52,52,VK_OEM_7,0xde,40,40,XK_apostrophe,0x0027,Quote,AC11,apostrophe,0x57,0x27 -KEY_APOSTROPHE,40,ANSI_Quote,0x27,0x28,0x52,0x52,52,VK_OEM_7,0xde,40,40,XK_quotedbl,0x0022,Quote,AC11,apostrophe,0x57,0x27 -KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_grave,0x0060,Backquote,TLDE,grave_accent,0x2a,0x32 -KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_grave,0x0060,Backquote,AB00,grave_accent,0x2a,0x32 -KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_asciitilde,0x007e,Backquote,TLDE,grave_accent,0x2a,0x32 -KEY_GRAVE,41,ANSI_Grave,0x32,0x29,0x0e,0x0e,53,VK_OEM_3,0xc0,41,41,XK_asciitilde,0x007e,Backquote,AB00,grave_accent,0x2a,0x32 -KEY_SHIFT,42,Shift,0x38,0x2a,0x12,0x12,225,VK_SHIFT,0x10,42,42,XK_Shift_L,0xffe1,ShiftLeft,LFSH,shift,0x63,0x38 -KEY_LEFTSHIFT,42,Shift,0x38,0x2a,0x12,0x12,225,VK_LSHIFT,0xa0,42,42,XK_Shift_L,0xffe1,ShiftLeft,LFSH,shift,0x63,0x38 -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,BKSL,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,AC12,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,BKSL,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,49,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,AC12,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,BKSL,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_backslash,0x005c,Backslash,AC12,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,BKSL,backslash,0x58,0x2a -KEY_BACKSLASH,43,ANSI_Backslash,0x2a,0x2b,0x5d,0x5c,50,VK_OEM_5,0xdc,43,43,XK_bar,0x007c,Backslash,AC12,backslash,0x58,0x2a -KEY_Z,44,ANSI_Z,0x6,0x2c,0x1a,0x1a,29,VK_Z,0x5a,44,44,XK_Z,0x005a,KeyZ,AB01,z,0x64,0x6 -KEY_Z,44,ANSI_Z,0x6,0x2c,0x1a,0x1a,29,VK_Z,0x5a,44,44,XK_z,0x007a,KeyZ,AB01,z,0x64,0x6 -KEY_X,45,ANSI_X,0x7,0x2d,0x22,0x22,27,VK_X,0x58,45,45,XK_X,0x0058,KeyX,AB02,x,0x65,0x7 -KEY_X,45,ANSI_X,0x7,0x2d,0x22,0x22,27,VK_X,0x58,45,45,XK_x,0x0078,KeyX,AB02,x,0x65,0x7 -KEY_C,46,ANSI_C,0x8,0x2e,0x21,0x21,6,VK_C,0x43,46,46,XK_C,0x0043,KeyC,AB03,c,0x66,0x8 -KEY_C,46,ANSI_C,0x8,0x2e,0x21,0x21,6,VK_C,0x43,46,46,XK_c,0x0063,KeyC,AB03,c,0x66,0x8 -KEY_V,47,ANSI_V,0x9,0x2f,0x2a,0x2a,25,VK_V,0x56,47,47,XK_V,0x0056,KeyV,AB04,v,0x67,0x9 -KEY_V,47,ANSI_V,0x9,0x2f,0x2a,0x2a,25,VK_V,0x56,47,47,XK_v,0x0076,KeyV,AB04,v,0x67,0x9 -KEY_B,48,ANSI_B,0xb,0x30,0x32,0x32,5,VK_B,0x42,48,48,XK_B,0x0042,KeyB,AB05,b,0x68,0xb -KEY_B,48,ANSI_B,0xb,0x30,0x32,0x32,5,VK_B,0x42,48,48,XK_b,0x0062,KeyB,AB05,b,0x68,0xb -KEY_N,49,ANSI_N,0x2d,0x31,0x31,0x31,17,VK_N,0x4e,49,49,XK_N,0x004e,KeyN,AB06,n,0x69,0x2d -KEY_N,49,ANSI_N,0x2d,0x31,0x31,0x31,17,VK_N,0x4e,49,49,XK_n,0x006e,KeyN,AB06,n,0x69,0x2d -KEY_M,50,ANSI_M,0x2e,0x32,0x3a,0x3a,16,VK_M,0x4d,50,50,XK_M,0x004d,KeyM,AB07,m,0x6a,0x2e -KEY_M,50,ANSI_M,0x2e,0x32,0x3a,0x3a,16,VK_M,0x4d,50,50,XK_m,0x006d,KeyM,AB07,m,0x6a,0x2e -KEY_COMMA,51,ANSI_Comma,0x2b,0x33,0x41,0x41,54,VK_OEM_COMMA,0xbc,51,51,XK_comma,0x002c,Comma,AB08,comma,0x6b,0x2b -KEY_COMMA,51,ANSI_Comma,0x2b,0x33,0x41,0x41,54,VK_OEM_COMMA,0xbc,51,51,XK_less,0x003c,Comma,AB08,comma,0x6b,0x2b -KEY_DOT,52,ANSI_Period,0x2f,0x34,0x49,0x49,55,VK_OEM_PERIOD,0xbe,52,52,XK_period,0x002e,Period,AB09,dot,0x6c,0x2f -KEY_DOT,52,ANSI_Period,0x2f,0x34,0x49,0x49,55,VK_OEM_PERIOD,0xbe,52,52,XK_greater,0x003e,Period,AB09,dot,0x6c,0x2f -KEY_SLASH,53,ANSI_Slash,0x2c,0x35,0x4a,0x4a,56,VK_OEM_2,0xbf,53,53,XK_slash,0x002f,Slash,AB10,slash,0x6d,0x2c -KEY_SLASH,53,ANSI_Slash,0x2c,0x35,0x4a,0x4a,56,VK_OEM_2,0xbf,53,53,XK_question,0x003f,Slash,AB10,slash,0x6d,0x2c -KEY_RIGHTSHIFT,54,RightShift,0x3c,0x36,0x59,0x59,229,VK_RSHIFT,0xa1,54,54,XK_Shift_R,0xffe2,ShiftRight,RTSH,shift_r,0x6e,0x7b -KEY_KPASTERISK,55,ANSI_KeypadMultiply,0x43,0x37,0x7c,0x7e,85,VK_MULTIPLY,0x6a,55,55,XK_multiply,0x00d7,NumpadMultiply,KPMU,asterisk,0x2f,0x43 -KEY_KPASTERISK,55,ANSI_KeypadMultiply,0x43,0x37,0x7c,0x7e,85,VK_MULTIPLY,0x6a,55,55,XK_multiply,0x00d7,NumpadMultiply,KPMU,kp_multiply,0x2f,0x43 -KEY_LEFTALT,56,Option,0x3a,0x38,0x11,0x19,226,VK_LMENU,0xa4,56,56,XK_Alt_L,0xffe9,AltLeft,LALT,alt,0x13,0x3a -KEY_LEFTALT,56,Option,0x3a,0x38,0x11,0x19,226,VK_MENU,0x12,56,56,XK_Alt_L,0xffe9,AltLeft,LALT,alt,0x13,0x3a -KEY_SPACE,57,Space,0x31,0x39,0x29,0x29,44,VK_SPACE,0x20,57,57,XK_space,0x0020,Space,SPCE,spc,0x79,0x31 -KEY_CAPSLOCK,58,CapsLock,0x39,0x3a,0x58,0x14,57,VK_CAPITAL,0x14,58,58,XK_Caps_Lock,0xffe5,CapsLock,CAPS,caps_lock,0x77,0x39 -KEY_F1,59,F1,0x7a,0x3b,0x05,0x07,58,VK_F1,0x70,59,59,XK_F1,0xffbe,F1,FK01,f1,0x05,0x7a -KEY_F2,60,F2,0x78,0x3c,0x06,0x0f,59,VK_F2,0x71,60,60,XK_F2,0xffbf,F2,FK02,f2,0x06,0x78 -KEY_F3,61,F3,0x63,0x3d,0x04,0x17,60,VK_F3,0x72,61,61,XK_F3,0xffc0,F3,FK03,f3,0x08,0x63 -KEY_F4,62,F4,0x76,0x3e,0x0c,0x1f,61,VK_F4,0x73,62,62,XK_F4,0xffc1,F4,FK04,f4,0x0a,0x76 -KEY_F5,63,F5,0x60,0x3f,0x03,0x27,62,VK_F5,0x74,63,63,XK_F5,0xffc2,F5,FK05,f5,0x0c,0x60 -KEY_F6,64,F6,0x61,0x40,0x0b,0x2f,63,VK_F6,0x75,64,64,XK_F6,0xffc3,F6,FK06,f6,0x0e,0x61 -KEY_F7,65,F7,0x62,0x41,0x83,0x37,64,VK_F7,0x76,65,65,XK_F7,0xffc4,F7,FK07,f7,0x10,0x62 -KEY_F8,66,F8,0x64,0x42,0x0a,0x3f,65,VK_F8,0x77,66,66,XK_F8,0xffc5,F8,FK08,f8,0x11,0x64 -KEY_F9,67,F9,0x65,0x43,0x01,0x47,66,VK_F9,0x78,67,67,XK_F9,0xffc6,F9,FK09,f9,0x12,0x65 -KEY_F10,68,F10,0x6d,0x44,0x09,0x4f,67,VK_F10,0x79,68,68,XK_F10,0xffc7,F10,FK10,f10,0x07,0x6d -KEY_NUMLOCK,69,ANSI_KeypadClear,0x47,0x45,0x77,0x76,83,VK_NUMLOCK,0x90,69,69,XK_Num_Lock,0xff7f,NumLock,NMLK,num_lock,0x62,0x47 -KEY_SCROLLLOCK,70,,,0x46,0x7e,0x5f,71,VK_SCROLL,0x91,70,70,XK_Scroll_Lock,0xff14,ScrollLock,SCLK,scroll_lock,0x17,0x6b -KEY_KP7,71,ANSI_Keypad7,0x59,0x47,0x6c,0x6c,95,VK_NUMPAD7,0x67,71,71,XK_KP_7,0xffb7,Numpad7,KP7,kp_7,0x44,0x59 -KEY_KP8,72,ANSI_Keypad8,0x5b,0x48,0x75,0x75,96,VK_NUMPAD8,0x68,72,72,XK_KP_8,0xffb8,Numpad8,KP8,kp_8,0x45,0x5b -KEY_KP9,73,ANSI_Keypad9,0x5c,0x49,0x7d,0x7d,97,VK_NUMPAD9,0x69,73,73,XK_KP_9,0xffb9,Numpad9,KP9,kp_9,0x46,0x5c -KEY_KPMINUS,74,ANSI_KeypadMinus,0x4e,0x4a,0x7b,0x4e,86,VK_SUBTRACT,0x6d,74,74,XK_KP_Subtract,0xffad,NumpadSubtract,KPSU,kp_subtract,0x47,0x4e -KEY_KP4,75,ANSI_Keypad4,0x56,0x4b,0x6b,0x6b,92,VK_NUMPAD4,0x64,75,75,XK_KP_4,0xffb4,Numpad4,KP4,kp_4,0x5b,0x56 -KEY_KP5,76,ANSI_Keypad5,0x57,0x4c,0x73,0x73,93,VK_NUMPAD5,0x65,76,76,XK_KP_5,0xffb5,Numpad5,KP5,kp_5,0x5c,0x57 -KEY_KP6,77,ANSI_Keypad6,0x58,0x4d,0x74,0x74,94,VK_NUMPAD6,0x66,77,77,XK_KP_6,0xffb6,Numpad6,KP6,kp_6,0x5d,0x58 -KEY_KPPLUS,78,ANSI_KeypadPlus,0x45,0x4e,0x79,0x7c,87,VK_ADD,0x6b,78,78,XK_KP_Add,0xffab,NumpadAdd,KPAD,kp_add,0x7d,0x45 -KEY_KP1,79,ANSI_Keypad1,0x53,0x4f,0x69,0x69,89,VK_NUMPAD1,0x61,79,79,XK_KP_1,0xffb1,Numpad1,KP1,kp_1,0x70,0x53 -KEY_KP2,80,ANSI_Keypad2,0x54,0x50,0x72,0x72,90,VK_NUMPAD2,0x62,80,80,XK_KP_2,0xffb2,Numpad2,KP2,kp_2,0x71,0x54 -KEY_KP3,81,ANSI_Keypad3,0x55,0x51,0x7a,0x7a,91,VK_NUMPAD3,0x63,81,81,XK_KP_3,0xffb3,Numpad3,KP3,kp_3,0x72,0x55 -KEY_KP0,82,ANSI_Keypad0,0x52,0x52,0x70,0x70,98,VK_NUMPAD0,0x60,82,82,XK_KP_0,0xffb0,Numpad0,KP0,kp_0,0x5e,0x52 -KEY_KPDOT,83,ANSI_KeypadDecimal,0x41,0x53,0x71,0x71,99,VK_DECIMAL,0x6e,83,83,XK_KP_Decimal,0xffae,NumpadDecimal,KPDL,kp_decimal,0x32,0x41 -KEY_KPDOT,83,ANSI_KeypadDecimal,0x41,0x53,0x71,0x71,99,VK_DECIMAL,0x6e,83,83,XK_KP_Decimal,0xffae,NumpadDecimal,KPDC,kp_decimal,0x32,0x41 -,84,,,0x54,,,,,,,,,,,,,, -KEY_ZENKAKUHANKAKU,85,,,0x76,0x5f,,148,,,,,,,Lang5,HZTG,,, -KEY_102ND,86,,,0x56,0x61,0x13,100,VK_OEM_102,0xe2,86,86,,,IntlBackslash,LSGT,less,0x7c, -KEY_F11,87,F11,0x67,0x57,0x78,0x56,68,VK_F11,0x7a,87,87,XK_F11,0xffc8,F11,FK11,f11,0x09,0x67 -KEY_F12,88,F12,0x6f,0x58,0x07,0x5e,69,VK_F12,0x7b,88,88,XK_F12,0xffc9,F12,FK12,f12,0x0b,0x6f -KEY_RO,89,JIS_Underscore,0x5e,0x73,0x51,,135,,,,,,,IntlRo,AB11,ro,, -KEY_KATAKANA,90,,,0x78,0x63,,146,VK_KANA,0x15,,,,,Katakana,KATA,,, -KEY_KATAKANA,90,,,0x78,0x63,,146,VK_KANA,0x15,,,,,Lang3,KATA,,, -KEY_HIRAGANA,91,,,0x77,0x62,0x87,147,,,,,,,Hiragana,HIRA,hiragana,, -KEY_HIRAGANA,91,,,0x77,0x62,0x87,147,,,,,,,Lang4,HIRA,hiragana,, -KEY_HENKAN,92,,,0x79,0x64,0x86,138,,,,,,,Convert,HENK,henkan,, -KEY_KATAKANAHIRAGANA,93,,,0x70,0x13,0x87,136,,,0xc8,0xc8,,,KanaMode,HKTG,katakanahiragana,, -KEY_MUHENKAN,94,,,0x7b,0x67,0x85,139,,,,,,,NonConvert,NFER,muhenkan,, -KEY_MUHENKAN,94,,,0x7b,0x67,0x85,139,,,,,,,NonConvert,MUHE,muhenkan,, -KEY_KPJPCOMMA,95,JIS_KeypadComma,0x5f,0x5c,0x27,,140,,,,,XK_KP_Separator,0xffac,,KPSP,,, -KEY_KPJPCOMMA,95,JIS_KeypadComma,0x5f,0x5c,0x27,,140,,,,,XK_KP_Separator,0xffac,,JPCM,,, -KEY_KPENTER,96,ANSI_KeypadEnter,0x4c,0xe01c,0xe05a,0x79,88,,,0x64,0x64,XK_KP_Enter,0xff8d,NumpadEnter,KPEN,kp_enter,0x5a,0x4c -KEY_RIGHTCTRL,97,RightControl,0x3e,0xe01d,0xe014,0x58,228,VK_RCONTROL,0xa3,0x65,0x65,XK_Control_R,0xffe4,ControlRight,RCTL,ctrl_r,0x4c,0x7d -KEY_KPSLASH,98,ANSI_KeypadDivide,0x4b,0xe035,0xe04a,0x4a,84,VK_DIVIDE,0x6f,0x68,0x68,XK_KP_Divide,0xffaf,NumpadDivide,KPDV,kp_divide,0x2e,0x4b -KEY_SYSRQ,99,,,0x54,0x7f,0x57,70,VK_SNAPSHOT,0x2c,0x67,0x67,XK_Sys_Req,0xff15,PrintScreen,PRSC,print,0x16,0x69 -KEY_SYSRQ,99,,,0x54,0x7f,0x57,70,VK_SNAPSHOT,0x2c,0x67,0x67,XK_Sys_Req,0xff15,PrintScreen,SYRQ,sysrq,0x16,0x69 -KEY_RIGHTALT,100,RightOption,0x3d,0xe038,0xe011,0x39,230,VK_RMENU,0xa5,0x69,0x69,XK_Alt_R,0xffea,AltRight,ALGR,alt_r,0x0d,0x7c -KEY_RIGHTALT,100,RightOption,0x3d,0xe038,0xe011,0x39,230,VK_RMENU,0xa5,0x69,0x69,XK_Alt_R,0xffea,AltRight,RALT,alt_r,0x0d,0x7c -KEY_LINEFEED,101,,,0x5b,,,,,,,,,,,LNFD,lf,0x6f, -KEY_HOME,102,Home,0x73,0xe047,0xe06c,0x6e,74,VK_HOME,0x24,0x59,0x59,XK_Home,0xff50,Home,HOME,home,0x34,0x73 -KEY_UP,103,UpArrow,0x7e,0xe048,0xe075,0x63,82,VK_UP,0x26,0x5a,0x5a,XK_Up,0xff52,ArrowUp,UP,up,0x14,0x3e -KEY_PAGEUP,104,PageUp,0x74,0xe049,0xe07d,0x6f,75,VK_PRIOR,0x21,0x5b,0x5b,XK_Page_Up,0xff55,PageUp,PGUP,pgup,0x60,0x74 -KEY_LEFT,105,LeftArrow,0x7b,0xe04b,0xe06b,0x61,80,VK_LEFT,0x25,0x5c,0x5c,XK_Left,0xff51,ArrowLeft,LEFT,left,0x18,0x3b -KEY_RIGHT,106,RightArrow,0x7c,0xe04d,0xe074,0x6a,79,VK_RIGHT,0x27,0x5e,0x5e,XK_Right,0xff53,ArrowRight,RGHT,right,0x1c,0x3c -KEY_END,107,End,0x77,0xe04f,0xe069,0x65,77,VK_END,0x23,0x5f,0x5f,XK_End,0xff57,End,END,end,0x4a,0x77 -KEY_DOWN,108,DownArrow,0x7d,0xe050,0xe072,0x60,81,VK_DOWN,0x28,0x60,0x60,XK_Down,0xff54,ArrowDown,DOWN,down,0x1b,0x3d -KEY_PAGEDOWN,109,PageDown,0x79,0xe051,0xe07a,0x6d,78,VK_NEXT,0x22,0x61,0x61,XK_Page_Down,0xff56,PageDown,PGDN,pgdn,0x7b,0x79 -KEY_INSERT,110,,,0xe052,0xe070,0x67,73,VK_INSERT,0x2d,0x62,0x62,XK_Insert,0xff63,Insert,INS,insert,0x2c,0x72 -KEY_DELETE,111,ForwardDelete,0x75,0xe053,0xe071,0x64,76,VK_DELETE,0x2e,0x63,0x63,XK_Delete,0xffff,Delete,DEL,delete,0x42,0x75 -KEY_DELETE,111,ForwardDelete,0x75,0xe053,0xe071,0x64,76,VK_DELETE,0x2e,0x63,0x63,XK_Delete,0xffff,Delete,DELE,,0x42,0x75 -KEY_MACRO,112,,,0xe06f,0xe06f,0x8e,,,,,,,,,I120,,, -KEY_MUTE,113,Mute,0x4a,0xe020,0xe023,0x9c,127,VK_VOLUME_MUTE,0xad,,,,,AudioVolumeMute,MUTE,audiomute,, -KEY_MUTE,113,Mute,0x4a,0xe020,0xe023,0x9c,239,VK_VOLUME_MUTE,0xad,,,,,AudioVolumeMute,MUTE,audiomute,, -KEY_VOLUMEDOWN,114,VolumeDown,0x49,0xe02e,0xe021,0x9d,129,VK_VOLUME_DOWN,0xae,,,,,AudioVolumeDown,VOL-,volumedown,, -KEY_VOLUMEDOWN,114,VolumeDown,0x49,0xe02e,0xe021,0x9d,238,VK_VOLUME_DOWN,0xae,,,,,AudioVolumeDown,VOL-,volumedown,, -KEY_VOLUMEUP,115,VolumeUp,0x48,0xe030,0xe032,0x95,128,VK_VOLUME_UP,0xaf,,,,,AudioVolumeUp,VOL+,volumeup,, -KEY_VOLUMEUP,115,VolumeUp,0x48,0xe030,0xe032,0x95,237,VK_VOLUME_UP,0xaf,,,,,AudioVolumeUp,VOL+,volumeup,, -KEY_POWER,116,,,0xe05e,0xe037,,102,,,,,,,Power,POWR,power,,0x7f7f -KEY_KPEQUAL,117,ANSI_KeypadEquals,0x51,0x59,0x0f,,103,,,0x76,0x76,XK_KP_Equal,0xffbd,NumpadEqual,KPEQ,kp_equals,0x2d,0x51 -KEY_KPPLUSMINUS,118,,,0xe04e,0xe079,,,,,,,,,,I126,,, -KEY_PAUSE,119,,,0xe046,0xe077,0x62,72,VK_PAUSE,0x013,0x66,0x66,XK_Pause,0xff13,Pause,PAUS,pause,0x15,0x71 -KEY_SCALE,120,,,0xe00b,,,,,,,,,,,I128,,, -KEY_KPCOMMA,121,,,0x7e,0x6d,,133,VK_SEPARATOR??,0x6c,,,,,NumpadComma,KPCO,kp_comma,, -KEY_KPCOMMA,121,,,0x7e,0x6d,,133,VK_SEPARATOR??,0x6c,,,,,NumpadComma,I129,,, -KEY_HANGEUL,122,JIS_Kana,0x68,0x72,,,144,VK_HANGEUL,0x15,,0x71,,,Lang1,HNGL,,, -KEY_HANJA,123,JIS_Eisu,0x66,0x71,,,145,VK_HANJA,0x19,,0x72,,,Lang2,HJCV,,, -KEY_YEN,124,JIS_Yen,0x5d,0x7d,0x6a,0x5d,137,,,0x7d,0x7d,,,IntlYen,AE13,yen,, -KEY_LEFTMETA,125,Command,0x37,0xe05b,0xe01f,0x8b,227,VK_LWIN,0x5b,0x6b,0x6b,XK_Meta_L,0xffe7,MetaLeft,LMTA,meta_l,0x78,0x37 -KEY_LEFTMETA,125,Command,0x37,0xe05b,0xe01f,0x8b,227,VK_LWIN,0x5b,0x6b,0x6b,XK_Meta_L,0xffe7,MetaLeft,LWIN,meta_l,0x78,0x37 -KEY_RIGHTMETA,126,RightCommand,0x36,0xe05c,0xe027,0x8c,231,VK_RWIN,0x5c,0x6c,0x6c,XK_Meta_R,0xffe8,MetaRight,RMTA,meta_r,0x7a,0x37 -KEY_RIGHTMETA,126,RightCommand,0x36,0xe05c,0xe027,0x8c,231,VK_RWIN,0x5c,0x6c,0x6c,XK_Meta_R,0xffe8,MetaRight,RWIN,meta_r,0x7a,0x37 -KEY_COMPOSE,127,,0x6e,0xe05d,0xe02f,0x8d,101,VK_APPS,0x5d,0x6d,0x6d,,,ContextMenu,MENU,compose,0x43, -KEY_COMPOSE,127,,0x6e,0xe05d,0xe02f,0x8d,101,VK_APPS,0x5d,0x6d,0x6d,,,ContextMenu,COMP,compose,0x43, -KEY_STOP,128,,,0xe068,0xe028,0x0a,120,VK_BROWSER_STOP,0xa9,,,,,BrowserStop,STOP,stop,0x01, -KEY_STOP,128,,,0xe068,0xe028,0x0a,243,VK_BROWSER_STOP,0xa9,,,,,BrowserStop,STOP,stop,0x01, -KEY_AGAIN,129,,,0xe005,,0x0b,121,,,,,,,Again,AGAI,again,0x03, -KEY_PROPS,130,,,0xe006,,0x0c,,,,,,,,Props,PROP,props,0x19, -KEY_UNDO,131,,,0xe007,,0x10,122,,,,,,,Undo,UNDO,undo,0x1a, -KEY_FRONT,132,,,0xe00c,,,119,,,,,,,,FRNT,front,0x31, -KEY_COPY,133,,,0xe078,,0x18,124,,,,,,,Copy,COPY,copy,0x33, -KEY_OPEN,134,,,0x64,,0x20,116,,,,,,,Open,OPEN,open,0x48, -KEY_PASTE,135,,,0x65,,0x28,125,,,,,,,Paste,PAST,paste,0x49, -KEY_FIND,136,,,0xe041,,0x30,126,,,,,,,Find,FIND,find,0x5f, -KEY_FIND,136,,,0xe041,,0x30,244,,,,,,,Find,FIND,find,0x5f, -KEY_CUT,137,,,0xe03c,,0x38,123,,,,,,,Cut,CUT,cut,0x61, -KEY_HELP,138,Help,0x72,0xe075,,0x09,117,VK_HELP,0x2f,,,XK_Help,0xff6a,Help,HELP,help,0x76, -KEY_MENU,139,,,0xe01e,,0x91,118,,,,,,,,I147,menu,, -KEY_CALC,140,,,0xe021,0xe02b,0xa3,251,,,,,,,LaunchApp2,I148,calculator,, -KEY_SETUP,141,,,0x66,,,,,,,,,,,I149,,, -KEY_SLEEP,142,,,0xe05f,0xe03f,,248,VK_SLEEP,0x5f,,,,,Sleep,I150,sleep,, -KEY_WAKEUP,143,,,0xe063,0xe05e,,,,,,,,,WakeUp,I151,wake,, -KEY_FILE,144,,,0x67,,,,,,,,,,,I152,,, -KEY_SENDFILE,145,,,0x68,,,,,,,,,,,I153,,, -KEY_DELETEFILE,146,,,0x69,,,,,,,,,,,I154,,, -KEY_XFER,147,,,0xe013,,0xa2,,,,,,,,,XFER,,, -KEY_XFER,147,,,0xe013,,0xa2,,,,,,,,,I155,,, -KEY_PROG1,148,,,0xe01f,,0xa0,,,,,,,,,I156,,, -KEY_PROG2,149,,,0xe017,,0xa1,,,,,,,,,I157,,, -KEY_WWW,150,,,0xe002,,,240,,,,,,,,I158,,, -KEY_MSDOS,151,,,0x6a,,,,,,,,,,,I159,,, -KEY_SCREENLOCK,152,,,0xe012,,0x96,249,,,,,,,,I160,,, -KEY_DIRECTION,153,,,0x6b,,,,,,,,,,,I161,,, -KEY_CYCLEWINDOWS,154,,,0xe026,,0x9b,,,,,,,,,I162,,, -KEY_MAIL,155,,,0xe06c,0xe048,,,,,,,,,LaunchMail,I163,mail,, -KEY_BOOKMARKS,156,,,0xe066,0xe018,,,,,,,,,BrowserFavorites,I164,ac_bookmarks,, -KEY_COMPUTER,157,,,0xe06b,0xe040,,,,,,,,,LaunchApp1,I165,computer,, -KEY_BACK,158,,,0xe06a,0xe038,,241,VK_BROWSER_BACK,0xa6,,,,,BrowserBack,I166,ac_back,, -KEY_FORWARD,159,,,0xe069,0xe030,,242,VK_BROWSER_FORWARD,0xa7,,,,,BrowserForward,I167,ac_forward,, -KEY_CLOSECD,160,,,0xe023,,0x9a,,,,,,,,,I168,,, -KEY_EJECTCD,161,,,0x6c,,,236,,,,,,,,I169,,, -KEY_EJECTCLOSECD,162,,,0xe07d,,,,,,,,,,Eject,I170,,, -KEY_NEXTSONG,163,,,0xe019,0xe04d,0x93,235,VK_MEDIA_NEXT_TRACK,0xb0,,,,,MediaTrackNext,I171,audionext,, -KEY_PLAYPAUSE,164,,,0xe022,0xe034,,232,VK_MEDIA_PLAY_PAUSE,0xb3,,,,,MediaPlayPause,I172,audioplay,, -KEY_PREVIOUSSONG,165,,,0xe010,0xe015,0x94,234,VK_MEDIA_PREV_TRACK,0xb1,,,,,MediaTrackPrevious,I173,audioprev,, -KEY_STOPCD,166,,,0xe024,0xe03b,0x98,233,VK_MEDIA_STOP,0xb2,,,,,MediaStop,I174,audiostop,, -KEY_RECORD,167,,,0xe031,,0x9e,,,,,,,,,I175,,, -KEY_REWIND,168,,,0xe018,,0x9f,,,,,,,,,I176,,, -KEY_PHONE,169,,,0x63,,,,,,,,,,,I177,,, -KEY_ISO,170,ISO_Section,0xa,,,,,,,,,,,,I178,,, -KEY_CONFIG,171,,,0xe001,,,,,,,,,,,I179,,, -KEY_HOMEPAGE,172,,,0xe032,0xe03a,0x97,,VK_BROWSER_HOME,0xac,,,,,BrowserHome,I180,ac_home,, -KEY_REFRESH,173,,,0xe067,0xe020,,250,VK_BROWSER_REFRESH,0xa8,,,,,BrowserRefresh,I181,ac_refresh,, -KEY_EXIT,174,,,,,,,,,,,,,,I182,,, -KEY_MOVE,175,,,,,,,,,,,,,,I183,,, -KEY_EDIT,176,,,0xe008,,,247,,,,,,,,I184,,, -KEY_SCROLLUP,177,,,0x75,,,245,,,,,,,,I185,,, -KEY_SCROLLDOWN,178,,,0xe00f,,,246,,,,,,,,I186,,, -KEY_KPLEFTPAREN,179,,,0xe076,,,182,,,,,,,NumpadParenLeft,I187,,, -KEY_KPRIGHTPAREN,180,,,0xe07b,,,183,,,,,,,NumpadParenRight,I188,,, -KEY_NEW,181,,,0xe009,,,,,,,,,,,I189,,, -KEY_REDO,182,,,0xe00a,,,,,,,,,,,I190,,, -KEY_F13,183,F13,0x69,0x5d,0x2f,0x7f,104,VK_F13,0x7c,0x6e,0x6e,,,F13,FK13,,,0x69 -KEY_F14,184,F14,0x6b,0x5e,0x37,0x80,105,VK_F14,0x7d,0x6f,0x6f,,,F14,FK14,,,0x6b -KEY_F15,185,F15,0x71,0x5f,0x3f,0x81,106,VK_F15,0x7e,0x70,0x70,,,F15,FK15,,,0x71 -KEY_F16,186,F16,0x6a,0x55,,0x82,107,VK_F16,0x7f,0x71,0x71,,,F16,FK16,,, -KEY_F17,187,F17,0x40,0xe003,,0x83,108,VK_F17,0x80,0x72,0x72,,,F17,FK17,,, -KEY_F18,188,F18,0x4f,0xe077,,,109,VK_F18,0x81,,,,,F18,FK18,,, -KEY_F19,189,F19,0x50,0xe004,,,110,VK_F19,0x82,,,,,F19,FK19,,, -KEY_F20,190,F20,0x5a,0x5a,,,111,VK_F20,0x83,,,,,F20,FK20,,, -KEY_F21,191,,,0x74,,,112,VK_F21,0x84,,,,,F21,FK21,,, -KEY_F22,192,,,0xe079,,,113,VK_F22,0x85,,,,,F22,FK22,,, -KEY_F23,193,,,0x6d,,,114,VK_F23,0x86,,,,,F23,FK23,,, -KEY_F24,194,,,0x6f,,,115,VK_F24,0x87,,,,,F24,FK24,,, -,195,,,0xe015,,,,,,,,,,,,,, -,196,,,0xe016,,,,,,,,,,,,,, -,197,,,0xe01a,,,,,,,,,,,,,, -,198,,,0xe01b,,,,,,,,,,,,,, -,199,,,0xe027,,,,,,,,,,,,,, -KEY_PLAYCD,200,,,0xe028,,,,,,,,,,,I208,,, -KEY_PAUSECD,201,,,0xe029,,,,,,,,,,,I209,,, -KEY_PROG3,202,,,0xe02b,,,,,,,,,,,I210,,, -KEY_PROG4,203,,,0xe02c,,,,,,,,,,,I211,,, -KEY_DASHBOARD,204,,,0xe02d,,,,,,,,,,,I212,,, -KEY_SUSPEND,205,,,0xe025,,,,,,,,,,Suspend,I213,,, -KEY_CLOSE,206,,,0xe02f,,,,,,,,,,,I214,,, -KEY_PLAY,207,,,0xe033,,,,VK_PLAY,0xfa,,,,,,I215,,, -KEY_FASTFORWARD,208,,,0xe034,,,,,,,,,,,I216,,, -KEY_BASSBOOST,209,,,0xe036,,,,,,,,,,,I217,,, -KEY_PRINT,210,,,0xe039,,,,VK_PRINT,0x2a,,,,,,I218,,, -KEY_HP,211,,,0xe03a,,,,,,,,,,,I219,,, -KEY_CAMERA,212,,,0xe03b,,,,,,,,,,,I220,,, -KEY_SOUND,213,,,0xe03d,,,,,,,,,,,I221,,, -KEY_QUESTION,214,,,0xe03e,,,,,,,,,,,I222,,, -KEY_EMAIL,215,,,0xe03f,,,,VK_LAUNCH_MAIL,0xb4,,,,,,I223,,, -KEY_CHAT,216,,,0xe040,,,,,,,,,,,I224,,, -KEY_SEARCH,217,,,0xe065,0xe010,,,VK_BROWSER_SEARCH,0xaa,,,,,BrowserSearch,I225,,, -KEY_CONNECT,218,,,0xe042,,,,,,,,,,,I226,,, -KEY_FINANCE,219,,,0xe043,,,,,,,,,,,I227,,, -KEY_SPORT,220,,,0xe044,,,,,,,,,,,I228,,, -KEY_SHOP,221,,,0xe045,,,,,,,,,,,I229,,, -KEY_ALTERASE,222,,,0xe014,,,,,,,,,,,I230,,, -KEY_CANCEL,223,,,0xe04a,,,,,,,,,,,I231,,, -KEY_BRIGHTNESSDOWN,224,,,0xe04c,,,,,,,,,,,I232,,, -KEY_BRIGHTNESSUP,225,,,0xe054,,,,,,,,,,,I233,,, -KEY_MEDIA,226,,,0xe06d,0xe050,,,,,,,,,MediaSelect,I234,mediaselect,, -KEY_SWITCHVIDEOMODE,227,,,0xe056,,,,,,,,,,,I235,,, -KEY_KBDILLUMTOGGLE,228,,,0xe057,,,,,,,,,,,I236,,, -KEY_KBDILLUMDOWN,229,,,0xe058,,,,,,,,,,,I237,,, -KEY_KBDILLUMUP,230,,,0xe059,,,,,,,,,,,I238,,, -KEY_SEND,231,,,0xe05a,,,,,,,,,,,I239,,, -KEY_REPLY,232,,,0xe064,,,,,,,,,,,I240,,, -KEY_FORWARDMAIL,233,,,0xe00e,,,,,,,,,,,I241,,, -KEY_SAVE,234,,,0xe055,,,,,,,,,,,I242,,, -KEY_DOCUMENTS,235,,,0xe070,,,,,,,,,,,I243,,, -KEY_BATTERY,236,,,0xe071,,,,,,,,,,,I244,,, -KEY_BLUETOOTH,237,,,0xe072,,,,,,,,,,,I245,,, -KEY_WLAN,238,,,0xe073,,,,,,,,,,,I246,,, -KEY_UWB,239,,,0xe074,,,,,,,,,,,I247,,, -KEY_UNKNOWN,240,,,,,,,,,,,,,,I248,,, -KEY_VIDEO_NEXT,241,,,,,,,,,,,,,,I249,,, -KEY_VIDEO_PREV,242,,,,,,,,,,,,,,I250,,, -KEY_BRIGHTNESS_CYCLE,243,,,,,,,,,,,,,,I251,,, -KEY_BRIGHTNESS_ZERO,244,,,,,,,,,,,,,,I252,,, -KEY_DISPLAY_OFF,245,,,,,,,,,,,,,,I253,,, -KEY_WIMAX,246,,,,,,,,,,,,,,,,, -,247,,,,,,,,,,,,,,,,, -,248,,,,,,,,,,,,,,,,, -,249,,,,,,,,,,,,,,,,, -,250,,,,,,,,,,,,,,,,, -,251,,,,,,,,,,,,,,,,, -,252,,,,,,,,,,,,,,,,, -,253,,,,,,,,,,,,,,,,, -,254,,,,,,,,,,,,,,,,, -,255,,,,0xe012,,,,,,,,,,,,, -BTN_MISC,0x100,,,,,,,,,,,,,,,,, -BTN_0,0x100,,,,,,,VK_LBUTTON,0x01,,,,,,,,, -BTN_1,0x101,,,,,,,VK_RBUTTON,0x02,,,,,,,,, -BTN_2,0x102,,,,,,,VK_MBUTTON,0x04,,,,,,,,, -BTN_3,0x103,,,,,,,VK_XBUTTON1,0x05,,,,,,,,, -BTN_4,0x104,,,,,,,VK_XBUTTON2,0x06,,,,,,,,, -BTN_5,0x105,,,,,,,,,,,,,,,,, -BTN_6,0x106,,,,,,,,,,,,,,,,, -BTN_7,0x107,,,,,,,,,,,,,,,,, -BTN_8,0x108,,,,,,,,,,,,,,,,, -BTN_9,0x109,,,,,,,,,,,,,,,,, -BTN_MOUSE,0x110,,,,,,,,,,,,,,,,, -BTN_LEFT,0x110,,,,,,,,,,,,,,,,, -BTN_RIGHT,0x111,,,,,,,,,,,,,,,,, -BTN_MIDDLE,0x112,,,,,,,,,,,,,,,,, -BTN_SIDE,0x113,,,,,,,,,,,,,,,,, -BTN_EXTRA,0x114,,,,,,,,,,,,,,,,, -BTN_FORWARD,0x115,,,,,,,,,,,,,,,,, -BTN_BACK,0x116,,,,,,,,,,,,,,,,, -BTN_TASK,0x117,,,,,,,,,,,,,,,,, -BTN_JOYSTICK,0x120,,,,,,,,,,,,,,,,, -BTN_TRIGGER,0x120,,,,,,,,,,,,,,,,, -BTN_THUMB,0x121,,,,,,,,,,,,,,,,, -BTN_THUMB2,0x122,,,,,,,,,,,,,,,,, -BTN_TOP,0x123,,,,,,,,,,,,,,,,, -BTN_TOP2,0x124,,,,,,,,,,,,,,,,, -BTN_PINKIE,0x125,,,,,,,,,,,,,,,,, -BTN_BASE,0x126,,,,,,,,,,,,,,,,, -BTN_BASE2,0x127,,,,,,,,,,,,,,,,, -BTN_BASE3,0x128,,,,,,,,,,,,,,,,, -BTN_BASE4,0x129,,,,,,,,,,,,,,,,, -BTN_BASE5,0x12a,,,,,,,,,,,,,,,,, -BTN_BASE6,0x12b,,,,,,,,,,,,,,,,, -BTN_DEAD,0x12f,,,,,,,,,,,,,,,,, -BTN_GAMEPAD,0x130,,,,,,,,,,,,,,,,, -BTN_A,0x130,,,,,,,,,,,,,,,,, -BTN_B,0x131,,,,,,,,,,,,,,,,, -BTN_C,0x132,,,,,,,,,,,,,,,,, -BTN_X,0x133,,,,,,,,,,,,,,,,, -BTN_Y,0x134,,,,,,,,,,,,,,,,, -BTN_Z,0x135,,,,,,,,,,,,,,,,, -BTN_TL,0x136,,,,,,,,,,,,,,,,, -BTN_TR,0x137,,,,,,,,,,,,,,,,, -BTN_TL2,0x138,,,,,,,,,,,,,,,,, -BTN_TR2,0x139,,,,,,,,,,,,,,,,, -BTN_SELECT,0x13a,,,,,,,,,,,,,,,,, -BTN_START,0x13b,,,,,,,,,,,,,,,,, -BTN_MODE,0x13c,,,,,,,,,,,,,,,,, -BTN_THUMBL,0x13d,,,,,,,,,,,,,,,,, -BTN_THUMBR,0x13e,,,,,,,,,,,,,,,,, -BTN_DIGI,0x140,,,,,,,,,,,,,,,,, -BTN_TOOL_PEN,0x140,,,,,,,,,,,,,,,,, -BTN_TOOL_RUBBER,0x141,,,,,,,,,,,,,,,,, -BTN_TOOL_BRUSH,0x142,,,,,,,,,,,,,,,,, -BTN_TOOL_PENCIL,0x143,,,,,,,,,,,,,,,,, -BTN_TOOL_AIRBRUSH,0x144,,,,,,,,,,,,,,,,, -BTN_TOOL_FINGER,0x145,,,,,,,,,,,,,,,,, -BTN_TOOL_MOUSE,0x146,,,,,,,,,,,,,,,,, -BTN_TOOL_LENS,0x147,,,,,,,,,,,,,,,,, -BTN_TOUCH,0x14a,,,,,,,,,,,,,,,,, -BTN_STYLUS,0x14b,,,,,,,,,,,,,,,,, -BTN_STYLUS2,0x14c,,,,,,,,,,,,,,,,, -BTN_TOOL_DOUBLETAP,0x14d,,,,,,,,,,,,,,,,, -BTN_TOOL_TRIPLETAP,0x14e,,,,,,,,,,,,,,,,, -BTN_TOOL_QUADTAP,0x14f,,,,,,,,,,,,,,,,, -BTN_WHEEL,0x150,,,,,,,,,,,,,,,,, -BTN_GEAR_DOWN,0x150,,,,,,,,,,,,,,,,, -BTN_GEAR_UP,0x151,,,,,,,,,,,,,,,,, -KEY_OK,0x160,,,,,,,,,,,,,,,,, -KEY_SELECT,0x161,,,,,,,VK_SELECT,0x29,,,XK_Select,0xff60,Select,SELE,,, -KEY_GOTO,0x162,,,,,,,,,,,,,,,,, -KEY_CLEAR,0x163,,,,,,,,,,,,,NumpadClear,CLR,,, -KEY_POWER2,0x164,,,,,,,,,,,,,,,,, -KEY_OPTION,0x165,,,,,,,,,,,,,,,,, -KEY_INFO,0x166,,,,,,,,,,,,,,,,, -KEY_TIME,0x167,,,,,,,,,,,,,,,,, -KEY_VENDOR,0x168,,,,,,,,,,,,,,,,, -KEY_ARCHIVE,0x169,,,,,,,,,,,,,,,,, -KEY_PROGRAM,0x16a,,,,,,,,,,,,,,,,, -KEY_CHANNEL,0x16b,,,,,,,,,,,,,,,,, -KEY_FAVORITES,0x16c,,,,,,,VK_BROWSER_FAVOURITES,0xab,,,,,,,,, -KEY_EPG,0x16d,,,,,,,,,,,,,,,,, -KEY_PVR,0x16e,,,,,,,,,,,,,,,,, -KEY_MHP,0x16f,,,,,,,,,,,,,,,,, -KEY_LANGUAGE,0x170,,,,,,,,,,,,,,,,, -KEY_TITLE,0x171,,,,,,,,,,,,,,,,, -KEY_SUBTITLE,0x172,,,,,,,,,,,,,,,,, -KEY_ANGLE,0x173,,,,,,,,,,,,,,,,, -KEY_ZOOM,0x174,,,,,,,VK_ZOOM,0xfb,,,,,,,,, -KEY_MODE,0x175,,,,,,,,,,,,,,,,, -KEY_KEYBOARD,0x176,,,,,,,,,,,,,,,,, -KEY_SCREEN,0x177,,,,,,,,,,,,,,,,, -KEY_PC,0x178,,,,,,,,,,,,,,,,, -KEY_TV,0x179,,,,,,,,,,,,,,,,, -KEY_TV2,0x17a,,,,,,,,,,,,,,,,, -KEY_VCR,0x17b,,,,,,,,,,,,,,,,, -KEY_VCR2,0x17c,,,,,,,,,,,,,,,,, -KEY_SAT,0x17d,,,,,,,,,,,,,,,,, -KEY_SAT2,0x17e,,,,,,,,,,,,,,,,, -KEY_CD,0x17f,,,,,,,,,,,,,,,,, -KEY_TAPE,0x180,,,,,,,,,,,,,,,,, -KEY_RADIO,0x181,,,,,,,,,,,,,,,,, -KEY_TUNER,0x182,,,,,,,,,,,,,,,,, -KEY_PLAYER,0x183,,,,,,,,,,,,,,,,, -KEY_TEXT,0x184,,,,,,,,,,,,,,,,, -KEY_DVD,0x185,,,,,,,,,,,,,,,,, -KEY_AUX,0x186,,,,,,,,,,,,,,,,, -KEY_MP3,0x187,,,,,,,,,,,,,,,,, -KEY_AUDIO,0x188,,,,,,,,,,,,,,,,, -KEY_VIDEO,0x189,,,,,,,,,,,,,,,,, -KEY_DIRECTORY,0x18a,,,,,,,,,,,,,,,,, -KEY_LIST,0x18b,,,,,,,,,,,,,,,,, -KEY_MEMO,0x18c,,,,,,,,,,,,,,,,, -KEY_CALENDAR,0x18d,,,,,,,,,,,,,,,,, -KEY_RED,0x18e,,,,,,,,,,,,,,,,, -KEY_GREEN,0x18f,,,,,,,,,,,,,,,,, -KEY_YELLOW,0x190,,,,,,,,,,,,,,,,, -KEY_BLUE,0x191,,,,,,,,,,,,,,,,, -KEY_CHANNELUP,0x192,,,,,,,,,,,,,,,,, -KEY_CHANNELDOWN,0x193,,,,,,,,,,,,,,,,, -KEY_FIRST,0x194,,,,,,,,,,,,,,,,, -KEY_LAST,0x195,,,,,,,,,,,,,,,,, -KEY_AB,0x196,,,,,,,,,,,,,,,,, -KEY_NEXT,0x197,,,,,,,,,,,,,,,,, -KEY_RESTART,0x198,,,,,,,,,,,,,,,,, -KEY_SLOW,0x199,,,,,,,,,,,,,,,,, -KEY_SHUFFLE,0x19a,,,,,,,,,,,,,,,,, -KEY_BREAK,0x19b,,,,,,,,,,,,,,BREA,,, -KEY_BREAK,0x19b,,,,,,,,,,,,,,BRK,,, -KEY_PREVIOUS,0x19c,,,,,,,,,,,,,,,,, -KEY_DIGITS,0x19d,,,,,,,,,,,,,,,,, -KEY_TEEN,0x19e,,,,,,,,,,,,,,,,, -KEY_TWEN,0x19f,,,,,,,,,,,,,,,,, -KEY_VIDEOPHONE,0x1a0,,,,,,,,,,,,,,,,, -KEY_GAMES,0x1a1,,,,,,,,,,,,,,,,, -KEY_ZOOMIN,0x1a2,,,,,,,,,,,,,,,,, -KEY_ZOOMOUT,0x1a3,,,,,,,,,,,,,,,,, -KEY_ZOOMRESET,0x1a4,,,,,,,,,,,,,,,,, -KEY_WORDPROCESSOR,0x1a5,,,,,,,,,,,,,,,,, -KEY_EDITOR,0x1a6,,,,,,,,,,,,,,,,, -KEY_SPREADSHEET,0x1a7,,,,,,,,,,,,,,,,, -KEY_GRAPHICSEDITOR,0x1a8,,,,,,,,,,,,,,,,, -KEY_PRESENTATION,0x1a9,,,,,,,,,,,,,,,,, -KEY_DATABASE,0x1aa,,,,,,,,,,,,,,,,, -KEY_NEWS,0x1ab,,,,,,,,,,,,,,,,, -KEY_VOICEMAIL,0x1ac,,,,,,,,,,,,,,,,, -KEY_ADDRESSBOOK,0x1ad,,,,,,,,,,,,,,,,, -KEY_MESSENGER,0x1ae,,,,,,,,,,,,,,,,, -KEY_DISPLAYTOGGLE,0x1af,,,,,,,,,,,,,,,,, -KEY_SPELLCHECK,0x1b0,,,,,,,,,,,,,,,,, -KEY_LOGOFF,0x1b1,,,,,,,,,,,,,,,,, -KEY_DOLLAR,0x1b2,,,,,,,,,,,,,,,,, -KEY_EURO,0x1b3,,,,,,,,,,,,,,,,, -KEY_FRAMEBACK,0x1b4,,,,,,,,,,,,,,,,, -KEY_FRAMEFORWARD,0x1b5,,,,,,,,,,,,,,,,, -KEY_CONTEXT_MENU,0x1b6,,,,,,,,,,,,,,,,, -KEY_MEDIA_REPEAT,0x1b7,,,,,,,,,,,,,,,,, -KEY_DEL_EOL,0x1c0,,,,,,,,,,,,,,,,, -KEY_DEL_EOS,0x1c1,,,,,,,,,,,,,,,,, -KEY_INS_LINE,0x1c2,,,,,,,,,,,,,,,,, -KEY_DEL_LINE,0x1c3,,,,,,,,,,,,,,,,, -KEY_FN,0x1d0,Function,0x3f,,,,,,,,,,,Fn,,,, -KEY_FN_ESC,0x1d1,,,,,,,,,,,,,,,,, -KEY_FN_F1,0x1d2,,,,,,,,,,,,,,,,, -KEY_FN_F2,0x1d3,,,,,,,,,,,,,,,,, -KEY_FN_F3,0x1d4,,,,,,,,,,,,,,,,, -KEY_FN_F4,0x1d5,,,,,,,,,,,,,,,,, -KEY_FN_F5,0x1d6,,,,,,,,,,,,,,,,, -KEY_FN_F6,0x1d7,,,,,,,,,,,,,,,,, -KEY_FN_F7,0x1d8,,,,,,,,,,,,,,,,, -KEY_FN_F8,0x1d9,,,,,,,,,,,,,,,,, -KEY_FN_F9,0x1da,,,,,,,,,,,,,,,,, -KEY_FN_F10,0x1db,,,,,,,,,,,,,,,,, -KEY_FN_F11,0x1dc,,,,,,,,,,,,,,,,, -KEY_FN_F12,0x1dd,,,,,,,,,,,,,,,,, -KEY_FN_1,0x1de,,,,,,,,,,,,,,,,, -KEY_FN_2,0x1df,,,,,,,,,,,,,,,,, -KEY_FN_D,0x1e0,,,,,,,,,,,,,,,,, -KEY_FN_E,0x1e1,,,,,,,,,,,,,,,,, -KEY_FN_F,0x1e2,,,,,,,,,,,,,,,,, -KEY_FN_S,0x1e3,,,,,,,,,,,,,,,,, -KEY_FN_B,0x1e4,,,,,,,,,,,,,,,,, -KEY_BRL_DOT1,0x1f1,,,,,,,,,,,,,,,,, -KEY_BRL_DOT2,0x1f2,,,,,,,,,,,,,,,,, -KEY_BRL_DOT3,0x1f3,,,,,,,,,,,,,,,,, -KEY_BRL_DOT4,0x1f4,,,,,,,,,,,,,,,,, -KEY_BRL_DOT5,0x1f5,,,,,,,,,,,,,,,,, -KEY_BRL_DOT6,0x1f6,,,,,,,,,,,,,,,,, -KEY_BRL_DOT7,0x1f7,,,,,,,,,,,,,,,,, -KEY_BRL_DOT8,0x1f8,,,,,,,,,,,,,,,,, -KEY_BRL_DOT9,0x1f9,,,,,,,,,,,,,,,,, -KEY_BRL_DOT10,0x1fa,,,,,,,,,,,,,,,,, -KEY_NUMERIC_0,0x200,,,,,,,,,,,,,,,,, -KEY_NUMERIC_1,0x201,,,,,,,,,,,,,,,,, -KEY_NUMERIC_2,0x202,,,,,,,,,,,,,,,,, -KEY_NUMERIC_3,0x203,,,,,,,,,,,,,,,,, -KEY_NUMERIC_4,0x204,,,,,,,,,,,,,,,,, -KEY_NUMERIC_5,0x205,,,,,,,,,,,,,,,,, -KEY_NUMERIC_6,0x206,,,,,,,,,,,,,,,,, -KEY_NUMERIC_7,0x207,,,,,,,,,,,,,,,,, -KEY_NUMERIC_8,0x208,,,,,,,,,,,,,,,,, -KEY_NUMERIC_9,0x209,,,,,,,,,,,,,,,,, -KEY_NUMERIC_STAR,0x20a,,,,,,,,,,,,,NumpadStar,,,, -KEY_NUMERIC_POUND,0x20b,,,,,,,,,,,,,NumpadHash,,,, -KEY_RFKILL,0x20c,,,,,,,,,,,,,,,,, diff --git a/keycodemapdb/meson.build b/keycodemapdb/meson.build deleted file mode 100644 index eb9416b..0000000 --- a/keycodemapdb/meson.build +++ /dev/null @@ -1 +0,0 @@ -project('keycodemapdb') diff --git a/keycodemapdb/tests/.gitignore b/keycodemapdb/tests/.gitignore deleted file mode 100644 index 0562305..0000000 --- a/keycodemapdb/tests/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -osx2win32.* -osx2win32_name.* -osx2xkb.* -osx2xkb_name.* -html2win32.* -html2win32_name.* -osx.* -osx_name.* -stdc -stdc++ -node_modules/ diff --git a/keycodemapdb/tests/Makefile b/keycodemapdb/tests/Makefile deleted file mode 100644 index e1b3875..0000000 --- a/keycodemapdb/tests/Makefile +++ /dev/null @@ -1,150 +0,0 @@ -TESTS := stdc stdc++ python2 python3 javascript - -check: $(TESTS) - @set -e; for fn in $(TESTS); do \ - ./$$fn; \ - echo $$fn: OK; \ - done - @echo Done. - -GEN := ../tools/keymap-gen -DATA := ../data/keymaps.csv -SOURCES := $(GEN) $(DATA) - -.DELETE_ON_ERROR: - -stdc: stdc.c osx2win32.h osx2win32.c osx2win32_name.h osx2win32_name.c \ - osx2xkb.h osx2xkb.c osx2xkb_name.h osx2xkb_name.c \ - html2win32.h html2win32.c html2win32_name.h html2win32_name.c \ - osx.h osx.c osx_name.h osx_name.c - $(CC) -Wall -o $@ $(filter %.c, $^) -osx2win32.c: $(SOURCES) - $(GEN) --lang stdc code-map $(DATA) osx win32 > $@ -osx2win32.h: $(SOURCES) - $(GEN) --lang stdc-header code-map $(DATA) osx win32 > $@ -osx2win32_name.c: $(SOURCES) - $(GEN) --lang stdc name-map $(DATA) osx win32 > $@ -osx2win32_name.h: $(SOURCES) - $(GEN) --lang stdc-header name-map $(DATA) osx win32 > $@ -osx2xkb.c: $(SOURCES) - $(GEN) --lang stdc code-map $(DATA) osx xkb > $@ -osx2xkb.h: $(SOURCES) - $(GEN) --lang stdc-header code-map $(DATA) osx xkb > $@ -osx2xkb_name.c: $(SOURCES) - $(GEN) --lang stdc name-map $(DATA) osx xkb > $@ -osx2xkb_name.h: $(SOURCES) - $(GEN) --lang stdc-header name-map $(DATA) osx xkb > $@ -html2win32.c: $(SOURCES) - $(GEN) --lang stdc code-map $(DATA) html win32 > $@ -html2win32.h: $(SOURCES) - $(GEN) --lang stdc-header code-map $(DATA) html win32 > $@ -html2win32_name.c: $(SOURCES) - $(GEN) --lang stdc name-map $(DATA) html win32 > $@ -html2win32_name.h: $(SOURCES) - $(GEN) --lang stdc-header name-map $(DATA) html win32 > $@ -osx.c: $(SOURCES) - $(GEN) --lang stdc code-table $(DATA) osx > $@ -osx.h: $(SOURCES) - $(GEN) --lang stdc-header code-table $(DATA) osx > $@ -osx_name.c: $(SOURCES) - $(GEN) --lang stdc name-table $(DATA) osx > $@ -osx_name.h: $(SOURCES) - $(GEN) --lang stdc-header name-table $(DATA) osx > $@ - -stdc++: stdc++.cc osx2win32.hh osx2win32.cc osx2win32_name.hh osx2win32_name.cc \ - osx2xkb.hh osx2xkb.cc osx2xkb_name.hh osx2xkb_name.cc \ - html2win32.hh html2win32.cc html2win32_name.hh html2win32_name.cc \ - osx.hh osx.cc osx_name.hh osx_name.cc - $(CXX) -Wall -std=c++11 -o $@ $(filter %.cc, $^) -osx2win32.cc: $(SOURCES) - $(GEN) --lang stdc++ code-map $(DATA) osx win32 > $@ -osx2win32.hh: $(SOURCES) - $(GEN) --lang stdc++-header code-map $(DATA) osx win32 > $@ -osx2win32_name.cc: $(SOURCES) - $(GEN) --lang stdc++ name-map $(DATA) osx win32 > $@ -osx2win32_name.hh: $(SOURCES) - $(GEN) --lang stdc++-header name-map $(DATA) osx win32 > $@ -osx2xkb.cc: $(SOURCES) - $(GEN) --lang stdc++ code-map $(DATA) osx xkb > $@ -osx2xkb.hh: $(SOURCES) - $(GEN) --lang stdc++-header code-map $(DATA) osx xkb > $@ -osx2xkb_name.cc: $(SOURCES) - $(GEN) --lang stdc++ name-map $(DATA) osx xkb > $@ -osx2xkb_name.hh: $(SOURCES) - $(GEN) --lang stdc++-header name-map $(DATA) osx xkb > $@ -html2win32.cc: $(SOURCES) - $(GEN) --lang stdc++ code-map $(DATA) html win32 > $@ -html2win32.hh: $(SOURCES) - $(GEN) --lang stdc++-header code-map $(DATA) html win32 > $@ -html2win32_name.cc: $(SOURCES) - $(GEN) --lang stdc++ name-map $(DATA) html win32 > $@ -html2win32_name.hh: $(SOURCES) - $(GEN) --lang stdc++-header name-map $(DATA) html win32 > $@ -osx.cc: $(SOURCES) - $(GEN) --lang stdc++ code-table $(DATA) osx > $@ -osx.hh: $(SOURCES) - $(GEN) --lang stdc++-header code-table $(DATA) osx > $@ -osx_name.cc: $(SOURCES) - $(GEN) --lang stdc++ name-table $(DATA) osx > $@ -osx_name.hh: $(SOURCES) - $(GEN) --lang stdc++-header name-table $(DATA) osx > $@ - -python2: osx2win32.py osx2win32_name.py \ - osx2xkb.py osx2xkb_name.py \ - html2win32.py html2win32_name.py \ - osx.py osx_name.py -osx2win32.py: $(SOURCES) - $(GEN) --lang python2 code-map $(DATA) osx win32 > $@ -osx2win32_name.py: $(SOURCES) - $(GEN) --lang python2 name-map $(DATA) osx win32 > $@ -osx2xkb.py: $(SOURCES) - $(GEN) --lang python2 code-map $(DATA) osx xkb > $@ -osx2xkb_name.py: $(SOURCES) - $(GEN) --lang python2 name-map $(DATA) osx xkb > $@ -html2win32.py: $(SOURCES) - $(GEN) --lang python2 code-map $(DATA) html win32 > $@ -html2win32_name.py: $(SOURCES) - $(GEN) --lang python2 name-map $(DATA) html win32 > $@ -osx.py: $(SOURCES) - $(GEN) --lang python2 code-table $(DATA) osx > $@ -osx_name.py: $(SOURCES) - $(GEN) --lang python2 name-table $(DATA) osx > $@ - -javascript: node_modules/babel-core \ - node_modules/babel-plugin-transform-es2015-modules-commonjs \ - osx2win32.js osx2win32_name.js \ - osx2xkb.js osx2xkb_name.js \ - html2win32.js html2win32_name.js \ - osx.js osx_name.js -node_modules/babel-core: - npm install babel-core -node_modules/babel-plugin-transform-es2015-modules-commonjs: - npm install babel-plugin-transform-es2015-modules-commonjs -osx2win32.js: $(SOURCES) - $(GEN) --lang js code-map $(DATA) osx win32 > $@ -osx2win32_name.js: $(SOURCES) - $(GEN) --lang js name-map $(DATA) osx win32 > $@ -osx2xkb.js: $(SOURCES) - $(GEN) --lang js code-map $(DATA) osx xkb > $@ -osx2xkb_name.js: $(SOURCES) - $(GEN) --lang js name-map $(DATA) osx xkb > $@ -html2win32.js: $(SOURCES) - $(GEN) --lang js code-map $(DATA) html win32 > $@ -html2win32_name.js: $(SOURCES) - $(GEN) --lang js name-map $(DATA) html win32 > $@ -osx.js: $(SOURCES) - $(GEN) --lang js code-table $(DATA) osx > $@ -osx_name.js: $(SOURCES) - $(GEN) --lang js name-table $(DATA) osx > $@ - -clean: - rm -rf node_modules - rm -f osx2win32.* - rm -f osx2win32_name.* - rm -f osx2xkb.* - rm -f osx2xkb_name.* - rm -f html2win32.* - rm -f html2win32_name.* - rm -f osx.* - rm -f osx_name.* - rm -f stdc stdc++ diff --git a/keycodemapdb/tests/javascript b/keycodemapdb/tests/javascript deleted file mode 100755 index 5179db2..0000000 --- a/keycodemapdb/tests/javascript +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env node -/* - * Keycode Map Generator JavaScript Tests - * - * Copyright 2017 Pierre Ossman for Cendio AB - * - * This file is dual license under the terms of the GPLv2 or later - * and 3-clause BSD licenses. - */ - -"use strict"; - -var assert = require('assert'); -var babel = require('babel-core'); -var fs = require('fs'); - -function include(fn) { - var options = { - plugins: ["transform-es2015-modules-commonjs"] - }; - - var code = babel.transformFileSync(fn, options).code; - fs.writeFileSync("." + fn + "_nodejs.js", code); - var imp = require("./." + fn + "_nodejs.js"); - fs.unlinkSync("./." + fn + "_nodejs.js"); - - return imp -} - -var code_map_osx_to_win32 = include("osx2win32.js").default; -var name_map_osx_to_win32 = include("osx2win32_name.js").default; - -var code_map_osx_to_xkb = include("osx2xkb.js").default; -var name_map_osx_to_xkb = include("osx2xkb_name.js").default; - -var code_map_html_to_win32 = include("html2win32.js").default; -var name_map_html_to_win32 = include("html2win32_name.js").default; - -var code_table_osx = include("osx.js").default; -var name_table_osx = include("osx_name.js").default; - -assert.equal(code_map_osx_to_win32[0x1d], 0x30); -assert.equal(name_map_osx_to_win32[0x1d], "VK_0"); - -assert.equal(code_map_osx_to_xkb[0x1d], "AE10"); -assert.equal(name_map_osx_to_xkb[0x1d], "AE10"); - -assert.equal(code_map_html_to_win32["ControlLeft"], 0x11); -assert.equal(name_map_html_to_win32["ControlLeft"], "VK_CONTROL"); - -assert.equal(code_table_osx[0x1d], 0x3b); -assert.equal(name_table_osx[0x1d], "Control"); - diff --git a/keycodemapdb/tests/python2 b/keycodemapdb/tests/python2 deleted file mode 100755 index 28a5b03..0000000 --- a/keycodemapdb/tests/python2 +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -python ./test.py diff --git a/keycodemapdb/tests/python3 b/keycodemapdb/tests/python3 deleted file mode 100755 index ded1f68..0000000 --- a/keycodemapdb/tests/python3 +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -python3 ./test.py diff --git a/keycodemapdb/tests/stdc++.cc b/keycodemapdb/tests/stdc++.cc deleted file mode 100644 index 5e3e8f5..0000000 --- a/keycodemapdb/tests/stdc++.cc +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Keycode Map Generator C++ Tests - * - * Copyright 2017 Pierre Ossman for Cendio AB - * - * This file is dual license under the terms of the GPLv2 or later - * and 3-clause BSD licenses. - */ - -#include -#include - -#include "osx2win32.hh" -#include "osx2win32_name.hh" - -#include "osx2xkb.hh" -#include "osx2xkb_name.hh" - -#include "html2win32.hh" -#include "html2win32_name.hh" - -#include "osx.hh" -#include "osx_name.hh" - -int main(int argc, char** argv) -{ - assert(code_map_osx_to_win32[0x1d] == 0x30); - assert(strcmp(name_map_osx_to_win32[0x1d], "VK_0") == 0); - - assert(strcmp(code_map_osx_to_xkb[0x1d], "AE10") == 0); - assert(strcmp(name_map_osx_to_xkb[0x1d], "AE10") == 0); - - assert(code_map_html_to_win32.at("ControlLeft") == 0x11); - assert(strcmp(name_map_html_to_win32.at("ControlLeft"), "VK_CONTROL") == 0); - - assert(code_table_osx[0x1d] == 0x3b); - assert(strcmp(name_table_osx[0x1d], "Control") == 0); - - return 0; -} diff --git a/keycodemapdb/tests/stdc.c b/keycodemapdb/tests/stdc.c deleted file mode 100644 index e4946fa..0000000 --- a/keycodemapdb/tests/stdc.c +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Keycode Map Generator C Tests - * - * Copyright 2017 Pierre Ossman for Cendio AB - * - * This file is dual license under the terms of the GPLv2 or later - * and 3-clause BSD licenses. - */ - -#include -#include - -#include "osx2win32.h" -#include "osx2win32_name.h" - -#include "osx2xkb.h" -#include "osx2xkb_name.h" - -#include "html2win32.h" -#include "html2win32_name.h" - -#include "osx.h" -#include "osx_name.h" - -#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) - -int main(int argc, char** argv) -{ - unsigned i; - - assert(code_map_osx_to_win32_len == ARRAY_SIZE(code_map_osx_to_win32)); - assert(code_map_osx_to_win32[0x1d] == 0x30); - assert(name_map_osx_to_win32_len == ARRAY_SIZE(name_map_osx_to_win32)); - assert(strcmp(name_map_osx_to_win32[0x1d], "VK_0") == 0); - - assert(code_map_osx_to_xkb_len == ARRAY_SIZE(code_map_osx_to_xkb)); - assert(strcmp(code_map_osx_to_xkb[0x1d], "AE10") == 0); - assert(name_map_osx_to_xkb_len == ARRAY_SIZE(name_map_osx_to_xkb)); - assert(strcmp(name_map_osx_to_xkb[0x1d], "AE10") == 0); - - assert(code_map_html_to_win32_len == ARRAY_SIZE(code_map_html_to_win32)); - for (i = 0;i < code_map_html_to_win32_len;i++) { - if (strcmp(code_map_html_to_win32[i].from, "ControlLeft") == 0) { - assert(code_map_html_to_win32[i].to == 0x11); - break; - } - } - assert(i != code_map_html_to_win32_len); - assert(name_map_html_to_win32_len == ARRAY_SIZE(name_map_html_to_win32)); - for (i = 0;i < name_map_html_to_win32_len;i++) { - if (strcmp(name_map_html_to_win32[i].from, "ControlLeft") == 0) { - assert(strcmp(name_map_html_to_win32[i].to, "VK_CONTROL") == 0); - break; - } - } - assert(i != name_map_html_to_win32_len); - - assert(code_table_osx_len == ARRAY_SIZE(code_table_osx)); - assert(code_table_osx[0x1d] == 0x3b); - assert(name_table_osx_len == ARRAY_SIZE(name_table_osx)); - assert(strcmp(name_table_osx[0x1d], "Control") == 0); - - return 0; -} diff --git a/keycodemapdb/tests/test.py b/keycodemapdb/tests/test.py deleted file mode 100644 index f265145..0000000 --- a/keycodemapdb/tests/test.py +++ /dev/null @@ -1,30 +0,0 @@ -# Keycode Map Generator Python Tests -# -# Copyright 2017 Pierre Ossman for Cendio AB -# -# This file is dual license under the terms of the GPLv2 or later -# and 3-clause BSD licenses. - -import osx2win32 -import osx2win32_name - -import osx2xkb -import osx2xkb_name - -import html2win32 -import html2win32_name - -import osx -import osx_name - -assert osx2win32.code_map_osx_to_win32[0x1d] == 0x30 -assert osx2win32_name.name_map_osx_to_win32[0x1d] == "VK_0" - -assert osx2xkb.code_map_osx_to_xkb[0x1d] == "AE10" -assert osx2xkb_name.name_map_osx_to_xkb[0x1d] == "AE10" - -assert html2win32.code_map_html_to_win32["ControlLeft"] == 0x11 -assert html2win32_name.name_map_html_to_win32["ControlLeft"] == "VK_CONTROL" - -assert osx.code_table_osx[0x1d] == 0x3b; -assert osx_name.name_table_osx[0x1d] == "Control"; diff --git a/keycodemapdb/thirdparty/LICENSE-argparse.txt b/keycodemapdb/thirdparty/LICENSE-argparse.txt deleted file mode 100644 index 640bc78..0000000 --- a/keycodemapdb/thirdparty/LICENSE-argparse.txt +++ /dev/null @@ -1,20 +0,0 @@ -argparse is (c) 2006-2009 Steven J. Bethard . - -The argparse module was contributed to Python as of Python 2.7 and thus -was licensed under the Python license. Same license applies to all files in -the argparse package project. - -For details about the Python License, please see doc/Python-License.txt. - -History -------- - -Before (and including) argparse 1.1, the argparse package was licensed under -Apache License v2.0. - -After argparse 1.1, all project files from the argparse project were deleted -due to license compatibility issues between Apache License 2.0 and GNU GPL v2. - -The project repository then had a clean start with some files taken from -Python 2.7.1, so definitely all files are under Python License now. - diff --git a/keycodemapdb/thirdparty/__init__.py b/keycodemapdb/thirdparty/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/keycodemapdb/thirdparty/argparse.py b/keycodemapdb/thirdparty/argparse.py deleted file mode 100644 index 70a77cc..0000000 --- a/keycodemapdb/thirdparty/argparse.py +++ /dev/null @@ -1,2392 +0,0 @@ -# Author: Steven J. Bethard . -# Maintainer: Thomas Waldmann - -"""Command-line parsing library - -This module is an optparse-inspired command-line parsing library that: - - - handles both optional and positional arguments - - produces highly informative usage messages - - supports parsers that dispatch to sub-parsers - -The following is a simple usage example that sums integers from the -command-line and writes the result to a file:: - - parser = argparse.ArgumentParser( - description='sum the integers at the command line') - parser.add_argument( - 'integers', metavar='int', nargs='+', type=int, - help='an integer to be summed') - parser.add_argument( - '--log', default=sys.stdout, type=argparse.FileType('w'), - help='the file where the sum should be written') - args = parser.parse_args() - args.log.write('%s' % sum(args.integers)) - args.log.close() - -The module contains the following public classes: - - - ArgumentParser -- The main entry point for command-line parsing. As the - example above shows, the add_argument() method is used to populate - the parser with actions for optional and positional arguments. Then - the parse_args() method is invoked to convert the args at the - command-line into an object with attributes. - - - ArgumentError -- The exception raised by ArgumentParser objects when - there are errors with the parser's actions. Errors raised while - parsing the command-line are caught by ArgumentParser and emitted - as command-line messages. - - - FileType -- A factory for defining types of files to be created. As the - example above shows, instances of FileType are typically passed as - the type= argument of add_argument() calls. - - - Action -- The base class for parser actions. Typically actions are - selected by passing strings like 'store_true' or 'append_const' to - the action= argument of add_argument(). However, for greater - customization of ArgumentParser actions, subclasses of Action may - be defined and passed as the action= argument. - - - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter, - ArgumentDefaultsHelpFormatter -- Formatter classes which - may be passed as the formatter_class= argument to the - ArgumentParser constructor. HelpFormatter is the default, - RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser - not to change the formatting for help text, and - ArgumentDefaultsHelpFormatter adds information about argument defaults - to the help. - -All other classes in this module are considered implementation details. -(Also note that HelpFormatter and RawDescriptionHelpFormatter are only -considered public as object names -- the API of the formatter objects is -still considered an implementation detail.) -""" - -__version__ = '1.4.0' # we use our own version number independant of the - # one in stdlib and we release this on pypi. - -__external_lib__ = True # to make sure the tests really test THIS lib, - # not the builtin one in Python stdlib - -__all__ = [ - 'ArgumentParser', - 'ArgumentError', - 'ArgumentTypeError', - 'FileType', - 'HelpFormatter', - 'ArgumentDefaultsHelpFormatter', - 'RawDescriptionHelpFormatter', - 'RawTextHelpFormatter', - 'Namespace', - 'Action', - 'ONE_OR_MORE', - 'OPTIONAL', - 'PARSER', - 'REMAINDER', - 'SUPPRESS', - 'ZERO_OR_MORE', -] - - -import copy as _copy -import os as _os -import re as _re -import sys as _sys -import textwrap as _textwrap - -from gettext import gettext as _ - -try: - set -except NameError: - # for python < 2.4 compatibility (sets module is there since 2.3): - from sets import Set as set - -try: - basestring -except NameError: - basestring = str - -try: - sorted -except NameError: - # for python < 2.4 compatibility: - def sorted(iterable, reverse=False): - result = list(iterable) - result.sort() - if reverse: - result.reverse() - return result - - -def _callable(obj): - return hasattr(obj, '__call__') or hasattr(obj, '__bases__') - - -SUPPRESS = '==SUPPRESS==' - -OPTIONAL = '?' -ZERO_OR_MORE = '*' -ONE_OR_MORE = '+' -PARSER = 'A...' -REMAINDER = '...' -_UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args' - -# ============================= -# Utility functions and classes -# ============================= - -class _AttributeHolder(object): - """Abstract base class that provides __repr__. - - The __repr__ method returns a string in the format:: - ClassName(attr=name, attr=name, ...) - The attributes are determined either by a class-level attribute, - '_kwarg_names', or by inspecting the instance __dict__. - """ - - def __repr__(self): - type_name = type(self).__name__ - arg_strings = [] - for arg in self._get_args(): - arg_strings.append(repr(arg)) - for name, value in self._get_kwargs(): - arg_strings.append('%s=%r' % (name, value)) - return '%s(%s)' % (type_name, ', '.join(arg_strings)) - - def _get_kwargs(self): - return sorted(self.__dict__.items()) - - def _get_args(self): - return [] - - -def _ensure_value(namespace, name, value): - if getattr(namespace, name, None) is None: - setattr(namespace, name, value) - return getattr(namespace, name) - - -# =============== -# Formatting Help -# =============== - -class HelpFormatter(object): - """Formatter for generating usage messages and argument help strings. - - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - - def __init__(self, - prog, - indent_increment=2, - max_help_position=24, - width=None): - - # default setting for width - if width is None: - try: - width = int(_os.environ['COLUMNS']) - except (KeyError, ValueError): - width = 80 - width -= 2 - - self._prog = prog - self._indent_increment = indent_increment - self._max_help_position = max_help_position - self._width = width - - self._current_indent = 0 - self._level = 0 - self._action_max_length = 0 - - self._root_section = self._Section(self, None) - self._current_section = self._root_section - - self._whitespace_matcher = _re.compile(r'\s+') - self._long_break_matcher = _re.compile(r'\n\n\n+') - - # =============================== - # Section and indentation methods - # =============================== - def _indent(self): - self._current_indent += self._indent_increment - self._level += 1 - - def _dedent(self): - self._current_indent -= self._indent_increment - assert self._current_indent >= 0, 'Indent decreased below 0.' - self._level -= 1 - - class _Section(object): - - def __init__(self, formatter, parent, heading=None): - self.formatter = formatter - self.parent = parent - self.heading = heading - self.items = [] - - def format_help(self): - # format the indented section - if self.parent is not None: - self.formatter._indent() - join = self.formatter._join_parts - for func, args in self.items: - func(*args) - item_help = join([func(*args) for func, args in self.items]) - if self.parent is not None: - self.formatter._dedent() - - # return nothing if the section was empty - if not item_help: - return '' - - # add the heading if the section was non-empty - if self.heading is not SUPPRESS and self.heading is not None: - current_indent = self.formatter._current_indent - heading = '%*s%s:\n' % (current_indent, '', self.heading) - else: - heading = '' - - # join the section-initial newline, the heading and the help - return join(['\n', heading, item_help, '\n']) - - def _add_item(self, func, args): - self._current_section.items.append((func, args)) - - # ======================== - # Message building methods - # ======================== - def start_section(self, heading): - self._indent() - section = self._Section(self, self._current_section, heading) - self._add_item(section.format_help, []) - self._current_section = section - - def end_section(self): - self._current_section = self._current_section.parent - self._dedent() - - def add_text(self, text): - if text is not SUPPRESS and text is not None: - self._add_item(self._format_text, [text]) - - def add_usage(self, usage, actions, groups, prefix=None): - if usage is not SUPPRESS: - args = usage, actions, groups, prefix - self._add_item(self._format_usage, args) - - def add_argument(self, action): - if action.help is not SUPPRESS: - - # find all invocations - get_invocation = self._format_action_invocation - invocations = [get_invocation(action)] - for subaction in self._iter_indented_subactions(action): - invocations.append(get_invocation(subaction)) - - # update the maximum item length - invocation_length = max([len(s) for s in invocations]) - action_length = invocation_length + self._current_indent - self._action_max_length = max(self._action_max_length, - action_length) - - # add the item to the list - self._add_item(self._format_action, [action]) - - def add_arguments(self, actions): - for action in actions: - self.add_argument(action) - - # ======================= - # Help-formatting methods - # ======================= - def format_help(self): - help = self._root_section.format_help() - if help: - help = self._long_break_matcher.sub('\n\n', help) - help = help.strip('\n') + '\n' - return help - - def _join_parts(self, part_strings): - return ''.join([part - for part in part_strings - if part and part is not SUPPRESS]) - - def _format_usage(self, usage, actions, groups, prefix): - if prefix is None: - prefix = _('usage: ') - - # if usage is specified, use that - if usage is not None: - usage = usage % dict(prog=self._prog) - - # if no optionals or positionals are available, usage is just prog - elif usage is None and not actions: - usage = '%(prog)s' % dict(prog=self._prog) - - # if optionals and positionals are available, calculate usage - elif usage is None: - prog = '%(prog)s' % dict(prog=self._prog) - - # split optionals from positionals - optionals = [] - positionals = [] - for action in actions: - if action.option_strings: - optionals.append(action) - else: - positionals.append(action) - - # build full usage string - format = self._format_actions_usage - action_usage = format(optionals + positionals, groups) - usage = ' '.join([s for s in [prog, action_usage] if s]) - - # wrap the usage parts if it's too long - text_width = self._width - self._current_indent - if len(prefix) + len(usage) > text_width: - - # break usage into wrappable parts - part_regexp = r'\(.*?\)+|\[.*?\]+|\S+' - opt_usage = format(optionals, groups) - pos_usage = format(positionals, groups) - opt_parts = _re.findall(part_regexp, opt_usage) - pos_parts = _re.findall(part_regexp, pos_usage) - assert ' '.join(opt_parts) == opt_usage - assert ' '.join(pos_parts) == pos_usage - - # helper for wrapping lines - def get_lines(parts, indent, prefix=None): - lines = [] - line = [] - if prefix is not None: - line_len = len(prefix) - 1 - else: - line_len = len(indent) - 1 - for part in parts: - if line_len + 1 + len(part) > text_width: - lines.append(indent + ' '.join(line)) - line = [] - line_len = len(indent) - 1 - line.append(part) - line_len += len(part) + 1 - if line: - lines.append(indent + ' '.join(line)) - if prefix is not None: - lines[0] = lines[0][len(indent):] - return lines - - # if prog is short, follow it with optionals or positionals - if len(prefix) + len(prog) <= 0.75 * text_width: - indent = ' ' * (len(prefix) + len(prog) + 1) - if opt_parts: - lines = get_lines([prog] + opt_parts, indent, prefix) - lines.extend(get_lines(pos_parts, indent)) - elif pos_parts: - lines = get_lines([prog] + pos_parts, indent, prefix) - else: - lines = [prog] - - # if prog is long, put it on its own line - else: - indent = ' ' * len(prefix) - parts = opt_parts + pos_parts - lines = get_lines(parts, indent) - if len(lines) > 1: - lines = [] - lines.extend(get_lines(opt_parts, indent)) - lines.extend(get_lines(pos_parts, indent)) - lines = [prog] + lines - - # join lines into usage - usage = '\n'.join(lines) - - # prefix with 'usage:' - return '%s%s\n\n' % (prefix, usage) - - def _format_actions_usage(self, actions, groups): - # find group indices and identify actions in groups - group_actions = set() - inserts = {} - for group in groups: - try: - start = actions.index(group._group_actions[0]) - except ValueError: - continue - else: - end = start + len(group._group_actions) - if actions[start:end] == group._group_actions: - for action in group._group_actions: - group_actions.add(action) - if not group.required: - if start in inserts: - inserts[start] += ' [' - else: - inserts[start] = '[' - inserts[end] = ']' - else: - if start in inserts: - inserts[start] += ' (' - else: - inserts[start] = '(' - inserts[end] = ')' - for i in range(start + 1, end): - inserts[i] = '|' - - # collect all actions format strings - parts = [] - for i, action in enumerate(actions): - - # suppressed arguments are marked with None - # remove | separators for suppressed arguments - if action.help is SUPPRESS: - parts.append(None) - if inserts.get(i) == '|': - inserts.pop(i) - elif inserts.get(i + 1) == '|': - inserts.pop(i + 1) - - # produce all arg strings - elif not action.option_strings: - part = self._format_args(action, action.dest) - - # if it's in a group, strip the outer [] - if action in group_actions: - if part[0] == '[' and part[-1] == ']': - part = part[1:-1] - - # add the action string to the list - parts.append(part) - - # produce the first way to invoke the option in brackets - else: - option_string = action.option_strings[0] - - # if the Optional doesn't take a value, format is: - # -s or --long - if action.nargs == 0: - part = '%s' % option_string - - # if the Optional takes a value, format is: - # -s ARGS or --long ARGS - else: - default = action.dest.upper() - args_string = self._format_args(action, default) - part = '%s %s' % (option_string, args_string) - - # make it look optional if it's not required or in a group - if not action.required and action not in group_actions: - part = '[%s]' % part - - # add the action string to the list - parts.append(part) - - # insert things at the necessary indices - for i in sorted(inserts, reverse=True): - parts[i:i] = [inserts[i]] - - # join all the action items with spaces - text = ' '.join([item for item in parts if item is not None]) - - # clean up separators for mutually exclusive groups - open = r'[\[(]' - close = r'[\])]' - text = _re.sub(r'(%s) ' % open, r'\1', text) - text = _re.sub(r' (%s)' % close, r'\1', text) - text = _re.sub(r'%s *%s' % (open, close), r'', text) - text = _re.sub(r'\(([^|]*)\)', r'\1', text) - text = text.strip() - - # return the text - return text - - def _format_text(self, text): - if '%(prog)' in text: - text = text % dict(prog=self._prog) - text_width = self._width - self._current_indent - indent = ' ' * self._current_indent - return self._fill_text(text, text_width, indent) + '\n\n' - - def _format_action(self, action): - # determine the required width and the entry label - help_position = min(self._action_max_length + 2, - self._max_help_position) - help_width = self._width - help_position - action_width = help_position - self._current_indent - 2 - action_header = self._format_action_invocation(action) - - # ho nelp; start on same line and add a final newline - if not action.help: - tup = self._current_indent, '', action_header - action_header = '%*s%s\n' % tup - - # short action name; start on the same line and pad two spaces - elif len(action_header) <= action_width: - tup = self._current_indent, '', action_width, action_header - action_header = '%*s%-*s ' % tup - indent_first = 0 - - # long action name; start on the next line - else: - tup = self._current_indent, '', action_header - action_header = '%*s%s\n' % tup - indent_first = help_position - - # collect the pieces of the action help - parts = [action_header] - - # if there was help for the action, add lines of help text - if action.help: - help_text = self._expand_help(action) - help_lines = self._split_lines(help_text, help_width) - parts.append('%*s%s\n' % (indent_first, '', help_lines[0])) - for line in help_lines[1:]: - parts.append('%*s%s\n' % (help_position, '', line)) - - # or add a newline if the description doesn't end with one - elif not action_header.endswith('\n'): - parts.append('\n') - - # if there are any sub-actions, add their help as well - for subaction in self._iter_indented_subactions(action): - parts.append(self._format_action(subaction)) - - # return a single string - return self._join_parts(parts) - - def _format_action_invocation(self, action): - if not action.option_strings: - metavar, = self._metavar_formatter(action, action.dest)(1) - return metavar - - else: - parts = [] - - # if the Optional doesn't take a value, format is: - # -s, --long - if action.nargs == 0: - parts.extend(action.option_strings) - - # if the Optional takes a value, format is: - # -s ARGS, --long ARGS - else: - default = action.dest.upper() - args_string = self._format_args(action, default) - for option_string in action.option_strings: - parts.append('%s %s' % (option_string, args_string)) - - return ', '.join(parts) - - def _metavar_formatter(self, action, default_metavar): - if action.metavar is not None: - result = action.metavar - elif action.choices is not None: - choice_strs = [str(choice) for choice in action.choices] - result = '{%s}' % ','.join(choice_strs) - else: - result = default_metavar - - def format(tuple_size): - if isinstance(result, tuple): - return result - else: - return (result, ) * tuple_size - return format - - def _format_args(self, action, default_metavar): - get_metavar = self._metavar_formatter(action, default_metavar) - if action.nargs is None: - result = '%s' % get_metavar(1) - elif action.nargs == OPTIONAL: - result = '[%s]' % get_metavar(1) - elif action.nargs == ZERO_OR_MORE: - result = '[%s [%s ...]]' % get_metavar(2) - elif action.nargs == ONE_OR_MORE: - result = '%s [%s ...]' % get_metavar(2) - elif action.nargs == REMAINDER: - result = '...' - elif action.nargs == PARSER: - result = '%s ...' % get_metavar(1) - else: - formats = ['%s' for _ in range(action.nargs)] - result = ' '.join(formats) % get_metavar(action.nargs) - return result - - def _expand_help(self, action): - params = dict(vars(action), prog=self._prog) - for name in list(params): - if params[name] is SUPPRESS: - del params[name] - for name in list(params): - if hasattr(params[name], '__name__'): - params[name] = params[name].__name__ - if params.get('choices') is not None: - choices_str = ', '.join([str(c) for c in params['choices']]) - params['choices'] = choices_str - return self._get_help_string(action) % params - - def _iter_indented_subactions(self, action): - try: - get_subactions = action._get_subactions - except AttributeError: - pass - else: - self._indent() - for subaction in get_subactions(): - yield subaction - self._dedent() - - def _split_lines(self, text, width): - text = self._whitespace_matcher.sub(' ', text).strip() - return _textwrap.wrap(text, width) - - def _fill_text(self, text, width, indent): - text = self._whitespace_matcher.sub(' ', text).strip() - return _textwrap.fill(text, width, initial_indent=indent, - subsequent_indent=indent) - - def _get_help_string(self, action): - return action.help - - -class RawDescriptionHelpFormatter(HelpFormatter): - """Help message formatter which retains any formatting in descriptions. - - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - - def _fill_text(self, text, width, indent): - return ''.join([indent + line for line in text.splitlines(True)]) - - -class RawTextHelpFormatter(RawDescriptionHelpFormatter): - """Help message formatter which retains formatting of all help text. - - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - - def _split_lines(self, text, width): - return text.splitlines() - - -class ArgumentDefaultsHelpFormatter(HelpFormatter): - """Help message formatter which adds default values to argument help. - - Only the name of this class is considered a public API. All the methods - provided by the class are considered an implementation detail. - """ - - def _get_help_string(self, action): - help = action.help - if '%(default)' not in action.help: - if action.default is not SUPPRESS: - defaulting_nargs = [OPTIONAL, ZERO_OR_MORE] - if action.option_strings or action.nargs in defaulting_nargs: - help += ' (default: %(default)s)' - return help - - -# ===================== -# Options and Arguments -# ===================== - -def _get_action_name(argument): - if argument is None: - return None - elif argument.option_strings: - return '/'.join(argument.option_strings) - elif argument.metavar not in (None, SUPPRESS): - return argument.metavar - elif argument.dest not in (None, SUPPRESS): - return argument.dest - else: - return None - - -class ArgumentError(Exception): - """An error from creating or using an argument (optional or positional). - - The string value of this exception is the message, augmented with - information about the argument that caused it. - """ - - def __init__(self, argument, message): - self.argument_name = _get_action_name(argument) - self.message = message - - def __str__(self): - if self.argument_name is None: - format = '%(message)s' - else: - format = 'argument %(argument_name)s: %(message)s' - return format % dict(message=self.message, - argument_name=self.argument_name) - - -class ArgumentTypeError(Exception): - """An error from trying to convert a command line string to a type.""" - pass - - -# ============== -# Action classes -# ============== - -class Action(_AttributeHolder): - """Information about how to convert command line strings to Python objects. - - Action objects are used by an ArgumentParser to represent the information - needed to parse a single argument from one or more strings from the - command line. The keyword arguments to the Action constructor are also - all attributes of Action instances. - - Keyword Arguments: - - - option_strings -- A list of command-line option strings which - should be associated with this action. - - - dest -- The name of the attribute to hold the created object(s) - - - nargs -- The number of command-line arguments that should be - consumed. By default, one argument will be consumed and a single - value will be produced. Other values include: - - N (an integer) consumes N arguments (and produces a list) - - '?' consumes zero or one arguments - - '*' consumes zero or more arguments (and produces a list) - - '+' consumes one or more arguments (and produces a list) - Note that the difference between the default and nargs=1 is that - with the default, a single value will be produced, while with - nargs=1, a list containing a single value will be produced. - - - const -- The value to be produced if the option is specified and the - option uses an action that takes no values. - - - default -- The value to be produced if the option is not specified. - - - type -- The type which the command-line arguments should be converted - to, should be one of 'string', 'int', 'float', 'complex' or a - callable object that accepts a single string argument. If None, - 'string' is assumed. - - - choices -- A container of values that should be allowed. If not None, - after a command-line argument has been converted to the appropriate - type, an exception will be raised if it is not a member of this - collection. - - - required -- True if the action must always be specified at the - command line. This is only meaningful for optional command-line - arguments. - - - help -- The help string describing the argument. - - - metavar -- The name to be used for the option's argument with the - help string. If None, the 'dest' value will be used as the name. - """ - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - self.option_strings = option_strings - self.dest = dest - self.nargs = nargs - self.const = const - self.default = default - self.type = type - self.choices = choices - self.required = required - self.help = help - self.metavar = metavar - - def _get_kwargs(self): - names = [ - 'option_strings', - 'dest', - 'nargs', - 'const', - 'default', - 'type', - 'choices', - 'help', - 'metavar', - ] - return [(name, getattr(self, name)) for name in names] - - def __call__(self, parser, namespace, values, option_string=None): - raise NotImplementedError(_('.__call__() not defined')) - - -class _StoreAction(Action): - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - if nargs == 0: - raise ValueError('nargs for store actions must be > 0; if you ' - 'have nothing to store, actions such as store ' - 'true or store const may be more appropriate') - if const is not None and nargs != OPTIONAL: - raise ValueError('nargs must be %r to supply const' % OPTIONAL) - super(_StoreAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, values) - - -class _StoreConstAction(Action): - - def __init__(self, - option_strings, - dest, - const, - default=None, - required=False, - help=None, - metavar=None): - super(_StoreConstAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - const=const, - default=default, - required=required, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - setattr(namespace, self.dest, self.const) - - -class _StoreTrueAction(_StoreConstAction): - - def __init__(self, - option_strings, - dest, - default=False, - required=False, - help=None): - super(_StoreTrueAction, self).__init__( - option_strings=option_strings, - dest=dest, - const=True, - default=default, - required=required, - help=help) - - -class _StoreFalseAction(_StoreConstAction): - - def __init__(self, - option_strings, - dest, - default=True, - required=False, - help=None): - super(_StoreFalseAction, self).__init__( - option_strings=option_strings, - dest=dest, - const=False, - default=default, - required=required, - help=help) - - -class _AppendAction(Action): - - def __init__(self, - option_strings, - dest, - nargs=None, - const=None, - default=None, - type=None, - choices=None, - required=False, - help=None, - metavar=None): - if nargs == 0: - raise ValueError('nargs for append actions must be > 0; if arg ' - 'strings are not supplying the value to append, ' - 'the append const action may be more appropriate') - if const is not None and nargs != OPTIONAL: - raise ValueError('nargs must be %r to supply const' % OPTIONAL) - super(_AppendAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=nargs, - const=const, - default=default, - type=type, - choices=choices, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - items = _copy.copy(_ensure_value(namespace, self.dest, [])) - items.append(values) - setattr(namespace, self.dest, items) - - -class _AppendConstAction(Action): - - def __init__(self, - option_strings, - dest, - const, - default=None, - required=False, - help=None, - metavar=None): - super(_AppendConstAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - const=const, - default=default, - required=required, - help=help, - metavar=metavar) - - def __call__(self, parser, namespace, values, option_string=None): - items = _copy.copy(_ensure_value(namespace, self.dest, [])) - items.append(self.const) - setattr(namespace, self.dest, items) - - -class _CountAction(Action): - - def __init__(self, - option_strings, - dest, - default=None, - required=False, - help=None): - super(_CountAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=0, - default=default, - required=required, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - new_count = _ensure_value(namespace, self.dest, 0) + 1 - setattr(namespace, self.dest, new_count) - - -class _HelpAction(Action): - - def __init__(self, - option_strings, - dest=SUPPRESS, - default=SUPPRESS, - help=None): - super(_HelpAction, self).__init__( - option_strings=option_strings, - dest=dest, - default=default, - nargs=0, - help=help) - - def __call__(self, parser, namespace, values, option_string=None): - parser.print_help() - parser.exit() - - -class _VersionAction(Action): - - def __init__(self, - option_strings, - version=None, - dest=SUPPRESS, - default=SUPPRESS, - help="show program's version number and exit"): - super(_VersionAction, self).__init__( - option_strings=option_strings, - dest=dest, - default=default, - nargs=0, - help=help) - self.version = version - - def __call__(self, parser, namespace, values, option_string=None): - version = self.version - if version is None: - version = parser.version - formatter = parser._get_formatter() - formatter.add_text(version) - parser.exit(message=formatter.format_help()) - - -class _SubParsersAction(Action): - - class _ChoicesPseudoAction(Action): - - def __init__(self, name, aliases, help): - metavar = dest = name - if aliases: - metavar += ' (%s)' % ', '.join(aliases) - sup = super(_SubParsersAction._ChoicesPseudoAction, self) - sup.__init__(option_strings=[], dest=dest, help=help, - metavar=metavar) - - def __init__(self, - option_strings, - prog, - parser_class, - dest=SUPPRESS, - help=None, - metavar=None): - - self._prog_prefix = prog - self._parser_class = parser_class - self._name_parser_map = {} - self._choices_actions = [] - - super(_SubParsersAction, self).__init__( - option_strings=option_strings, - dest=dest, - nargs=PARSER, - choices=self._name_parser_map, - help=help, - metavar=metavar) - - def add_parser(self, name, **kwargs): - # set prog from the existing prefix - if kwargs.get('prog') is None: - kwargs['prog'] = '%s %s' % (self._prog_prefix, name) - - aliases = kwargs.pop('aliases', ()) - - # create a pseudo-action to hold the choice help - if 'help' in kwargs: - help = kwargs.pop('help') - choice_action = self._ChoicesPseudoAction(name, aliases, help) - self._choices_actions.append(choice_action) - - # create the parser and add it to the map - parser = self._parser_class(**kwargs) - self._name_parser_map[name] = parser - - # make parser available under aliases also - for alias in aliases: - self._name_parser_map[alias] = parser - - return parser - - def _get_subactions(self): - return self._choices_actions - - def __call__(self, parser, namespace, values, option_string=None): - parser_name = values[0] - arg_strings = values[1:] - - # set the parser name if requested - if self.dest is not SUPPRESS: - setattr(namespace, self.dest, parser_name) - - # select the parser - try: - parser = self._name_parser_map[parser_name] - except KeyError: - tup = parser_name, ', '.join(self._name_parser_map) - msg = _('unknown parser %r (choices: %s)' % tup) - raise ArgumentError(self, msg) - - # parse all the remaining options into the namespace - # store any unrecognized options on the object, so that the top - # level parser can decide what to do with them - namespace, arg_strings = parser.parse_known_args(arg_strings, namespace) - if arg_strings: - vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, []) - getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings) - - -# ============== -# Type classes -# ============== - -class FileType(object): - """Factory for creating file object types - - Instances of FileType are typically passed as type= arguments to the - ArgumentParser add_argument() method. - - Keyword Arguments: - - mode -- A string indicating how the file is to be opened. Accepts the - same values as the builtin open() function. - - bufsize -- The file's desired buffer size. Accepts the same values as - the builtin open() function. - """ - - def __init__(self, mode='r', bufsize=None): - self._mode = mode - self._bufsize = bufsize - - def __call__(self, string): - # the special argument "-" means sys.std{in,out} - if string == '-': - if 'r' in self._mode: - return _sys.stdin - elif 'w' in self._mode: - return _sys.stdout - else: - msg = _('argument "-" with mode %r' % self._mode) - raise ValueError(msg) - - try: - # all other arguments are used as file names - if self._bufsize: - return open(string, self._mode, self._bufsize) - else: - return open(string, self._mode) - except IOError: - err = _sys.exc_info()[1] - message = _("can't open '%s': %s") - raise ArgumentTypeError(message % (string, err)) - - def __repr__(self): - args = [self._mode, self._bufsize] - args_str = ', '.join([repr(arg) for arg in args if arg is not None]) - return '%s(%s)' % (type(self).__name__, args_str) - -# =========================== -# Optional and Positional Parsing -# =========================== - -class Namespace(_AttributeHolder): - """Simple object for storing attributes. - - Implements equality by attribute names and values, and provides a simple - string representation. - """ - - def __init__(self, **kwargs): - for name in kwargs: - setattr(self, name, kwargs[name]) - - __hash__ = None - - def __eq__(self, other): - return vars(self) == vars(other) - - def __ne__(self, other): - return not (self == other) - - def __contains__(self, key): - return key in self.__dict__ - - -class _ActionsContainer(object): - - def __init__(self, - description, - prefix_chars, - argument_default, - conflict_handler): - super(_ActionsContainer, self).__init__() - - self.description = description - self.argument_default = argument_default - self.prefix_chars = prefix_chars - self.conflict_handler = conflict_handler - - # set up registries - self._registries = {} - - # register actions - self.register('action', None, _StoreAction) - self.register('action', 'store', _StoreAction) - self.register('action', 'store_const', _StoreConstAction) - self.register('action', 'store_true', _StoreTrueAction) - self.register('action', 'store_false', _StoreFalseAction) - self.register('action', 'append', _AppendAction) - self.register('action', 'append_const', _AppendConstAction) - self.register('action', 'count', _CountAction) - self.register('action', 'help', _HelpAction) - self.register('action', 'version', _VersionAction) - self.register('action', 'parsers', _SubParsersAction) - - # raise an exception if the conflict handler is invalid - self._get_handler() - - # action storage - self._actions = [] - self._option_string_actions = {} - - # groups - self._action_groups = [] - self._mutually_exclusive_groups = [] - - # defaults storage - self._defaults = {} - - # determines whether an "option" looks like a negative number - self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$') - - # whether or not there are any optionals that look like negative - # numbers -- uses a list so it can be shared and edited - self._has_negative_number_optionals = [] - - # ==================== - # Registration methods - # ==================== - def register(self, registry_name, value, object): - registry = self._registries.setdefault(registry_name, {}) - registry[value] = object - - def _registry_get(self, registry_name, value, default=None): - return self._registries[registry_name].get(value, default) - - # ================================== - # Namespace default accessor methods - # ================================== - def set_defaults(self, **kwargs): - self._defaults.update(kwargs) - - # if these defaults match any existing arguments, replace - # the previous default on the object with the new one - for action in self._actions: - if action.dest in kwargs: - action.default = kwargs[action.dest] - - def get_default(self, dest): - for action in self._actions: - if action.dest == dest and action.default is not None: - return action.default - return self._defaults.get(dest, None) - - - # ======================= - # Adding argument actions - # ======================= - def add_argument(self, *args, **kwargs): - """ - add_argument(dest, ..., name=value, ...) - add_argument(option_string, option_string, ..., name=value, ...) - """ - - # if no positional args are supplied or only one is supplied and - # it doesn't look like an option string, parse a positional - # argument - chars = self.prefix_chars - if not args or len(args) == 1 and args[0][0] not in chars: - if args and 'dest' in kwargs: - raise ValueError('dest supplied twice for positional argument') - kwargs = self._get_positional_kwargs(*args, **kwargs) - - # otherwise, we're adding an optional argument - else: - kwargs = self._get_optional_kwargs(*args, **kwargs) - - # if no default was supplied, use the parser-level default - if 'default' not in kwargs: - dest = kwargs['dest'] - if dest in self._defaults: - kwargs['default'] = self._defaults[dest] - elif self.argument_default is not None: - kwargs['default'] = self.argument_default - - # create the action object, and add it to the parser - action_class = self._pop_action_class(kwargs) - if not _callable(action_class): - raise ValueError('unknown action "%s"' % action_class) - action = action_class(**kwargs) - - # raise an error if the action type is not callable - type_func = self._registry_get('type', action.type, action.type) - if not _callable(type_func): - raise ValueError('%r is not callable' % type_func) - - return self._add_action(action) - - def add_argument_group(self, *args, **kwargs): - group = _ArgumentGroup(self, *args, **kwargs) - self._action_groups.append(group) - return group - - def add_mutually_exclusive_group(self, **kwargs): - group = _MutuallyExclusiveGroup(self, **kwargs) - self._mutually_exclusive_groups.append(group) - return group - - def _add_action(self, action): - # resolve any conflicts - self._check_conflict(action) - - # add to actions list - self._actions.append(action) - action.container = self - - # index the action by any option strings it has - for option_string in action.option_strings: - self._option_string_actions[option_string] = action - - # set the flag if any option strings look like negative numbers - for option_string in action.option_strings: - if self._negative_number_matcher.match(option_string): - if not self._has_negative_number_optionals: - self._has_negative_number_optionals.append(True) - - # return the created action - return action - - def _remove_action(self, action): - self._actions.remove(action) - - def _add_container_actions(self, container): - # collect groups by titles - title_group_map = {} - for group in self._action_groups: - if group.title in title_group_map: - msg = _('cannot merge actions - two groups are named %r') - raise ValueError(msg % (group.title)) - title_group_map[group.title] = group - - # map each action to its group - group_map = {} - for group in container._action_groups: - - # if a group with the title exists, use that, otherwise - # create a new group matching the container's group - if group.title not in title_group_map: - title_group_map[group.title] = self.add_argument_group( - title=group.title, - description=group.description, - conflict_handler=group.conflict_handler) - - # map the actions to their new group - for action in group._group_actions: - group_map[action] = title_group_map[group.title] - - # add container's mutually exclusive groups - # NOTE: if add_mutually_exclusive_group ever gains title= and - # description= then this code will need to be expanded as above - for group in container._mutually_exclusive_groups: - mutex_group = self.add_mutually_exclusive_group( - required=group.required) - - # map the actions to their new mutex group - for action in group._group_actions: - group_map[action] = mutex_group - - # add all actions to this container or their group - for action in container._actions: - group_map.get(action, self)._add_action(action) - - def _get_positional_kwargs(self, dest, **kwargs): - # make sure required is not specified - if 'required' in kwargs: - msg = _("'required' is an invalid argument for positionals") - raise TypeError(msg) - - # mark positional arguments as required if at least one is - # always required - if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]: - kwargs['required'] = True - if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs: - kwargs['required'] = True - - # return the keyword arguments with no option strings - return dict(kwargs, dest=dest, option_strings=[]) - - def _get_optional_kwargs(self, *args, **kwargs): - # determine short and long option strings - option_strings = [] - long_option_strings = [] - for option_string in args: - # error on strings that don't start with an appropriate prefix - if not option_string[0] in self.prefix_chars: - msg = _('invalid option string %r: ' - 'must start with a character %r') - tup = option_string, self.prefix_chars - raise ValueError(msg % tup) - - # strings starting with two prefix characters are long options - option_strings.append(option_string) - if option_string[0] in self.prefix_chars: - if len(option_string) > 1: - if option_string[1] in self.prefix_chars: - long_option_strings.append(option_string) - - # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x' - dest = kwargs.pop('dest', None) - if dest is None: - if long_option_strings: - dest_option_string = long_option_strings[0] - else: - dest_option_string = option_strings[0] - dest = dest_option_string.lstrip(self.prefix_chars) - if not dest: - msg = _('dest= is required for options like %r') - raise ValueError(msg % option_string) - dest = dest.replace('-', '_') - - # return the updated keyword arguments - return dict(kwargs, dest=dest, option_strings=option_strings) - - def _pop_action_class(self, kwargs, default=None): - action = kwargs.pop('action', default) - return self._registry_get('action', action, action) - - def _get_handler(self): - # determine function from conflict handler string - handler_func_name = '_handle_conflict_%s' % self.conflict_handler - try: - return getattr(self, handler_func_name) - except AttributeError: - msg = _('invalid conflict_resolution value: %r') - raise ValueError(msg % self.conflict_handler) - - def _check_conflict(self, action): - - # find all options that conflict with this option - confl_optionals = [] - for option_string in action.option_strings: - if option_string in self._option_string_actions: - confl_optional = self._option_string_actions[option_string] - confl_optionals.append((option_string, confl_optional)) - - # resolve any conflicts - if confl_optionals: - conflict_handler = self._get_handler() - conflict_handler(action, confl_optionals) - - def _handle_conflict_error(self, action, conflicting_actions): - message = _('conflicting option string(s): %s') - conflict_string = ', '.join([option_string - for option_string, action - in conflicting_actions]) - raise ArgumentError(action, message % conflict_string) - - def _handle_conflict_resolve(self, action, conflicting_actions): - - # remove all conflicting options - for option_string, action in conflicting_actions: - - # remove the conflicting option - action.option_strings.remove(option_string) - self._option_string_actions.pop(option_string, None) - - # if the option now has no option string, remove it from the - # container holding it - if not action.option_strings: - action.container._remove_action(action) - - -class _ArgumentGroup(_ActionsContainer): - - def __init__(self, container, title=None, description=None, **kwargs): - # add any missing keyword arguments by checking the container - update = kwargs.setdefault - update('conflict_handler', container.conflict_handler) - update('prefix_chars', container.prefix_chars) - update('argument_default', container.argument_default) - super_init = super(_ArgumentGroup, self).__init__ - super_init(description=description, **kwargs) - - # group attributes - self.title = title - self._group_actions = [] - - # share most attributes with the container - self._registries = container._registries - self._actions = container._actions - self._option_string_actions = container._option_string_actions - self._defaults = container._defaults - self._has_negative_number_optionals = \ - container._has_negative_number_optionals - - def _add_action(self, action): - action = super(_ArgumentGroup, self)._add_action(action) - self._group_actions.append(action) - return action - - def _remove_action(self, action): - super(_ArgumentGroup, self)._remove_action(action) - self._group_actions.remove(action) - - -class _MutuallyExclusiveGroup(_ArgumentGroup): - - def __init__(self, container, required=False): - super(_MutuallyExclusiveGroup, self).__init__(container) - self.required = required - self._container = container - - def _add_action(self, action): - if action.required: - msg = _('mutually exclusive arguments must be optional') - raise ValueError(msg) - action = self._container._add_action(action) - self._group_actions.append(action) - return action - - def _remove_action(self, action): - self._container._remove_action(action) - self._group_actions.remove(action) - - -class ArgumentParser(_AttributeHolder, _ActionsContainer): - """Object for parsing command line strings into Python objects. - - Keyword Arguments: - - prog -- The name of the program (default: sys.argv[0]) - - usage -- A usage message (default: auto-generated from arguments) - - description -- A description of what the program does - - epilog -- Text following the argument descriptions - - parents -- Parsers whose arguments should be copied into this one - - formatter_class -- HelpFormatter class for printing help messages - - prefix_chars -- Characters that prefix optional arguments - - fromfile_prefix_chars -- Characters that prefix files containing - additional arguments - - argument_default -- The default value for all arguments - - conflict_handler -- String indicating how to handle conflicts - - add_help -- Add a -h/-help option - """ - - def __init__(self, - prog=None, - usage=None, - description=None, - epilog=None, - version=None, - parents=[], - formatter_class=HelpFormatter, - prefix_chars='-', - fromfile_prefix_chars=None, - argument_default=None, - conflict_handler='error', - add_help=True): - - if version is not None: - import warnings - warnings.warn( - """The "version" argument to ArgumentParser is deprecated. """ - """Please use """ - """"add_argument(..., action='version', version="N", ...)" """ - """instead""", DeprecationWarning) - - superinit = super(ArgumentParser, self).__init__ - superinit(description=description, - prefix_chars=prefix_chars, - argument_default=argument_default, - conflict_handler=conflict_handler) - - # default setting for prog - if prog is None: - prog = _os.path.basename(_sys.argv[0]) - - self.prog = prog - self.usage = usage - self.epilog = epilog - self.version = version - self.formatter_class = formatter_class - self.fromfile_prefix_chars = fromfile_prefix_chars - self.add_help = add_help - - add_group = self.add_argument_group - self._positionals = add_group(_('positional arguments')) - self._optionals = add_group(_('optional arguments')) - self._subparsers = None - - # register types - def identity(string): - return string - self.register('type', None, identity) - - # add help and version arguments if necessary - # (using explicit default to override global argument_default) - if '-' in prefix_chars: - default_prefix = '-' - else: - default_prefix = prefix_chars[0] - if self.add_help: - self.add_argument( - default_prefix+'h', default_prefix*2+'help', - action='help', default=SUPPRESS, - help=_('show this help message and exit')) - if self.version: - self.add_argument( - default_prefix+'v', default_prefix*2+'version', - action='version', default=SUPPRESS, - version=self.version, - help=_("show program's version number and exit")) - - # add parent arguments and defaults - for parent in parents: - self._add_container_actions(parent) - try: - defaults = parent._defaults - except AttributeError: - pass - else: - self._defaults.update(defaults) - - # ======================= - # Pretty __repr__ methods - # ======================= - def _get_kwargs(self): - names = [ - 'prog', - 'usage', - 'description', - 'version', - 'formatter_class', - 'conflict_handler', - 'add_help', - ] - return [(name, getattr(self, name)) for name in names] - - # ================================== - # Optional/Positional adding methods - # ================================== - def add_subparsers(self, **kwargs): - if self._subparsers is not None: - self.error(_('cannot have multiple subparser arguments')) - - # add the parser class to the arguments if it's not present - kwargs.setdefault('parser_class', type(self)) - - if 'title' in kwargs or 'description' in kwargs: - title = _(kwargs.pop('title', 'subcommands')) - description = _(kwargs.pop('description', None)) - self._subparsers = self.add_argument_group(title, description) - else: - self._subparsers = self._positionals - - # prog defaults to the usage message of this parser, skipping - # optional arguments and with no "usage:" prefix - if kwargs.get('prog') is None: - formatter = self._get_formatter() - positionals = self._get_positional_actions() - groups = self._mutually_exclusive_groups - formatter.add_usage(self.usage, positionals, groups, '') - kwargs['prog'] = formatter.format_help().strip() - - # create the parsers action and add it to the positionals list - parsers_class = self._pop_action_class(kwargs, 'parsers') - action = parsers_class(option_strings=[], **kwargs) - self._subparsers._add_action(action) - - # return the created parsers action - return action - - def _add_action(self, action): - if action.option_strings: - self._optionals._add_action(action) - else: - self._positionals._add_action(action) - return action - - def _get_optional_actions(self): - return [action - for action in self._actions - if action.option_strings] - - def _get_positional_actions(self): - return [action - for action in self._actions - if not action.option_strings] - - # ===================================== - # Command line argument parsing methods - # ===================================== - def parse_args(self, args=None, namespace=None): - args, argv = self.parse_known_args(args, namespace) - if argv: - msg = _('unrecognized arguments: %s') - self.error(msg % ' '.join(argv)) - return args - - def parse_known_args(self, args=None, namespace=None): - # args default to the system args - if args is None: - args = _sys.argv[1:] - - # default Namespace built from parser defaults - if namespace is None: - namespace = Namespace() - - # add any action defaults that aren't present - for action in self._actions: - if action.dest is not SUPPRESS: - if not hasattr(namespace, action.dest): - if action.default is not SUPPRESS: - setattr(namespace, action.dest, action.default) - - # add any parser defaults that aren't present - for dest in self._defaults: - if not hasattr(namespace, dest): - setattr(namespace, dest, self._defaults[dest]) - - # parse the arguments and exit if there are any errors - try: - namespace, args = self._parse_known_args(args, namespace) - if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR): - args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR)) - delattr(namespace, _UNRECOGNIZED_ARGS_ATTR) - return namespace, args - except ArgumentError: - err = _sys.exc_info()[1] - self.error(str(err)) - - def _parse_known_args(self, arg_strings, namespace): - # replace arg strings that are file references - if self.fromfile_prefix_chars is not None: - arg_strings = self._read_args_from_files(arg_strings) - - # map all mutually exclusive arguments to the other arguments - # they can't occur with - action_conflicts = {} - for mutex_group in self._mutually_exclusive_groups: - group_actions = mutex_group._group_actions - for i, mutex_action in enumerate(mutex_group._group_actions): - conflicts = action_conflicts.setdefault(mutex_action, []) - conflicts.extend(group_actions[:i]) - conflicts.extend(group_actions[i + 1:]) - - # find all option indices, and determine the arg_string_pattern - # which has an 'O' if there is an option at an index, - # an 'A' if there is an argument, or a '-' if there is a '--' - option_string_indices = {} - arg_string_pattern_parts = [] - arg_strings_iter = iter(arg_strings) - for i, arg_string in enumerate(arg_strings_iter): - - # all args after -- are non-options - if arg_string == '--': - arg_string_pattern_parts.append('-') - for arg_string in arg_strings_iter: - arg_string_pattern_parts.append('A') - - # otherwise, add the arg to the arg strings - # and note the index if it was an option - else: - option_tuple = self._parse_optional(arg_string) - if option_tuple is None: - pattern = 'A' - else: - option_string_indices[i] = option_tuple - pattern = 'O' - arg_string_pattern_parts.append(pattern) - - # join the pieces together to form the pattern - arg_strings_pattern = ''.join(arg_string_pattern_parts) - - # converts arg strings to the appropriate and then takes the action - seen_actions = set() - seen_non_default_actions = set() - - def take_action(action, argument_strings, option_string=None): - seen_actions.add(action) - argument_values = self._get_values(action, argument_strings) - - # error if this argument is not allowed with other previously - # seen arguments, assuming that actions that use the default - # value don't really count as "present" - if argument_values is not action.default: - seen_non_default_actions.add(action) - for conflict_action in action_conflicts.get(action, []): - if conflict_action in seen_non_default_actions: - msg = _('not allowed with argument %s') - action_name = _get_action_name(conflict_action) - raise ArgumentError(action, msg % action_name) - - # take the action if we didn't receive a SUPPRESS value - # (e.g. from a default) - if argument_values is not SUPPRESS: - action(self, namespace, argument_values, option_string) - - # function to convert arg_strings into an optional action - def consume_optional(start_index): - - # get the optional identified at this index - option_tuple = option_string_indices[start_index] - action, option_string, explicit_arg = option_tuple - - # identify additional optionals in the same arg string - # (e.g. -xyz is the same as -x -y -z if no args are required) - match_argument = self._match_argument - action_tuples = [] - while True: - - # if we found no optional action, skip it - if action is None: - extras.append(arg_strings[start_index]) - return start_index + 1 - - # if there is an explicit argument, try to match the - # optional's string arguments to only this - if explicit_arg is not None: - arg_count = match_argument(action, 'A') - - # if the action is a single-dash option and takes no - # arguments, try to parse more single-dash options out - # of the tail of the option string - chars = self.prefix_chars - if arg_count == 0 and option_string[1] not in chars: - action_tuples.append((action, [], option_string)) - char = option_string[0] - option_string = char + explicit_arg[0] - new_explicit_arg = explicit_arg[1:] or None - optionals_map = self._option_string_actions - if option_string in optionals_map: - action = optionals_map[option_string] - explicit_arg = new_explicit_arg - else: - msg = _('ignored explicit argument %r') - raise ArgumentError(action, msg % explicit_arg) - - # if the action expect exactly one argument, we've - # successfully matched the option; exit the loop - elif arg_count == 1: - stop = start_index + 1 - args = [explicit_arg] - action_tuples.append((action, args, option_string)) - break - - # error if a double-dash option did not use the - # explicit argument - else: - msg = _('ignored explicit argument %r') - raise ArgumentError(action, msg % explicit_arg) - - # if there is no explicit argument, try to match the - # optional's string arguments with the following strings - # if successful, exit the loop - else: - start = start_index + 1 - selected_patterns = arg_strings_pattern[start:] - arg_count = match_argument(action, selected_patterns) - stop = start + arg_count - args = arg_strings[start:stop] - action_tuples.append((action, args, option_string)) - break - - # add the Optional to the list and return the index at which - # the Optional's string args stopped - assert action_tuples - for action, args, option_string in action_tuples: - take_action(action, args, option_string) - return stop - - # the list of Positionals left to be parsed; this is modified - # by consume_positionals() - positionals = self._get_positional_actions() - - # function to convert arg_strings into positional actions - def consume_positionals(start_index): - # match as many Positionals as possible - match_partial = self._match_arguments_partial - selected_pattern = arg_strings_pattern[start_index:] - arg_counts = match_partial(positionals, selected_pattern) - - # slice off the appropriate arg strings for each Positional - # and add the Positional and its args to the list - for action, arg_count in zip(positionals, arg_counts): - args = arg_strings[start_index: start_index + arg_count] - start_index += arg_count - take_action(action, args) - - # slice off the Positionals that we just parsed and return the - # index at which the Positionals' string args stopped - positionals[:] = positionals[len(arg_counts):] - return start_index - - # consume Positionals and Optionals alternately, until we have - # passed the last option string - extras = [] - start_index = 0 - if option_string_indices: - max_option_string_index = max(option_string_indices) - else: - max_option_string_index = -1 - while start_index <= max_option_string_index: - - # consume any Positionals preceding the next option - next_option_string_index = min([ - index - for index in option_string_indices - if index >= start_index]) - if start_index != next_option_string_index: - positionals_end_index = consume_positionals(start_index) - - # only try to parse the next optional if we didn't consume - # the option string during the positionals parsing - if positionals_end_index > start_index: - start_index = positionals_end_index - continue - else: - start_index = positionals_end_index - - # if we consumed all the positionals we could and we're not - # at the index of an option string, there were extra arguments - if start_index not in option_string_indices: - strings = arg_strings[start_index:next_option_string_index] - extras.extend(strings) - start_index = next_option_string_index - - # consume the next optional and any arguments for it - start_index = consume_optional(start_index) - - # consume any positionals following the last Optional - stop_index = consume_positionals(start_index) - - # if we didn't consume all the argument strings, there were extras - extras.extend(arg_strings[stop_index:]) - - # if we didn't use all the Positional objects, there were too few - # arg strings supplied. - if positionals: - self.error(_('too few arguments')) - - # make sure all required actions were present, and convert defaults. - for action in self._actions: - if action not in seen_actions: - if action.required: - name = _get_action_name(action) - self.error(_('argument %s is required') % name) - else: - # Convert action default now instead of doing it before - # parsing arguments to avoid calling convert functions - # twice (which may fail) if the argument was given, but - # only if it was defined already in the namespace - if (action.default is not None and - isinstance(action.default, basestring) and - hasattr(namespace, action.dest) and - action.default is getattr(namespace, action.dest)): - setattr(namespace, action.dest, - self._get_value(action, action.default)) - - # make sure all required groups had one option present - for group in self._mutually_exclusive_groups: - if group.required: - for action in group._group_actions: - if action in seen_non_default_actions: - break - - # if no actions were used, report the error - else: - names = [_get_action_name(action) - for action in group._group_actions - if action.help is not SUPPRESS] - msg = _('one of the arguments %s is required') - self.error(msg % ' '.join(names)) - - # return the updated namespace and the extra arguments - return namespace, extras - - def _read_args_from_files(self, arg_strings): - # expand arguments referencing files - new_arg_strings = [] - for arg_string in arg_strings: - - # for regular arguments, just add them back into the list - if arg_string[0] not in self.fromfile_prefix_chars: - new_arg_strings.append(arg_string) - - # replace arguments referencing files with the file content - else: - try: - args_file = open(arg_string[1:]) - try: - arg_strings = [] - for arg_line in args_file.read().splitlines(): - for arg in self.convert_arg_line_to_args(arg_line): - arg_strings.append(arg) - arg_strings = self._read_args_from_files(arg_strings) - new_arg_strings.extend(arg_strings) - finally: - args_file.close() - except IOError: - err = _sys.exc_info()[1] - self.error(str(err)) - - # return the modified argument list - return new_arg_strings - - def convert_arg_line_to_args(self, arg_line): - return [arg_line] - - def _match_argument(self, action, arg_strings_pattern): - # match the pattern for this action to the arg strings - nargs_pattern = self._get_nargs_pattern(action) - match = _re.match(nargs_pattern, arg_strings_pattern) - - # raise an exception if we weren't able to find a match - if match is None: - nargs_errors = { - None: _('expected one argument'), - OPTIONAL: _('expected at most one argument'), - ONE_OR_MORE: _('expected at least one argument'), - } - default = _('expected %s argument(s)') % action.nargs - msg = nargs_errors.get(action.nargs, default) - raise ArgumentError(action, msg) - - # return the number of arguments matched - return len(match.group(1)) - - def _match_arguments_partial(self, actions, arg_strings_pattern): - # progressively shorten the actions list by slicing off the - # final actions until we find a match - result = [] - for i in range(len(actions), 0, -1): - actions_slice = actions[:i] - pattern = ''.join([self._get_nargs_pattern(action) - for action in actions_slice]) - match = _re.match(pattern, arg_strings_pattern) - if match is not None: - result.extend([len(string) for string in match.groups()]) - break - - # return the list of arg string counts - return result - - def _parse_optional(self, arg_string): - # if it's an empty string, it was meant to be a positional - if not arg_string: - return None - - # if it doesn't start with a prefix, it was meant to be positional - if not arg_string[0] in self.prefix_chars: - return None - - # if the option string is present in the parser, return the action - if arg_string in self._option_string_actions: - action = self._option_string_actions[arg_string] - return action, arg_string, None - - # if it's just a single character, it was meant to be positional - if len(arg_string) == 1: - return None - - # if the option string before the "=" is present, return the action - if '=' in arg_string: - option_string, explicit_arg = arg_string.split('=', 1) - if option_string in self._option_string_actions: - action = self._option_string_actions[option_string] - return action, option_string, explicit_arg - - # search through all possible prefixes of the option string - # and all actions in the parser for possible interpretations - option_tuples = self._get_option_tuples(arg_string) - - # if multiple actions match, the option string was ambiguous - if len(option_tuples) > 1: - options = ', '.join([option_string - for action, option_string, explicit_arg in option_tuples]) - tup = arg_string, options - self.error(_('ambiguous option: %s could match %s') % tup) - - # if exactly one action matched, this segmentation is good, - # so return the parsed action - elif len(option_tuples) == 1: - option_tuple, = option_tuples - return option_tuple - - # if it was not found as an option, but it looks like a negative - # number, it was meant to be positional - # unless there are negative-number-like options - if self._negative_number_matcher.match(arg_string): - if not self._has_negative_number_optionals: - return None - - # if it contains a space, it was meant to be a positional - if ' ' in arg_string: - return None - - # it was meant to be an optional but there is no such option - # in this parser (though it might be a valid option in a subparser) - return None, arg_string, None - - def _get_option_tuples(self, option_string): - result = [] - - # option strings starting with two prefix characters are only - # split at the '=' - chars = self.prefix_chars - if option_string[0] in chars and option_string[1] in chars: - if '=' in option_string: - option_prefix, explicit_arg = option_string.split('=', 1) - else: - option_prefix = option_string - explicit_arg = None - for option_string in self._option_string_actions: - if option_string.startswith(option_prefix): - action = self._option_string_actions[option_string] - tup = action, option_string, explicit_arg - result.append(tup) - - # single character options can be concatenated with their arguments - # but multiple character options always have to have their argument - # separate - elif option_string[0] in chars and option_string[1] not in chars: - option_prefix = option_string - explicit_arg = None - short_option_prefix = option_string[:2] - short_explicit_arg = option_string[2:] - - for option_string in self._option_string_actions: - if option_string == short_option_prefix: - action = self._option_string_actions[option_string] - tup = action, option_string, short_explicit_arg - result.append(tup) - elif option_string.startswith(option_prefix): - action = self._option_string_actions[option_string] - tup = action, option_string, explicit_arg - result.append(tup) - - # shouldn't ever get here - else: - self.error(_('unexpected option string: %s') % option_string) - - # return the collected option tuples - return result - - def _get_nargs_pattern(self, action): - # in all examples below, we have to allow for '--' args - # which are represented as '-' in the pattern - nargs = action.nargs - - # the default (None) is assumed to be a single argument - if nargs is None: - nargs_pattern = '(-*A-*)' - - # allow zero or one arguments - elif nargs == OPTIONAL: - nargs_pattern = '(-*A?-*)' - - # allow zero or more arguments - elif nargs == ZERO_OR_MORE: - nargs_pattern = '(-*[A-]*)' - - # allow one or more arguments - elif nargs == ONE_OR_MORE: - nargs_pattern = '(-*A[A-]*)' - - # allow any number of options or arguments - elif nargs == REMAINDER: - nargs_pattern = '([-AO]*)' - - # allow one argument followed by any number of options or arguments - elif nargs == PARSER: - nargs_pattern = '(-*A[-AO]*)' - - # all others should be integers - else: - nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs) - - # if this is an optional action, -- is not allowed - if action.option_strings: - nargs_pattern = nargs_pattern.replace('-*', '') - nargs_pattern = nargs_pattern.replace('-', '') - - # return the pattern - return nargs_pattern - - # ======================== - # Value conversion methods - # ======================== - def _get_values(self, action, arg_strings): - # for everything but PARSER args, strip out '--' - if action.nargs not in [PARSER, REMAINDER]: - arg_strings = [s for s in arg_strings if s != '--'] - - # optional argument produces a default when not present - if not arg_strings and action.nargs == OPTIONAL: - if action.option_strings: - value = action.const - else: - value = action.default - if isinstance(value, basestring): - value = self._get_value(action, value) - self._check_value(action, value) - - # when nargs='*' on a positional, if there were no command-line - # args, use the default if it is anything other than None - elif (not arg_strings and action.nargs == ZERO_OR_MORE and - not action.option_strings): - if action.default is not None: - value = action.default - else: - value = arg_strings - self._check_value(action, value) - - # single argument or optional argument produces a single value - elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]: - arg_string, = arg_strings - value = self._get_value(action, arg_string) - self._check_value(action, value) - - # REMAINDER arguments convert all values, checking none - elif action.nargs == REMAINDER: - value = [self._get_value(action, v) for v in arg_strings] - - # PARSER arguments convert all values, but check only the first - elif action.nargs == PARSER: - value = [self._get_value(action, v) for v in arg_strings] - self._check_value(action, value[0]) - - # all other types of nargs produce a list - else: - value = [self._get_value(action, v) for v in arg_strings] - for v in value: - self._check_value(action, v) - - # return the converted value - return value - - def _get_value(self, action, arg_string): - type_func = self._registry_get('type', action.type, action.type) - if not _callable(type_func): - msg = _('%r is not callable') - raise ArgumentError(action, msg % type_func) - - # convert the value to the appropriate type - try: - result = type_func(arg_string) - - # ArgumentTypeErrors indicate errors - except ArgumentTypeError: - name = getattr(action.type, '__name__', repr(action.type)) - msg = str(_sys.exc_info()[1]) - raise ArgumentError(action, msg) - - # TypeErrors or ValueErrors also indicate errors - except (TypeError, ValueError): - name = getattr(action.type, '__name__', repr(action.type)) - msg = _('invalid %s value: %r') - raise ArgumentError(action, msg % (name, arg_string)) - - # return the converted value - return result - - def _check_value(self, action, value): - # converted value must be one of the choices (if specified) - if action.choices is not None and value not in action.choices: - tup = value, ', '.join(map(repr, action.choices)) - msg = _('invalid choice: %r (choose from %s)') % tup - raise ArgumentError(action, msg) - - # ======================= - # Help-formatting methods - # ======================= - def format_usage(self): - formatter = self._get_formatter() - formatter.add_usage(self.usage, self._actions, - self._mutually_exclusive_groups) - return formatter.format_help() - - def format_help(self): - formatter = self._get_formatter() - - # usage - formatter.add_usage(self.usage, self._actions, - self._mutually_exclusive_groups) - - # description - formatter.add_text(self.description) - - # positionals, optionals and user-defined groups - for action_group in self._action_groups: - formatter.start_section(action_group.title) - formatter.add_text(action_group.description) - formatter.add_arguments(action_group._group_actions) - formatter.end_section() - - # epilog - formatter.add_text(self.epilog) - - # determine help from format above - return formatter.format_help() - - def format_version(self): - import warnings - warnings.warn( - 'The format_version method is deprecated -- the "version" ' - 'argument to ArgumentParser is no longer supported.', - DeprecationWarning) - formatter = self._get_formatter() - formatter.add_text(self.version) - return formatter.format_help() - - def _get_formatter(self): - return self.formatter_class(prog=self.prog) - - # ===================== - # Help-printing methods - # ===================== - def print_usage(self, file=None): - if file is None: - file = _sys.stdout - self._print_message(self.format_usage(), file) - - def print_help(self, file=None): - if file is None: - file = _sys.stdout - self._print_message(self.format_help(), file) - - def print_version(self, file=None): - import warnings - warnings.warn( - 'The print_version method is deprecated -- the "version" ' - 'argument to ArgumentParser is no longer supported.', - DeprecationWarning) - self._print_message(self.format_version(), file) - - def _print_message(self, message, file=None): - if message: - if file is None: - file = _sys.stderr - file.write(message) - - # =============== - # Exiting methods - # =============== - def exit(self, status=0, message=None): - if message: - self._print_message(message, _sys.stderr) - _sys.exit(status) - - def error(self, message): - """error(message: string) - - Prints a usage message incorporating the message to stderr and - exits. - - If you override this in a subclass, it should not return -- it - should either exit or raise an exception. - """ - self.print_usage(_sys.stderr) - self.exit(2, _('%s: error: %s\n') % (self.prog, message)) diff --git a/keycodemapdb/tools/keymap-gen b/keycodemapdb/tools/keymap-gen deleted file mode 100755 index 22b4f71..0000000 --- a/keycodemapdb/tools/keymap-gen +++ /dev/null @@ -1,1147 +0,0 @@ -#!/usr/bin/python -# -*- python -*- -# -# Keycode Map Generator -# -# Copyright (C) 2009-2017 Red Hat, Inc. -# -# This file is dual license under the terms of the GPLv2 or later -# and 3-clause BSD licenses. -# - -# Requires >= 2.6 -from __future__ import print_function - -import csv -try: - import argparse -except: - import os, sys - sys.path.append(os.path.join(os.path.dirname(__file__), "../thirdparty")) - import argparse -import hashlib -import time -import sys - -class Database: - - # Linux: linux/input.h - MAP_LINUX = "linux" - - # OS-X: Carbon/HIToolbox/Events.h - MAP_OSX = "osx" - - # AT Set 1: linux/drivers/input/keyboard/atkbd.c - # (atkbd_set2_keycode + atkbd_unxlate_table) - MAP_ATSET1 = "atset1" - - # AT Set 2: linux/drivers/input/keyboard/atkbd.c - # (atkbd_set2_keycode) - MAP_ATSET2 = "atset2" - - # AT Set 3: linux/drivers/input/keyboard/atkbd.c - # (atkbd_set3_keycode) - MAP_ATSET3 = "atset3" - - # Linux RAW: linux/drivers/char/keyboard.c (x86_keycodes) - MAP_XTKBD = "xtkbd" - - # USB HID: linux/drivers/hid/usbhid/usbkbd.c (usb_kbd_keycode) - MAP_USB = "usb" - - # Win32: mingw32/winuser.h - MAP_WIN32 = "win32" - - # XWin XT: xorg-server/hw/xwin/{winkeybd.c,winkeynames.h} - # (xt + manually transcribed) - MAP_XWINXT = "xwinxt" - - # X11: http://cgit.freedesktop.org/xorg/proto/x11proto/plain/keysymdef.h - MAP_X11 = "x11" - - # XKBD XT: xf86-input-keyboard/src/at_scancode.c - # (xt + manually transcribed) - MAP_XKBDXT = "xkbdxt" - - # Xorg with evdev: linux + an offset - MAP_XORGEVDEV = "xorgevdev" - - # Xorg with kbd: xkbdxt + an offset - MAP_XORGKBD = "xorgkbd" - - # Xorg with OS-X: osx + an offset - MAP_XORGXQUARTZ = "xorgxquartz" - - # Xorg + Cygwin: xwinxt + an offset - MAP_XORGXWIN = "xorgxwin" - - # QEMU key numbers: xtkbd + special re-encoding of high bit - MAP_QNUM = "qnum" - - # HTML codes - MAP_HTML = "html" - - # XKB key names - MAP_XKB = "xkb" - - # QEMU keycodes - MAP_QCODE = "qcode" - - # Sun / Sparc scan codes - # Reference: "SPARC International Keyboard Spec 1", page 7 "US scan set" - MAP_SUN = "sun" - - # Apple Desktop Bus - # Reference: http://www.archive.org/stream/apple-guide-macintosh-family-hardware/Apple_Guide_to_the_Macintosh_Family_Hardware_2e#page/n345/mode/2up - MAP_ADB = "adb" - - MAP_LIST = ( - MAP_LINUX, - MAP_OSX, - MAP_ATSET1, - MAP_ATSET2, - MAP_ATSET3, - MAP_USB, - MAP_WIN32, - MAP_XWINXT, - MAP_XKBDXT, - MAP_X11, - MAP_HTML, - MAP_XKB, - MAP_QCODE, - MAP_SUN, - MAP_ADB, - - # These are derived from maps above - MAP_XTKBD, - MAP_XORGEVDEV, - MAP_XORGKBD, - MAP_XORGXQUARTZ, - MAP_XORGXWIN, - MAP_QNUM, - ) - - CODE_COLUMNS = { - MAP_LINUX: 1, - MAP_OSX: 3, - MAP_ATSET1: 4, - MAP_ATSET2: 5, - MAP_ATSET3: 6, - MAP_USB: 7, - MAP_WIN32: 9, - MAP_XWINXT: 10, - MAP_XKBDXT: 11, - MAP_X11: 13, - MAP_HTML: 14, - MAP_XKB: 15, - MAP_SUN: 17, - MAP_ADB: 18, - } - - ENUM_COLUMNS = { - MAP_QCODE: 14, - } - - NAME_COLUMNS = { - MAP_LINUX: 0, - MAP_OSX: 2, - MAP_WIN32: 8, - MAP_X11: 12, - MAP_HTML: 14, - MAP_XKB: 15, - MAP_QCODE: 16, - } - - ENUM_BOUND = { - MAP_QCODE: "Q_KEY_CODE__MAX", - } - - def __init__(self): - - self.mapto = {} - self.mapfrom = {} - self.mapname = {} - self.mapchecksum = None - - for name in self.MAP_LIST: - # Key is a MAP_LINUX, value is a MAP_XXX - self.mapto[name] = {} - # key is a MAP_XXX, value is a MAP_LINUX - self.mapfrom[name] = {} - - for name in self.NAME_COLUMNS.keys(): - # key is a MAP_LINUX, value is a string - self.mapname[name] = {} - - def _generate_checksum(self, filename): - hash = hashlib.sha256() - with open(filename, "rb") as f: - for chunk in iter(lambda: f.read(4096), b""): - hash.update(chunk) - self.mapchecksum = hash.hexdigest() - - def load(self, filename): - self._generate_checksum(filename) - - with open(filename, 'r') as f: - reader = csv.reader(f) - - first = True - - for row in reader: - # Discard column headings - if first: - first = False - continue - - # We special case MAP_LINUX since that is out - # master via which all other mappings are done - linux = self.load_linux(row) - - # Now load all the remaining master data values - self.load_data(row, linux) - - # Then load all the keycode names - self.load_names(row, linux) - - # Finally calculate derived key maps - self.derive_data(row, linux) - - def load_linux(self, row): - col = self.CODE_COLUMNS[self.MAP_LINUX] - linux = row[col] - - if linux.startswith("0x"): - linux = int(linux, 16) - else: - linux = int(linux, 10) - - self.mapto[self.MAP_LINUX][linux] = linux - self.mapfrom[self.MAP_LINUX][linux] = linux - - return linux - - - def load_data(self, row, linux): - for mapname in self.CODE_COLUMNS: - if mapname == self.MAP_LINUX: - continue - - col = self.CODE_COLUMNS[mapname] - val = row[col] - - if val == "": - continue - - if val.startswith("0x"): - val = int(val, 16) - elif val.isdigit(): - val = int(val, 10) - - self.mapto[mapname][linux] = val - self.mapfrom[mapname][val] = linux - - def load_names(self, row, linux): - for mapname in self.NAME_COLUMNS: - col = self.NAME_COLUMNS[mapname] - val = row[col] - - if val == "": - continue - - self.mapname[mapname][linux] = val - - - def derive_data(self, row, linux): - # Linux RAW is XT scan codes with special encoding of the - # 0xe0 scan codes - if linux in self.mapto[self.MAP_ATSET1]: - at1 = self.mapto[self.MAP_ATSET1][linux] - if at1 > 0x7f: - assert((at1 & ~0x7f) == 0xe000) - xtkbd = 0x100 | (at1 & 0x7f) - else: - xtkbd = at1 - self.mapto[self.MAP_XTKBD][linux] = xtkbd - self.mapfrom[self.MAP_XTKBD][xtkbd] = linux - - # Xorg KBD is XKBD XT offset by 8 - if linux in self.mapto[self.MAP_XKBDXT]: - xorgkbd = self.mapto[self.MAP_XKBDXT][linux] + 8 - self.mapto[self.MAP_XORGKBD][linux] = xorgkbd - self.mapfrom[self.MAP_XORGKBD][xorgkbd] = linux - - # Xorg evdev is Linux offset by 8 - self.mapto[self.MAP_XORGEVDEV][linux] = linux + 8 - self.mapfrom[self.MAP_XORGEVDEV][linux + 8] = linux - - # Xorg XQuartx is OS-X offset by 8 - if linux in self.mapto[self.MAP_OSX]: - xorgxquartz = self.mapto[self.MAP_OSX][linux] + 8 - self.mapto[self.MAP_XORGXQUARTZ][linux] = xorgxquartz - self.mapfrom[self.MAP_XORGXQUARTZ][xorgxquartz] = linux - - # Xorg Xwin (aka Cygwin) is XWin XT offset by 8 - if linux in self.mapto[self.MAP_XWINXT]: - xorgxwin = self.mapto[self.MAP_XWINXT][linux] + 8 - self.mapto[self.MAP_XORGXWIN][linux] = xorgxwin - self.mapfrom[self.MAP_XORGXWIN][xorgxwin] = linux - - # QNUM keycodes are XT scan codes with a slightly - # different encoding of 0xe0 scan codes - if linux in self.mapto[self.MAP_ATSET1]: - at1 = self.mapto[self.MAP_ATSET1][linux] - if at1 > 0x7f: - assert((at1 & ~0x7f) == 0xe000) - qnum = 0x80 | (at1 & 0x7f) - else: - qnum = at1 - self.mapto[self.MAP_QNUM][linux] = qnum - self.mapfrom[self.MAP_QNUM][qnum] = linux - - # Hack for compatibility with previous mistakes in handling - # Print/SysRq. The preferred qnum for Print/SysRq is 0x54, - # but QEMU previously allowed 0xb7 too - if qnum == 0x54: - self.mapfrom[self.MAP_QNUM][0xb7] = self.mapfrom[self.MAP_QNUM][0x54] - - if linux in self.mapname[self.MAP_QCODE]: - qcodeenum = self.mapname[self.MAP_QCODE][linux] - qcodeenum = "Q_KEY_CODE_" + qcodeenum.upper() - self.mapto[self.MAP_QCODE][linux] = qcodeenum - self.mapfrom[self.MAP_QCODE][qcodeenum] = linux - -class LanguageGenerator(object): - - def _boilerplate(self, lines): - raise NotImplementedError() - - def generate_header(self, database, args): - self._boilerplate([ - "This file is auto-generated from keymaps.csv", - "Database checksum sha256(%s)" % database.mapchecksum, - "To re-generate, run:", - " %s" % args, - ]) - -class LanguageSrcGenerator(LanguageGenerator): - - TYPE_INT = "integer" - TYPE_STRING = "string" - TYPE_ENUM = "enum" - - def _array_start(self, varname, length, defvalue, fromtype, totype): - raise NotImplementedError() - - def _array_end(self, fromtype, totype): - raise NotImplementedError() - - def _array_entry(self, index, value, comment, fromtype, totype): - raise NotImplementedError() - - def generate_code_map(self, varname, database, frommapname, tomapname): - if frommapname not in database.mapfrom: - raise Exception("Unknown map %s, expected one of %s" % ( - frommapname, ", ".join(database.mapfrom.keys()))) - if tomapname not in database.mapto: - raise Exception("Unknown map %s, expected one of %s" % ( - tomapname, ", ".join(database.mapto.keys()))) - - tolinux = database.mapfrom[frommapname] - fromlinux = database.mapto[tomapname] - - if varname is None: - varname = "code_map_%s_to_%s" % (frommapname, tomapname) - - if frommapname in database.ENUM_COLUMNS: - fromtype = self.TYPE_ENUM - elif type(list(tolinux.keys())[0]) == str: - fromtype = self.TYPE_STRING - else: - fromtype = self.TYPE_INT - - if tomapname in database.ENUM_COLUMNS: - totype = self.TYPE_ENUM - elif type(list(fromlinux.values())[0]) == str: - totype = self.TYPE_STRING - else: - totype = self.TYPE_INT - - keys = list(tolinux.keys()) - keys.sort() - if fromtype == self.TYPE_INT: - keys = range(keys[-1] + 1) - - if fromtype == self.TYPE_ENUM: - keymax = database.ENUM_BOUND[frommapname] - else: - keymax = len(keys) - - defvalue = fromlinux.get(0, None) - if fromtype == self.TYPE_ENUM: - self._array_start(varname, keymax, defvalue, fromtype, totype) - else: - self._array_start(varname, keymax, None, fromtype, totype) - - for src in keys: - linux = tolinux.get(src, None) - if linux is None: - dst = None - else: - dst = fromlinux.get(linux, defvalue) - - comment = "%s -> %s -> %s" % (self._label(database, frommapname, src, linux), - self._label(database, Database.MAP_LINUX, linux, linux), - self._label(database, tomapname, dst, linux)) - self._array_entry(src, dst, comment, fromtype, totype) - self._array_end(fromtype, totype) - - def generate_code_table(self, varname, database, mapname): - if mapname not in database.mapto: - raise Exception("Unknown map %s, expected one of %s" % ( - mapname, ", ".join(database.mapto.keys()))) - - keys = list(database.mapto[Database.MAP_LINUX].keys()) - keys.sort() - names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys] - - if varname is None: - varname = "code_table_%s" % mapname - - if mapname in database.ENUM_COLUMNS: - totype = self.TYPE_ENUM - elif type(list(database.mapto[mapname].values())[0]) == str: - totype = self.TYPE_STRING - else: - totype = self.TYPE_INT - - self._array_start(varname, len(keys), None, self.TYPE_INT, totype) - - defvalue = database.mapto[mapname].get(0, None) - for i in range(len(keys)): - key = keys[i] - dst = database.mapto[mapname].get(key, defvalue) - self._array_entry(i, dst, names[i], self.TYPE_INT, totype) - - self._array_end(self.TYPE_INT, totype) - - def generate_name_map(self, varname, database, frommapname, tomapname): - if frommapname not in database.mapfrom: - raise Exception("Unknown map %s, expected one of %s" % ( - frommapname, ", ".join(database.mapfrom.keys()))) - if tomapname not in database.mapname: - raise Exception("Unknown map %s, expected one of %s" % ( - tomapname, ", ".join(database.mapname.keys()))) - - tolinux = database.mapfrom[frommapname] - fromlinux = database.mapname[tomapname] - - if varname is None: - varname = "name_map_%s_to_%s" % (frommapname, tomapname) - - keys = list(tolinux.keys()) - keys.sort() - if type(keys[0]) == int: - keys = range(keys[-1] + 1) - - if type(keys[0]) == int: - fromtype = self.TYPE_INT - else: - fromtype = self.TYPE_STRING - - self._array_start(varname, len(keys), None, fromtype, self.TYPE_STRING) - - for src in keys: - linux = tolinux.get(src, None) - if linux is None: - dst = None - else: - dst = fromlinux.get(linux, None) - - comment = "%s -> %s -> %s" % (self._label(database, frommapname, src, linux), - self._label(database, Database.MAP_LINUX, linux, linux), - self._label(database, tomapname, dst, linux)) - self._array_entry(src, dst, comment, fromtype, self.TYPE_STRING) - self._array_end(fromtype, self.TYPE_STRING) - - def generate_name_table(self, varname, database, mapname): - if mapname not in database.mapname: - raise Exception("Unknown map %s, expected one of %s" % ( - mapname, ", ".join(database.mapname.keys()))) - - keys = list(database.mapto[Database.MAP_LINUX].keys()) - keys.sort() - names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys] - - if varname is None: - varname = "name_table_%s" % mapname - - self._array_start(varname, len(keys), None, self.TYPE_INT, self.TYPE_STRING) - - for i in range(len(keys)): - key = keys[i] - dst = database.mapname[mapname].get(key, None) - self._array_entry(i, dst, names[i], self.TYPE_INT, self.TYPE_STRING) - - self._array_end(self.TYPE_INT, self.TYPE_STRING) - - def _label(self, database, mapname, val, linux): - if mapname in database.mapname: - return "%s:%s (%s)" % (mapname, val, database.mapname[mapname].get(linux, "unnamed")) - else: - return "%s:%s" % (mapname, val) - -class LanguageDocGenerator(LanguageGenerator): - - def _array_start_name_doc(self, varname, namemap): - raise NotImplementedError() - - def _array_start_code_doc(self, varname, namemap, codemap): - raise NotImplementedError() - - def _array_end(self): - raise NotImplementedError() - - def _array_name_entry(self, value, name): - raise NotImplementedError() - - def _array_code_entry(self, value, name): - raise NotImplementedError() - - def generate_name_docs(self, title, subtitle, database, mapname): - if mapname not in database.mapname: - raise Exception("Unknown map %s, expected one of %s" % ( - mapname, ", ".join(database.mapname.keys()))) - - keys = list(database.mapto[Database.MAP_LINUX].keys()) - keys.sort() - names = [database.mapname[Database.MAP_LINUX].get(key, "unnamed") for key in keys] - - if title is None: - title = mapname - if subtitle is None: - subtitle = "Docs for %s" % mapname - - self._array_start_name_doc(title, subtitle, mapname) - - for i in range(len(keys)): - key = keys[i] - dst = database.mapname[mapname].get(key, None) - self._array_name_entry(key, dst) - - self._array_end() - - - def generate_code_docs(self, title, subtitle, database, mapname): - if mapname not in database.mapfrom: - raise Exception("Unknown map %s, expected one of %s" % ( - mapname, ", ".join(database.mapfrom.keys()))) - - tolinux = database.mapfrom[mapname] - keys = list(tolinux.keys()) - keys.sort() - if mapname in database.mapname: - names = database.mapname[mapname] - namemap = mapname - else: - names = database.mapname[Database.MAP_LINUX] - namemap = Database.MAP_LINUX - - if title is None: - title = mapname - if subtitle is None: - subtitle = "Docs for %s" % mapname - - self._array_start_code_doc(title, subtitle, mapname, namemap) - - for i in range(len(keys)): - key = keys[i] - self._array_code_entry(key, names.get(tolinux[key], "unnamed")) - - self._array_end() - -class CLanguageGenerator(LanguageSrcGenerator): - - def __init__(self, inttypename, strtypename, lentypename): - self.inttypename = inttypename - self.strtypename = strtypename - self.lentypename = lentypename - - def _boilerplate(self, lines): - print("/*") - for line in lines: - print(" * %s" % line) - print("*/") - - def _array_start(self, varname, length, defvalue, fromtype, totype): - self._varname = varname; - totypename = self.strtypename if totype == self.TYPE_STRING else self.inttypename - if fromtype in (self.TYPE_INT, self.TYPE_ENUM): - if type(length) == str: - print("const %s %s[%s] = {" % (totypename, varname, length)) - else: - print("const %s %s[%d] = {" % (totypename, varname, length)) - else: - print("const struct _%s {" % varname) - print(" const %s from;" % self.strtypename) - print(" const %s to;" % totypename) - print("} %s[] = {" % varname) - - if defvalue != None: - if totype == self.TYPE_ENUM: - if type(length) == str: - print(" [0 ... %s-1] = %s," % (length, defvalue)) - else: - print(" [0 ... 0x%x-1] = %s," % (length, defvalue)) - else: - if type(length) == str: - print(" [0 ... %s-1] = 0x%x," % (length, defvalue)) - else: - print(" [0 ... 0x%x-1] = 0x%x," % (length, defvalue)) - - def _array_end(self, fromtype, totype): - print("};") - print("const %s %s_len = sizeof(%s)/sizeof(%s[0]);" % - (self.lentypename, self._varname, self._varname, self._varname)) - - def _array_entry(self, index, value, comment, fromtype, totype): - if value is None: - return - if fromtype == self.TYPE_INT: - indexfmt = "0x%x" - elif fromtype == self.TYPE_ENUM: - indexfmt = "%s" - else: - indexfmt = "\"%s\"" - - if totype == self.TYPE_INT: - valuefmt = "0x%x" - elif totype == self.TYPE_ENUM: - valuefmt = "%s" - else: - valuefmt = "\"%s\"" - - if fromtype != self.TYPE_STRING: - print((" [" + indexfmt + "] = " + valuefmt + ", /* %s */") % (index, value, comment)) - else: - print((" {" + indexfmt + ", " + valuefmt + "}, /* %s */") % (index, value, comment)) - -class StdCLanguageGenerator(CLanguageGenerator): - - def __init__(self): - super(StdCLanguageGenerator, self).__init__("unsigned short", "char *", "unsigned int") - -class GLib2LanguageGenerator(CLanguageGenerator): - - def __init__(self): - super(GLib2LanguageGenerator, self).__init__("guint16", "gchar *", "guint") - -class CHeaderLanguageGenerator(LanguageSrcGenerator): - - def __init__(self, inttypename, strtypename, lentypename): - self.inttypename = inttypename - self.strtypename = strtypename - self.lentypename = lentypename - - def _boilerplate(self, lines): - print("/*") - for line in lines: - print(" * %s" % line) - print("*/") - - def _array_start(self, varname, length, defvalue, fromtype, totype): - self._varname = varname - if fromtype == self.TYPE_STRING: - self._length = 0 - else: - self._length = length - - def _array_end(self, fromtype, totype): - totypename = self.strtypename if totype == self.TYPE_STRING else self.inttypename - if fromtype == self.TYPE_STRING: - vartypename = "struct _%s" % self._varname - print("%s {" % vartypename) - print(" const %s from;" % self.strtypename) - print(" const %s to;" % totypename) - print("};") - else: - vartypename = totypename - if type(self._length) == str: - print("extern const %s %s[%s];" % (vartypename, self._varname, self._length)) - else: - print("extern const %s %s[%d];" % (vartypename, self._varname, self._length)) - print("extern const %s %s_len;" % (self.lentypename, self._varname)) - - def _array_entry(self, index, value, comment, fromtype, totype): - if value is None: - return - if fromtype == self.TYPE_STRING: - self._length += 1 - -class StdCHeaderLanguageGenerator(CHeaderLanguageGenerator): - - def __init__(self): - super(StdCHeaderLanguageGenerator, self).__init__("unsigned short", "char *", "unsigned int") - -class GLib2HeaderLanguageGenerator(CHeaderLanguageGenerator): - - def __init__(self): - super(GLib2HeaderLanguageGenerator, self).__init__("guint16", "gchar *", "guint") - -class CppLanguageGenerator(CLanguageGenerator): - - def _array_start(self, varname, length, defvalue, fromtype, totype): - if fromtype == self.TYPE_ENUM: - raise NotImplementedError("Enums not supported as source in C++ generator") - totypename = "const " + self.strtypename if totype == self.TYPE_STRING else self.inttypename - if fromtype == self.TYPE_INT: - print("#include ") - print("extern const std::vector<%s> %s;" % (totypename, varname)); - print("const std::vector<%s> %s = {" % (totypename, varname)) - else: - print("#include ") - print("#include ") - print("extern const std::map %s;" % (totypename, varname)) - print("const std::map %s = {" % (totypename, varname)) - - def _array_end(self, fromtype, totype): - print("};") - - # designated initializers not available in C++ - def _array_entry(self, index, value, comment, fromtype, totype): - if fromtype == self.TYPE_STRING: - return super(CppLanguageGenerator, self)._array_entry(index, value, comment, fromtype, totype) - - if value is None: - print(" 0, /* %s */" % comment) - elif totype == self.TYPE_INT: - print(" 0x%x, /* %s */" % (value, comment)) - elif totype == self.TYPE_ENUM: - print(" %s, /* %s */" % (value, comment)) - else: - print(" \"%s\", /* %s */" % (value, comment)) - -class StdCppLanguageGenerator(CppLanguageGenerator): - - def __init__(self): - super(StdCppLanguageGenerator, self).__init__("unsigned short", "char *", "unsigned int") - -class CppHeaderLanguageGenerator(CHeaderLanguageGenerator): - - def _array_start(self, varname, length, defvalue, fromtype, totype): - if fromtype == self.TYPE_ENUM: - raise NotImplementedError("Enums not supported as source in C++ generator") - totypename = "const " + self.strtypename if totype == self.TYPE_STRING else self.inttypename - if fromtype == self.TYPE_INT: - print("#include ") - print("extern const std::vector<%s> %s;" % (totypename, varname)); - else: - print("#include ") - print("#include ") - print("extern const std::map %s;" % (totypename, varname)) - - def _array_end(self, fromtype, totype): - pass - - # designated initializers not available in C++ - def _array_entry(self, index, value, comment, fromtype, totype): - pass - -class StdCppHeaderLanguageGenerator(CppHeaderLanguageGenerator): - - def __init__(self): - super(StdCppHeaderLanguageGenerator, self).__init__("unsigned short", "char *", "unsigned int") - -class PythonLanguageGenerator(LanguageSrcGenerator): - - def _boilerplate(self, lines): - print("#") - for line in lines: - print("# %s" % line) - print("#") - - def _array_start(self, varname, length, defvalue, fromtype, totype): - if fromtype == self.TYPE_ENUM: - raise NotImplementedError("Enums not supported as source in Python generator") - - if fromtype != self.TYPE_STRING: - print("%s = [" % varname) - else: - print("%s = {" % varname) - - def _array_end(self, fromtype, totype): - if fromtype != self.TYPE_STRING: - print("]") - else: - print("}") - - def _array_entry(self, index, value, comment, fromtype, totype): - if fromtype == self.TYPE_INT: - if value is None: - print(" None, # %s" % (comment)) - elif totype == self.TYPE_INT: - print(" 0x%x, # %s" % (value, comment)) - elif totype == self.TYPE_ENUM: - print(" %s, # %s" % (value, comment)) - else: - print(" \"%s\", # %s" % (value, comment)) - else: - if value is None: - print(" \"%s\": None, # %s" % (index, comment)) - elif totype == self.TYPE_INT: - print(" \"%s\": 0x%x, # %s" % (index, value, comment)) - elif totype == self.TYPE_ENUM: - print(" \"%s\": %s, # %s" % (index, value, comment)) - else: - print(" \"%s\": \"%s\", # %s" % (index, value, comment)) - -class PerlLanguageGenerator(LanguageSrcGenerator): - - def _boilerplate(self, lines): - print("#") - for line in lines: - print("# %s" % line) - print("#") - - def _array_start(self, varname, length, defvalue, fromtype, totype): - if fromtype == self.TYPE_ENUN: - raise NotImplementedError("Enums not supported as source in Python generator") - if fromtype == self.TYPE_INT: - print("my @%s = (" % varname) - else: - print("my %%%s = (" % varname) - - def _array_end(self, fromtype, totype): - print(");") - - def _array_entry(self, index, value, comment, fromtype, totype): - if fromtype == self.TYPE_INT: - if value is None: - print(" undef, # %s" % (comment)) - elif totype == self.TYPE_INT: - print(" 0x%x, # %s" % (value, comment)) - elif totype == self.TYPE_ENUM: - print(" %s, # %s" % (value, comment)) - else: - print(" \"%s\", # %s" % (value, comment)) - else: - if value is None: - print(" \"%s\", undef, # %s" % (index, comment)) - elif totype == self.TYPE_INT: - print(" \"%s\", 0x%x, # %s" % (index, value, comment)) - elif totype == self.TYPE_ENUM: - print(" \"%s\", 0x%x, # %s" % (index, value, comment)) - else: - print(" \"%s\", \"%s\", # %s" % (index, value, comment)) - -class JavaScriptLanguageGenerator(LanguageSrcGenerator): - - def _boilerplate(self, lines): - print("/*") - for line in lines: - print(" * %s" % line) - print("*/") - - def _array_start(self, varname, length, defvalue, fromtype, totype): - print("export default {") - - def _array_end(self, fromtype, totype): - print("};") - - def _array_entry(self, index, value, comment, fromtype, totype): - if value is None: - return - - if fromtype == self.TYPE_INT: - fromfmt = "0x%x" - elif fromtype == self.TYPE_ENUM: - fromfmt = "%s" - else: - fromfmt = "\"%s\"" - - if totype == self.TYPE_INT: - tofmt = "0x%x" - elif totype == self.TYPE_ENUM: - tofmt = "%s" - else: - tofmt = "\"%s\"" - - print((" " + fromfmt + ": " + tofmt + ", /* %s */") % (index, value, comment)) - -class PodLanguageGenerator(LanguageDocGenerator): - - def _boilerplate(self, lines): - print("#") - for line in lines: - print("# %s" % line) - print("#") - - def _array_start_name_doc(self, title, subtitle, namemap): - print("=head1 NAME") - print("") - print("%s - %s" % (title, subtitle)) - print("") - print("=head1 DESCRIPTION") - print("") - print("List of %s key code names, with corresponding key code values" % namemap) - print("") - print("=over 4") - print("") - - def _array_start_code_doc(self, title, subtitle, codemap, namemap): - print("=head1 NAME") - print("") - print("%s - %s" % (title, subtitle)) - print("") - print("=head1 DESCRIPTION") - print("") - print("List of %s key code values, with corresponding %s key code names" % (codemap, namemap)) - print("") - print("=over 4") - print("") - - def _array_end(self): - print("=back") - print("") - - def _array_name_entry(self, value, name): - print("=item %s" % name) - print("") - print("Key value %d (0x%x)" % (value, value)) - print("") - - def _array_code_entry(self, value, name): - print("=item %d (0x%x)" % (value, value)) - print("") - print("Key name %s" % name) - print("") - -class RSTLanguageGenerator(LanguageDocGenerator): - - def _boilerplate(self, lines): - print("..") - for line in lines: - print(" %s" % line) - print("") - - def _array_start_name_doc(self, title, subtitle, namemap): - print("=" * len(title)) - print(title) - print("=" * len(title)) - print("") - print("-" * len(subtitle)) - print(subtitle) - print("-" * len(subtitle)) - print("") - print(":Manual section: 7") - print(":Manual group: Virtualization Support") - print("") - print("DESCRIPTION") - print("===========") - print("List of %s key code names, with corresponding key code values" % namemap) - print("") - - def _array_start_code_doc(self, title, subtitle, codemap, namemap): - print("=" * len(title)) - print(title) - print("=" * len(title)) - print("") - print("-" * len(subtitle)) - print(subtitle) - print("-" * len(subtitle)) - print("") - print(":Manual section: 7") - print(":Manual group: Virtualization Support") - print("") - print("DESCRIPTION") - print("===========") - print("List of %s key code values, with corresponding %s key code names" % (codemap, namemap)) - print("") - - def _array_end(self): - print("") - - def _array_name_entry(self, value, name): - print("* %s" % name) - print("") - print(" Key value %d (0x%x)" % (value, value)) - print("") - - def _array_code_entry(self, value, name): - print("* %d (0x%x)" % (value, value)) - print("") - print(" Key name %s" % name) - print("") - -SRC_GENERATORS = { - "stdc": StdCLanguageGenerator(), - "stdc-header": StdCHeaderLanguageGenerator(), - "stdc++": StdCppLanguageGenerator(), - "stdc++-header": StdCppHeaderLanguageGenerator(), - "glib2": GLib2LanguageGenerator(), - "glib2-header": GLib2HeaderLanguageGenerator(), - "python2": PythonLanguageGenerator(), - "python3": PythonLanguageGenerator(), - "perl": PerlLanguageGenerator(), - "js": JavaScriptLanguageGenerator(), -} -DOC_GENERATORS = { - "pod": PodLanguageGenerator(), - "rst": RSTLanguageGenerator(), -} - -def code_map(args): - database = Database() - database.load(args.keymaps) - - cliargs = ["keymap-gen", "code-map", "--lang=%s" % args.lang] - if args.varname is not None: - cliargs.append("--varname=%s" % args.varname) - cliargs.extend(["keymaps.csv", args.frommapname, args.tomapname]) - SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) - - SRC_GENERATORS[args.lang].generate_code_map(args.varname, database, args.frommapname, args.tomapname) - -def code_table(args): - database = Database() - database.load(args.keymaps) - - cliargs = ["keymap-gen", "code-table", "--lang=%s" % args.lang] - if args.varname is not None: - cliargs.append("--varname=%s" % args.varname) - cliargs.extend(["keymaps.csv", args.mapname]) - SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) - - SRC_GENERATORS[args.lang].generate_code_table(args.varname, database, args.mapname) - -def name_map(args): - database = Database() - database.load(args.keymaps) - - cliargs = ["keymap-gen", "name-map", "--lang=%s" % args.lang] - if args.varname is not None: - cliargs.append("--varname=%s" % args.varname) - cliargs.extend(["keymaps.csv", args.frommapname, args.tomapname]) - SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) - - SRC_GENERATORS[args.lang].generate_name_map(args.varname, database, args.frommapname, args.tomapname) - -def name_table(args): - database = Database() - database.load(args.keymaps) - - - cliargs = ["keymap-gen", "name-table", "--lang=%s" % args.lang] - if args.varname is not None: - cliargs.append("--varname=%s" % args.varname) - cliargs.extend(["keymaps.csv", args.mapname]) - SRC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) - - SRC_GENERATORS[args.lang].generate_name_table(args.varname, database, args.mapname) - -def code_docs(args): - database = Database() - database.load(args.keymaps) - - - cliargs = ["keymap-gen", "code-docs", "--lang=%s" % args.lang] - if args.title is not None: - cliargs.append("--title=%s" % args.title) - if args.subtitle is not None: - cliargs.append("--subtitle=%s" % args.subtitle) - cliargs.extend(["keymaps.csv", args.mapname]) - DOC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) - - DOC_GENERATORS[args.lang].generate_code_docs(args.title, args.subtitle, database, args.mapname) - -def name_docs(args): - database = Database() - database.load(args.keymaps) - - - cliargs = ["keymap-gen", "name-docs", "--lang=%s" % args.lang] - if args.title is not None: - cliargs.append("--title=%s" % args.title) - if args.subtitle is not None: - cliargs.append("--subtitle=%s" % args.subtitle) - cliargs.extend(["keymaps.csv", args.mapname]) - DOC_GENERATORS[args.lang].generate_header(database, " ".join(cliargs)) - - DOC_GENERATORS[args.lang].generate_name_docs(args.title, args.subtitle, database, args.mapname) - -def usage(): - print ("Please select a command:") - print (" 'code-map', 'code-table', 'name-map', 'name-table', 'docs'") - sys.exit(1) - -def main(): - parser = argparse.ArgumentParser() - - subparsers = parser.add_subparsers(help="sub-command help") - - codemapparser = subparsers.add_parser("code-map", help="Generate a mapping between code tables") - codemapparser.add_argument("--varname", default=None, help="Data variable name") - codemapparser.add_argument("--lang", default="stdc", - help="Output language (%s)" % ( - ",".join(SRC_GENERATORS.keys()))) - codemapparser.add_argument("keymaps", help="Path to keymap CSV data file") - codemapparser.add_argument("frommapname", help="Source code table name") - codemapparser.add_argument("tomapname", help="Target code table name") - codemapparser.set_defaults(func=code_map) - - codetableparser = subparsers.add_parser("code-table", help="Generate a flat code table") - codetableparser.add_argument("--lang", default="stdc", - help="Output language (%s)" % ( - ",".join(SRC_GENERATORS.keys()))) - codetableparser.add_argument("--varname", default=None, help="Data variable name") - codetableparser.add_argument("keymaps", help="Path to keymap CSV data file") - codetableparser.add_argument("mapname", help="Code table name") - codetableparser.set_defaults(func=code_table) - - namemapparser = subparsers.add_parser("name-map", help="Generate a mapping to names") - namemapparser.add_argument("--lang", default="stdc", - help="Output language (%s)" % ( - ",".join(SRC_GENERATORS.keys()))) - namemapparser.add_argument("--varname", default=None, help="Data variable name") - namemapparser.add_argument("keymaps", help="Path to keymap CSV data file") - namemapparser.add_argument("frommapname", help="Source code table name") - namemapparser.add_argument("tomapname", help="Target name table name") - namemapparser.set_defaults(func=name_map) - - nametableparser = subparsers.add_parser("name-table", help="Generate a flat name table") - nametableparser.add_argument("--lang", default="stdc", - help="Output language, (%s)" % ( - ",".join(SRC_GENERATORS.keys()))) - nametableparser.add_argument("--varname", default=None, help="Data variable name") - nametableparser.add_argument("keymaps", help="Path to keymap CSV data file") - nametableparser.add_argument("mapname", help="Name table name") - nametableparser.set_defaults(func=name_table) - - codedocsparser = subparsers.add_parser("code-docs", help="Generate code documentation") - codedocsparser.add_argument("--lang", default="pod", - help="Output language (%s)" % ( - ",".join(DOC_GENERATORS.keys()))) - codedocsparser.add_argument("--title", default=None, help="Document title") - codedocsparser.add_argument("--subtitle", default=None, help="Document subtitle") - codedocsparser.add_argument("keymaps", help="Path to keymap CSV data file") - codedocsparser.add_argument("mapname", help="Code table name") - codedocsparser.set_defaults(func=code_docs) - - namedocsparser = subparsers.add_parser("name-docs", help="Generate name documentation") - namedocsparser.add_argument("--lang", default="pod", - help="Output language (%s)" % ( - ",".join(DOC_GENERATORS.keys()))) - namedocsparser.add_argument("--title", default=None, help="Document title") - namedocsparser.add_argument("--subtitle", default=None, help="Document subtitle") - namedocsparser.add_argument("keymaps", help="Path to keymap CSV data file") - namedocsparser.add_argument("mapname", help="Name table name") - namedocsparser.set_defaults(func=name_docs) - - args = parser.parse_args() - if hasattr(args, "func"): - args.func(args) - else: - usage() - - -main() -- 2.39.2 From alexandre.derumier at groupe-cyllene.com Tue Oct 17 16:48:09 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Tue, 17 Oct 2023 14:48:09 +0000 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: <160a8e8ddfa2ae6b1213543d11e1df521753d1de.camel@groupe-cyllene.com> Hi Stefan, Thanks for sharing ! I'll try to deeply test it this week or next week. Maybe try to see if we can use pve ipam as cache in front of external ipam. -------- Message initial -------- De: Stefan Hanreich R?pondre ?: Proxmox VE development discussion ?: pve-devel at lists.proxmox.com Objet: [pve-devel] [WIP v2 cluster/network/manager/qemu- server/container 00/10] Add support for DHCP servers to SDN Date: 17/10/2023 15:54:57 This is a WIP patch series, since I will be gone for 3 weeks and wanted to share my current progress with the DHCP support for SDN. This patch series adds support for automatically deploying dnsmasq as a DHCP server to a simple SDN Zone. While certainly not 100% polished on some ends (looking at restarting systemd services in particular), the general idea behind the mechanism shows. I wanted to gather some feedback on how I approached designing the plugins and the config regeneration process before comitting to this design by creating an API and UI around it. You need to install dnsmasq (and disable it afterwards): ? apt install dnsmasq && systemctl disable --now dnsmasq You can use the following example configuration for deploying a DHCP server in a SDN subnet: /etc/pve/sdn/dhcp.cfg: ? dnsmasq: nat /etc/pve/sdn/zones.cfg: ? simple: DHCPNAT ????????? ipam pve /etc/pve/sdn/vnets.cfg: ? vnet: dhcpnat ????????? zone DHCPNAT /etc/pve/sdn/subnets.cfg: ? subnet: DHCPNAT-10.1.0.0-16 ????????? vnet dhcpnat ????????? dhcp-dns-server 10.1.0.1 ????????? dhcp-range server=nat,start-address=10.1.0.100,end- address=10.1.0.200 ????????? gateway 10.1.0.1 ????????? snat 1 Then apply the SDN configuration: ? pvesh set /cluster/sdn You need to apply the SDN configuration once after adding the dhcp- range lines to the configuration, since the running configuration is used for managing DHCP. It will not work otherwise! For testing it can be helpful to monitor the following files (e.g. with watch) to find out what is happening ? * /etc/dnsmasq.d//ethers (on each node) ? * /etc/pve/priv/ipam.db Changes from v1 -> v2: ? * added hooks for handling DHCP when starting / stopping / .. VMs and CTs ? * Get an IP from IPAM and register that IP in the DHCP server ??? (pve only for now) ? * remove lease-time, since it is now infinite and managed by the VM lifecycle ? * add hooks for setting & deleting DHCP mappings to DHCP plugins ? * modified interface of the abstract class to reflect new requirements ? * added helpers in existing SDN classes ? * simplified DHCP configuration settings pve-cluster: Stefan Hanreich (1): ? cluster files: add dhcp.cfg ?src/PVE/Cluster.pm? | 1 + ?src/pmxcfs/status.c | 1 + ?2 files changed, 2 insertions(+) pve-network: Stefan Hanreich (6): ? subnets: vnets: preparations for DHCP plugins ? dhcp: add abstract class for DHCP plugins ? dhcp: subnet: add DHCP options to subnet configuration ? dhcp: add DHCP plugin for dnsmasq ? ipam: Add helper methods for DHCP to PVE IPAM ? dhcp: regenerate config for DHCP servers on reload ?debian/control???????????????????????? |?? 1 + ?src/PVE/Network/SDN.pm???????????????? |? 11 +- ?src/PVE/Network/SDN/Dhcp.pm??????????? | 192 +++++++++++++++++++++++++ ?src/PVE/Network/SDN/Dhcp/Dnsmasq.pm??? | 186 ++++++++++++++++++++++++ ?src/PVE/Network/SDN/Dhcp/Makefile????? |?? 8 ++ ?src/PVE/Network/SDN/Dhcp/Plugin.pm???? |? 83 +++++++++++ ?src/PVE/Network/SDN/Ipams/PVEPlugin.pm |? 64 +++++++++ ?src/PVE/Network/SDN/Makefile?????????? |?? 3 +- ?src/PVE/Network/SDN/SubnetPlugin.pm??? |? 32 +++++ ?src/PVE/Network/SDN/Subnets.pm???????? |? 43 ++++-- ?src/PVE/Network/SDN/Vnets.pm?????????? |? 27 ++-- ?11 files changed, 622 insertions(+), 28 deletions(-) ?create mode 100644 src/PVE/Network/SDN/Dhcp.pm ?create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm ?create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile ?create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm pve-manager: Stefan Hanreich (1): ? sdn: regenerate DHCP config on reload ?PVE/API2/Network.pm | 1 + ?1 file changed, 1 insertion(+) qemu-server: Stefan Hanreich (1): ? sdn: dhcp: add DHCP setup to vm-network-scripts ?PVE/QemuServer.pm???????????????? | 14 ++++++++++++++ ?vm-network-scripts/pve-bridge???? |? 3 +++ ?vm-network-scripts/pve-bridgedown | 19 +++++++++++++++++++ ?3 files changed, 36 insertions(+) pve-container: Stefan Hanreich (1): ? sdn: dhcp: setup DHCP mappings in LXC hooks ?src/PVE/LXC.pm??????????? | 10 ++++++++++ ?src/lxc-pve-poststop-hook |? 1 + ?src/lxc-pve-prestart-hook |? 9 +++++++++ ?3 files changed, 20 insertions(+) Summary over all repositories: ? 20 files changed, 681 insertions(+), 28 deletions(-) From s.hanreich at proxmox.com Tue Oct 17 18:04:24 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 18:04:24 +0200 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: Some additional things we've discussed off-list: Currently for VMs Migration & Hibernation are not working - everything else in the lifecycle of VMs/CTs should be covered. For Migration: It currently creates an additional mapping in the IPAM and doesn't delete the existing mapping from the DHCP server config. I'd say we change this to upsert instead of add (for both IPAM and DHCP Plugins). This means when adding a mapping for a VM, check if there is already one, and then return that mapping without doing anything. Then we just need to delete the existing mapping from the source during the migration, which could simply be done somewhere in QemuMigrate. Having upsert, instead of add, would also make implementations with distributed DHCP servers like kea easier. For Hibernation: Having Upsert would solve the issue with Hibernation as well. Here we need to make sure to not delete the entry from the IPAM, since we use a memory snapshot rather than using the guest's hibernation feature. That means the DHCP lease 'persists' in the VM. We will also need to expose the functionality via the Web UI, for that I had the following things in mind: * Add Create/Edit/Delete DHCP to either `Options` or in a new IPAM/DHCP panel (see below) * Add a tree view of the current PVE IPAM state, similar to the resource mapping, as a new panel * add the `dhcp-range` fields to the Subnet Edit Dialog (possibly in a new tab in the edit dialogue) Another thing: What happens when a user changes the MAC address via the UI? I'd either disallow it completely or we need to update the DHCP configuration files and IPAM On 10/17/23 15:54, Stefan Hanreich wrote: > This is a WIP patch series, since I will be gone for 3 weeks and wanted to > share my current progress with the DHCP support for SDN. > > This patch series adds support for automatically deploying dnsmasq as a DHCP > server to a simple SDN Zone. > > While certainly not 100% polished on some ends (looking at restarting systemd > services in particular), the general idea behind the mechanism shows. I wanted > to gather some feedback on how I approached designing the plugins and the > config regeneration process before comitting to this design by creating an API > and UI around it. > > You need to install dnsmasq (and disable it afterwards): > > apt install dnsmasq && systemctl disable --now dnsmasq > > > You can use the following example configuration for deploying a DHCP server in > a SDN subnet: > > /etc/pve/sdn/dhcp.cfg: > > dnsmasq: nat > > > /etc/pve/sdn/zones.cfg: > > simple: DHCPNAT > ipam pve > > > /etc/pve/sdn/vnets.cfg: > > vnet: dhcpnat > zone DHCPNAT > > > /etc/pve/sdn/subnets.cfg: > > subnet: DHCPNAT-10.1.0.0-16 > vnet dhcpnat > dhcp-dns-server 10.1.0.1 > dhcp-range server=nat,start-address=10.1.0.100,end-address=10.1.0.200 > gateway 10.1.0.1 > snat 1 > > > Then apply the SDN configuration: > > pvesh set /cluster/sdn > > You need to apply the SDN configuration once after adding the dhcp-range lines > to the configuration, since the running configuration is used for managing > DHCP. It will not work otherwise! > > For testing it can be helpful to monitor the following files (e.g. with watch) > to find out what is happening > * /etc/dnsmasq.d//ethers (on each node) > * /etc/pve/priv/ipam.db > > Changes from v1 -> v2: > * added hooks for handling DHCP when starting / stopping / .. VMs and CTs > * Get an IP from IPAM and register that IP in the DHCP server > (pve only for now) > * remove lease-time, since it is now infinite and managed by the VM lifecycle > * add hooks for setting & deleting DHCP mappings to DHCP plugins > * modified interface of the abstract class to reflect new requirements > * added helpers in existing SDN classes > * simplified DHCP configuration settings > > > > pve-cluster: > > Stefan Hanreich (1): > cluster files: add dhcp.cfg > > src/PVE/Cluster.pm | 1 + > src/pmxcfs/status.c | 1 + > 2 files changed, 2 insertions(+) > > > pve-network: > > Stefan Hanreich (6): > subnets: vnets: preparations for DHCP plugins > dhcp: add abstract class for DHCP plugins > dhcp: subnet: add DHCP options to subnet configuration > dhcp: add DHCP plugin for dnsmasq > ipam: Add helper methods for DHCP to PVE IPAM > dhcp: regenerate config for DHCP servers on reload > > debian/control | 1 + > src/PVE/Network/SDN.pm | 11 +- > src/PVE/Network/SDN/Dhcp.pm | 192 +++++++++++++++++++++++++ > src/PVE/Network/SDN/Dhcp/Dnsmasq.pm | 186 ++++++++++++++++++++++++ > src/PVE/Network/SDN/Dhcp/Makefile | 8 ++ > src/PVE/Network/SDN/Dhcp/Plugin.pm | 83 +++++++++++ > src/PVE/Network/SDN/Ipams/PVEPlugin.pm | 64 +++++++++ > src/PVE/Network/SDN/Makefile | 3 +- > src/PVE/Network/SDN/SubnetPlugin.pm | 32 +++++ > src/PVE/Network/SDN/Subnets.pm | 43 ++++-- > src/PVE/Network/SDN/Vnets.pm | 27 ++-- > 11 files changed, 622 insertions(+), 28 deletions(-) > create mode 100644 src/PVE/Network/SDN/Dhcp.pm > create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm > create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile > create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm > > > pve-manager: > > Stefan Hanreich (1): > sdn: regenerate DHCP config on reload > > PVE/API2/Network.pm | 1 + > 1 file changed, 1 insertion(+) > > > qemu-server: > > Stefan Hanreich (1): > sdn: dhcp: add DHCP setup to vm-network-scripts > > PVE/QemuServer.pm | 14 ++++++++++++++ > vm-network-scripts/pve-bridge | 3 +++ > vm-network-scripts/pve-bridgedown | 19 +++++++++++++++++++ > 3 files changed, 36 insertions(+) > > > pve-container: > > Stefan Hanreich (1): > sdn: dhcp: setup DHCP mappings in LXC hooks > > src/PVE/LXC.pm | 10 ++++++++++ > src/lxc-pve-poststop-hook | 1 + > src/lxc-pve-prestart-hook | 9 +++++++++ > 3 files changed, 20 insertions(+) > > > Summary over all repositories: > 20 files changed, 681 insertions(+), 28 deletions(-) > From s.hanreich at proxmox.com Tue Oct 17 18:05:55 2023 From: s.hanreich at proxmox.com (Stefan Hanreich) Date: Tue, 17 Oct 2023 18:05:55 +0200 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: <160a8e8ddfa2ae6b1213543d11e1df521753d1de.camel@groupe-cyllene.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> <160a8e8ddfa2ae6b1213543d11e1df521753d1de.camel@groupe-cyllene.com> Message-ID: > Maybe try to see if we can use pve ipam as cache in front of external > ipam. Yes, it would also be cool if you could look at implementing the two newly added methods from the PVEPlugin for Netbox / Phpipam, since you have more experience with those. I also looked into merging those two methods, but haven't really found an elegant solution which is why I left them as separate methods for now. Kind Regards From ykonotopov at gnome.org Tue Oct 17 18:19:47 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Tue, 17 Oct 2023 20:19:47 +0400 Subject: [PATCH v3 storage 1/1] fix #254: iscsi: add support for multipath iscsi targets Message-ID: <20231017161948.139795-1-ykonotopov@gnome.org> Changes since v2: * custom configuration file is removed in favor of open-iscsi query * restored `portal` property format since we should not rely on initial configuration but should use discovered configuration instead * changed check_connection() to query all available portals * style fixes Changes since v1: * added configuration file that stores discovered portals * implemented iscsi login in case there missing sessions for any of portals * style fixes * use existent `-list` property format instead of custom one * use PVE::Tools::split_list() instead of custom split function Yuri Konotopov (1): fix #254: iscsi: add support for multipath iscsi targets PVE/Storage.pm | 2 +- PVE/Storage/ISCSIPlugin.pm | 117 ++++++++++++++++++++++++++++--------- 2 files changed, 89 insertions(+), 30 deletions(-) -- 2.41.0 From ykonotopov at gnome.org Tue Oct 17 18:19:48 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Tue, 17 Oct 2023 20:19:48 +0400 Subject: [PATCH v3 storage 1/1] fix #254: iscsi: add support for multipath iscsi targets In-Reply-To: <20231017161948.139795-1-ykonotopov@gnome.org> References: <20231017161948.139795-1-ykonotopov@gnome.org> Message-ID: <20231017161948.139795-2-ykonotopov@gnome.org> With this patch Proxmox now tries to login to all discovered portals in case some of them are not logged yet. In case of multipath configuration when initially configured portal is missing for some reason Proxmox don't lose iscsi storage now and can succesfully restore iscsi connection between reboots. Signed-off-by: Yuri Konotopov --- PVE/Storage.pm | 2 +- PVE/Storage/ISCSIPlugin.pm | 117 ++++++++++++++++++++++++++++--------- 2 files changed, 89 insertions(+), 30 deletions(-) diff --git a/PVE/Storage.pm b/PVE/Storage.pm index cec3996..87755ac 100755 --- a/PVE/Storage.pm +++ b/PVE/Storage.pm @@ -1433,7 +1433,7 @@ sub scan_iscsi { die "unable to parse/resolve portal address '${portal_in}'\n"; } - return PVE::Storage::ISCSIPlugin::iscsi_discovery($portal); + return PVE::Storage::ISCSIPlugin::iscsi_discovery([ $portal ]); } sub storage_default_format { diff --git a/PVE/Storage/ISCSIPlugin.pm b/PVE/Storage/ISCSIPlugin.pm index a79fcb0..1d47f3a 100644 --- a/PVE/Storage/ISCSIPlugin.pm +++ b/PVE/Storage/ISCSIPlugin.pm @@ -18,6 +18,9 @@ use base qw(PVE::Storage::Plugin); my $ISCSIADM = '/usr/bin/iscsiadm'; $ISCSIADM = undef if ! -X $ISCSIADM; +# Example: 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f +my $ISCSI_TARGET_RE = qr/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/m; + sub check_iscsi_support { my $noerr = shift; @@ -45,11 +48,12 @@ sub iscsi_session_list { eval { run_command($cmd, errmsg => 'iscsi session scan failed', outfunc => sub { my $line = shift; - - if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)(\s+\S+)?\s*$/) { - my ($session, $target) = ($1, $2); + # example: tcp: [1] 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f (non-flash) + if ($line =~ m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)(\s+\S+)?\s*$/) { + my ($session_id, $portal, $target) = ($1, $2, $3); # there can be several sessions per target (multipath) - push @{$res->{$target}}, $session; + my %session = ( session_id => $session_id, portal => $portal ); + push @{$res->{$target}}, \%session; } }); }; @@ -68,42 +72,77 @@ sub iscsi_test_portal { return PVE::Network::tcp_ping($server, $port || 3260, 2); } -sub iscsi_discovery { - my ($portal) = @_; +sub iscsi_portals { + my ($target, $portal_in) = @_; check_iscsi_support (); - my $res = {}; - return $res if !iscsi_test_portal($portal); # fixme: raise exception here? + my $res = []; + my $cmd = [$ISCSIADM, '--mode', 'node']; + eval { + run_command($cmd, outfunc => sub { + my $line = shift; - my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; - run_command($cmd, outfunc => sub { - my $line = shift; + if ($line =~ $ISCSI_TARGET_RE) { + my ($portal, $portal_target) = ($1, $2); + if ($portal_target eq $target) { + push @{$res}, $portal; + } + } + }); + }; - if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { - my $portal = $1; - my $target = $2; - # one target can have more than one portal (multipath). - push @{$res->{$target}}, $portal; - } - }); + if ($@) { + warn $@; + return [ $portal_in ]; + } + + return $res; +} + +sub iscsi_discovery { + my ($portals) = @_; + + check_iscsi_support (); + + my $res = {}; + for my $portal ($portals->@*) { + next if !iscsi_test_portal($portal); # fixme: raise exception here? + + my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; + eval { + run_command($cmd, outfunc => sub { + my $line = shift; + + if ($line =~ $ISCSI_TARGET_RE) { + my ($portal, $target) = ($1, $2); + # one target can have more than one portal (multipath) + # and sendtargets should return all of them in single call + push @{$res->{$target}}, $portal; + } + }); + }; + + # In case of multipath we want to exit on any portal available + last if !$@; + } return $res; } sub iscsi_login { - my ($target, $portal_in) = @_; + my ($target, $portals) = @_; check_iscsi_support(); - eval { iscsi_discovery($portal_in); }; + eval { iscsi_discovery($portals); }; warn $@ if $@; run_command([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login']); } sub iscsi_logout { - my ($target, $portal) = @_; + my ($target) = @_; check_iscsi_support(); @@ -133,7 +172,7 @@ sub iscsi_session_rescan { } foreach my $session (@$session_list) { - my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session, '--rescan']; + my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session->{session_id}, '--rescan']; eval { run_command($cmd, outfunc => sub {}); }; warn $@ if $@; } @@ -379,14 +418,28 @@ sub activate_storage { return if !check_iscsi_support(1); - my $session = iscsi_session($cache, $scfg->{target}); + my $sessions = iscsi_session($cache, $scfg->{target}); + my $portals = iscsi_portals($scfg->{target}, $scfg->{portal}); + my $do_login = !defined($sessions); - if (!defined ($session)) { - eval { iscsi_login($scfg->{target}, $scfg->{portal}); }; + if (!$do_login) { + # We should check that sessions for all portals are available + my $session_portals = [ map { $_->{portal} } (@$sessions) ]; + + for my $portal (@$portals) { + if (!grep(/^\Q$portal\E$/, @$session_portals)) { + $do_login = 1; + last; + } + } + } + + if ($do_login) { + eval { iscsi_login($scfg->{target}, $portals); }; warn $@ if $@; } else { # make sure we get all devices - iscsi_session_rescan($session); + iscsi_session_rescan($sessions); } } @@ -396,15 +449,21 @@ sub deactivate_storage { return if !check_iscsi_support(1); if (defined(iscsi_session($cache, $scfg->{target}))) { - iscsi_logout($scfg->{target}, $scfg->{portal}); + iscsi_logout($scfg->{target}); } } sub check_connection { my ($class, $storeid, $scfg) = @_; - my $portal = $scfg->{portal}; - return iscsi_test_portal($portal); + my $portals = iscsi_portals($scfg->{target}, $scfg->{portal}); + + for my $portal (@$portals) { + my $result = iscsi_test_portal($portal); + return $result if $result; + } + + return 0; } sub volume_resize { -- 2.41.0 From t.lamprecht at proxmox.com Tue Oct 17 18:28:22 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 17 Oct 2023 18:28:22 +0200 Subject: [pve-devel] [RFC] towards automated integration testing In-Reply-To: <9406389e-da89-475e-b8ad-e37efb72df10@proxmox.com> References: <1bb25817-66e7-406a-bd4b-0699de6cba31@proxmox.com> <832bc039-57d0-4d27-ac48-721c5c82af83@proxmox.com> <125370ed-9b55-42d2-b544-28683a17be79@proxmox.com> <9406389e-da89-475e-b8ad-e37efb72df10@proxmox.com> Message-ID: <57929886-9ef9-45b9-94c4-f0f66dc2532a@proxmox.com> Am 17/10/2023 um 14:33 schrieb Lukas Wagner: > On 10/17/23 08:35, Thomas Lamprecht wrote: >> ?From top of my head I'd rather do some attribute based dependency >> annotation, so that one can depend on single tests, or whole fixture >> on others single tests or whole fixture. >> > > The more thought I spend on it, the more I believe that inter-testcase > deps should be avoided as much as possible. In unit testing, (hidden) We don't plan unit testing here though and the dependencies I proposed are the contrary from hidden, rather explicit annotated ones. > dependencies between tests are in my experience the no. 1 cause of > flaky tests, and I see no reason why this would not also apply for > end-to-end integration testing. Any source on that being the no 1 source of flaky tests? IMO that should not make any difference, in the end you just allow better reuse through composition of other tests (e.g., migration builds upon clustering *set up*, not tests, if I just want to run migration I can do clustering setup without executing its tests). Not providing that could also mean that one has to move all logic in the test-script, resulting in a single test per "fixture", reducing granularity and parallelity of some running tests. I also think that > I'd suggest to only allow test cases to depend on fixtures. The fixtures > themselves could have setup/teardown hooks that allow setting up and > cleaning up a test scenario. If needed, we could also have something > like 'fixture inheritance', where a fixture can 'extend' another, > supplying additional setup/teardown. > Example: the 'outermost' or 'parent' fixture might define that we > want a 'basic PVE installation' with the latest .debs deployed, > while another fixture that inherits from that one might set up a > storage of a certain type, useful for all tests that require specific > that type of storage. Maybe our disagreement stems mostly from different design pictures in our head, I probably am a bit less fixed (heh) on the fixtures, or at least the naming of that term and might use test system, or intra test system when for your design plan fixture would be the better word. > On the other hand, instead of inheritance, a 'role/trait'-based system > might also work (composition >>> inheritance, after all) - and > maybe that also aligns better with the 'properties' mentioned in > your other mail (I mean this here:? "ostype=win*", "memory>=10G"). > > This is essentially a very similar pattern as in numerous other testing > frameworks (xUnit, pytest, etc.); I think it makes sense to > build upon this battle-proven approach. Those are all unit testing tools though that we do already in the sources and IIRC those do not really provide what we need here. While starting out simple(r) and avoiding too much complexity has certainly it's merits, I don't think we should try to draw/align too many parallels with those tools here for us. > > Regarding execution order, I'd now even suggest the polar opposite of my > prior idea. Instead of enforcing some execution order, we could also > actively shuffle execution order from run to run, at least for tests > using the same fixture. > The seed used for the RNG should be put into the test > report and could also be provided via a flag to the test runner, in case > we need to repeat a specific test sequence . Hmm, this also has a chance to make tests flaky and get a bit annoying, like perl's hash scrambling, but not a bad idea, I'd just not do that by default on the "armed" test system that builds on package/git/patch updates, but possibly in addition with reporting turned off like the double tests for idempotency-checking I wrote in my previous message. > In that way, the runner would actively help us to hunt down > hidden inter-TC deps, making our test suite hopefully less brittle and > more robust in the long term. Agree, but as mentioned above I'd not enable it by default on the dev facing automated systems, but possibly for manual runs from devs and a separate "test-test-system" ^^ In summary, the most important points for me is a decoupled test-system from the automation system that can manage it, ideally such that I can decide relatively flexible on manual runs, IMO that should not be to much work and it guarantees for clean cut APIs from which future development, or integration surely will benefit too. The rest is possibly hard to determine clearly on this stage, as it's easy (at least for me) to get lost in different understandings of terms and design perception, but hard to convey those very clearly about "pipe dreams", so at this stage I'll cede to add discussion churn until there's something more concrete that I can grasp on my terms (through reading/writing code), but that should not deter others from giving input still while at this stage. Thanks for your work on this. - Thomas From alexandre.derumier at groupe-cyllene.com Tue Oct 17 23:00:43 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Tue, 17 Oct 2023 21:00:43 +0000 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: References: <20231017135507.2220948-1-s.hanreich@proxmox.com> <160a8e8ddfa2ae6b1213543d11e1df521753d1de.camel@groupe-cyllene.com> Message-ID: <086c25d24dda53a60129ba0dd0d5494680b15ab1.camel@groupe-cyllene.com> -------- Message initial -------- De: Stefan Hanreich ?: Proxmox VE development discussion , "DERUMIER, Alexandre" Objet: Re: [pve-devel] [WIP v2 cluster/network/manager/qemu- server/container 00/10] Add support for DHCP servers to SDN Date: 17/10/2023 18:05:55 > Maybe try to see if we can use pve ipam as cache in front of external > ipam. >>Yes, it would also be cool if you could look at implementing the two >>newly added methods from the PVEPlugin for Netbox / Phpipam, since >>you >>have more experience with those. >>I also looked into merging those two methods, but haven't really >>found >>an elegant solution which is why I left them as separate methods for >>now. >> >>Kind Regards Yes, sure , no problem ! (I don't have read your code yet) From c.heiss at proxmox.com Wed Oct 18 10:17:19 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 18 Oct 2023 10:17:19 +0200 Subject: [pve-devel] [PATCH installer] run env: always re-create run env file in test mode Message-ID: <20231018081752.214251-1-c.heiss@proxmox.com> When debugging or otherwise deliberately running the `dump-env` low-level installer command in test mode, chances are that you'd want the run env file to be re-created as well. No impact on the normal installation flow. Signed-off-by: Christoph Heiss --- Noticed over time that I have to run `rm [..]/run/proxmox-installer/run-env-info.json` way to often. Also, the fact that this file is cached is probably not that obvious to anyone who doesn't know the installer by heart. Proxmox/Install/RunEnv.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Proxmox/Install/RunEnv.pm b/Proxmox/Install/RunEnv.pm index 9878f55..19b5387 100644 --- a/Proxmox/Install/RunEnv.pm +++ b/Proxmox/Install/RunEnv.pm @@ -241,7 +241,7 @@ my sub detect_country_tracing_to : prototype($$) { sub query_installation_environment : prototype() { # check first if somebody already cached this for us and re-use that my $run_env_file = Proxmox::Install::ISOEnv::get('run-env-cache-file'); - if (-f "$run_env_file") { + if (-f "$run_env_file" && !Proxmox::Install::ISOEnv::is_test_mode()) { log_info("re-using cached runtime env from $run_env_file"); my $cached_env = eval { my $run_env_raw = Proxmox::Sys::File::file_read_all($run_env_file); -- 2.42.0 From l.wagner at proxmox.com Wed Oct 18 10:43:43 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Wed, 18 Oct 2023 10:43:43 +0200 Subject: [pve-devel] [RFC] towards automated integration testing In-Reply-To: <57929886-9ef9-45b9-94c4-f0f66dc2532a@proxmox.com> References: <1bb25817-66e7-406a-bd4b-0699de6cba31@proxmox.com> <832bc039-57d0-4d27-ac48-721c5c82af83@proxmox.com> <125370ed-9b55-42d2-b544-28683a17be79@proxmox.com> <9406389e-da89-475e-b8ad-e37efb72df10@proxmox.com> <57929886-9ef9-45b9-94c4-f0f66dc2532a@proxmox.com> Message-ID: <62512278-42a0-4994-95aa-53904a953034@proxmox.com> On 10/17/23 18:28, Thomas Lamprecht wrote: > Am 17/10/2023 um 14:33 schrieb Lukas Wagner: >> On 10/17/23 08:35, Thomas Lamprecht wrote: >>> ?From top of my head I'd rather do some attribute based dependency >>> annotation, so that one can depend on single tests, or whole fixture >>> on others single tests or whole fixture. >>> >> >> The more thought I spend on it, the more I believe that inter-testcase >> deps should be avoided as much as possible. In unit testing, (hidden) > > We don't plan unit testing here though and the dependencies I proposed > are the contrary from hidden, rather explicit annotated ones. > >> dependencies between tests are in my experience the no. 1 cause of >> flaky tests, and I see no reason why this would not also apply for >> end-to-end integration testing. > > Any source on that being the no 1 source of flaky tests? IMO that > should not make any difference, in the end you just allow better Of course I don't have bullet-proof evidence for the 'no. 1' claim, but it's just my personal experience, which comes partly from a former job (where was I coincidentally also responsible for setting up automated testing ;) - there it was for a firmware project), partly from the work I did for my master's thesis (which was also in the broader area of software testing). I would say it's just the consequence of having multiple test cases manipulating a shared, stateful entity, be it directly or indirectly via side effects. Things get of course even more difficult and messy if concurrent test execution enters the picture ;) > reuse through composition of other tests (e.g., migration builds > upon clustering *set up*, not tests, if I just want to run > migration I can do clustering setup without executing its tests). > > Not providing that could also mean that one has to move all logic > in the test-script, resulting in a single test per "fixture", reducing > granularity and parallelity of some running tests. > > I also think that > >> I'd suggest to only allow test cases to depend on fixtures. The fixtures >> themselves could have setup/teardown hooks that allow setting up and >> cleaning up a test scenario. If needed, we could also have something >> like 'fixture inheritance', where a fixture can 'extend' another, >> supplying additional setup/teardown. >> Example: the 'outermost' or 'parent' fixture might define that we >> want a 'basic PVE installation' with the latest .debs deployed, >> while another fixture that inherits from that one might set up a >> storage of a certain type, useful for all tests that require specific >> that type of storage. > > Maybe our disagreement stems mostly from different design pictures in > our head, I probably am a bit less fixed (heh) on the fixtures, or at > least the naming of that term and might use test system, or intra test > system when for your design plan fixture would be the better word. I think it's mostly a terminology problem. In my previous definition of 'fixture' I was maybe too fixated (heh) on it being 'the test infrastructure/VMs that must be set up/instantatiated'. Maybe it helps to think about it more generally as 'common setup/cleanup steps for a set of test cases, which *might* include setting up test infra (although I have not figured out a good way how that would be modeled with the desired decoupling between test runner and test-VM-setup-thingy). > >> On the other hand, instead of inheritance, a 'role/trait'-based system >> might also work (composition >>> inheritance, after all) - and >> maybe that also aligns better with the 'properties' mentioned in >> your other mail (I mean this here:? "ostype=win*", "memory>=10G"). >> >> This is essentially a very similar pattern as in numerous other testing >> frameworks (xUnit, pytest, etc.); I think it makes sense to >> build upon this battle-proven approach. > > Those are all unit testing tools though that we do already in the > sources and IIRC those do not really provide what we need here. > While starting out simple(r) and avoiding too much complexity has > certainly it's merits, I don't think we should try to draw/align > too many parallels with those tools here for us. > > > In summary, the most important points for me is a decoupled test-system > from the automation system that can manage it, ideally such that I can > decide relatively flexible on manual runs, IMO that should not be to much > work and it guarantees for clean cut APIs from which future development, > or integration surely will benefit too. > > The rest is possibly hard to determine clearly on this stage, as it's easy > (at least for me) to get lost in different understandings of terms and > design perception, but hard to convey those very clearly about "pipe dreams", > so at this stage I'll cede to add discussion churn until there's something > more concrete that I can grasp on my terms (through reading/writing code), > but that should not deter others from giving input still while at this stage. Agreed. I think we agree on the most important requirements/aspects of this project and that's a good foundation for my upcoming efforts. At this point, the best move forward for me is to start experimenting with some ideas and start with the actual implementation. When I have something concrete to show, may it be a prototype or some sort of minimum viable product, it's much easier to discuss any further details and design aspects. Thanks! -- - Lukas From alexandre.derumier at groupe-cyllene.com Wed Oct 18 11:59:06 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Wed, 18 Oct 2023 09:59:06 +0000 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: >>Another thing: What happens when a user changes the MAC address via >>the >>UI? I'd either disallow it completely or we need to update the DHCP >>configuration files and IPAM when mac address is changed online, the nic is doing unplug then replug. So technically, it's just - unplug nic: delete ipam / clean dhcp - hotplug nic: add ipam/ add dhcp. The guest os should automaticaly delete old ip on unplug, and reask a new ip with dhcp on hotplug. From alexandre.derumier at groupe-cyllene.com Wed Oct 18 12:13:14 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Wed, 18 Oct 2023 10:13:14 +0000 Subject: [pve-devel] [WIP v2 pve-network 05/10] dhcp: add DHCP plugin for dnsmasq In-Reply-To: <20231017135507.2220948-6-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> <20231017135507.2220948-6-s.hanreich@proxmox.com> Message-ID: <881898d0410bce53410306c55bf17f4511f781cb.camel@groupe-cyllene.com> >>Adding new MAC address mappings is done with a simple reload that >>does >>not disrupt the dnsmasq daemon in any way. Don't have read the code yet, but note that dhcp-hostsfile= for mac reservation (different than dynamic leases), can be modify without any reload of dnsmasq. From h.laimer at proxmox.com Wed Oct 18 12:34:25 2023 From: h.laimer at proxmox.com (Hannes Laimer) Date: Wed, 18 Oct 2023 12:34:25 +0200 Subject: [pve-devel] [PATCH pve-manager 0/2] add bulk suspend Message-ID: <20231018103427.747129-1-h.laimer@proxmox.com> Adds support for bulk suspending VMs as it already exists for stop. Hannes Laimer (2): api: add suspendall endpoint ui: add bulk suspend support PVE/API2/Nodes.pm | 118 ++++++++++++++++++++++++++++++ www/manager6/Utils.js | 1 + www/manager6/form/VMSelector.js | 4 + www/manager6/node/CmdMenu.js | 14 ++++ www/manager6/node/Config.js | 14 ++++ www/manager6/window/BulkAction.js | 2 +- 6 files changed, 152 insertions(+), 1 deletion(-) -- 2.39.2 From h.laimer at proxmox.com Wed Oct 18 12:34:27 2023 From: h.laimer at proxmox.com (Hannes Laimer) Date: Wed, 18 Oct 2023 12:34:27 +0200 Subject: [pve-devel] [PATCH pve-manager 2/2] ui: add bulk suspend support In-Reply-To: <20231018103427.747129-1-h.laimer@proxmox.com> References: <20231018103427.747129-1-h.laimer@proxmox.com> Message-ID: <20231018103427.747129-3-h.laimer@proxmox.com> Signed-off-by: Hannes Laimer --- www/manager6/Utils.js | 1 + www/manager6/form/VMSelector.js | 4 ++++ www/manager6/node/CmdMenu.js | 14 ++++++++++++++ www/manager6/node/Config.js | 14 ++++++++++++++ www/manager6/window/BulkAction.js | 2 +- 5 files changed, 34 insertions(+), 1 deletion(-) diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index 06b63315..e74fd394 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -1991,6 +1991,7 @@ Ext.define('PVE.Utils', { spiceshell: ['', gettext('Shell') + ' (Spice)'], startall: ['', gettext('Start all VMs and Containers')], stopall: ['', gettext('Stop all VMs and Containers')], + suspendall: ['', gettext('Suspend all VMs')], unknownimgdel: ['', gettext('Destroy image from unknown guest')], wipedisk: ['Device', gettext('Wipe Disk')], vncproxy: ['VM/CT', gettext('Console')], diff --git a/www/manager6/form/VMSelector.js b/www/manager6/form/VMSelector.js index d59847f2..ad0bfc03 100644 --- a/www/manager6/form/VMSelector.js +++ b/www/manager6/form/VMSelector.js @@ -233,6 +233,10 @@ Ext.define('PVE.form.VMSelector', { case 'stopall': statusfilter = 'running'; break; + case 'suspendall': + statusfilter = 'running'; + me.getStore().addFilter({ property: 'type', value: /qemu/ }); + break; } if (statusfilter !== '') { me.getStore().addFilter([{ diff --git a/www/manager6/node/CmdMenu.js b/www/manager6/node/CmdMenu.js index dc56ef08..c64a54d2 100644 --- a/www/manager6/node/CmdMenu.js +++ b/www/manager6/node/CmdMenu.js @@ -56,6 +56,20 @@ Ext.define('PVE.node.CmdMenu', { }); }, }, + { + text: gettext('Bulk Suspend'), + itemId: 'bulkstop', + iconCls: 'fa fa-fw fa-download', + handler: function() { + Ext.create('PVE.window.BulkAction', { + nodename: this.up('menu').nodename, + title: gettext('Bulk Suspend'), + btnText: gettext('Suspend'), + action: 'suspendall', + autoShow: true, + }); + }, + }, { text: gettext('Bulk Migrate'), itemId: 'bulkmigrate', diff --git a/www/manager6/node/Config.js b/www/manager6/node/Config.js index 6ed2172a..ac5e6b25 100644 --- a/www/manager6/node/Config.js +++ b/www/manager6/node/Config.js @@ -65,6 +65,20 @@ Ext.define('PVE.node.Config', { }); }, }, + { + text: gettext('Bulk Suspend'), + iconCls: 'fa fa-fw fa-download', + disabled: !caps.vms['VM.PowerMgmt'], + handler: function() { + Ext.create('PVE.window.BulkAction', { + autoShow: true, + nodename: nodename, + title: gettext('Bulk Suspend'), + btnText: gettext('Suspend'), + action: 'suspendall', + }); + }, + }, { text: gettext('Bulk Migrate'), iconCls: 'fa fa-fw fa-send-o', diff --git a/www/manager6/window/BulkAction.js b/www/manager6/window/BulkAction.js index 949e167e..1a6d83fd 100644 --- a/www/manager6/window/BulkAction.js +++ b/www/manager6/window/BulkAction.js @@ -10,7 +10,7 @@ Ext.define('PVE.window.BulkAction', { }, border: false, - // the action to set, currently there are: `startall`, `migrateall`, `stopall` + // the action to set, currently there are: `startall`, `migrateall`, `stopall`, `suspendall` action: undefined, submit: function(params) { -- 2.39.2 From h.laimer at proxmox.com Wed Oct 18 12:34:26 2023 From: h.laimer at proxmox.com (Hannes Laimer) Date: Wed, 18 Oct 2023 12:34:26 +0200 Subject: [pve-devel] [PATCH pve-manager 1/2] api: add suspendall endpoint In-Reply-To: <20231018103427.747129-1-h.laimer@proxmox.com> References: <20231018103427.747129-1-h.laimer@proxmox.com> Message-ID: <20231018103427.747129-2-h.laimer@proxmox.com> Signed-off-by: Hannes Laimer --- code is mostly taken from the already existing stopal endpoint, since all checks are basically the same for both suspend and stop. PVE/API2/Nodes.pm | 118 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/PVE/API2/Nodes.pm b/PVE/API2/Nodes.pm index 0843c3a3..bb77474f 100644 --- a/PVE/API2/Nodes.pm +++ b/PVE/API2/Nodes.pm @@ -286,6 +286,7 @@ __PACKAGE__->register_method ({ { name => 'spiceshell' }, { name => 'startall' }, { name => 'status' }, + { name => 'suspendall' }, { name => 'stopall' }, { name => 'storage' }, { name => 'subscription' }, @@ -2019,6 +2020,123 @@ __PACKAGE__->register_method ({ return $rpcenv->fork_worker('stopall', undef, $authuser, $code); }}); +my $create_suspend_worker = sub { + my ($nodename, $vmid) = @_; + return if !PVE::QemuServer::check_running($vmid, 1); + print STDERR "Suspending VM $vmid\n"; + return PVE::API2::Qemu->vm_suspend( + { node => $nodename, vmid => $vmid, todisk => 1 } + ); +}; + +__PACKAGE__->register_method ({ + name => 'suspendall', + path => 'suspendall', + method => 'POST', + protected => 1, + permissions => { + description => "The 'VM.PowerMgmt' permission is required on '/' or on '/vms/' for " + ."each ID passed via the 'vms' parameter.", + user => 'all', + }, + proxyto => 'node', + description => "Suspend all VMs.", + parameters => { + additionalProperties => 0, + properties => { + node => get_standard_option('pve-node'), + vms => { + description => "Only consider Guests with these IDs.", + type => 'string', format => 'pve-vmid-list', + optional => 1, + }, + }, + }, + returns => { + type => 'string', + }, + code => sub { + my ($param) = @_; + + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + if (!$rpcenv->check($authuser, "/", [ 'VM.PowerMgmt' ], 1)) { + my @vms = PVE::Tools::split_list($param->{vms}); + if (scalar(@vms) > 0) { + $rpcenv->check($authuser, "/vms/$_", [ 'VM.PowerMgmt' ]) for @vms; + } else { + raise_perm_exc("/, VM.PowerMgmt"); + } + } + + my $nodename = $param->{node}; + $nodename = PVE::INotify::nodename() if $nodename eq 'localhost'; + + my $code = sub { + + $rpcenv->{type} = 'priv'; # to start tasks in background + + my $stopList = $get_start_stop_list->($nodename, undef, $param->{vms}); + + my $cpuinfo = PVE::ProcFSTools::read_cpuinfo(); + my $datacenterconfig = cfs_read_file('datacenter.cfg'); + # if not set by user spawn max cpu count number of workers + my $maxWorkers = $datacenterconfig->{max_workers} || $cpuinfo->{cpus}; + + for my $order (sort {$b <=> $a} keys %$stopList) { + my $vmlist = $stopList->{$order}; + my $workers = {}; + + my $finish_worker = sub { + my $pid = shift; + my $worker = delete $workers->{$pid} || return; + + syslog('info', "end task $worker->{upid}"); + }; + + for my $vmid (sort {$b <=> $a} keys %$vmlist) { + my $d = $vmlist->{$vmid}; + my $upid = eval { + $create_suspend_worker->($nodename, $vmid) + }; + warn $@ if $@; + next if !$upid; + + my $task = PVE::Tools::upid_decode($upid, 1); + next if !$task; + + my $pid = $task->{pid}; + + $workers->{$pid} = { type => $d->{type}, upid => $upid, vmid => $vmid }; + while (scalar(keys %$workers) >= $maxWorkers) { + foreach my $p (keys %$workers) { + if (!PVE::ProcFSTools::check_process_running($p)) { + $finish_worker->($p); + } + } + sleep(1); + } + } + while (scalar(keys %$workers)) { + for my $p (keys %$workers) { + if (!PVE::ProcFSTools::check_process_running($p)) { + $finish_worker->($p); + } + } + sleep(1); + } + } + + syslog('info', "all VMs suspended"); + + return; + }; + + return $rpcenv->fork_worker('suspendall', undef, $authuser, $code); + }}); + + my $create_migrate_worker = sub { my ($nodename, $type, $vmid, $target, $with_local_disks) = @_; -- 2.39.2 From f.schauer at proxmox.com Wed Oct 18 14:00:07 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Wed, 18 Oct 2023 14:00:07 +0200 Subject: [pve-devel] [PATCH installer v2] fix #4869: Show state in management interface ComboBox In-Reply-To: <20231017135708.128807-1-m.sandoval@proxmox.com> References: <20231017135708.128807-1-m.sandoval@proxmox.com> Message-ID: Tested this in the installer and it looks good to me. Tested-by: Filip Schauer On 17/10/2023 15:57, Maximiliano Sandoval R wrote: > From: Maximiliano Sandoval > > This is a continuation of > https://lists.proxmox.com/pipermail/pve-devel/2023-August/058639.html. > > Signed-off-by: Maximiliano Sandoval R > --- > proxinstall | 23 +++++++++++++++++++++-- > 1 file changed, 21 insertions(+), 2 deletions(-) > > diff --git a/proxinstall b/proxinstall > index d5b2565..d1f8ae2 100755 > --- a/proxinstall > +++ b/proxinstall > @@ -341,10 +341,20 @@ sub create_ipconf_view { > > my ($cidr_box, $ipconf_entry_addr, $ipconf_entry_mask) = create_cidr_inputs($cidr); > > - my $device_cb = Gtk3::ComboBoxText->new(); > + my $device_model = Gtk3::ListStore->new('Glib::String', 'Glib::String'); > + my $device_cb = Gtk3::ComboBox->new_with_model($device_model); > $device_cb->set_active(0); > $device_cb->set_visible(1); > > + my $icon_cell = Gtk3::CellRendererText->new(); > + $device_cb->pack_start($icon_cell, 0); > + $device_cb->add_attribute($icon_cell, 'text', 0); > + $icon_cell->set_property('foreground', 'green'); > + > + my $cell = Gtk3::CellRendererText->new(); > + $device_cb->pack_start($cell, 0); > + $device_cb->add_attribute($cell, 'text', 1); > + > my $get_device_desc = sub { > my $iface = shift; > return "$iface->{name} - $iface->{mac} ($iface->{driver})"; > @@ -374,7 +384,16 @@ sub create_ipconf_view { > my $i = 0; > for my $index (sort keys $ipconf->{ifaces}->%*) { > my $iface = $ipconf->{ifaces}->{$index}; > - $device_cb->append_text($get_device_desc->($iface)); > + my $iter = $device_model->append(); > + my $symbol; > + { > + use utf8; > + $symbol = "$iface->{state}" eq "UP" ? '?' : ' '; > + } > + $device_model->set($iter, > + 0 => $symbol, > + 1 => $get_device_desc->($iface), > + ); > $device_active_map->{$i} = $index; > $device_active_reverse_map->{$iface->{name}} = $i; > if ($ipconf_first_view && $index == $ipconf->{default}) { From t.lamprecht at proxmox.com Wed Oct 18 18:59:36 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Wed, 18 Oct 2023 18:59:36 +0200 Subject: [pve-devel] applied: [PATCH pve-installer v9] update the PCI(e) docs In-Reply-To: <20230720093248.44712-1-n.ullreich@proxmox.com> References: <20230720093248.44712-1-n.ullreich@proxmox.com> Message-ID: Am 20/07/2023 um 11:32 schrieb Noel Ullreich: > A little update to the PCI(e) docs. The PCI wiki article has been > reworked as well, in line with changes from this patch. > > Along some minor grammar fixes added: > * how to check if kernelmodules are being loaded > * how to check which drivers to blacklist > * how to add softdeps for module loading > * where to find kernel params > > Signed-off-by: Noel Ullreich > --- > changes from v1: > * fixed spelling mistakes > * reduced code snippets of how to check iommu groupings to one > * moved where to find kernel params to kernel cmdline section > * removed wrong info on display output. will add correct info to > Examples-Wiki > * changed module names to variable-names, so that people can't > blindly copy-paste. > * restructured commit message ;) > > changes from v2: > * while moving where to find the kernel params to the kernel > cmdline section, I forgot to remove it from the pci(e) section > * fixed typo in the link to the kernel param section > > changes from v3: > * Some restructuring of the layout as well as moving parts of the > PCI examples wiki to the docs here. This should lead to well- > structured, concise docs that are independent from the PCI wiki. > * found some more minor grammar errors > * found a spelling mistake in qm.adoc > > changes from v4: > * formatted the git message wrong again :/ > > changes from v5: > * fixed links to wiki > * moved where to find kernel params to end of its chapter > * the `vfio_virqfd` does not need to be loaded anymore with kernel 6.2 > > changes from v6: the public wiki was updated -> fixed the links > > changes from v7: > Forum user Leesteken noted that for Intel cpus IOMMU is not automatically > activated anymore. > https://forum.proxmox.com/threads/fix-pci-passthrough-documentation.122521/post-566926 > Thanks Leesteken :) > > changes from v8: > Amended Dominiks notes: > * added a note to remove `vfio_virqfd` with pve 7 eol > * fixed formatting of a note-block > > qm-pci-passthrough.adoc | 165 +++++++++++++++++++++++++++++++--------- > qm.adoc | 2 +- > system-booting.adoc | 8 ++ > 3 files changed, 137 insertions(+), 38 deletions(-) > > applied, thanks! From d.csapak at proxmox.com Thu Oct 19 09:20:01 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Thu, 19 Oct 2023 09:20:01 +0200 Subject: [pve-devel] [PATCH proxmox-backup/common/storage/wt v2] add tar.zst download in pve In-Reply-To: <20220713094317.2423116-1-d.csapak@proxmox.com> References: <20220713094317.2423116-1-d.csapak@proxmox.com> Message-ID: <5a2d3c7e-1b49-4534-93c6-e61b1aef20bc@proxmox.com> ping (sorry i waited for such a long time ;) ) it probably does not apply anymore, but i'd like to get feedback before starting the rebase From c.heiss at proxmox.com Thu Oct 19 09:28:24 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Thu, 19 Oct 2023 09:28:24 +0200 Subject: [pve-devel] [PATCH manager v2] pvesh: decode streamed responses In-Reply-To: <20230607141511.560351-1-c.heiss@proxmox.com> References: <20230607141511.560351-1-c.heiss@proxmox.com> Message-ID: Ping, still applies. On Wed, Jun 07, 2023 at 04:15:11PM +0200, Christoph Heiss wrote: > > This allows to use `pvesh` on endpoints like /nodes/{node}/journal, > which return streamed (and possibly gzip'd) responses. > > Currently, e.g. `pvesh get /nodes/localhost/journal --lastentries 10` > fails with: > > gzip: stdout: Broken pipe > got hash object, but result schema specified array! > > Using e.g. `--output-format yaml` resulted in: > > --- > download: > content-encoding: gzip > content-type: application/json > fh: &1 !!perl/ref > =: *1 > stream: 1 > > gzip: stdout: Broken pipe > Failed to write > > This is due the API call returning a "download" object (as seen above), > which contains (among some other things) a file handle to read the > response from. > > With this patch, the response from such endpoints is now correctly read > and displayed. Only handles combinations of `Content-Encoding` == 'gzip' > and either 'text/plain' or 'application/json' for `Content-Type`. > > This tries to mimic the behavior of the API server implementation when > encountering `download` objects. > > Tested this with all four output formats 'text', 'json', 'json-pretty' > and 'yaml', as well as "cross-node" in a local test cluster. > > Signed-off-by: Christoph Heiss > --- > Changes v1 -> v2: > * Fix some style nits > * Move content-{encoding,type} checks before actual decoding > > As far as I could see (aka. grep for it), the only two endpoints which > implement this are /nodes/{node}/journal and > /nodes/{node}/tasks/{upid}/log, latter one only with `--download 1` > set. > > PVE/CLI/pvesh.pm | 39 +++++++++++++++++++++++++++++++++++++++ > 1 file changed, 39 insertions(+) > > diff --git a/PVE/CLI/pvesh.pm b/PVE/CLI/pvesh.pm > index 9acf292a..c0c8187b 100755 > --- a/PVE/CLI/pvesh.pm > +++ b/PVE/CLI/pvesh.pm > @@ -15,6 +15,7 @@ use PVE::CLIHandler; > use PVE::API2Tools; > use PVE::API2; > use JSON; > +use IO::Uncompress::Gunzip qw(gunzip); > > use base qw(PVE::CLIHandler); > > @@ -281,6 +282,41 @@ my $cond_add_standard_output_properties = sub { > return PVE::RESTHandler::add_standard_output_properties($props, $keys); > }; > > +my $handle_streamed_response = sub { > + my ($download) = @_; > + my ($fh, $path, $encoding, $type) = > + $download->@{'fh', 'path', 'content-encoding', 'content-type'}; > + > + die "{download} returned but neither fh nor path given\n" > + if !defined($fh) && !defined($path); > + > + die "unknown 'content-encoding' $encoding\n" > + if defined($encoding) && $encoding ne 'gzip'; > + > + die "unknown 'content-type' $type\n" > + if defined($type) && $type !~ qw!^(text/plain)|(application/json)$!; > + > + if (defined($path)) { > + open($fh, '<', $path) > + or die "open stream path '$path' for reading failed: $!\n"; > + } > + > + local $/; > + my $data = <$fh>; > + > + if (defined($encoding)) { > + my $out; > + gunzip(\$data => \$out); > + $data = $out; > + } > + > + if (defined($type) && $type eq 'application/json') { > + $data = decode_json($data)->{data}; > + } > + > + return $data; > +}; > + > sub call_api_method { > my ($cmd, $param) = @_; > > @@ -310,6 +346,9 @@ sub call_api_method { > } > > $data = $handler->handle($info, $param); > + > + $data = &$handle_streamed_response($data->{download}) > + if ref($data) eq 'HASH' && ref($data->{download}) eq 'HASH'; > } > > return if $opt_nooutput || $stdopts->{quiet}; > -- > 2.40.1 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > From l.wagner at proxmox.com Thu Oct 19 10:28:32 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Thu, 19 Oct 2023 10:28:32 +0200 Subject: [pve-devel] [PATCH v3 many 0/7] notifications: add SMTP endpoint In-Reply-To: <70abe760-c79f-4108-893f-8093bc43a278@proxmox.com> References: <20230918111443.465970-1-l.wagner@proxmox.com> <70abe760-c79f-4108-893f-8093bc43a278@proxmox.com> Message-ID: On 10/17/23 09:27, Lukas Wagner wrote: > Ping - would be great to get some reviews on this to get this merged for > the next release. > > On 9/18/23 13:14, Lukas Wagner wrote: >> This patch series adds support for a new notification endpoint type, >> smtp. As the name suggests, this new endpoint allows PVE to talk >> to SMTP server directly, without using the system's MTA (postfix). >> There will be some conceptual changes to the notification system, requiring some changes to this series as well. A new version will be posted at some point. -- - Lukas From l.wagner at proxmox.com Thu Oct 19 10:30:07 2023 From: l.wagner at proxmox.com (Lukas Wagner) Date: Thu, 19 Oct 2023 10:30:07 +0200 Subject: [pve-devel] [PATCH v2 many 00/11] notifications: feed system mails into proxmox_notify In-Reply-To: <17a0be7e-84bc-45c7-877d-27cb0c30d095@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> <17a0be7e-84bc-45c7-877d-27cb0c30d095@proxmox.com> Message-ID: On 10/17/23 09:28, Lukas Wagner wrote: > Ping - would be great to get some reviews on this to get this merged for > the next release. > > On 10/2/23 10:06, Lukas Wagner wrote: >> The aim of this patch series is to adapt `proxmox-mail-forward` >> so that it forwards emails that were sent to the local root user >> through the `proxmox_notify` crate. >> There will be some conceptual changes to the notification system, requiring some changes to this series as well. A new version will be posted at some point. -- - Lukas From m.sandoval at proxmox.com Thu Oct 19 10:35:31 2023 From: m.sandoval at proxmox.com (Maximiliano Sandoval R) Date: Thu, 19 Oct 2023 10:35:31 +0200 Subject: [pve-devel] [PATCH installer v3] fix #4869: Show state in management interface ComboBox Message-ID: <20231019083531.28056-1-m.sandoval@proxmox.com> From: Maximiliano Sandoval This is a continuation of https://lists.proxmox.com/pipermail/pve-devel/2023-August/058639.html. Signed-off-by: Maximiliano Sandoval R --- v3 was tested only with `make check-pve`. v2 was properly tested by Filip Schauer in a VM. Differences from v2: - Use a escaped utf8 character instead of using `use uft-8;` Differences from v1: - Instead of adding a `- UP` or `- DOWN` to the interface label, we display a green BLACK CIRCLE ? similar to what systemd does. To achieve this we separate the GtkWidget in the dropdown in two, and set the color of the new one to green. It was discussed off-list that adding a character for interfaces that were down introduced too much visual noise. proxinstall | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/proxinstall b/proxinstall index d5b2565..11e537b 100755 --- a/proxinstall +++ b/proxinstall @@ -341,10 +341,20 @@ sub create_ipconf_view { my ($cidr_box, $ipconf_entry_addr, $ipconf_entry_mask) = create_cidr_inputs($cidr); - my $device_cb = Gtk3::ComboBoxText->new(); + my $device_model = Gtk3::ListStore->new('Glib::String', 'Glib::String'); + my $device_cb = Gtk3::ComboBox->new_with_model($device_model); $device_cb->set_active(0); $device_cb->set_visible(1); + my $icon_cell = Gtk3::CellRendererText->new(); + $device_cb->pack_start($icon_cell, 0); + $device_cb->add_attribute($icon_cell, 'text', 0); + $icon_cell->set_property('foreground', 'green'); + + my $cell = Gtk3::CellRendererText->new(); + $device_cb->pack_start($cell, 0); + $device_cb->add_attribute($cell, 'text', 1); + my $get_device_desc = sub { my $iface = shift; return "$iface->{name} - $iface->{mac} ($iface->{driver})"; @@ -374,7 +384,12 @@ sub create_ipconf_view { my $i = 0; for my $index (sort keys $ipconf->{ifaces}->%*) { my $iface = $ipconf->{ifaces}->{$index}; - $device_cb->append_text($get_device_desc->($iface)); + my $iter = $device_model->append(); + my $symbol = "$iface->{state}" eq "UP" ? "\x{25CF}" : ' '; + $device_model->set($iter, + 0 => $symbol, + 1 => $get_device_desc->($iface), + ); $device_active_map->{$i} = $index; $device_active_reverse_map->{$iface->{name}} = $i; if ($ipconf_first_view && $index == $ipconf->{default}) { -- 2.39.2 From t.lamprecht at proxmox.com Thu Oct 19 10:53:22 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Thu, 19 Oct 2023 10:53:22 +0200 Subject: [pve-devel] [PATCH ifupdown2] patch: fix bond mac address at boot. In-Reply-To: <20230901091231.155229-1-aderumier@odiso.com> References: <20230901091231.155229-1-aderumier@odiso.com> Message-ID: <9b1d6cf3-3adc-4ada-a107-0c7923fdefda@proxmox.com> Am 01/09/2023 um 11:12 schrieb Alexandre Derumier: > since systemd v241, like for bridge, the bond mac is setup > randomly at boot, instead inherit from first slave. > > Then, on next ifreload, ifupdown2 was already fixing it, > but with an down/up of the bond (with potentials impact on the network). > Hmm, we now got a few reports in the forum that get quite a few "Received packet on bond0 with own address as source" warnings after upgrading to ifupdown2 to the version that just ships this patch here: https://forum.proxmox.com/threads/kernel-vmbr0-received-packet-on-bond0-with-own-address-as-source-address.133152/#post-597121 https://forum.proxmox.com/threads/vmbr0-received-packet-on-bond0-with-own-address-as-source-address.135044/#post-597283 Seem like the switches send the ECTP loopback packages sometimes back over the other link due both having the same MAC address, Wolfgang thinks that shouldn't matter though.. They mention that a downgrade helps so even if this is a switch (configuration) issue on their side it might not be ideal to regress here ? do you have any idea what's going on? From d.csapak at proxmox.com Thu Oct 19 11:13:52 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Thu, 19 Oct 2023 11:13:52 +0200 Subject: [pve-devel] [PATCH common/storage/widget-toolkit v3] add tar.zst download in pve Message-ID: <20231019091355.2799960-1-d.csapak@proxmox.com> like we have the tar.zst download button for pbs itself, add it for pve for both vms and container file-restore pve-storage depends on pve-common changes from v2: * rebase on master * drop applied patches changes from v1: * split format into 'format' and 'zstd' * also use those parameters for file-restore (keep 'tar' for the pve api for gui compatibility) * use an '#[api]' enum for the format type pve-common: Dominik Csapak (1): PBSClient: add 'tar' parameter to file_restore_extract src/PVE/PBSClient.pm | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) pve-storage: Dominik Csapak (1): api/filerestore: add 'tar' parameter to 'download' api src/PVE/API2/Storage/FileRestore.pm | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) proxmox-widget-toolkit: Dominik Csapak (1): window/FileBrowser: enable tar button by default src/window/FileBrowser.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) -- 2.30.2 From d.csapak at proxmox.com Thu Oct 19 11:13:55 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Thu, 19 Oct 2023 11:13:55 +0200 Subject: [pve-devel] [PATCH widget-toolkit v3 1/1] window/FileBrowser: enable tar button by default In-Reply-To: <20231019091355.2799960-1-d.csapak@proxmox.com> References: <20231019091355.2799960-1-d.csapak@proxmox.com> Message-ID: <20231019091355.2799960-4-d.csapak@proxmox.com> all endpoints now can handle the 'tar' parameter, so add it for all Signed-off-by: Dominik Csapak --- src/window/FileBrowser.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/window/FileBrowser.js b/src/window/FileBrowser.js index 4e4c639..e036d9f 100644 --- a/src/window/FileBrowser.js +++ b/src/window/FileBrowser.js @@ -61,10 +61,6 @@ Ext.define("Proxmox.window.FileBrowser", { 'd': true, // directories }, - // enable tar download, this will add a menu to the "Download" button when the selection - // can be downloaded as `.tar` files - enableTar: false, - // prefix to prepend to downloaded file names downloadPrefix: '', }, @@ -126,7 +122,7 @@ Ext.define("Proxmox.window.FileBrowser", { view.lookup('selectText').setText(st); let canDownload = view.downloadURL && view.downloadableFileTypes[data.type]; - let enableMenu = view.enableTar && data.type === 'd'; + let enableMenu = data.type === 'd'; let downloadBtn = view.lookup('downloadBtn'); downloadBtn.setDisabled(!canDownload || enableMenu); -- 2.30.2 From d.csapak at proxmox.com Thu Oct 19 11:13:53 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Thu, 19 Oct 2023 11:13:53 +0200 Subject: [pve-devel] [PATCH common v3 1/1] PBSClient: add 'tar' parameter to file_restore_extract In-Reply-To: <20231019091355.2799960-1-d.csapak@proxmox.com> References: <20231019091355.2799960-1-d.csapak@proxmox.com> Message-ID: <20231019091355.2799960-2-d.csapak@proxmox.com> so that we can get a 'tar.zst' from proxmox-file-restore by giving '--format tar --zstd' to the file-restore binary Signed-off-by: Dominik Csapak --- src/PVE/PBSClient.pm | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/PVE/PBSClient.pm b/src/PVE/PBSClient.pm index ec05a1c..e63af03 100644 --- a/src/PVE/PBSClient.pm +++ b/src/PVE/PBSClient.pm @@ -416,7 +416,7 @@ sub file_restore_extract_prepare { # this blocks while data is transfered, call this from a background worker sub file_restore_extract { - my ($self, $output_file, $snapshot, $filepath, $base64) = @_; + my ($self, $output_file, $snapshot, $filepath, $base64, $tar) = @_; (my $namespace, $snapshot) = split_namespaced_parameter($self, $snapshot); @@ -430,10 +430,15 @@ sub file_restore_extract { my $fn = fileno($fh); my $errfunc = sub { print $_[0], "\n"; }; + my $cmd = [ $snapshot, $filepath, "-", "--base64", $base64 ? 1 : 0]; + if ($tar) { + push @$cmd, '--format', 'tar', '--zstd', 1; + } + return run_raw_client_cmd( $self, "extract", - [ $snapshot, $filepath, "-", "--base64", $base64 ? 1 : 0 ], + $cmd, binary => "proxmox-file-restore", namespace => $namespace, errfunc => $errfunc, -- 2.30.2 From d.csapak at proxmox.com Thu Oct 19 11:13:54 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Thu, 19 Oct 2023 11:13:54 +0200 Subject: [pve-devel] [PATCH storage v3 1/1] api/filerestore: add 'tar' parameter to 'download' api In-Reply-To: <20231019091355.2799960-1-d.csapak@proxmox.com> References: <20231019091355.2799960-1-d.csapak@proxmox.com> Message-ID: <20231019091355.2799960-3-d.csapak@proxmox.com> to be able to download 'tar.zst' archives Signed-off-by: Dominik Csapak --- src/PVE/API2/Storage/FileRestore.pm | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/PVE/API2/Storage/FileRestore.pm b/src/PVE/API2/Storage/FileRestore.pm index 764ebfb..6c9cda6 100644 --- a/src/PVE/API2/Storage/FileRestore.pm +++ b/src/PVE/API2/Storage/FileRestore.pm @@ -167,6 +167,12 @@ __PACKAGE__->register_method ({ description => 'base64-path to the directory or file to download.', type => 'string', }, + tar => { + description => "Download dirs as 'tar.zst' instead of 'zip'.", + type => 'boolean', + optional => 1, + default => 0, + }, }, }, returns => { @@ -182,6 +188,7 @@ __PACKAGE__->register_method ({ my $path = extract_param($param, 'filepath'); my $storeid = extract_param($param, 'storage'); my $volid = $parse_volname_or_id->($storeid, $param->{volume}); + my $tar = extract_param($param, 'tar') // 0; my $cfg = PVE::Storage::config(); my $scfg = PVE::Storage::storage_config($cfg, $storeid); @@ -199,7 +206,7 @@ __PACKAGE__->register_method ({ $rpcenv->fork_worker('pbs-download', undef, $user, sub { my $name = decode_base64($path); print "Starting download of file: $name\n"; - $client->file_restore_extract($fifo, $snap, $path, 1); + $client->file_restore_extract($fifo, $snap, $path, 1, $tar); }); my $ret = { -- 2.30.2 From alexandre.derumier at groupe-cyllene.com Thu Oct 19 11:39:17 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Thu, 19 Oct 2023 09:39:17 +0000 Subject: [pve-devel] [PATCH ifupdown2] patch: fix bond mac address at boot. In-Reply-To: <9b1d6cf3-3adc-4ada-a107-0c7923fdefda@proxmox.com> References: <20230901091231.155229-1-aderumier@odiso.com> <9b1d6cf3-3adc-4ada-a107-0c7923fdefda@proxmox.com> Message-ID: -------- Message initial -------- De: Thomas Lamprecht ?: Proxmox VE development discussion , Alexandre Derumier Objet: Re: [pve-devel] [PATCH ifupdown2] patch: fix bond mac address at boot. Date: 19/10/2023 10:53:22 Am 01/09/2023 um 11:12 schrieb Alexandre Derumier: > since systemd v241, like for bridge, the bond mac is setup > randomly at boot, instead inherit from first slave. > > Then, on next ifreload, ifupdown2 was already fixing it, > but with an down/up of the bond (with potentials impact on the > network). > >>Hmm, we now got a few reports in the forum that get quite a few >>"Received packet on bond0 with own address as source" warnings >>after upgrading to ifupdown2 to the version that just ships this >>patch here: mmmm, that's strange. Does it occur only once ? (when ifupdown2 is upgraded && reload it done?) Do you known which bond mode is used here ? BTW, I had send another patch fixing it at systemd level directly (and fixing another bug) https://lists.proxmox.com/pipermail/pve-devel/2023-September/059129.html >>Seem like the switches send the ECTP loopback packages sometimes >>back over the other link due both having the same MAC address, >>Wolfgang thinks that shouldn't matter though.. That's strange, because with or without this patch, both links have the same mac anyway. (same random mac generated vs same mac inherited from first real interface). For me, it's look like more a config problem at the physical switch where no port group/port channel/... I don't see any error like this on my network with lacp bond. (and the current patch is really fixing bugs on reload, where physical switch can block ports in protection if the mac is switching) From f.schauer at proxmox.com Thu Oct 19 14:18:56 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Thu, 19 Oct 2023 14:18:56 +0200 Subject: [pve-devel] [PATCH RFC container] Add device passthrough Message-ID: <20231019121856.379185-1-f.schauer@proxmox.com> Signed-off-by: Filip Schauer --- Is it reasonable to add a "dev[n]" argument to the pct.conf, given that device mount points only allow passing through block devices? src/PVE/LXC.pm | 14 ++++++++++++++ src/PVE/LXC/Config.pm | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index c9b5ba7..6090534 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -639,6 +639,20 @@ sub update_lxc_config { $raw .= "lxc.mount.auto = sys:mixed\n"; } + foreach my $k (keys %$conf) { + next if $k !~ m/^dev(\d+)$/; + my $devpath = $conf->{$k}; + die "Device $devpath does not exist\n" unless (-e $devpath); + + my ($mode, $rdev) = (stat($devpath))[2, 6]; + die "Could not find major and minor ids of device $devpath.\n" unless ($mode && $rdev); + + my $major = PVE::Tools::dev_t_major($rdev); + my $minor = PVE::Tools::dev_t_minor($rdev); + $raw .= "lxc.cgroup2.devices.allow = c $major:$minor rw\n"; + $raw .= "lxc.mount.entry = $devpath " . substr($devpath, 1) . " none bind,create=file\n"; + } + # WARNING: DO NOT REMOVE this without making sure that loop device nodes # cannot be exposed to the container with r/w access (cgroup perms). # When this is enabled mounts will still remain in the monitor's namespace diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm index 56e1f10..4665ab1 100644 --- a/src/PVE/LXC/Config.pm +++ b/src/PVE/LXC/Config.pm @@ -29,6 +29,7 @@ mkdir $lockdir; mkdir "/etc/pve/nodes/$nodename/lxc"; my $MAX_MOUNT_POINTS = 256; my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS; +my $MAX_DEVICES = 256; # BEGIN implemented abstract methods from PVE::AbstractConfig @@ -908,6 +909,37 @@ for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) { } } +PVE::JSONSchema::register_format('pve-lxc-dev-string', \&verify_lxc_dev_string); +sub verify_lxc_dev_string { + my ($dev, $noerr) = @_; + + if ($dev !~ m!^/dev/!) { + return undef if $noerr; + die "$dev does not start with /dev/\n"; + } + + return $dev; +} + +my $dev_desc = { + dev => { + type => 'string', + default_key => 1, + format => 'pve-lxc-dev-string', + format_description => 'Path', + description => 'Device to pass through to the container', + verbose_description => 'Path to the device to pass through to the container' + } +}; + +for (my $i = 0; $i < $MAX_DEVICES; $i++) { + $confdesc->{"dev$i"} = { + optional => 1, + type => 'string', format => $dev_desc, + description => "Device to pass through to the container", + } +} + sub parse_pct_config { my ($filename, $raw, $strict) = @_; -- 2.39.2 From t.lamprecht at proxmox.com Thu Oct 19 14:20:46 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Thu, 19 Oct 2023 14:20:46 +0200 Subject: [pve-devel] applied: [PATCH installer v3] fix #4869: Show state in management interface ComboBox In-Reply-To: <20231019083531.28056-1-m.sandoval@proxmox.com> References: <20231019083531.28056-1-m.sandoval@proxmox.com> Message-ID: Am 19/10/2023 um 10:35 schrieb Maximiliano Sandoval R: > From: Maximiliano Sandoval > > This is a continuation of > https://lists.proxmox.com/pipermail/pve-devel/2023-August/058639.html. > > Signed-off-by: Maximiliano Sandoval R > --- > v3 was tested only with `make check-pve`. v2 was properly tested by Filip > Schauer in a VM. > > Differences from v2: > - Use a escaped utf8 character instead of using `use uft-8;` > > Differences from v1: > - Instead of adding a `- UP` or `- DOWN` to the interface label, we display a > green BLACK CIRCLE ? similar to what systemd does. To achieve this we > separate the GtkWidget in the dropdown in two, and set the color of the new > one to green. It was discussed off-list that adding a character for > interfaces that were down introduced too much visual noise. > > proxinstall | 19 +++++++++++++++++-- > 1 file changed, 17 insertions(+), 2 deletions(-) > > applied, thanks! I also added this to the TUI, it's important that we keep feature-parity for those. From d.csapak at proxmox.com Thu Oct 19 15:36:05 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Thu, 19 Oct 2023 15:36:05 +0200 Subject: [pve-devel] [PATCH manager 1/3] ui: tags: fix focus for edit mode Message-ID: <20231019133607.1416999-1-d.csapak@proxmox.com> such that one can tab through the editable tag fields. We have to handle that manually, since ExtJs does not expect contenteditable html tags for focus handling. Signed-off-by: Dominik Csapak --- www/manager6/form/Tag.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/www/manager6/form/Tag.js b/www/manager6/form/Tag.js index be72d7ba..6b1d6aa5 100644 --- a/www/manager6/form/Tag.js +++ b/www/manager6/form/Tag.js @@ -13,6 +13,15 @@ Ext.define('Proxmox.form.Tag', { '', ], + focusable: true, + getFocusEl: function() { + return Ext.get(this.tagEl()); + }, + + onFocus: function() { + this.selectText(); + }, + // contains tags not to show in the picker and not allowing to set filter: [], -- 2.30.2 From d.csapak at proxmox.com Thu Oct 19 15:36:06 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Thu, 19 Oct 2023 15:36:06 +0200 Subject: [pve-devel] [PATCH manager 2/3] ui: tags: prevent pasting non plain-text content In-Reply-To: <20231019133607.1416999-1-d.csapak@proxmox.com> References: <20231019133607.1416999-1-d.csapak@proxmox.com> Message-ID: <20231019133607.1416999-2-d.csapak@proxmox.com> by setting 'contentEditable' to 'plaintext-only' instead of true. Otherwise it was possible to paste html code into the field (an error was thrown by the backend since it does not match the regex) Signed-off-by: Dominik Csapak --- www/manager6/form/Tag.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/manager6/form/Tag.js b/www/manager6/form/Tag.js index 6b1d6aa5..39afe04b 100644 --- a/www/manager6/form/Tag.js +++ b/www/manager6/form/Tag.js @@ -45,7 +45,7 @@ Ext.define('Proxmox.form.Tag', { selectText: function(collapseToEnd) { let me = this; let tagEl = me.tagEl(); - tagEl.contentEditable = true; + tagEl.contentEditable = "plaintext-only"; let range = document.createRange(); range.selectNodeContents(tagEl); if (collapseToEnd) { @@ -98,7 +98,7 @@ Ext.define('Proxmox.form.Tag', { let me = this; let tagEl = me.tagEl(); if (tagEl) { - tagEl.contentEditable = mode === 'editable'; + tagEl.contentEditable = mode === 'editable' ? 'plaintext-only' : false; } me.removeCls(me.mode); me.addCls(mode); -- 2.30.2 From d.csapak at proxmox.com Thu Oct 19 15:36:07 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Thu, 19 Oct 2023 15:36:07 +0200 Subject: [pve-devel] [PATCH manager 3/3] ui: wizards: allow adding tags in the qemu/lxc create wizard In-Reply-To: <20231019133607.1416999-1-d.csapak@proxmox.com> References: <20231019133607.1416999-1-d.csapak@proxmox.com> Message-ID: <20231019133607.1416999-3-d.csapak@proxmox.com> in the general tab in the advanced section. For that to work, we introduce a new option for the TagEditContainer named 'editOnly', which controls now the cancel/finish buttons, automatically enter edit mode and disable enter/escape keypresses. We also prevent now the loading of tags while in edit mode, so the tags don't change while editing (this can be jarring and unexpected). In the wizard, we override the layout such that the tags wrap when there are too many, and make the field scrollable and set a height, so that the user can enter as many tags as he wants without having the field overflow or cut off. To properly align the input with the '+' button, we have to add a custom css class there. (In the hbox we could set the alignment, but this is not possible in the 'column' layout) Signed-off-by: Dominik Csapak --- www/css/ext6-pve.css | 5 +++++ www/manager6/form/TagEdit.js | 35 +++++++++++++++++++++++++++---- www/manager6/lxc/CreateWizard.js | 22 +++++++++++++++++++ www/manager6/qemu/CreateWizard.js | 22 +++++++++++++++++++ 4 files changed, 80 insertions(+), 4 deletions(-) diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css index 85cf4039..25bc7ea7 100644 --- a/www/css/ext6-pve.css +++ b/www/css/ext6-pve.css @@ -721,3 +721,8 @@ table.osds td:first-of-type { .pmx-opacity-75 { opacity: 0.75; } + +/* tag edit fields must be aligned manually in the wizard */ +.proxmox-wizard.proxmox-tags-full .x-component.x-column { + margin: 3px 2px; +} diff --git a/www/manager6/form/TagEdit.js b/www/manager6/form/TagEdit.js index 094f4462..7d5b19ec 100644 --- a/www/manager6/form/TagEdit.js +++ b/www/manager6/form/TagEdit.js @@ -9,6 +9,7 @@ Ext.define('PVE.panel.TagEditContainer', { // set to false to hide the 'no tags' field and the edit button canEdit: true, + editOnly: false, controller: { xclass: 'Ext.app.ViewController', @@ -216,6 +217,9 @@ Ext.define('PVE.panel.TagEditContainer', { me.tagsChanged(); }, keypress: function(key) { + if (vm.get('hideFinishButtons')) { + return; + } if (key === 'Enter') { me.editClick(); } else if (key === 'Escape') { @@ -253,20 +257,40 @@ Ext.define('PVE.panel.TagEditContainer', { me.loadTags(view.tags); } me.getViewModel().set('canEdit', view.canEdit); + me.getViewModel().set('editOnly', view.editOnly); me.mon(Ext.GlobalEvents, 'loadedUiOptions', () => { + let vm = me.getViewModel(); view.toggleCls('hide-handles', PVE.UIOptions.shouldSortTags()); - me.loadTags(me.oldTags, true); // refresh tag colors and order + me.loadTags(me.oldTags, !vm.get('editMode')); // refresh tag colors and order }); + + if (view.editOnly) { + me.toggleEdit(); + } }, }, + getTags: function() { + let me =this; + let controller = me.getController(); + let tags = []; + controller.forEachTag((cmp) => { + if (cmp.tag.length) { + tags.push(cmp.tag); + } + }); + + return tags; + }, + viewModel: { data: { tagCount: 0, editMode: false, canEdit: true, isDirty: false, + editOnly: true, }, formulas: { @@ -276,6 +300,9 @@ Ext.define('PVE.panel.TagEditContainer', { hideEditBtn: function(get) { return get('editMode') || !get('canEdit'); }, + hideFinishButtons: function(get) { + return !get('editMode') || get('editOnly'); + }, }, }, @@ -311,7 +338,7 @@ Ext.define('PVE.panel.TagEditContainer', { xtype: 'tbseparator', ui: 'horizontal', bind: { - hidden: '{!editMode}', + hidden: '{hideFinishButtons}', }, hidden: true, }, @@ -320,7 +347,7 @@ Ext.define('PVE.panel.TagEditContainer', { iconCls: 'fa fa-times', tooltip: gettext('Cancel Edit'), bind: { - hidden: '{!editMode}', + hidden: '{hideFinishButtons}', }, hidden: true, margin: '0 5 0 0', @@ -332,7 +359,7 @@ Ext.define('PVE.panel.TagEditContainer', { iconCls: 'fa fa-check', tooltip: gettext('Finish Edit'), bind: { - hidden: '{!editMode}', + hidden: '{hideFinishButtons}', disabled: '{!isDirty}', }, hidden: true, diff --git a/www/manager6/lxc/CreateWizard.js b/www/manager6/lxc/CreateWizard.js index e3635297..e725f8c1 100644 --- a/www/manager6/lxc/CreateWizard.js +++ b/www/manager6/lxc/CreateWizard.js @@ -29,6 +29,14 @@ Ext.define('PVE.lxc.CreateWizard', { xtype: 'inputpanel', title: gettext('General'), onlineHelp: 'pct_general', + onGetValues: function(values) { + let me = this; + let tags = me.down('pveTagEditContainer').getTags(); + if (tags.length) { + values.tags = tags.join(','); + } + return values; + }, column1: [ { xtype: 'pveNodeSelector', @@ -178,6 +186,20 @@ Ext.define('PVE.lxc.CreateWizard', { }, }, ], + advancedColumnB: [ + { + xtype: 'displayfield', + fieldLabel: gettext("Tags"), + }, + { + xtype: 'pveTagEditContainer', + userCls: 'proxmox-tags-full proxmox-wizard', + editOnly: true, + scrollable: true, + layout: 'column', + height: 120, + }, + ], }, { xtype: 'inputpanel', diff --git a/www/manager6/qemu/CreateWizard.js b/www/manager6/qemu/CreateWizard.js index a65067ea..2ccad83b 100644 --- a/www/manager6/qemu/CreateWizard.js +++ b/www/manager6/qemu/CreateWizard.js @@ -108,6 +108,22 @@ Ext.define('PVE.qemu.CreateWizard', { fieldLabel: gettext('Shutdown timeout'), }, ], + + advancedColumnB: [ + { + xtype: 'displayfield', + fieldLabel: gettext("Tags"), + }, + { + xtype: 'pveTagEditContainer', + userCls: 'proxmox-tags-full proxmox-wizard', + editOnly: true, + scrollable: true, + layout: 'column', + height: 120, + }, + ], + onGetValues: function(values) { ['name', 'pool', 'onboot', 'agent'].forEach(function(field) { if (!values[field]) { @@ -115,6 +131,12 @@ Ext.define('PVE.qemu.CreateWizard', { } }); + let tags = this.down('pveTagEditContainer').getTags(); + + if (tags.length) { + values.tags = tags.join(','); + } + var res = PVE.Parser.printStartup({ order: values.order, up: values.up, -- 2.30.2 From d.csapak at proxmox.com Thu Oct 19 15:59:30 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Thu, 19 Oct 2023 15:59:30 +0200 Subject: [pve-devel] [PATCH manager 2/3] ui: tags: prevent pasting non plain-text content In-Reply-To: <20231019133607.1416999-2-d.csapak@proxmox.com> References: <20231019133607.1416999-1-d.csapak@proxmox.com> <20231019133607.1416999-2-d.csapak@proxmox.com> Message-ID: sry disregard this patch only, that property value is not supported in firefox :( From s.sterz at proxmox.com Thu Oct 19 16:59:12 2023 From: s.sterz at proxmox.com (Stefan Sterz) Date: Thu, 19 Oct 2023 16:59:12 +0200 Subject: [pve-devel] [PATCH pve-kernel] backport exposing FLUSHBYASID when running nested VMs on AMD CPUs Message-ID: <20231019145912.3152371-1-s.sterz@proxmox.com> this exposes the FLUSHBYASID CPU flag to nested VMs when running on an AMD CPU. also reverts a made up check that would advertise FLUSHBYASID as not supported. this enable certain modern hypervisors such as VMWare ESXi 7 and Workstation 17 to run nested VMs properly again. Signed-off-by: Stefan Sterz --- ...k-for-reserved-encodings-of-TLB_CONT.patch | 49 +++++++++++++++++++ ...-Advertise-support-for-flush-by-ASID.patch | 39 +++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 patches/kernel/0014-Revert-nSVM-Check-for-reserved-encodings-of-TLB_CONT.patch create mode 100644 patches/kernel/0015-KVM-nSVM-Advertise-support-for-flush-by-ASID.patch diff --git a/patches/kernel/0014-Revert-nSVM-Check-for-reserved-encodings-of-TLB_CONT.patch b/patches/kernel/0014-Revert-nSVM-Check-for-reserved-encodings-of-TLB_CONT.patch new file mode 100644 index 0000000..2c77272 --- /dev/null +++ b/patches/kernel/0014-Revert-nSVM-Check-for-reserved-encodings-of-TLB_CONT.patch @@ -0,0 +1,49 @@ +From 379ad2e0326c55682d0bb9391f16f1072fe400d2 Mon Sep 17 00:00:00 2001 +From: Stefan Sterz +Date: Wed, 18 Oct 2023 10:45:45 +0200 +Subject: [PATCH 1/2] Revert "nSVM: Check for reserved encodings of TLB_CONTROL + in nested VMCB" + +This reverts commit 174a921b6975ef959dd82ee9e8844067a62e3ec1. + +Signed-off-by: Stefan Sterz +--- + arch/x86/kvm/svm/nested.c | 15 --------------- + 1 file changed, 15 deletions(-) + +diff --git a/arch/x86/kvm/svm/nested.c b/arch/x86/kvm/svm/nested.c +index add65dd59756..61a6c0235519 100644 +--- a/arch/x86/kvm/svm/nested.c ++++ b/arch/x86/kvm/svm/nested.c +@@ -242,18 +242,6 @@ static bool nested_svm_check_bitmap_pa(struct kvm_vcpu *vcpu, u64 pa, u32 size) + kvm_vcpu_is_legal_gpa(vcpu, addr + size - 1); + } + +-static bool nested_svm_check_tlb_ctl(struct kvm_vcpu *vcpu, u8 tlb_ctl) +-{ +- /* Nested FLUSHBYASID is not supported yet. */ +- switch(tlb_ctl) { +- case TLB_CONTROL_DO_NOTHING: +- case TLB_CONTROL_FLUSH_ALL_ASID: +- return true; +- default: +- return false; +- } +-} +- + static bool __nested_vmcb_check_controls(struct kvm_vcpu *vcpu, + struct vmcb_ctrl_area_cached *control) + { +@@ -273,9 +261,6 @@ static bool __nested_vmcb_check_controls(struct kvm_vcpu *vcpu, + IOPM_SIZE))) + return false; + +- if (CC(!nested_svm_check_tlb_ctl(vcpu, control->tlb_ctl))) +- return false; +- + return true; + } + +-- +2.39.2 + diff --git a/patches/kernel/0015-KVM-nSVM-Advertise-support-for-flush-by-ASID.patch b/patches/kernel/0015-KVM-nSVM-Advertise-support-for-flush-by-ASID.patch new file mode 100644 index 0000000..611a90c --- /dev/null +++ b/patches/kernel/0015-KVM-nSVM-Advertise-support-for-flush-by-ASID.patch @@ -0,0 +1,39 @@ +From 42af81abf0b96ab661591d024aed55c05dd85b91 Mon Sep 17 00:00:00 2001 +From: Sean Christopherson +Date: Wed, 18 Oct 2023 12:41:04 -0700 +Subject: [PATCH 2/2] KVM: nSVM: Advertise support for flush-by-ASID + +Advertise support for FLUSHBYASID when nested SVM is enabled, as KVM can +always emulate flushing TLB entries for a vmcb12 ASID, e.g. by running L2 +with a new, fresh ASID in vmcb02. Some modern hypervisors, e.g. VMWare +Workstation 17, require FLUSHBYASID support and will refuse to run if it's +not present. + +Punt on proper support, as "Honor L1's request to flush an ASID on nested +VMRUN" is one of the TODO items in the (incomplete) list of issues that +need to be addressed in order for KVM to NOT do a full TLB flush on every +nested SVM transition (see nested_svm_transition_tlb_flush()). + +Reported-by: Stefan Sterz +Closes: https://lkml.kernel.org/r/b9915c9c-4cf6-051a-2d91-44cc6380f455%40proxmox.com +Signed-off-by: Sean Christopherson +Signed-off-by: Stefan Sterz +--- + arch/x86/kvm/svm/svm.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/arch/x86/kvm/svm/svm.c b/arch/x86/kvm/svm/svm.c +index 9a194aa1a75a..0fde9b0c464b 100644 +--- a/arch/x86/kvm/svm/svm.c ++++ b/arch/x86/kvm/svm/svm.c +@@ -4880,6 +4880,7 @@ static __init void svm_set_cpu_caps(void) + if (nested) { + kvm_cpu_cap_set(X86_FEATURE_SVM); + kvm_cpu_cap_set(X86_FEATURE_VMCBCLEAN); ++ kvm_cpu_cap_set(X86_FEATURE_FLUSHBYASID); + + if (nrips) + kvm_cpu_cap_set(X86_FEATURE_NRIPS); +-- +2.39.2 + -- 2.39.2 From w.bumiller at proxmox.com Fri Oct 20 09:08:03 2023 From: w.bumiller at proxmox.com (Wolfgang Bumiller) Date: Fri, 20 Oct 2023 09:08:03 +0200 Subject: [pve-devel] [PATCH RFC container] Add device passthrough In-Reply-To: <20231019121856.379185-1-f.schauer@proxmox.com> References: <20231019121856.379185-1-f.schauer@proxmox.com> Message-ID: On Thu, Oct 19, 2023 at 02:18:56PM +0200, Filip Schauer wrote: > Signed-off-by: Filip Schauer > --- > Is it reasonable to add a "dev[n]" argument to the pct.conf, given that > device mount points only allow passing through block devices? Why would they only allow block devices? Also, Dominik recently added resource mappings for qemu for USB & PCI. PCI might be tricky, but for USB we may be able to use these mappings as well. That said, "raw" `/dev` node pass-through still makes sense as a separate thing for containers anyway since raw `lxc....` entries in the container config can currently be very inconvenient to deal with particularly with unprivileged containers (read on below for why...) > > src/PVE/LXC.pm | 14 ++++++++++++++ > src/PVE/LXC/Config.pm | 32 ++++++++++++++++++++++++++++++++ > 2 files changed, 46 insertions(+) > > diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm > index c9b5ba7..6090534 100644 > --- a/src/PVE/LXC.pm > +++ b/src/PVE/LXC.pm > @@ -639,6 +639,20 @@ sub update_lxc_config { > $raw .= "lxc.mount.auto = sys:mixed\n"; > } > > + foreach my $k (keys %$conf) { > + next if $k !~ m/^dev(\d+)$/; We should add a helper `sub foreach_passthrough_device()` taking a sub which gets `$key, $raw_value, $sanitized_path`. The last one would currently be the `substr($path, 1)`. Having to do this later on just makes it easier to mess this up. Also, seeing this now this reminds me that - but not as part of this series - we should add a lot of `foreach_*` subs for this sort of iteration like we have in qemu-server and replace such existing constructs if possible... > + my $devpath = $conf->{$k}; > + die "Device $devpath does not exist\n" unless (-e $devpath); > + > + my ($mode, $rdev) = (stat($devpath))[2, 6]; > + die "Could not find major and minor ids of device $devpath.\n" unless ($mode && $rdev); > + > + my $major = PVE::Tools::dev_t_major($rdev); > + my $minor = PVE::Tools::dev_t_minor($rdev); > + $raw .= "lxc.cgroup2.devices.allow = c $major:$minor rw\n"; ^ the 'c' isn't necessarily correct. You can use `S_ISBLK($mode) ? 'b' : 'c'`. `S_ISBLK` comes from the `Fcntl` package. (You can find more info on file modes and S_ISBLK in `man 7 inode`) > + $raw .= "lxc.mount.entry = $devpath " . substr($devpath, 1) . " none bind,create=file\n"; ^ See above - the substr() there stands out quite weird :-) > + } > + > # WARNING: DO NOT REMOVE this without making sure that loop device nodes > # cannot be exposed to the container with r/w access (cgroup perms). > # When this is enabled mounts will still remain in the monitor's namespace > diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm > index 56e1f10..4665ab1 100644 > --- a/src/PVE/LXC/Config.pm > +++ b/src/PVE/LXC/Config.pm > @@ -29,6 +29,7 @@ mkdir $lockdir; > mkdir "/etc/pve/nodes/$nodename/lxc"; > my $MAX_MOUNT_POINTS = 256; > my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS; > +my $MAX_DEVICES = 256; > > # BEGIN implemented abstract methods from PVE::AbstractConfig > > @@ -908,6 +909,37 @@ for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) { > } > } > > +PVE::JSONSchema::register_format('pve-lxc-dev-string', \&verify_lxc_dev_string); > +sub verify_lxc_dev_string { > + my ($dev, $noerr) = @_; > + > + if ($dev !~ m!^/dev/!) { > + return undef if $noerr; > + die "$dev does not start with /dev/\n"; This is where it gets interesting :-) I can't find it now, but I faintly recall that at some point I had suggested collecting devices in a different path than `/dev` to create device nodes with the correct owner/group/permissions for unprivileged containers to people on the forum. See, we don't just need the device to be visible in the container, but we also need it to be able to give the container's root user - which for unprivileged containers is usually uid 100000 - access to it. It *may* be fine for *some* devices to just `chown()` them in `/dev`, but we generally don't want to reown devices on the host itself, so it may make more sense to `mknod()` the same device somewhere in `/var/lib/lxc/$vmid/passthrough` and `chown()` it there. (One alternative would of course be ACL entries on the host, but IMO having a directory specific for a container where you can see all its accessible devics has its advantages...) (With newer kernels we might be able to instead use an idmapped bind mount, though, but pve-8's initial kernel does not support idmapping on tmpfs yet :-/ (that starts at 6.3 IIRC)) We should also think about whether we'd like to support the containers calling `mknod()` to "create" (but not really) those devices themselves. for privileged containers this is easy enough, privileged means privileged, so they can just do whatever. For unprivileged containers, however, `mknod()` does not work at all, and even if it did, we cannot allow it directly, since we have no guarantee that after the next reboot the major/minor numbers will be the same after the next reboot. So if we do want to support this, we'd need to teach `pve-lxc-syscalld` to know about allowed devices and give it the ability to hot-bind-mount such nodes (rather than actually mknod()ing them). > + } > + > + return $dev; > +} > + > +my $dev_desc = { > + dev => { > + type => 'string', > + default_key => 1, > + format => 'pve-lxc-dev-string', > + format_description => 'Path', > + description => 'Device to pass through to the container', > + verbose_description => 'Path to the device to pass through to the container' > + } > +}; > + > +for (my $i = 0; $i < $MAX_DEVICES; $i++) { > + $confdesc->{"dev$i"} = { > + optional => 1, > + type => 'string', format => $dev_desc, > + description => "Device to pass through to the container", > + } > +} > + > sub parse_pct_config { > my ($filename, $raw, $strict) = @_; > > -- > 2.39.2 From d.csapak at proxmox.com Fri Oct 20 09:51:43 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Fri, 20 Oct 2023 09:51:43 +0200 Subject: [pve-devel] [PATCH RFC container] Add device passthrough In-Reply-To: References: <20231019121856.379185-1-f.schauer@proxmox.com> Message-ID: <82d37443-ca7f-4cf3-aae0-36444e4769af@proxmox.com> On 10/20/23 09:08, Wolfgang Bumiller wrote: > On Thu, Oct 19, 2023 at 02:18:56PM +0200, Filip Schauer wrote: >> Signed-off-by: Filip Schauer >> --- >> Is it reasonable to add a "dev[n]" argument to the pct.conf, given that >> device mount points only allow passing through block devices? > > Why would they only allow block devices? > > Also, Dominik recently added resource mappings for qemu for USB & PCI. > PCI might be tricky, but for USB we may be able to use these mappings as > well. > That said, "raw" `/dev` node pass-through still makes sense as a > separate thing for containers anyway since raw `lxc....` entries in the > container config can currently be very inconvenient to deal with > particularly with unprivileged containers (read on below for why...) just to chime in here, i don't think it'll be easily possible to reuse the pci/usb maps as is since we'd have to map from pciid /usb-vendor/device (or path) to a device node? i don't think thats generally possible, since the driver does not always make that info easily available (e.g. multi gpu setup and /dev/dri/cardX, or usb-to-serial adapters and /dev/ttySX ?) i guess it could work, but we probably would have to implement that for every driver out there? what i would like to see however is to integrate a new type of mapping for container devices specifically so that the ux is the same (create mappings for whole cluster, assigning privileges, etc) we still can provide a 'raw' mechanic as well for those who only ever use root at pam on a single node, but I'd not be against only using mappings either From t.lamprecht at proxmox.com Fri Oct 20 10:29:38 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 20 Oct 2023 10:29:38 +0200 Subject: [pve-devel] [PATCH RFC container] Add device passthrough In-Reply-To: <82d37443-ca7f-4cf3-aae0-36444e4769af@proxmox.com> References: <20231019121856.379185-1-f.schauer@proxmox.com> <82d37443-ca7f-4cf3-aae0-36444e4769af@proxmox.com> Message-ID: <54225ac2-b494-4c11-9150-1ad647a65eaf@proxmox.com> Am 20/10/2023 um 09:51 schrieb Dominik Csapak: > On 10/20/23 09:08, Wolfgang Bumiller wrote: >> Also, Dominik recently added resource mappings for qemu for USB & PCI. >> PCI might be tricky, but for USB we may be able to use these mappings as >> well. >> That said, "raw" `/dev` node pass-through still makes sense as a >> separate thing for containers anyway since raw `lxc....` entries in the >> container config can currently be very inconvenient to deal with >> particularly with unprivileged containers (read on below for why...) > > just to chime in here, i don't think it'll be easily possible to reuse > the pci/usb maps as is since we'd have to map from pciid /usb-vendor/device > (or path) to a device node? i don't think thats generally possible, since > the driver does not always make that info easily available > (e.g. multi gpu setup and /dev/dri/cardX, or usb-to-serial adapters > and /dev/ttySX ?) i guess it could work, but we probably would have > to implement that for every driver out there? > USB should be workable via resolving to /dev/bus/usb/*, PCI could be, theoretically, but isn't now and probably won't be anytime soon ? i.e., as Wolfgang mentioned off list, there's a reason that there's no /dev/bus/pci/ > what i would like to see however is to integrate a new type of mapping > for container devices specifically so that the ux is the same > (create mappings for whole cluster, assigning privileges, etc) I'd try hard to re-use the USB mappings, those seem to be one of the most common pass-through setups for containers (e.g., for those home automation zigbee/matter/... adapters, or in some countries DVB-T ones, be it for TV or ADS-B plane tracking). If we can make USB work then we'd have the basic concept of attaching a mapping done, adding a new type of (block/char) device mapping could then be an independent task for later to keep scope a bit smaller. Fixing Wolfgang's comments for workable pass-through for unprivileged containers is probably the most important change needed for now. I'd then even be open to apply this with (root at pam only!) absolute path to /dev support only, but IMO resolving the mapping itself should not be too hard, so if using /dev/bus/usb/ works having that in there from the start would be definitively nice. From d.csapak at proxmox.com Fri Oct 20 10:39:06 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Fri, 20 Oct 2023 10:39:06 +0200 Subject: [pve-devel] [PATCH RFC container] Add device passthrough In-Reply-To: <54225ac2-b494-4c11-9150-1ad647a65eaf@proxmox.com> References: <20231019121856.379185-1-f.schauer@proxmox.com> <82d37443-ca7f-4cf3-aae0-36444e4769af@proxmox.com> <54225ac2-b494-4c11-9150-1ad647a65eaf@proxmox.com> Message-ID: <61041ddd-5475-48fd-a7ef-d1816bed25a2@proxmox.com> On 10/20/23 10:29, Thomas Lamprecht wrote: > Am 20/10/2023 um 09:51 schrieb Dominik Csapak: >> On 10/20/23 09:08, Wolfgang Bumiller wrote: >>> Also, Dominik recently added resource mappings for qemu for USB & PCI. >>> PCI might be tricky, but for USB we may be able to use these mappings as >>> well. >>> That said, "raw" `/dev` node pass-through still makes sense as a >>> separate thing for containers anyway since raw `lxc....` entries in the >>> container config can currently be very inconvenient to deal with >>> particularly with unprivileged containers (read on below for why...) >> >> just to chime in here, i don't think it'll be easily possible to reuse >> the pci/usb maps as is since we'd have to map from pciid /usb-vendor/device >> (or path) to a device node? i don't think thats generally possible, since >> the driver does not always make that info easily available >> (e.g. multi gpu setup and /dev/dri/cardX, or usb-to-serial adapters >> and /dev/ttySX ?) i guess it could work, but we probably would have >> to implement that for every driver out there? >> > > USB should be workable via resolving to /dev/bus/usb/*, PCI could be, > theoretically, but isn't now and probably won't be anytime soon ? i.e., > as Wolfgang mentioned off list, there's a reason that there's no > /dev/bus/pci/ > >> what i would like to see however is to integrate a new type of mapping >> for container devices specifically so that the ux is the same >> (create mappings for whole cluster, assigning privileges, etc) > > I'd try hard to re-use the USB mappings, those seem to be one of the > most common pass-through setups for containers (e.g., for those > home automation zigbee/matter/... adapters, or in some countries DVB-T > ones, be it for TV or ADS-B plane tracking). i guess, but sadly the /dev/bus/usb endpoint is mostly not what you want to pass-through but the driver specific /dev/ttySX /dev/dvb/X and so on (there are situations where the /dev/bus/usb path is the wanted one, but there are many where it isn't) and while we can map from those to the usb device/vendor/path the reverse mapping is not so easy (at least when i tried i did not found a generic way via udev or similar i would welcome it though if there is a way to do that of course > > If we can make USB work then we'd have the basic concept of attaching > a mapping done, adding a new type of (block/char) device mapping could > then be an independent task for later to keep scope a bit smaller. > > Fixing Wolfgang's comments for workable pass-through for unprivileged > containers is probably the most important change needed for now. > > I'd then even be open to apply this with (root at pam only!) absolute > path to /dev support only, but IMO resolving the mapping itself should > not be too hard, so if using /dev/bus/usb/ works having that in there > from the start would be definitively nice. From d.csapak at proxmox.com Fri Oct 20 11:23:28 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Fri, 20 Oct 2023 11:23:28 +0200 Subject: [pve-devel] [PATCH v3 storage 1/1] fix #254: iscsi: add support for multipath iscsi targets In-Reply-To: <20231017161948.139795-2-ykonotopov@gnome.org> References: <20231017161948.139795-1-ykonotopov@gnome.org> <20231017161948.139795-2-ykonotopov@gnome.org> Message-ID: hi, nice elegant solution for not needing to change the config or introducing a new file :) there is one caveats with this solution, but that should not matter for most situations: in case the configured portal is not accessible, any new node in the cluster (or one that did not activate the storage until then) will not be able to activate that storage (without manually doing a discovery on another portal) i don't think this is a huge problem though, the patch still makes the situation a whole lot better the patch looks mostly fine, some comments inline On 10/17/23 18:19, Yuri Konotopov wrote: > With this patch Proxmox now tries to login to all discovered portals in > case some of them are not logged yet. > In case of multipath configuration when initially configured portal is > missing for some reason Proxmox don't lose iscsi storage now and can > succesfully restore iscsi connection between reboots. > > Signed-off-by: Yuri Konotopov > --- > PVE/Storage.pm | 2 +- > PVE/Storage/ISCSIPlugin.pm | 117 ++++++++++++++++++++++++++++--------- > 2 files changed, 89 insertions(+), 30 deletions(-) > > diff --git a/PVE/Storage.pm b/PVE/Storage.pm > index cec3996..87755ac 100755 > --- a/PVE/Storage.pm > +++ b/PVE/Storage.pm > @@ -1433,7 +1433,7 @@ sub scan_iscsi { > die "unable to parse/resolve portal address '${portal_in}'\n"; > } > > - return PVE::Storage::ISCSIPlugin::iscsi_discovery($portal); > + return PVE::Storage::ISCSIPlugin::iscsi_discovery([ $portal ]); > } > > sub storage_default_format { > diff --git a/PVE/Storage/ISCSIPlugin.pm b/PVE/Storage/ISCSIPlugin.pm > index a79fcb0..1d47f3a 100644 > --- a/PVE/Storage/ISCSIPlugin.pm > +++ b/PVE/Storage/ISCSIPlugin.pm > @@ -18,6 +18,9 @@ use base qw(PVE::Storage::Plugin); > my $ISCSIADM = '/usr/bin/iscsiadm'; > $ISCSIADM = undef if ! -X $ISCSIADM; > > +# Example: 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f > +my $ISCSI_TARGET_RE = qr/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/m; the '/m' here is unnecessary since we only match single lines anyway > + > sub check_iscsi_support { > my $noerr = shift; > > @@ -45,11 +48,12 @@ sub iscsi_session_list { > eval { > run_command($cmd, errmsg => 'iscsi session scan failed', outfunc => sub { > my $line = shift; > - > - if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)(\s+\S+)?\s*$/) { > - my ($session, $target) = ($1, $2); > + # example: tcp: [1] 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f (non-flash) > + if ($line =~ m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)(\s+\S+)?\s*$/) { nit: the last capture group is not really necessary AFAICS (since we don't use it) > + my ($session_id, $portal, $target) = ($1, $2, $3); > # there can be several sessions per target (multipath) > - push @{$res->{$target}}, $session; > + my %session = ( session_id => $session_id, portal => $portal ); > + push @{$res->{$target}}, \%session; > } > }); > }; > @@ -68,42 +72,77 @@ sub iscsi_test_portal { > return PVE::Network::tcp_ping($server, $port || 3260, 2); > } > > -sub iscsi_discovery { > - my ($portal) = @_; > +sub iscsi_portals { > + my ($target, $portal_in) = @_; > > check_iscsi_support (); > > - my $res = {}; > - return $res if !iscsi_test_portal($portal); # fixme: raise exception here? > + my $res = []; > + my $cmd = [$ISCSIADM, '--mode', 'node']; > + eval { > + run_command($cmd, outfunc => sub { > + my $line = shift; > > - my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; > - run_command($cmd, outfunc => sub { > - my $line = shift; > + if ($line =~ $ISCSI_TARGET_RE) { > + my ($portal, $portal_target) = ($1, $2); > + if ($portal_target eq $target) { > + push @{$res}, $portal; > + } > + } > + }); > + }; > > - if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { > - my $portal = $1; > - my $target = $2; > - # one target can have more than one portal (multipath). > - push @{$res->{$target}}, $portal; > - } > - }); > + if ($@) { > + warn $@; > + return [ $portal_in ]; > + } > + > + return $res; > +} > + > +sub iscsi_discovery { > + my ($portals) = @_; > + > + check_iscsi_support (); > + > + my $res = {}; > + for my $portal ($portals->@*) { > + next if !iscsi_test_portal($portal); # fixme: raise exception here? > + > + my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; > + eval { > + run_command($cmd, outfunc => sub { > + my $line = shift; > + > + if ($line =~ $ISCSI_TARGET_RE) { > + my ($portal, $target) = ($1, $2); > + # one target can have more than one portal (multipath) > + # and sendtargets should return all of them in single call > + push @{$res->{$target}}, $portal; > + } > + }); > + }; > + > + # In case of multipath we want to exit on any portal available > + last if !$@; i don't really like this, because if the discovery output did not include any line that we could parse as a target, it still exits the loop i'd rather check if the $res variable has picked up any targets, since we need at least one? e.g. something like this: last if scalar(keys %$res) > 0; that way we ensure that we have at least one target or we go to the next portal does that make sense? > + } > > return $res; > } > > sub iscsi_login { > - my ($target, $portal_in) = @_; > + my ($target, $portals) = @_; > > check_iscsi_support(); > > - eval { iscsi_discovery($portal_in); }; > + eval { iscsi_discovery($portals); }; > warn $@ if $@; > > run_command([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login']); > } > > sub iscsi_logout { > - my ($target, $portal) = @_; > + my ($target) = @_; > > check_iscsi_support(); > > @@ -133,7 +172,7 @@ sub iscsi_session_rescan { > } > > foreach my $session (@$session_list) { > - my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session, '--rescan']; > + my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session->{session_id}, '--rescan']; > eval { run_command($cmd, outfunc => sub {}); }; > warn $@ if $@; > } > @@ -379,14 +418,28 @@ sub activate_storage { > > return if !check_iscsi_support(1); > > - my $session = iscsi_session($cache, $scfg->{target}); > + my $sessions = iscsi_session($cache, $scfg->{target}); > + my $portals = iscsi_portals($scfg->{target}, $scfg->{portal}); > + my $do_login = !defined($sessions); > > - if (!defined ($session)) { > - eval { iscsi_login($scfg->{target}, $scfg->{portal}); }; > + if (!$do_login) { > + # We should check that sessions for all portals are available > + my $session_portals = [ map { $_->{portal} } (@$sessions) ]; > + > + for my $portal (@$portals) { > + if (!grep(/^\Q$portal\E$/, @$session_portals)) { > + $do_login = 1; > + last; > + } > + } > + } > + > + if ($do_login) { > + eval { iscsi_login($scfg->{target}, $portals); }; > warn $@ if $@; > } else { > # make sure we get all devices > - iscsi_session_rescan($session); > + iscsi_session_rescan($sessions); > } > } > > @@ -396,15 +449,21 @@ sub deactivate_storage { > return if !check_iscsi_support(1); > > if (defined(iscsi_session($cache, $scfg->{target}))) { > - iscsi_logout($scfg->{target}, $scfg->{portal}); > + iscsi_logout($scfg->{target}); > } > } > > sub check_connection { > my ($class, $storeid, $scfg) = @_; > > - my $portal = $scfg->{portal}; > - return iscsi_test_portal($portal); > + my $portals = iscsi_portals($scfg->{target}, $scfg->{portal}); > + > + for my $portal (@$portals) { > + my $result = iscsi_test_portal($portal); > + return $result if $result; > + } > + > + return 0; > } > > sub volume_resize { From c.heiss at proxmox.com Fri Oct 20 11:46:44 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 20 Oct 2023 11:46:44 +0200 Subject: [pve-devel] [PATCH installer 1/5] net: move hostname/fqdn regexes into common code In-Reply-To: <20231020094651.432513-1-c.heiss@proxmox.com> References: <20231020094651.432513-1-c.heiss@proxmox.com> Message-ID: <20231020094651.432513-2-c.heiss@proxmox.com> Such that they can be re-used by other parts. No functional changes. Signed-off-by: Christoph Heiss --- Proxmox/Sys/Net.pm | 3 +++ proxinstall | 9 +++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Proxmox/Sys/Net.pm b/Proxmox/Sys/Net.pm index ba368c1..f5a9885 100644 --- a/Proxmox/Sys/Net.pm +++ b/Proxmox/Sys/Net.pm @@ -6,6 +6,9 @@ use warnings; use base qw(Exporter); our @EXPORT_OK = qw(parse_ip_address parse_ip_mask); +our $HOSTNAME_RE = "(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]*[a-zA-Z0-9])?)"; +our $FQDN_RE = "(?:${HOSTNAME_RE}\.)*${HOSTNAME_RE}"; + my $IPV4OCTET = "(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9])?[0-9])"; my $IPV4RE = "(?:(?:$IPV4OCTET\\.){3}$IPV4OCTET)"; my $IPV6H16 = "(?:[0-9a-fA-F]{1,4})"; diff --git a/proxinstall b/proxinstall index d5b2565..88f194f 100755 --- a/proxinstall +++ b/proxinstall @@ -429,8 +429,6 @@ sub create_ipconf_view { $text =~ s/^\s+//; $text =~ s/\s+$//; - my $namere = "([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)"; - # Debian does not support purely numeric hostnames if ($text && $text =~ /^[0-9]+(?:\.|$)/) { Proxmox::UI::message("Purely numeric hostnames are not allowed."); @@ -438,8 +436,11 @@ sub create_ipconf_view { return; } - if ($text && $text =~ m/^(${namere}\.)*${namere}$/ && $text !~ m/.example.invalid$/ && - $text =~ m/^([^\.]+)\.(\S+)$/) { + if ($text + && $text =~ m/^${Proxmox::Sys::Net::FQDN_RE}$/ + && $text !~ m/.example.invalid$/ + && $text =~ m/^([^\.]+)\.(\S+)$/ + ) { Proxmox::Install::Config::set_hostname($1); Proxmox::Install::Config::set_domain($2); } else { -- 2.42.0 From c.heiss at proxmox.com Fri Oct 20 11:46:47 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 20 Oct 2023 11:46:47 +0200 Subject: [pve-devel] [PATCH installer 4/5] tui: use hostname from run env if available In-Reply-To: <20231020094651.432513-1-c.heiss@proxmox.com> References: <20231020094651.432513-1-c.heiss@proxmox.com> Message-ID: <20231020094651.432513-5-c.heiss@proxmox.com> This now tries to use the hostname from the DHCP lease if it was set, falling back to the product name as before. Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/options.rs | 22 +++++++++++++++------- proxmox-tui-installer/src/setup.rs | 3 +++ 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs index a0a81c9..9e54da7 100644 --- a/proxmox-tui-installer/src/options.rs +++ b/proxmox-tui-installer/src/options.rs @@ -337,11 +337,16 @@ pub struct NetworkOptions { pub dns_server: IpAddr, } +impl NetworkOptions { + const DEFAULT_DOMAIN: &str = "example.invalid"; +} + impl Default for NetworkOptions { fn default() -> Self { let fqdn = format!( - "{}.example.invalid", - crate::current_product().default_hostname() + "{}.{}", + crate::current_product().default_hostname(), + Self::DEFAULT_DOMAIN ); // TODO: Retrieve automatically Self { @@ -363,11 +368,14 @@ impl From<&NetworkInfo> for NetworkOptions { this.dns_server = *ip; } - if let Some(domain) = &info.dns.domain { - let hostname = crate::current_product().default_hostname(); - if let Ok(fqdn) = Fqdn::from(&format!("{hostname}.{domain}")) { - this.fqdn = fqdn; - } + let hostname = info + .hostname + .as_deref() + .unwrap_or_else(|| crate::current_product().default_hostname()); + let domain = info.dns.domain.as_deref().unwrap_or(Self::DEFAULT_DOMAIN); + + if let Ok(fqdn) = Fqdn::from(&format!("{hostname}.{domain}")) { + this.fqdn = fqdn; } if let Some(routes) = &info.routes { diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs index 942e319..c7b32e5 100644 --- a/proxmox-tui-installer/src/setup.rs +++ b/proxmox-tui-installer/src/setup.rs @@ -408,6 +408,9 @@ pub struct NetworkInfo { /// (Contains no entries for devices with only link-local addresses.) #[serde(default)] pub interfaces: HashMap, + + /// The hostname of this machine, if set by the DHCP server. + pub hostname: Option, } #[derive(Clone, Deserialize)] -- 2.42.0 From c.heiss at proxmox.com Fri Oct 20 11:46:45 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 20 Oct 2023 11:46:45 +0200 Subject: [pve-devel] [PATCH installer 2/5] run env: retrieve and store hostname from DHCP lease if available In-Reply-To: <20231020094651.432513-1-c.heiss@proxmox.com> References: <20231020094651.432513-1-c.heiss@proxmox.com> Message-ID: <20231020094651.432513-3-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- Proxmox/Install/RunEnv.pm | 4 ++++ Proxmox/Sys/Net.pm | 25 +++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Proxmox/Install/RunEnv.pm b/Proxmox/Install/RunEnv.pm index 19b5387..7589679 100644 --- a/Proxmox/Install/RunEnv.pm +++ b/Proxmox/Install/RunEnv.pm @@ -263,6 +263,10 @@ sub query_installation_environment : prototype() { routes => $routes, dns => query_dns(), }; + + # Cannot be put directly in the above hash as it might return undef .. + $output->{network}->{hostname} = Proxmox::Sys::Net::get_dhcp_hostname(); + # FIXME: move whatever makes sense over to Proxmox::Sys::Net:: and keep that as single source, # it can then use some different structure just fine (after adapting the GTK GUI to that) but # **never** to (slightly different!) things for the same stuff... diff --git a/Proxmox/Sys/Net.pm b/Proxmox/Sys/Net.pm index f5a9885..35d2abd 100644 --- a/Proxmox/Sys/Net.pm +++ b/Proxmox/Sys/Net.pm @@ -189,4 +189,29 @@ sub get_ip_config { } } +# Tries to detect the hostname for this system via DHCP, if available. +# DHCP server can set option 12 to inform the client about it's hostname [0]. +# dhclient dumps all options set by the DHCP server it in lease file, so just +# read it from there. +# [0] RFC 2132, section 3.14 +sub get_dhcp_hostname : prototype() { + my $leasefile = '/var/lib/dhcp/dhclient.leases'; + return if ! -f $leasefile; + + open (my $fh, '<', $leasefile) or return; + + my $name = undef; + while (my $line = <$fh>) { + # "The name may or may not be qualified with the local domain name" + # Thus, only match the first part. + if ($line =~ m/^\s+option host-name \"(${FQDN_RE})\";$/) { + $name = $1; + last; + } + } + + close($fh); + return $1 if defined($name) && $name =~ m/^([^\.]+)(?:\.(?:\S+))?$/; +} + 1; -- 2.42.0 From c.heiss at proxmox.com Fri Oct 20 11:46:43 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 20 Oct 2023 11:46:43 +0200 Subject: [pve-devel] [PATCH installer 0/5] use hostname from DHCP lease if available Message-ID: <20231020094651.432513-1-c.heiss@proxmox.com> DHCP servers can set option 12 ("host-name") for client leases [0], telling them about their hostname. It's very much non-invasive and falls back to the default values as done currently. This came up while talking to Aaron, which he noticed (esp. during trainings) that this would be a very useful feature too have. I have tested this with the "host-name" entry set and unset, as well as any combinations of that with the domain name being set or unset. [0] https://datatracker.ietf.org/doc/html/rfc2132#section-3.14 Christoph Heiss (5): net: move hostname/fqdn regexes into common code run env: retrieve and store hostname from DHCP lease if available proxinstall: use hostname from run env if available tui: use hostname from run env if available tui: add some tests for `NetworkInfo` -> `NetworkOptions` conversion Proxmox/Install/RunEnv.pm | 4 + Proxmox/Sys/Net.pm | 28 ++++++ proxinstall | 15 +-- proxmox-tui-installer/src/options.rs | 132 +++++++++++++++++++++++++-- proxmox-tui-installer/src/setup.rs | 3 + proxmox-tui-installer/src/utils.rs | 2 +- 6 files changed, 169 insertions(+), 15 deletions(-) -- 2.42.0 From c.heiss at proxmox.com Fri Oct 20 11:46:46 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 20 Oct 2023 11:46:46 +0200 Subject: [pve-devel] [PATCH installer 3/5] proxinstall: use hostname from run env if available In-Reply-To: <20231020094651.432513-1-c.heiss@proxmox.com> References: <20231020094651.432513-1-c.heiss@proxmox.com> Message-ID: <20231020094651.432513-4-c.heiss@proxmox.com> This now tries to use the hostname from the DHCP lease if it was set, falling back to the product name as before. Signed-off-by: Christoph Heiss --- proxinstall | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/proxinstall b/proxinstall index 88f194f..4d61fa7 100755 --- a/proxinstall +++ b/proxinstall @@ -402,9 +402,11 @@ sub create_ipconf_view { $vbox->pack_start($devicebox, 0, 0, 2); my $fqdn = Proxmox::Install::Config::get_fqdn(); - my $hn = $fqdn // "$iso_env->{product}." . ($ipconf->{domain} // "example.invalid"); + my $hostname = $run_env->{network}->{hostname} || $iso_env->{product}; + my $domain = $ipconf->{domain} || "example.invalid"; + $fqdn //= "$hostname.$domain"; - my ($hostbox, $hostentry) = create_text_input($hn, 'Hostname (FQDN):'); + my ($hostbox, $hostentry) = create_text_input($fqdn, 'Hostname (FQDN):'); $vbox->pack_start($hostbox, 0, 0, 2); $vbox->pack_start($cidr_box, 0, 0, 2); -- 2.42.0 From c.heiss at proxmox.com Fri Oct 20 11:46:48 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 20 Oct 2023 11:46:48 +0200 Subject: [pve-devel] [PATCH installer 5/5] tui: add some tests for `NetworkInfo` -> `NetworkOptions` conversion In-Reply-To: <20231020094651.432513-1-c.heiss@proxmox.com> References: <20231020094651.432513-1-c.heiss@proxmox.com> Message-ID: <20231020094651.432513-6-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/options.rs | 110 ++++++++++++++++++++++++++- proxmox-tui-installer/src/utils.rs | 2 +- 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs index 9e54da7..d4614aa 100644 --- a/proxmox-tui-installer/src/options.rs +++ b/proxmox-tui-installer/src/options.rs @@ -328,7 +328,7 @@ impl Default for PasswordOptions { } } -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct NetworkOptions { pub ifname: String, pub fqdn: Fqdn, @@ -450,3 +450,111 @@ impl InstallerOptions { ] } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::setup::{ + Dns, Gateway, Interface, IsoInfo, IsoLocations, NetworkInfo, ProductConfig, ProxmoxProduct, + Routes, SetupInfo, + }; + use std::{collections::HashMap, path::PathBuf}; + + fn fill_setup_info() { + crate::init_setup_info(SetupInfo { + config: ProductConfig { + fullname: "Proxmox VE".to_owned(), + product: ProxmoxProduct::PVE, + enable_btrfs: true, + }, + iso_info: IsoInfo { + release: String::new(), + isorelease: String::new(), + }, + locations: IsoLocations { + iso: PathBuf::new(), + }, + }); + } + + #[test] + fn network_options_from_setup_network_info() { + fill_setup_info(); + + let mut interfaces = HashMap::new(); + interfaces.insert( + "eth0".to_owned(), + Interface { + name: "eth0".to_owned(), + index: 0, + mac: "01:23:45:67:89:ab".to_owned(), + addresses: Some(vec![ + CidrAddress::new(Ipv4Addr::new(192, 168, 0, 2), 24).unwrap() + ]), + }, + ); + + let mut info = NetworkInfo { + dns: Dns { + domain: Some("bar.com".to_owned()), + dns: Vec::new(), + }, + routes: Some(Routes { + gateway4: Some(Gateway { + dev: "eth0".to_owned(), + gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)), + }), + gateway6: None, + }), + interfaces, + hostname: Some("foo".to_owned()), + }; + + assert_eq!( + NetworkOptions::from(&info), + NetworkOptions { + ifname: "eth0".to_owned(), + fqdn: Fqdn::from("foo.bar.com").unwrap(), + address: CidrAddress::new(Ipv4Addr::new(192, 168, 0, 2), 24).unwrap(), + gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)), + dns_server: Ipv4Addr::UNSPECIFIED.into(), + } + ); + + info.hostname = None; + assert_eq!( + NetworkOptions::from(&info), + NetworkOptions { + ifname: "eth0".to_owned(), + fqdn: Fqdn::from("pve.bar.com").unwrap(), + address: CidrAddress::new(Ipv4Addr::new(192, 168, 0, 2), 24).unwrap(), + gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)), + dns_server: Ipv4Addr::UNSPECIFIED.into(), + } + ); + + info.dns.domain = None; + assert_eq!( + NetworkOptions::from(&info), + NetworkOptions { + ifname: "eth0".to_owned(), + fqdn: Fqdn::from("pve.example.invalid").unwrap(), + address: CidrAddress::new(Ipv4Addr::new(192, 168, 0, 2), 24).unwrap(), + gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)), + dns_server: Ipv4Addr::UNSPECIFIED.into(), + } + ); + + info.hostname = Some("foo".to_owned()); + assert_eq!( + NetworkOptions::from(&info), + NetworkOptions { + ifname: "eth0".to_owned(), + fqdn: Fqdn::from("foo.example.invalid").unwrap(), + address: CidrAddress::new(Ipv4Addr::new(192, 168, 0, 2), 24).unwrap(), + gateway: IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)), + dns_server: Ipv4Addr::UNSPECIFIED.into(), + } + ); + } +} diff --git a/proxmox-tui-installer/src/utils.rs b/proxmox-tui-installer/src/utils.rs index 516f9c6..89349ed 100644 --- a/proxmox-tui-installer/src/utils.rs +++ b/proxmox-tui-installer/src/utils.rs @@ -33,7 +33,7 @@ pub enum CidrAddressParseError { /// assert_eq!(ipv4.to_string(), "192.168.0.1/24"); /// assert_eq!(ipv6.to_string(), "2001:db8::c0a8:1/32"); /// ``` -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct CidrAddress { addr: IpAddr, mask: usize, -- 2.42.0 From d.csapak at proxmox.com Fri Oct 20 15:27:36 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Fri, 20 Oct 2023 15:27:36 +0200 Subject: [pve-devel] [PATCH manager v2 4/5] ui: GuestStatusView: show privileged status as new row In-Reply-To: <20230705111257.759836-5-c.heiss@proxmox.com> References: <20230705111257.759836-1-c.heiss@proxmox.com> <20230705111257.759836-5-c.heiss@proxmox.com> Message-ID: <54d4c5b7-ebd9-4e0d-b789-1669455b059e@proxmox.com> one small comment inline On 7/5/23 13:12, Christoph Heiss wrote: > As that info is not available through the store (which stores the > status), it must be fetched separately. > > Signed-off-by: Christoph Heiss > --- > www/manager6/panel/GuestStatusView.js | 32 +++++++++++++++++++++++++++ > 1 file changed, 32 insertions(+) > > diff --git a/www/manager6/panel/GuestStatusView.js b/www/manager6/panel/GuestStatusView.js > index 8db1f492c..ca2f03453 100644 > --- a/www/manager6/panel/GuestStatusView.js > +++ b/www/manager6/panel/GuestStatusView.js > @@ -11,6 +11,29 @@ Ext.define('PVE.panel.GuestStatusView', { > }; > }, > > + controller: { > + xclass: 'Ext.app.ViewController', > + > + init: view => { while this works, it's a rather unusual use for us normally we use the standard 'function(view) {}' syntax the advantage of that is that the 'this' object then is the controller if someone wants to use the controller in the future and starts using 'this' it won't work with the arrow syntax (it will be undefined) > + if (view.pveSelNode.data.type !== 'lxc') { > + return; > + } > + > + const nodename = view.pveSelNode.data.node; > + const vmid = view.pveSelNode.data.vmid; > + > + Proxmox.Utils.API2Request({ > + url: `/api2/extjs/nodes/${nodename}/lxc/${vmid}/config`, > + waitMsgTargetView: view, > + method: 'GET', > + success: ({ result }) => { > + view.down('#unprivileged').updateValue( > + Proxmox.Utils.format_boolean(result.data.unprivileged)); > + }, > + }); > + }, > + }, > + > layout: { > type: 'vbox', > align: 'stretch', > @@ -58,6 +81,15 @@ Ext.define('PVE.panel.GuestStatusView', { > }, > printBar: false, > }, > + { > + itemId: 'unprivileged', > + iconCls: 'fa fa-lock fa-fw', > + title: gettext('Unprivileged'), > + printBar: false, > + cbind: { > + hidden: '{isQemu}', > + }, > + }, > { > xtype: 'box', > height: 15, From d.csapak at proxmox.com Fri Oct 20 15:31:14 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Fri, 20 Oct 2023 15:31:14 +0200 Subject: [pve-devel] [PATCH manager v2 5/5] ui: GuestStatusView: show distro logo and name in summary header In-Reply-To: <20230705111257.759836-6-c.heiss@proxmox.com> References: <20230705111257.759836-1-c.heiss@proxmox.com> <20230705111257.759836-6-c.heiss@proxmox.com> Message-ID: one small comment inline On 7/5/23 13:12, Christoph Heiss wrote: > It fits neatly there, is rather unintrusive and yet still visible at > first sight. It also solves the problem of having to create a bigger > row, so that the icon is still easily recognisable. At the default > font-size of 13pt, this really wasn't the case. > > Verified that each supported distro is present in the font and the name > matches up and tested through all supported distros (including > 'unmanaged'). > > Signed-off-by: Christoph Heiss > --- > www/manager6/panel/GuestStatusView.js | 19 ++++++++++++++++++- > 1 file changed, 18 insertions(+), 1 deletion(-) > > diff --git a/www/manager6/panel/GuestStatusView.js b/www/manager6/panel/GuestStatusView.js > index ca2f03453..c773f252d 100644 > --- a/www/manager6/panel/GuestStatusView.js > +++ b/www/manager6/panel/GuestStatusView.js > @@ -29,6 +29,7 @@ Ext.define('PVE.panel.GuestStatusView', { > success: ({ result }) => { > view.down('#unprivileged').updateValue( > Proxmox.Utils.format_boolean(result.data.unprivileged)); > + view.ostype = result.data.ostype; > }, > }); > }, > @@ -166,6 +167,22 @@ Ext.define('PVE.panel.GuestStatusView', { > + ')'; > } > > - me.setTitle(me.getRecordValue('name') + text); > + let title = `
${me.getRecordValue('name') + text}
`; > + > + if (me.pveSelNode.data.type === 'lxc' && me.ostype && me.ostype !== 'unmanaged') { > + // Manual mappings for distros with special casing > + const namemap = { > + 'archlinux': 'Arch Linux', > + 'nixos': 'NixOS', > + 'opensuse': 'openSUSE', > + 'centos': 'CentOS', > + }; > + > + const distro = namemap[me.ostype] ?? Ext.String.capitalize(me.ostype); > + title += `
> +  ${distro}
`; > + } while in practice it won't make a difference, we probably want to wrap the distro in an Ext.htmlEncode we don't allow arbitrary values via the api, but if i edit the config it simply returns the value, e.g. having ostype: test in the container config leads to bold text here though i don't think this is a blocker for this series > + > + me.setTitle(title); > }, > }); From d.csapak at proxmox.com Fri Oct 20 15:33:19 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Fri, 20 Oct 2023 15:33:19 +0200 Subject: [pve-devel] [PATCH font-logos/manager v2 0/5] fix #2435: lxc: show distro and privileged status in summary In-Reply-To: <20230705111257.759836-1-c.heiss@proxmox.com> References: <20230705111257.759836-1-c.heiss@proxmox.com> Message-ID: <58d30e37-be45-42bf-82f6-1e9e57a53bb1@proxmox.com> the series looks mostly fine to me, i wrote two small comments in the relevant patches, but both are not super important to fix IMO aside from that consider this series Reviewed-by: Dominik Csapak Tested-by: Dominik Csapak From t.lamprecht at proxmox.com Fri Oct 20 17:21:41 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 20 Oct 2023 17:21:41 +0200 Subject: [pve-devel] applied-series: [PATCH installer 0/5] use hostname from DHCP lease if available In-Reply-To: <20231020094651.432513-1-c.heiss@proxmox.com> References: <20231020094651.432513-1-c.heiss@proxmox.com> Message-ID: Am 20/10/2023 um 11:46 schrieb Christoph Heiss: > DHCP servers can set option 12 ("host-name") for client leases [0], > telling them about their hostname. It's very much non-invasive and falls > back to the default values as done currently. > > This came up while talking to Aaron, which he noticed (esp. during > trainings) that this would be a very useful feature too have. > > I have tested this with the "host-name" entry set and unset, as well as > any combinations of that with the domain name being set or unset. > > [0] https://datatracker.ietf.org/doc/html/rfc2132#section-3.14 > > Christoph Heiss (5): > net: move hostname/fqdn regexes into common code > run env: retrieve and store hostname from DHCP lease if available > proxinstall: use hostname from run env if available > tui: use hostname from run env if available > tui: add some tests for `NetworkInfo` -> `NetworkOptions` conversion > > Proxmox/Install/RunEnv.pm | 4 + > Proxmox/Sys/Net.pm | 28 ++++++ > proxinstall | 15 +-- > proxmox-tui-installer/src/options.rs | 132 +++++++++++++++++++++++++-- > proxmox-tui-installer/src/setup.rs | 3 + > proxmox-tui-installer/src/utils.rs | 2 +- > 6 files changed, 169 insertions(+), 15 deletions(-) > applied series, squashed in a trivial fix to fix the tests though, you probably based of an older git state where the Interface struct doesn't have the "state" member yet, thanks! From t.lamprecht at proxmox.com Fri Oct 20 17:36:44 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 20 Oct 2023 17:36:44 +0200 Subject: [pve-devel] applied: [PATCH v2 debcargo-conf 01/11] package mail-parser 0.8.2 In-Reply-To: <20231002080624.198759-2-l.wagner@proxmox.com> References: <20231002080624.198759-1-l.wagner@proxmox.com> <20231002080624.198759-2-l.wagner@proxmox.com> Message-ID: Am 02/10/2023 um 10:06 schrieb Lukas Wagner: > Signed-off-by: Lukas Wagner > --- > src/mail-parser/debian/changelog | 6 ++ > src/mail-parser/debian/copyright | 49 ++++++++++++ > .../debian/copyright.debcargo.hint | 77 +++++++++++++++++++ > src/mail-parser/debian/debcargo.toml | 2 + > 4 files changed, 134 insertions(+) > create mode 100644 src/mail-parser/debian/changelog > create mode 100644 src/mail-parser/debian/copyright > create mode 100644 src/mail-parser/debian/copyright.debcargo.hint > create mode 100644 src/mail-parser/debian/debcargo.toml > > applied this one already uploaded to our staging repo, thanks! From ykonotopov at gnome.org Fri Oct 20 18:56:14 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Fri, 20 Oct 2023 20:56:14 +0400 Subject: [PATCH v3 storage 1/1] fix #254: iscsi: add support for multipath iscsi targets In-Reply-To: References: <20231017161948.139795-1-ykonotopov@gnome.org> <20231017161948.139795-2-ykonotopov@gnome.org> Message-ID: 20.10.2023 13:23, Dominik Csapak ?????: > hi, Hi, Dominik! Thanks for review! I will address all issues in the next patch version. > > in case the configured portal is not accessible, any new node > in the cluster (or one that did not activate the storage until then) > will not be able to activate that storage > (without manually doing a discovery on another portal) > > i don't think this is a huge problem though, the patch > still makes the situation a whole lot better I agree that this should not be a big issue. This looks like even an issue outside of Proxmox. Administrator can manually login to available portal or replace configured portal in Proxmox config as a workaround. -- Best regards, Yuri Konotopov From ykonotopov at gnome.org Sat Oct 21 11:37:30 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Sat, 21 Oct 2023 13:37:30 +0400 Subject: [PATCH v4 storage 0/1] fix #254: iscsi: add support for multipath iscsi targets Message-ID: <20231021093731.17753-1-ykonotopov@gnome.org> Changes since v3: * drop redundant `m` modifier from `ISCSI_TARGET_RE` regexp * drop redundant group from `iscsi_session_list` regexp * improve stop loop condition in `iscsi_discovery` Changes since v2: * custom configuration file is removed in favor of open-iscsi query * restored `portal` property format since we should not rely on initial configuration but should use discovered configuration instead * changed check_connection() to query all available portals * style fixes Changes since v1: * added configuration file that stores discovered portals * implemented iscsi login in case there missing sessions for any of portals * style fixes * use existent `-list` property format instead of custom one * use PVE::Tools::split_list() instead of custom split function Yuri Konotopov (1): fix #254: iscsi: add support for multipath iscsi targets PVE/Storage.pm | 2 +- PVE/Storage/ISCSIPlugin.pm | 117 ++++++++++++++++++++++++++++--------- 2 files changed, 89 insertions(+), 30 deletions(-) -- 2.41.0 From ykonotopov at gnome.org Sat Oct 21 11:37:31 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Sat, 21 Oct 2023 13:37:31 +0400 Subject: [PATCH v4 storage 1/1] fix #254: iscsi: add support for multipath iscsi targets In-Reply-To: <20231021093731.17753-1-ykonotopov@gnome.org> References: <20231021093731.17753-1-ykonotopov@gnome.org> Message-ID: <20231021093731.17753-2-ykonotopov@gnome.org> With this patch Proxmox now tries to login to all discovered portals in case some of them are not logged yet. In case of multipath configuration when initially configured portal is missing for some reason Proxmox don't lose iscsi storage now and can succesfully restore iscsi connection between reboots. Signed-off-by: Yuri Konotopov --- PVE/Storage.pm | 2 +- PVE/Storage/ISCSIPlugin.pm | 117 ++++++++++++++++++++++++++++--------- 2 files changed, 89 insertions(+), 30 deletions(-) diff --git a/PVE/Storage.pm b/PVE/Storage.pm index cec3996..87755ac 100755 --- a/PVE/Storage.pm +++ b/PVE/Storage.pm @@ -1433,7 +1433,7 @@ sub scan_iscsi { die "unable to parse/resolve portal address '${portal_in}'\n"; } - return PVE::Storage::ISCSIPlugin::iscsi_discovery($portal); + return PVE::Storage::ISCSIPlugin::iscsi_discovery([ $portal ]); } sub storage_default_format { diff --git a/PVE/Storage/ISCSIPlugin.pm b/PVE/Storage/ISCSIPlugin.pm index a79fcb0..b4ab1dd 100644 --- a/PVE/Storage/ISCSIPlugin.pm +++ b/PVE/Storage/ISCSIPlugin.pm @@ -18,6 +18,9 @@ use base qw(PVE::Storage::Plugin); my $ISCSIADM = '/usr/bin/iscsiadm'; $ISCSIADM = undef if ! -X $ISCSIADM; +# Example: 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f +my $ISCSI_TARGET_RE = qr/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/; + sub check_iscsi_support { my $noerr = shift; @@ -45,11 +48,12 @@ sub iscsi_session_list { eval { run_command($cmd, errmsg => 'iscsi session scan failed', outfunc => sub { my $line = shift; - - if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)(\s+\S+)?\s*$/) { - my ($session, $target) = ($1, $2); + # example: tcp: [1] 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f (non-flash) + if ($line =~ m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s+\S+?\s*$/) { + my ($session_id, $portal, $target) = ($1, $2, $3); # there can be several sessions per target (multipath) - push @{$res->{$target}}, $session; + my %session = ( session_id => $session_id, portal => $portal ); + push @{$res->{$target}}, \%session; } }); }; @@ -68,42 +72,77 @@ sub iscsi_test_portal { return PVE::Network::tcp_ping($server, $port || 3260, 2); } -sub iscsi_discovery { - my ($portal) = @_; +sub iscsi_portals { + my ($target, $portal_in) = @_; check_iscsi_support (); - my $res = {}; - return $res if !iscsi_test_portal($portal); # fixme: raise exception here? + my $res = []; + my $cmd = [$ISCSIADM, '--mode', 'node']; + eval { + run_command($cmd, outfunc => sub { + my $line = shift; - my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; - run_command($cmd, outfunc => sub { - my $line = shift; + if ($line =~ $ISCSI_TARGET_RE) { + my ($portal, $portal_target) = ($1, $2); + if ($portal_target eq $target) { + push @{$res}, $portal; + } + } + }); + }; - if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { - my $portal = $1; - my $target = $2; - # one target can have more than one portal (multipath). - push @{$res->{$target}}, $portal; - } - }); + if ($@) { + warn $@; + return [ $portal_in ]; + } + + return $res; +} + +sub iscsi_discovery { + my ($portals) = @_; + + check_iscsi_support (); + + my $res = {}; + for my $portal ($portals->@*) { + next if !iscsi_test_portal($portal); # fixme: raise exception here? + + my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; + eval { + run_command($cmd, outfunc => sub { + my $line = shift; + + if ($line =~ $ISCSI_TARGET_RE) { + my ($portal, $target) = ($1, $2); + # one target can have more than one portal (multipath) + # and sendtargets should return all of them in single call + push @{$res->{$target}}, $portal; + } + }); + }; + + # In case of multipath we can stop after receiving targets from any available portal + last if scalar(keys %$res) > 0; + } return $res; } sub iscsi_login { - my ($target, $portal_in) = @_; + my ($target, $portals) = @_; check_iscsi_support(); - eval { iscsi_discovery($portal_in); }; + eval { iscsi_discovery($portals); }; warn $@ if $@; run_command([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login']); } sub iscsi_logout { - my ($target, $portal) = @_; + my ($target) = @_; check_iscsi_support(); @@ -133,7 +172,7 @@ sub iscsi_session_rescan { } foreach my $session (@$session_list) { - my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session, '--rescan']; + my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session->{session_id}, '--rescan']; eval { run_command($cmd, outfunc => sub {}); }; warn $@ if $@; } @@ -379,14 +418,28 @@ sub activate_storage { return if !check_iscsi_support(1); - my $session = iscsi_session($cache, $scfg->{target}); + my $sessions = iscsi_session($cache, $scfg->{target}); + my $portals = iscsi_portals($scfg->{target}, $scfg->{portal}); + my $do_login = !defined($sessions); - if (!defined ($session)) { - eval { iscsi_login($scfg->{target}, $scfg->{portal}); }; + if (!$do_login) { + # We should check that sessions for all portals are available + my $session_portals = [ map { $_->{portal} } (@$sessions) ]; + + for my $portal (@$portals) { + if (!grep(/^\Q$portal\E$/, @$session_portals)) { + $do_login = 1; + last; + } + } + } + + if ($do_login) { + eval { iscsi_login($scfg->{target}, $portals); }; warn $@ if $@; } else { # make sure we get all devices - iscsi_session_rescan($session); + iscsi_session_rescan($sessions); } } @@ -396,15 +449,21 @@ sub deactivate_storage { return if !check_iscsi_support(1); if (defined(iscsi_session($cache, $scfg->{target}))) { - iscsi_logout($scfg->{target}, $scfg->{portal}); + iscsi_logout($scfg->{target}); } } sub check_connection { my ($class, $storeid, $scfg) = @_; - my $portal = $scfg->{portal}; - return iscsi_test_portal($portal); + my $portals = iscsi_portals($scfg->{target}, $scfg->{portal}); + + for my $portal (@$portals) { + my $result = iscsi_test_portal($portal); + return $result if $result; + } + + return 0; } sub volume_resize { -- 2.41.0 From t.lamprecht at proxmox.com Sat Oct 21 12:46:16 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Sat, 21 Oct 2023 12:46:16 +0200 Subject: [pve-devel] applied: [PATCH pve-kernel] backport exposing FLUSHBYASID when running nested VMs on AMD CPUs In-Reply-To: <20231019145912.3152371-1-s.sterz@proxmox.com> References: <20231019145912.3152371-1-s.sterz@proxmox.com> Message-ID: <49b6b551-14f7-43a8-a25a-49e41388cfa4@proxmox.com> Am 19/10/2023 um 16:59 schrieb Stefan Sterz: > this exposes the FLUSHBYASID CPU flag to nested VMs when running on an > AMD CPU. also reverts a made up check that would advertise > FLUSHBYASID as not supported. this enable certain modern hypervisors > such as VMWare ESXi 7 and Workstation 17 to run nested VMs properly > again. > > Signed-off-by: Stefan Sterz > --- > ...k-for-reserved-encodings-of-TLB_CONT.patch | 49 +++++++++++++++++++ > ...-Advertise-support-for-flush-by-ASID.patch | 39 +++++++++++++++ > 2 files changed, 88 insertions(+) > create mode 100644 patches/kernel/0014-Revert-nSVM-Check-for-reserved-encodings-of-TLB_CONT.patch > create mode 100644 patches/kernel/0015-KVM-nSVM-Advertise-support-for-flush-by-ASID.patch > > applied, thanks! From t.lamprecht at proxmox.com Sat Oct 21 16:36:39 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Sat, 21 Oct 2023 16:36:39 +0200 Subject: [pve-devel] applied: [PATCH manager v2] pvesh: decode streamed responses In-Reply-To: <20230607141511.560351-1-c.heiss@proxmox.com> References: <20230607141511.560351-1-c.heiss@proxmox.com> Message-ID: Am 07/06/2023 um 16:15 schrieb Christoph Heiss: > This allows to use `pvesh` on endpoints like /nodes/{node}/journal, > which return streamed (and possibly gzip'd) responses. > > Currently, e.g. `pvesh get /nodes/localhost/journal --lastentries 10` > fails with: > > gzip: stdout: Broken pipe > got hash object, but result schema specified array! > > Using e.g. `--output-format yaml` resulted in: > > --- > download: > content-encoding: gzip > content-type: application/json > fh: &1 !!perl/ref > =: *1 > stream: 1 > > gzip: stdout: Broken pipe > Failed to write > > This is due the API call returning a "download" object (as seen above), > which contains (among some other things) a file handle to read the > response from. > > With this patch, the response from such endpoints is now correctly read > and displayed. Only handles combinations of `Content-Encoding` == 'gzip' > and either 'text/plain' or 'application/json' for `Content-Type`. > > This tries to mimic the behavior of the API server implementation when > encountering `download` objects. > > Tested this with all four output formats 'text', 'json', 'json-pretty' > and 'yaml', as well as "cross-node" in a local test cluster. > > Signed-off-by: Christoph Heiss > --- > Changes v1 -> v2: > * Fix some style nits > * Move content-{encoding,type} checks before actual decoding > > As far as I could see (aka. grep for it), the only two endpoints which > implement this are /nodes/{node}/journal and > /nodes/{node}/tasks/{upid}/log, latter one only with `--download 1` > set. > > PVE/CLI/pvesh.pm | 39 +++++++++++++++++++++++++++++++++++++++ > 1 file changed, 39 insertions(+) > > applied, thanks! From d.csapak at proxmox.com Mon Oct 23 10:11:05 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Mon, 23 Oct 2023 10:11:05 +0200 Subject: [pve-devel] [PATCH v4 storage 1/1] fix #254: iscsi: add support for multipath iscsi targets In-Reply-To: <20231021093731.17753-2-ykonotopov@gnome.org> References: <20231021093731.17753-1-ykonotopov@gnome.org> <20231021093731.17753-2-ykonotopov@gnome.org> Message-ID: Hi, sorry but I just noticed that it seems you did not create this patch on top of our current master? at least here it does not apply cleanly, since the files got moved to src/ (in may already) so could you please rebase your patches on the current master branch and send it again? (i did not get around to check the v4 yet, but a rebase shouldn't be a problem) From f.ebner at proxmox.com Mon Oct 23 10:59:08 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Mon, 23 Oct 2023 10:59:08 +0200 Subject: [pve-devel] [PATCH qemu-server] vzdump: assemble: improve error messages Message-ID: <20231023085908.22902-1-f.ebner@proxmox.com> by including the errno. Might make it clearer what the issue is in cases like: https://forum.proxmox.com/threads/135261/ Also add the missing newlines, the missing "to" in the second message, switch to the more common "or die" and avoid line bloat while at it. Signed-off-by: Fiona Ebner --- PVE/VZDump/QemuServer.pm | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/PVE/VZDump/QemuServer.pm b/PVE/VZDump/QemuServer.pm index f811cbf2..be7d8e1e 100644 --- a/PVE/VZDump/QemuServer.pm +++ b/PVE/VZDump/QemuServer.pm @@ -222,10 +222,8 @@ sub assemble { my $firewall_src = "/etc/pve/firewall/$vmid.fw"; my $firewall_dest = "$task->{tmpdir}/qemu-server.fw"; - my $outfd = IO::File->new (">$outfile") || - die "unable to open '$outfile'"; - my $conffd = IO::File->new ($conffile, 'r') || - die "unable open '$conffile'"; + my $outfd = IO::File->new(">$outfile") or die "unable to open '$outfile' - $!\n"; + my $conffd = IO::File->new($conffile, 'r') or die "unable to open '$conffile' - $!\n"; my $found_snapshot; my $found_pending; -- 2.39.2 From f.gruenbichler at proxmox.com Mon Oct 23 11:47:20 2023 From: f.gruenbichler at proxmox.com (Fabian =?iso-8859-1?q?Gr=FCnbichler?=) Date: Mon, 23 Oct 2023 11:47:20 +0200 Subject: [pve-devel] [PATCH installer] install: install correct grub metapackage for the current boot-mode In-Reply-To: <20230928140533.653796-1-s.ivanov@proxmox.com> References: <20230928140533.653796-1-s.ivanov@proxmox.com> Message-ID: <1698054138.34j3wefqts.astroid@yuna.none> On September 28, 2023 4:05 pm, Stoiko Ivanov wrote: > grub packages in debian split between: > * meta-packages, which handles (among other things) the reinstalling > grub to the actual device/ESP in case of a version upgrade (grub-pc, > grub-efi-amd64) > * bin-packages, which contain the actual boot-loaders > The bin-packages can coexist on a system, but the meta-package > conflict with each other (didn't check why, but I don't see a hard > conflict on a quick glance) > > Currently our ISO installs grub-pc unconditionally (and both bin > packages, since we install the legacy bootloader also on uefi-booted > systems). This results in uefi-systems not getting a new grub > installed automatically upon upgrade. > > Reported in our community-forum from users who upgraded to PVE 8.0, > and still run into an issue fixed in grub for bookworm: > https://forum.proxmox.com/threads/.123512/ > > Reproduced and analyzed by Friedrich. > > This patch changes the installer, to install the meta-package fitting > for the boot-mode. > > We do not set the debconf variable install_devices, because in my > tests a plain debian installed in uefi mode has this set, and a > `grep -ri install_devices /var/lib/dpkg/info` yields only results with > grub-pc. this paragraph confused me phrasing-wise (what it means is that the 'install_devices' variable is only defined for 'grub-pc', and thus (still) only set for that package/namespace) > Reported-by: Friedrich Weber > Signed-off-by: Stoiko Ivanov Reviewed-by: Fabian Gr?nbichler > --- > quickly tested by building an ISO (with the necessary modifications to > ship both packages as .deb) and installing in legacy mode and uefi mode > once. did a similar test with comparing /boot before/after installing the latest grub security update, which showed that the expected variant gets updated (legacy if system was originally installed legacy, efi if it was efi, both of course with no manual switch of boot mode or meta packages in between). as discussed off list, we might still want to improve the handling of the "other" variant somehow, at least by detecting out-of-dateness and/or missing meta packages somewhere after a switch over. > Proxmox/Install.pm | 6 ++++++ > 1 file changed, 6 insertions(+) > > diff --git a/Proxmox/Install.pm b/Proxmox/Install.pm > index 1117fc4..d775ac0 100644 > --- a/Proxmox/Install.pm > +++ b/Proxmox/Install.pm > @@ -1057,6 +1057,12 @@ _EOD > chomp; > my $path = $_; > my ($deb) = $path =~ m/${proxmox_pkgdir}\/(.*\.deb)/; > + > + # the grub-pc/grub-efi-amd64 packages (w/o -bin) are the ones actually updating grub > + # upon upgrade - and conflict with each other - install the fitting one only > + next if ($deb =~ /grub-pc_/ && $run_env->{boot_type} ne 'bios'); > + next if ($deb =~ /grub-efi-amd64_/ && $run_env->{boot_type} ne 'efi'); > + > update_progress($count/$pkg_count, 0.5, 0.75, "extracting $deb"); > print STDERR "extracting: $deb\n"; > syscmd("chroot $targetdir dpkg $dpkg_opts --force-depends --no-triggers --unpack /tmp/pkg/$deb") == 0 > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > From s.lendl at proxmox.com Mon Oct 23 12:27:06 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Mon, 23 Oct 2023 12:27:06 +0200 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN Message-ID: <875y2xab2d.fsf@gmail.com> I am currently working on the SDN feature. This is an initial review of the patch series and I am trying to make a strong case against ephemeral DHCP IP reservation. The current state of the patch series invokes the IPAM on every VM/CT start/stop to add or remove the IP from the IPAM. This triggers the dnsmasq config generation on the specific host with only the MAC/IP mapping of that particular host. >From reading the discussion of the v1 patch series I understand this approach tries to implement the ephemeral IP reservation strategy. From off-list conversations with Stefan Hanreich, I agree that having ephemeral IP reservation coordinated by the IPAM requires us to re-implement DHCP functionality in the IPAM and heavily rely on syncing between the different services. To maintain reliable sync we need to hook into many different places where the IPAM need to be queried. Any issues with the implementation may lead to IPAM and DHCP local config state running out of sync causing network issues duplicate multiple IPs. Furthermore, every interaction with the IPAM requires a cluster-wide lock on the IPAM. Having a central cluster-wide lock on every VM start/stop/migrate will significantly limit parallel operations. Event starting two VMs in parallel will be limited by this central lock. At boot trying to start many VMs (ideally as much in parallel as possible) is limited by the central IPAM lock even further. I argue that we shall not support ephemeral IPs altogether. The alternative is to make all IPAM reservations persistent. Using persistent IPs only reduces the interactions of VM/CTs with the IPAM to a minimum of NIC joining a subnet and NIC leaving a subnet. I am deliberately not referring to VMs because a VM may be part of multiple VNets or even multiple times in the same VNet (regardless if that is sensible). Cases the IPAM needs to be involved: - NIC with DHCP enabled VNet is added to VM config - NIC with DHCP enabled VNet is removed from VM config - NIC is assigned to another Bridge can be treated as individual leave + join events Cases that are explicitly not covered but may be added if desired: - Manually assign an IP address on a NIC will not be automatically visible in the IPAM - Manually change the MAC on a NIC don't do that > you are on your own. Not handled > change in IPAM manually Once an IP is reserved via IPAM, the dnsmasq config can be generated stateless and idempotent from the pve IPAM and is identical on all nodes regardless if a VM/CT actually resides on that node or is running or stopped. This is especially useful for VM migration because the IP stays consistent without spacial considering. Snapshot/revert, backup/restore, suspend/hibernate/resume cases are automatically covered because the IP will already be reserved for that MAC. If the admin wants to change, the IP of a VM this can be done via the IPAM API/UI which will have to be implemented separately. A limitation of this approach vs dynamic IP reservation is that the IP range on the subnet needs to be large enough to hold all IPs of all, even stopped, VMs in that subnet. This is in contrast to default DHCP functionality where only the number of actively running VMs is limited. It should be enough to mention this in the docs. I will further review the code an try to implement the aforementioned approach. Best regards, Stefan Lendl Stefan Hanreich writes: > This is a WIP patch series, since I will be gone for 3 weeks and wanted to > share my current progress with the DHCP support for SDN. > > This patch series adds support for automatically deploying dnsmasq as a DHCP > server to a simple SDN Zone. > > While certainly not 100% polished on some ends (looking at restarting systemd > services in particular), the general idea behind the mechanism shows. I wanted > to gather some feedback on how I approached designing the plugins and the > config regeneration process before comitting to this design by creating an API > and UI around it. > > You need to install dnsmasq (and disable it afterwards): > > apt install dnsmasq && systemctl disable --now dnsmasq > > > You can use the following example configuration for deploying a DHCP server in > a SDN subnet: > > /etc/pve/sdn/dhcp.cfg: > > dnsmasq: nat > > > /etc/pve/sdn/zones.cfg: > > simple: DHCPNAT > ipam pve > > > /etc/pve/sdn/vnets.cfg: > > vnet: dhcpnat > zone DHCPNAT > > > /etc/pve/sdn/subnets.cfg: > > subnet: DHCPNAT-10.1.0.0-16 > vnet dhcpnat > dhcp-dns-server 10.1.0.1 > dhcp-range server=nat,start-address=10.1.0.100,end-address=10.1.0.200 > gateway 10.1.0.1 > snat 1 > > > Then apply the SDN configuration: > > pvesh set /cluster/sdn > > You need to apply the SDN configuration once after adding the dhcp-range lines > to the configuration, since the running configuration is used for managing > DHCP. It will not work otherwise! > > For testing it can be helpful to monitor the following files (e.g. with watch) > to find out what is happening > * /etc/dnsmasq.d//ethers (on each node) > * /etc/pve/priv/ipam.db > > Changes from v1 -> v2: > * added hooks for handling DHCP when starting / stopping / .. VMs and CTs > * Get an IP from IPAM and register that IP in the DHCP server > (pve only for now) > * remove lease-time, since it is now infinite and managed by the VM lifecycle > * add hooks for setting & deleting DHCP mappings to DHCP plugins > * modified interface of the abstract class to reflect new requirements > * added helpers in existing SDN classes > * simplified DHCP configuration settings > > > > pve-cluster: > > Stefan Hanreich (1): > cluster files: add dhcp.cfg > > src/PVE/Cluster.pm | 1 + > src/pmxcfs/status.c | 1 + > 2 files changed, 2 insertions(+) > > > pve-network: > > Stefan Hanreich (6): > subnets: vnets: preparations for DHCP plugins > dhcp: add abstract class for DHCP plugins > dhcp: subnet: add DHCP options to subnet configuration > dhcp: add DHCP plugin for dnsmasq > ipam: Add helper methods for DHCP to PVE IPAM > dhcp: regenerate config for DHCP servers on reload > > debian/control | 1 + > src/PVE/Network/SDN.pm | 11 +- > src/PVE/Network/SDN/Dhcp.pm | 192 +++++++++++++++++++++++++ > src/PVE/Network/SDN/Dhcp/Dnsmasq.pm | 186 ++++++++++++++++++++++++ > src/PVE/Network/SDN/Dhcp/Makefile | 8 ++ > src/PVE/Network/SDN/Dhcp/Plugin.pm | 83 +++++++++++ > src/PVE/Network/SDN/Ipams/PVEPlugin.pm | 64 +++++++++ > src/PVE/Network/SDN/Makefile | 3 +- > src/PVE/Network/SDN/SubnetPlugin.pm | 32 +++++ > src/PVE/Network/SDN/Subnets.pm | 43 ++++-- > src/PVE/Network/SDN/Vnets.pm | 27 ++-- > 11 files changed, 622 insertions(+), 28 deletions(-) > create mode 100644 src/PVE/Network/SDN/Dhcp.pm > create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm > create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile > create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm > > > pve-manager: > > Stefan Hanreich (1): > sdn: regenerate DHCP config on reload > > PVE/API2/Network.pm | 1 + > 1 file changed, 1 insertion(+) > > > qemu-server: > > Stefan Hanreich (1): > sdn: dhcp: add DHCP setup to vm-network-scripts > > PVE/QemuServer.pm | 14 ++++++++++++++ > vm-network-scripts/pve-bridge | 3 +++ > vm-network-scripts/pve-bridgedown | 19 +++++++++++++++++++ > 3 files changed, 36 insertions(+) > > > pve-container: > > Stefan Hanreich (1): > sdn: dhcp: setup DHCP mappings in LXC hooks > > src/PVE/LXC.pm | 10 ++++++++++ > src/lxc-pve-poststop-hook | 1 + > src/lxc-pve-prestart-hook | 9 +++++++++ > 3 files changed, 20 insertions(+) > > > Summary over all repositories: > 20 files changed, 681 insertions(+), 28 deletions(-) > > -- > murpp v0.4.0 > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel From c.ebner at proxmox.com Mon Oct 23 13:18:32 2023 From: c.ebner at proxmox.com (Christian Ebner) Date: Mon, 23 Oct 2023 13:18:32 +0200 Subject: [pve-devel] [RFC v2 pve-container pve-manager 0/3] add partial restore Message-ID: <20231023111835.238407-1-c.ebner@proxmox.com> This patch series adds functionality to partially restore containers from backup, by allowing the user to selectively include/exclude mountpoints for restore. Mountpoints not included in the backup will not be deleted and recreated but rather attached to the container as unused disk after the restore. The same is true for mountpoints selected by the user to be excluded during restore. As is, there is a slight discrepancy in partial restore behaviour for PBS based and tar based backups, in cases where mountpoints are mounted in the file tree of another mountpoint. For tar based backups, all files below the uppermost mountpoint excluded from backup will not be restored, as the tar `--exclude` limits the file restore. For PBS based backups, each mountpoint is now backed up as individual pxar archive, therefore allowing to handle the restore more fine grained, by restoring the pxar archive directly to the moutpoint. This splitting of the PBS based backups into one archive per mountpoint introduces also side effects for backups created with the previous single root.pxar archive approach. During restore, the pxar archive per mountpoint cannot be found, producing a soft error message. Restore of the files is than handled directly from the main root.pxar archive. Any feedback is highly appreciated. pve-container: Christian Ebner (2): backup: do not delete not backed-up mps on restore api: allow to exclude mountpoins from restore src/PVE/API2/LXC.pm | 44 +++++++++++++++++++---- src/PVE/LXC/Create.pm | 83 ++++++++++++++++++++++++++++++++++++------- src/PVE/VZDump/LXC.pm | 10 +++--- 3 files changed, 113 insertions(+), 24 deletions(-) pve-manager: Christian Ebner (1): ui: lxc restore: add selective mountpoint restore www/manager6/Makefile | 1 + www/manager6/grid/BackupRestoreTargets.js | 37 +++++++++++++++++++++++ www/manager6/window/Restore.js | 34 +++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 www/manager6/grid/BackupRestoreTargets.js -- 2.39.2 From c.ebner at proxmox.com Mon Oct 23 13:18:34 2023 From: c.ebner at proxmox.com (Christian Ebner) Date: Mon, 23 Oct 2023 13:18:34 +0200 Subject: [pve-devel] [RFC v2 pve-container pve-manager 2/3] api: allow to exclude mountpoins from restore In-Reply-To: <20231023111835.238407-1-c.ebner@proxmox.com> References: <20231023111835.238407-1-c.ebner@proxmox.com> Message-ID: <20231023111835.238407-3-c.ebner@proxmox.com> Adds an optional parameter which allows to specify the names of mountpoints which should be excluded from restore. These mountpoints are not (re-)created and files located below the mountpoint are not restored from backup. Signed-off-by: Christian Ebner --- changes since v1: not present in v1 src/PVE/API2/LXC.pm | 14 ++++++++++++-- src/PVE/LXC/Create.pm | 11 +++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm index 090dddf..523afb8 100644 --- a/src/PVE/API2/LXC.pm +++ b/src/PVE/API2/LXC.pm @@ -162,6 +162,12 @@ __PACKAGE__->register_method({ type => 'boolean', description => "Mark this as restore task.", }, + 'exclude-mps' => { + optional => 1, + type => 'string', + description => "Comma separated lists of mountpoint to exclude from restore.", + pattern => '(mp\d+)(,mp\d+)*', + }, unique => { optional => 1, type => 'boolean', @@ -222,6 +228,8 @@ __PACKAGE__->register_method({ # 'unprivileged' is read-only, so we can't pass it to update_pct_config my $unprivileged = extract_param($param, 'unprivileged'); my $restore = extract_param($param, 'restore'); + my $exclude_mps = extract_param($param, 'exclude-mps'); + my %excludes = map {$_ => 1} split(',', $exclude_mps) if $exclude_mps; my $unique = extract_param($param, 'unique'); $param->{cpuunits} = PVE::CGroup::clamp_cpu_shares($param->{cpuunits}) @@ -427,8 +435,9 @@ __PACKAGE__->register_method({ if !defined($mp_param->{rootfs}); PVE::LXC::Config->foreach_volume($mp_param, sub { my ($ms, $mountpoint) = @_; - if ($ms eq 'rootfs' || $mountpoint->{backup}) { - # backup conf contains the mp, clear for retsore + if ($ms eq 'rootfs' || ($mountpoint->{backup} && !$excludes{$ms})) { + # backup conf contains the mp and it is not in exclude list, + # clear for retsore $clear_mps->{$ms} = $mountpoint; } else { # do not add as mp, will be attach as unused at the end @@ -485,6 +494,7 @@ __PACKAGE__->register_method({ my $restore_opts = { 'orig_mps' => $orig_mp_param, + 'excludes' => \%excludes, }; PVE::LXC::Create::restore_archive( $storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit, $restore_opts); diff --git a/src/PVE/LXC/Create.pm b/src/PVE/LXC/Create.pm index 98ab4a4..34929d5 100644 --- a/src/PVE/LXC/Create.pm +++ b/src/PVE/LXC/Create.pm @@ -129,6 +129,11 @@ sub restore_proxmox_backup_archive { return; } + if (defined($restore_opts->{excludes}->{$name})) { + print "'$name': mp excluded from restore.\n"; + return; + } + if ($name ne 'rootfs' && (!defined($orig_mp->{backup}) || !$orig_mp->{backup})) { print "'$name': mp not included in original backup.\n"; return; @@ -221,6 +226,12 @@ sub restore_tar_archive { push @$cmd, '--skip-old-files'; push @$cmd, '--anchored'; push @$cmd, '--exclude' , './dev/*'; + + my $orig_mps = $restore_opts->{orig_mps}; + my $excludes = $restore_opts->{excludes}; + foreach my $name (keys %$excludes) { + push @$cmd, '--exclude', ".$orig_mps->{$name}->{mp}" if defined($orig_mps->{$name}->{mp}); + } if (defined($bwlimit)) { $cmd = [ ['cstream', '-t', $bwlimit*1024], $cmd ]; -- 2.39.2 From c.ebner at proxmox.com Mon Oct 23 13:18:35 2023 From: c.ebner at proxmox.com (Christian Ebner) Date: Mon, 23 Oct 2023 13:18:35 +0200 Subject: [pve-devel] [RFC v2 pve-container pve-manager 3/3] ui: lxc restore: add selective mountpoint restore In-Reply-To: <20231023111835.238407-1-c.ebner@proxmox.com> References: <20231023111835.238407-1-c.ebner@proxmox.com> Message-ID: <20231023111835.238407-4-c.ebner@proxmox.com> Adds a grid to the lxc backup restore window allowing the user to select mountpoints which should be included/excluded from a restore. Signed-off-by: Christian Ebner --- changes since v1: not present in v1 www/manager6/Makefile | 1 + www/manager6/grid/BackupRestoreTargets.js | 37 +++++++++++++++++++++++ www/manager6/window/Restore.js | 34 +++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 www/manager6/grid/BackupRestoreTargets.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 17e0ad05..c096a8be 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -88,6 +88,7 @@ JSSRC= \ form/TagEdit.js \ form/MultiFileButton.js \ grid/BackupView.js \ + grid/BackupRestoreTargets.js \ grid/FirewallAliases.js \ grid/FirewallOptions.js \ grid/FirewallRules.js \ diff --git a/www/manager6/grid/BackupRestoreTargets.js b/www/manager6/grid/BackupRestoreTargets.js new file mode 100644 index 00000000..b811989e --- /dev/null +++ b/www/manager6/grid/BackupRestoreTargets.js @@ -0,0 +1,37 @@ +Ext.define('PVE.BackupRestoreTargets', { + extend: 'Ext.grid.Panel', + alias: 'widget.pveBackupRestoreTargets', + store: { + fields: [ + { + name: 'restore', + type: 'boolean', + }, + { + name: 'target', + type: 'string', + }, + ], + }, + + getMountpoints: function() { + return this.store.getData(); + }, + + setMountpoints: function(mountpoints) { + this.store.loadData(mountpoints); + }, + + columns: [ + { + text: gettext('Mountpoint'), + dataIndex: 'mountpoint', + flex: 1, + }, + { + text: gettext('Restore'), + dataIndex: 'restore', + xtype: 'checkcolumn', + }, + ], +}); diff --git a/www/manager6/window/Restore.js b/www/manager6/window/Restore.js index 36aecc39..85e382fb 100644 --- a/www/manager6/window/Restore.js +++ b/www/manager6/window/Restore.js @@ -67,6 +67,13 @@ Ext.define('PVE.window.Restore', { params.unprivileged = values.unprivileged; } confirmMsg = Proxmox.Utils.format_task_description('vzrestore', params.vmid); + let viewModel = view.getViewModel(); + let excludes = viewModel.get('mountpoints') + .filter(mp => mp.restore === false) + .map(mp => mp.mountpoint); + if (excludes.length > 0) { + params['exclude-mps'] = excludes.join(','); + } } else if (view.vmtype === 'qemu') { params.archive = view.volid; confirmMsg = Proxmox.Utils.format_task_description('qmrestore', params.vmid); @@ -111,6 +118,7 @@ Ext.define('PVE.window.Restore', { afterRender: function() { let view = this.getView(); + let viewModel = view.getViewModel(); Proxmox.Utils.API2Request({ url: `/nodes/${view.nodename}/vzdump/extractconfig`, @@ -123,6 +131,7 @@ Ext.define('PVE.window.Restore', { success: function(response, options) { let allStoragesAvailable = true; + let mountpoints = []; response.result.data.split('\n').forEach(line => { let [_, key, value] = line.match(/^([^:]+):\s*(\S+)\s*$/) ?? []; @@ -139,8 +148,14 @@ Ext.define('PVE.window.Restore', { view.lookupReference('nameField').setEmptyText(value); } else if (key === 'memory' || key === 'cores' || key === 'sockets') { view.lookupReference(`${key}Field`).setEmptyText(value); + } else if (key.match(/mp\d+/) && value.includes('backup=1')) { + mountpoints.push({ + 'mountpoint': key, + 'restore': true, + }); } }); + viewModel.set('mountpoints', mountpoints); if (!allStoragesAvailable) { let storagesel = view.down('pveStorageSelector[name=storage]'); @@ -152,6 +167,12 @@ Ext.define('PVE.window.Restore', { }, }, + viewModel: { + config: { + mountpoints: [], + }, + }, + initComponent: function() { let me = this; @@ -354,6 +375,19 @@ Ext.define('PVE.window.Restore', { ], }); + if (me.vmtype === 'lxc') { + items.push({ + title: `${gettext('Restore Mountpoints')}:`, + name: 'mountpointRestoreTargets', + xtype: 'pveBackupRestoreTargets', + hidden: true, // Hide until config loaded + bind: { + mountpoints: '{mountpoints}', + hidden: '{!mountpoints.length}', + }, + }); + } + let title = gettext('Restore') + ": " + (me.vmtype === 'lxc' ? 'CT' : 'VM'); if (me.vmid) { title = `${gettext('Overwrite')} ${title} ${me.vmid}`; -- 2.39.2 From c.ebner at proxmox.com Mon Oct 23 13:18:33 2023 From: c.ebner at proxmox.com (Christian Ebner) Date: Mon, 23 Oct 2023 13:18:33 +0200 Subject: [pve-devel] [RFC v2 pve-container pve-manager 1/3] backup: do not delete not backed-up mps on restore In-Reply-To: <20231023111835.238407-1-c.ebner@proxmox.com> References: <20231023111835.238407-1-c.ebner@proxmox.com> Message-ID: <20231023111835.238407-2-c.ebner@proxmox.com> The current behaviour of the restore is to recreate all backed up mountpoints and remove all previous ones, causing potential data loss on restore when the mountpoint was not included in the backup and the user not aware of this behaviour. By checking the mountpoint configuration from the backup, only recreate the disks which are included in the backup and add them as mountpoints. Leave all other mountpoints untouched and attach them as unused disks as final step of the restore. To facilitate selective restore from PBS backups, split the currently single root pxar archive into a pxar archive for each individual mountpoint, while remaining backwards compatible. Signed-off-by: Christian Ebner --- changes since v1: - deep clone of orig_mp_param, otherwise the variable points to the same data. - refactor restore_archive params src/PVE/API2/LXC.pm | 34 +++++++++++++++----- src/PVE/LXC/Create.pm | 72 +++++++++++++++++++++++++++++++++++-------- src/PVE/VZDump/LXC.pm | 10 +++--- 3 files changed, 92 insertions(+), 24 deletions(-) diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm index 28d14de..090dddf 100644 --- a/src/PVE/API2/LXC.pm +++ b/src/PVE/API2/LXC.pm @@ -4,6 +4,7 @@ use strict; use warnings; use Socket qw(SOCK_STREAM); +use Storable qw(dclone); use PVE::SafeSyslog; use PVE::Tools qw(extract_param run_command); @@ -381,8 +382,10 @@ __PACKAGE__->register_method({ my $vollist = []; eval { my $orig_mp_param; # only used if $restore + my $clear_mps; if ($restore) { die "can't overwrite running container\n" if PVE::LXC::check_running($vmid); + if ($archive ne '-') { my $orig_conf; print "recovering backed-up configuration from '$archive'\n"; @@ -419,11 +422,19 @@ __PACKAGE__->register_method({ print "recovering backed-up configuration from '$archive'\n"; (undef, $orig_mp_param) = PVE::LXC::Create::recover_config($storage_cfg, $archive, $vmid); } - $mp_param = $orig_mp_param; + $mp_param = dclone $orig_mp_param; die "rootfs configuration could not be recovered, please check and specify manually!\n" if !defined($mp_param->{rootfs}); PVE::LXC::Config->foreach_volume($mp_param, sub { my ($ms, $mountpoint) = @_; + if ($ms eq 'rootfs' || $mountpoint->{backup}) { + # backup conf contains the mp, clear for retsore + $clear_mps->{$ms} = $mountpoint; + } else { + # do not add as mp, will be attach as unused at the end + delete $mp_param->{$ms}; + return; + } my $type = $mountpoint->{type}; if ($type eq 'volume') { die "unable to detect disk size - please specify $ms (size)\n" @@ -459,18 +470,24 @@ __PACKAGE__->register_method({ $vollist = PVE::LXC::create_disks($storage_cfg, $vmid, $mp_param, $conf); - # we always have the 'create' lock so check for more than 1 entry - if (scalar(keys %$old_conf) > 1) { - # destroy old container volumes - PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $old_conf, { lock => 'create' }); - } + # Delete old mountpoints which are restored from backup. + PVE::LXC::Config->foreach_volume($old_conf, sub { + my ($name, $mountpoint, undef) = @_; + return if !defined($clear_mps->{$name}); + PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $mountpoint->{volume}); + }); eval { my $rootdir = PVE::LXC::mount_all($vmid, $storage_cfg, $conf, 1); $bwlimit = PVE::Storage::get_bandwidth_limit('restore', [keys %used_storages], $bwlimit); print "restoring '$archive' now..\n" if $restore && $archive ne '-'; - PVE::LXC::Create::restore_archive($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit); + + my $restore_opts = { + 'orig_mps' => $orig_mp_param, + }; + PVE::LXC::Create::restore_archive( + $storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit, $restore_opts); if ($restore) { print "merging backed-up and given configuration..\n"; @@ -501,6 +518,9 @@ __PACKAGE__->register_method({ $conf->{template} = 1; } PVE::LXC::Config->write_config($vmid, $conf); + + # Attach all additionally found mountpoints as unused disks. + PVE::LXC::rescan($vmid, 1, 0); }; if (my $err = $@) { PVE::LXC::destroy_disks($storage_cfg, $vollist); diff --git a/src/PVE/LXC/Create.pm b/src/PVE/LXC/Create.pm index f4c3220..98ab4a4 100644 --- a/src/PVE/LXC/Create.pm +++ b/src/PVE/LXC/Create.pm @@ -83,27 +83,33 @@ sub detect_architecture { } sub restore_archive { - my ($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit) = @_; + my ($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $restore_opts) = @_; + + PVE::LXC::Config->foreach_volume($restore_opts->{orig_mps}, sub { + my ($name, $vol, undef) = @_; + $restore_opts->{orig_mps}->{$name} = $vol; + }); my ($storeid, $volname) = PVE::Storage::parse_volume_id($archive, 1); if (defined($storeid)) { my $scfg = PVE::Storage::storage_check_enabled($storage_cfg, $storeid); if ($scfg->{type} eq 'pbs') { - return restore_proxmox_backup_archive($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit); + return restore_proxmox_backup_archive( + $storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $restore_opts); } } $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $archive) if $archive ne '-'; - restore_tar_archive($archive, $rootdir, $conf, $no_unpack_error, $bwlimit); + restore_tar_archive($archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $restore_opts); } sub restore_proxmox_backup_archive { - my ($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit) = @_; + my ($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $restore_opts) = @_; my ($storeid, $volname) = PVE::Storage::parse_volume_id($archive); my $scfg = PVE::Storage::storage_config($storage_cfg, $storeid); - my ($vtype, $name, undef, undef, undef, undef, $format) = + my ($vtype, $snapshot, undef, undef, undef, undef, $format) = PVE::Storage::parse_volname($storage_cfg, $archive); die "got unexpected vtype '$vtype'\n" if $vtype ne 'backup'; @@ -114,14 +120,47 @@ sub restore_proxmox_backup_archive { my $userns_cmd = PVE::LXC::userns_command($id_map); my $cmd = "restore"; - my $param = [$name, "root.pxar", $rootdir, '--allow-existing-dirs']; + PVE::LXC::Config->foreach_volume($conf, sub { + my ($name, $vol, undef) = @_; - if ($no_unpack_error) { - push(@$param, '--ignore-extract-device-errors'); - } + my $orig_mp = $restore_opts->{orig_mps}->{$name}; + if (!defined($orig_mp)) { + print "'$name': mp not present in original backup.\n"; + return; + } - PVE::Storage::PBSPlugin::run_raw_client_cmd( - $scfg, $storeid, $cmd, $param, userns_cmd => $userns_cmd); + if ($name ne 'rootfs' && (!defined($orig_mp->{backup}) || !$orig_mp->{backup})) { + print "'$name': mp not included in original backup.\n"; + return; + } + + $name = 'root' if $name eq 'rootfs'; + my $target = "$rootdir/.$vol->{mp}"; + if ($orig_mp->{mp} ne $vol->{mp}) { + print "'$name': path differs from backed-up path, restore to backed-up path.\n"; + print " If this is not intended, change the path after restore.\n"; + $target = "$rootdir/.$orig_mp->{mp}"; + } + + my $param = [$snapshot, "$name.pxar", $target, '--allow-existing-dirs']; + + if ($no_unpack_error) { + push(@$param, '--ignore-extract-device-errors'); + } + + eval { + # This will fail for backups created without mp archive splitting + PVE::Storage::PBSPlugin::run_raw_client_cmd( + $scfg, $storeid, $cmd, $param, userns_cmd => $userns_cmd); + }; + my $err = $@; + if ($err) { + # Only handle root restore failure as hard error + die $err if $name eq 'root'; + print "extracting moutpoint '$name' failed:\n$err"; + print "backup created with older version?\n"; + } + }); # if arch is set, we do not try to autodetect it return if defined($conf->{arch}); @@ -130,11 +169,20 @@ sub restore_proxmox_backup_archive { } sub restore_tar_archive { - my ($archive, $rootdir, $conf, $no_unpack_error, $bwlimit) = @_; + my ($archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $restore_opts) = @_; my ($id_map, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf); my $userns_cmd = PVE::LXC::userns_command($id_map); + PVE::LXC::Config->foreach_volume($conf, sub { + my ($name, $vol, undef) = @_; + my $orig_mp = $restore_opts->{orig_mps}->{$name}; + if (defined($orig_mp->{mp}) && $orig_mp->{mp} ne $vol->{mp}) { + print "'$name': path differs from backed-up path, restore to backed-up path.\n"; + print " If this is not intended, change the path after restore.\n"; + } + }); + my $archive_fh; my $tar_input = '<&STDIN'; my @compression_opt; diff --git a/src/PVE/VZDump/LXC.pm b/src/PVE/VZDump/LXC.pm index c68a06f..0d411b8 100644 --- a/src/PVE/VZDump/LXC.pm +++ b/src/PVE/VZDump/LXC.pm @@ -381,11 +381,11 @@ sub archive { push @$param, "fw.conf:$fw_conf"; } - my $rootdir = $snapdir; - push @$param, "root.pxar:$rootdir"; - - foreach my $disk (@sources) { - push @$param, '--include-dev', "$snapdir/$disk"; + foreach my $disk (@$disks) { + my $name = $disk->{name}; + # Needed for backwards compatibility with previous backups + $name = 'root' if $name eq 'rootfs'; + push @$param, "$name.pxar:$snapdir/.$disk->{mp}"; } push @$param, '--skip-lost-and-found' if $userns_cmd; -- 2.39.2 From c.ebner at proxmox.com Mon Oct 23 13:21:19 2023 From: c.ebner at proxmox.com (Christian Ebner) Date: Mon, 23 Oct 2023 13:21:19 +0200 (CEST) Subject: [pve-devel] [RFC pve-container] backup: do not delete not backed-up mps on restore In-Reply-To: <20231017123827.313014-1-c.ebner@proxmox.com> References: <20231017123827.313014-1-c.ebner@proxmox.com> Message-ID: <447752374.1198.1698060079244@webmail.proxmox.com> There is an newer version of the patch, see https://lists.proxmox.com/pipermail/pve-devel/2023-October/059566.html Please ignore this one. > On 17.10.2023 14:38 CEST Christian Ebner wrote: > > > The current behaviour of the restore is to recreate all backed up > mountpoints and remove all previous ones, causing potential data loss on > restore when the mountpoint was not included in the backup and the user > not aware of this behaviour. > > By checking the mountpoint configuration from the backup, only recreate > the disks which are included in the backup and add them as mountpoints. > Leave all other mountpoints untouched and attach them as unused disks > as final step of the restore. > > To facilitate selective restore from PBS backups, split the currently > single root pxar archive into a pxar archive for each individual > mountpoint, while remaining backwards compatible. > > Signed-off-by: Christian Ebner > --- > > I'm sending this as RFC since backwards compatibility for restore still > suffers from the fact, that mountpoints are now expected to have their > corresponding pxar archive for PBS backups, all previous backups however > do not follow this scheme. > For now, this is handled by treating restore errors of these archives as > soft errors, meaning restore will continue. This is not ideal and I am > very much open for suggestions on how this might be handled better. > > src/PVE/API2/LXC.pm | 26 ++++++++++++---- > src/PVE/LXC/Create.pm | 72 +++++++++++++++++++++++++++++++++++-------- > src/PVE/VZDump/LXC.pm | 10 +++--- > 3 files changed, 85 insertions(+), 23 deletions(-) > > diff --git a/src/PVE/API2/LXC.pm b/src/PVE/API2/LXC.pm > index 28d14de..3c7b22d 100644 > --- a/src/PVE/API2/LXC.pm > +++ b/src/PVE/API2/LXC.pm > @@ -381,8 +381,10 @@ __PACKAGE__->register_method({ > my $vollist = []; > eval { > my $orig_mp_param; # only used if $restore > + my $clear_mps; > if ($restore) { > die "can't overwrite running container\n" if PVE::LXC::check_running($vmid); > + > if ($archive ne '-') { > my $orig_conf; > print "recovering backed-up configuration from '$archive'\n"; > @@ -424,6 +426,14 @@ __PACKAGE__->register_method({ > if !defined($mp_param->{rootfs}); > PVE::LXC::Config->foreach_volume($mp_param, sub { > my ($ms, $mountpoint) = @_; > + if ($ms eq 'rootfs' || $mountpoint->{backup}) { > + # backup conf contains the mp, clear for retsore > + $clear_mps->{$ms} = $mountpoint; > + } else { > + # do not add as mp, will be attach as unused at the end > + delete $mp_param->{$ms}; > + return; > + } > my $type = $mountpoint->{type}; > if ($type eq 'volume') { > die "unable to detect disk size - please specify $ms (size)\n" > @@ -459,18 +469,19 @@ __PACKAGE__->register_method({ > > $vollist = PVE::LXC::create_disks($storage_cfg, $vmid, $mp_param, $conf); > > - # we always have the 'create' lock so check for more than 1 entry > - if (scalar(keys %$old_conf) > 1) { > - # destroy old container volumes > - PVE::LXC::destroy_lxc_container($storage_cfg, $vmid, $old_conf, { lock => 'create' }); > - } > + # Delete old mountpoints which are restored from backup. > + PVE::LXC::Config->foreach_volume($old_conf, sub { > + my ($name, $mountpoint, undef) = @_; > + return if !defined($clear_mps->{$name}); > + PVE::LXC::delete_mountpoint_volume($storage_cfg, $vmid, $mountpoint->{volume}); > + }); > > eval { > my $rootdir = PVE::LXC::mount_all($vmid, $storage_cfg, $conf, 1); > $bwlimit = PVE::Storage::get_bandwidth_limit('restore', [keys %used_storages], $bwlimit); > print "restoring '$archive' now..\n" > if $restore && $archive ne '-'; > - PVE::LXC::Create::restore_archive($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit); > + PVE::LXC::Create::restore_archive($storage_cfg, $archive, $rootdir, $conf, $ignore_unpack_errors, $bwlimit, $orig_mp_param); > > if ($restore) { > print "merging backed-up and given configuration..\n"; > @@ -501,6 +512,9 @@ __PACKAGE__->register_method({ > $conf->{template} = 1; > } > PVE::LXC::Config->write_config($vmid, $conf); > + > + # Attach all additionally found mountpoints as unused disks. > + PVE::LXC::rescan($vmid, 1, 0); > }; > if (my $err = $@) { > PVE::LXC::destroy_disks($storage_cfg, $vollist); > diff --git a/src/PVE/LXC/Create.pm b/src/PVE/LXC/Create.pm > index f4c3220..74d7954 100644 > --- a/src/PVE/LXC/Create.pm > +++ b/src/PVE/LXC/Create.pm > @@ -83,27 +83,33 @@ sub detect_architecture { > } > > sub restore_archive { > - my ($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit) = @_; > + my ($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $orig_mp_param) = @_; > > + my $orig_mps; > + PVE::LXC::Config->foreach_volume($orig_mp_param, sub { > + my ($name, $vol, undef) = @_; > + $orig_mps->{$name} = $vol; > + }); > my ($storeid, $volname) = PVE::Storage::parse_volume_id($archive, 1); > if (defined($storeid)) { > my $scfg = PVE::Storage::storage_check_enabled($storage_cfg, $storeid); > if ($scfg->{type} eq 'pbs') { > - return restore_proxmox_backup_archive($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit); > + return restore_proxmox_backup_archive( > + $storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $orig_mps); > } > } > > $archive = PVE::Storage::abs_filesystem_path($storage_cfg, $archive) if $archive ne '-'; > - restore_tar_archive($archive, $rootdir, $conf, $no_unpack_error, $bwlimit); > + restore_tar_archive($archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $orig_mps); > } > > sub restore_proxmox_backup_archive { > - my ($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit) = @_; > + my ($storage_cfg, $archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $orig_mps) = @_; > > my ($storeid, $volname) = PVE::Storage::parse_volume_id($archive); > my $scfg = PVE::Storage::storage_config($storage_cfg, $storeid); > > - my ($vtype, $name, undef, undef, undef, undef, $format) = > + my ($vtype, $snapshot, undef, undef, undef, undef, $format) = > PVE::Storage::parse_volname($storage_cfg, $archive); > > die "got unexpected vtype '$vtype'\n" if $vtype ne 'backup'; > @@ -114,14 +120,47 @@ sub restore_proxmox_backup_archive { > my $userns_cmd = PVE::LXC::userns_command($id_map); > > my $cmd = "restore"; > - my $param = [$name, "root.pxar", $rootdir, '--allow-existing-dirs']; > + PVE::LXC::Config->foreach_volume($conf, sub { > + my ($name, $vol, undef) = @_; > > - if ($no_unpack_error) { > - push(@$param, '--ignore-extract-device-errors'); > - } > + my $orig_mp = $orig_mps->{$name}; > + if (!defined($orig_mp)) { > + print "'$name': mp not present in original backup.\n"; > + return; > + } > > - PVE::Storage::PBSPlugin::run_raw_client_cmd( > - $scfg, $storeid, $cmd, $param, userns_cmd => $userns_cmd); > + if ($name ne 'rootfs' && (!defined($orig_mp->{backup}) || !$orig_mp->{backup})) { > + print "'$name': mp not included in original backup.\n"; > + return; > + } > + > + $name = 'root' if $name eq 'rootfs'; > + my $target = "$rootdir/.$vol->{mp}"; > + if ($orig_mp->{mp} ne $vol->{mp}) { > + print "'$name': path differs from backed-up path, restore to backed-up path.\n"; > + print " If this is not intended, change the path after restore.\n"; > + $target = "$rootdir/.$orig_mp->{mp}"; > + } > + > + my $param = [$snapshot, "$name.pxar", $target, '--allow-existing-dirs']; > + > + if ($no_unpack_error) { > + push(@$param, '--ignore-extract-device-errors'); > + } > + > + eval { > + # This will fail for backups created without mp archive splitting > + PVE::Storage::PBSPlugin::run_raw_client_cmd( > + $scfg, $storeid, $cmd, $param, userns_cmd => $userns_cmd); > + }; > + my $err = $@; > + if ($err) { > + # Only handle root restore failure as hard error > + die $err if $name eq 'root'; > + print "extracting moutpoint '$name' failed:\n$err"; > + print "backup created with older version?\n"; > + } > + }); > > # if arch is set, we do not try to autodetect it > return if defined($conf->{arch}); > @@ -130,11 +169,20 @@ sub restore_proxmox_backup_archive { > } > > sub restore_tar_archive { > - my ($archive, $rootdir, $conf, $no_unpack_error, $bwlimit) = @_; > + my ($archive, $rootdir, $conf, $no_unpack_error, $bwlimit, $orig_mps) = @_; > > my ($id_map, $rootuid, $rootgid) = PVE::LXC::parse_id_maps($conf); > my $userns_cmd = PVE::LXC::userns_command($id_map); > > + PVE::LXC::Config->foreach_volume($conf, sub { > + my ($name, $vol, undef) = @_; > + my $orig_mp = $orig_mps->{$name}; > + if (defined($orig_mp->{mp}) && $orig_mp->{mp} ne $vol->{mp}) { > + print "'$name': path differs from backed-up path, restore to backed-up path.\n"; > + print " If this is not intended, change the path after restore.\n"; > + } > + }); > + > my $archive_fh; > my $tar_input = '<&STDIN'; > my @compression_opt; > diff --git a/src/PVE/VZDump/LXC.pm b/src/PVE/VZDump/LXC.pm > index c68a06f..0d411b8 100644 > --- a/src/PVE/VZDump/LXC.pm > +++ b/src/PVE/VZDump/LXC.pm > @@ -381,11 +381,11 @@ sub archive { > push @$param, "fw.conf:$fw_conf"; > } > > - my $rootdir = $snapdir; > - push @$param, "root.pxar:$rootdir"; > - > - foreach my $disk (@sources) { > - push @$param, '--include-dev', "$snapdir/$disk"; > + foreach my $disk (@$disks) { > + my $name = $disk->{name}; > + # Needed for backwards compatibility with previous backups > + $name = 'root' if $name eq 'rootfs'; > + push @$param, "$name.pxar:$snapdir/.$disk->{mp}"; > } > > push @$param, '--skip-lost-and-found' if $userns_cmd; > -- > 2.39.2 From s.lendl at proxmox.com Mon Oct 23 14:40:10 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Mon, 23 Oct 2023 14:40:10 +0200 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: <20231017135507.2220948-1-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> Message-ID: <87v8axbjh1.fsf@gmail.com> I am currently working on the SDN feature. This is an initial review of the patch series and I am trying to make a strong case against ephemeral DHCP IP reservation. The current state of the patch series invokes the IPAM on every VM/CT start/stop to add or remove the IP from the IPAM. This triggers the dnsmasq config generation on the specific host with only the MAC/IP mapping of that particular host. >From reading the discussion of the v1 patch series I understand this approach tries to implement the ephemeral IP reservation strategy. From off-list conversations with Stefan Hanreich, I agree that having ephemeral IP reservation coordinated by the IPAM requires us to re-implement DHCP functionality in the IPAM and heavily rely on syncing between the different services. To maintain reliable sync we need to hook into many different places where the IPAM need to be queried. Any issues with the implementation may lead to IPAM and DHCP local config state running out of sync causing network issues duplicate multiple IPs. Furthermore, every interaction with the IPAM requires a cluster-wide lock on the IPAM. Having a central cluster-wide lock on every VM start/stop/migrate will significantly limit parallel operations. Event starting two VMs in parallel will be limited by this central lock. At boot trying to start many VMs (ideally as much in parallel as possible) is limited by the central IPAM lock even further. I argue that we shall not support ephemeral IPs altogether. The alternative is to make all IPAM reservations persistent. Using persistent IPs only reduces the interactions of VM/CTs with the IPAM to a minimum of NIC joining a subnet and NIC leaving a subnet. I am deliberately not referring to VMs because a VM may be part of multiple VNets or even multiple times in the same VNet (regardless if that is sensible). Cases the IPAM needs to be involved: - NIC with DHCP enabled VNet is added to VM config - NIC with DHCP enabled VNet is removed from VM config - NIC is assigned to another Bridge can be treated as individual leave + join events Cases that are explicitly not covered but may be added if desired: - Manually assign an IP address on a NIC will not be automatically visible in the IPAM - Manually change the MAC on a NIC don't do that > you are on your own. Not handled > change in IPAM manually Once an IP is reserved via IPAM, the dnsmasq config can be generated stateless and idempotent from the pve IPAM and is identical on all nodes regardless if a VM/CT actually resides on that node or is running or stopped. This is especially useful for VM migration because the IP stays consistent without spacial considering. Snapshot/revert, backup/restore, suspend/hibernate/resume cases are automatically covered because the IP will already be reserved for that MAC. If the admin wants to change, the IP of a VM this can be done via the IPAM API/UI which will have to be implemented separately. A limitation of this approach vs dynamic IP reservation is that the IP range on the subnet needs to be large enough to hold all IPs of all, even stopped, VMs in that subnet. This is in contrast to default DHCP functionality where only the number of actively running VMs is limited. It should be enough to mention this in the docs. I will further review the code an try to implement the aforementioned approach. Best regards, Stefan Lendl Stefan Hanreich writes: > This is a WIP patch series, since I will be gone for 3 weeks and wanted to > share my current progress with the DHCP support for SDN. > > This patch series adds support for automatically deploying dnsmasq as a DHCP > server to a simple SDN Zone. > > While certainly not 100% polished on some ends (looking at restarting systemd > services in particular), the general idea behind the mechanism shows. I wanted > to gather some feedback on how I approached designing the plugins and the > config regeneration process before comitting to this design by creating an API > and UI around it. > > You need to install dnsmasq (and disable it afterwards): > > apt install dnsmasq && systemctl disable --now dnsmasq > > > You can use the following example configuration for deploying a DHCP server in > a SDN subnet: > > /etc/pve/sdn/dhcp.cfg: > > dnsmasq: nat > > > /etc/pve/sdn/zones.cfg: > > simple: DHCPNAT > ipam pve > > > /etc/pve/sdn/vnets.cfg: > > vnet: dhcpnat > zone DHCPNAT > > > /etc/pve/sdn/subnets.cfg: > > subnet: DHCPNAT-10.1.0.0-16 > vnet dhcpnat > dhcp-dns-server 10.1.0.1 > dhcp-range server=nat,start-address=10.1.0.100,end-address=10.1.0.200 > gateway 10.1.0.1 > snat 1 > > > Then apply the SDN configuration: > > pvesh set /cluster/sdn > > You need to apply the SDN configuration once after adding the dhcp-range lines > to the configuration, since the running configuration is used for managing > DHCP. It will not work otherwise! > > For testing it can be helpful to monitor the following files (e.g. with watch) > to find out what is happening > * /etc/dnsmasq.d//ethers (on each node) > * /etc/pve/priv/ipam.db > > Changes from v1 -> v2: > * added hooks for handling DHCP when starting / stopping / .. VMs and CTs > * Get an IP from IPAM and register that IP in the DHCP server > (pve only for now) > * remove lease-time, since it is now infinite and managed by the VM lifecycle > * add hooks for setting & deleting DHCP mappings to DHCP plugins > * modified interface of the abstract class to reflect new requirements > * added helpers in existing SDN classes > * simplified DHCP configuration settings > > > > pve-cluster: > > Stefan Hanreich (1): > cluster files: add dhcp.cfg > > src/PVE/Cluster.pm | 1 + > src/pmxcfs/status.c | 1 + > 2 files changed, 2 insertions(+) > > > pve-network: > > Stefan Hanreich (6): > subnets: vnets: preparations for DHCP plugins > dhcp: add abstract class for DHCP plugins > dhcp: subnet: add DHCP options to subnet configuration > dhcp: add DHCP plugin for dnsmasq > ipam: Add helper methods for DHCP to PVE IPAM > dhcp: regenerate config for DHCP servers on reload > > debian/control | 1 + > src/PVE/Network/SDN.pm | 11 +- > src/PVE/Network/SDN/Dhcp.pm | 192 +++++++++++++++++++++++++ > src/PVE/Network/SDN/Dhcp/Dnsmasq.pm | 186 ++++++++++++++++++++++++ > src/PVE/Network/SDN/Dhcp/Makefile | 8 ++ > src/PVE/Network/SDN/Dhcp/Plugin.pm | 83 +++++++++++ > src/PVE/Network/SDN/Ipams/PVEPlugin.pm | 64 +++++++++ > src/PVE/Network/SDN/Makefile | 3 +- > src/PVE/Network/SDN/SubnetPlugin.pm | 32 +++++ > src/PVE/Network/SDN/Subnets.pm | 43 ++++-- > src/PVE/Network/SDN/Vnets.pm | 27 ++-- > 11 files changed, 622 insertions(+), 28 deletions(-) > create mode 100644 src/PVE/Network/SDN/Dhcp.pm > create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm > create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile > create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm > > > pve-manager: > > Stefan Hanreich (1): > sdn: regenerate DHCP config on reload > > PVE/API2/Network.pm | 1 + > 1 file changed, 1 insertion(+) > > > qemu-server: > > Stefan Hanreich (1): > sdn: dhcp: add DHCP setup to vm-network-scripts > > PVE/QemuServer.pm | 14 ++++++++++++++ > vm-network-scripts/pve-bridge | 3 +++ > vm-network-scripts/pve-bridgedown | 19 +++++++++++++++++++ > 3 files changed, 36 insertions(+) > > > pve-container: > > Stefan Hanreich (1): > sdn: dhcp: setup DHCP mappings in LXC hooks > > src/PVE/LXC.pm | 10 ++++++++++ > src/lxc-pve-poststop-hook | 1 + > src/lxc-pve-prestart-hook | 9 +++++++++ > 3 files changed, 20 insertions(+) > > > Summary over all repositories: > 20 files changed, 681 insertions(+), 28 deletions(-) > > -- > murpp v0.4.0 > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel _______________________________________________ pve-devel mailing list pve-devel at lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel From f.ebner at proxmox.com Mon Oct 23 14:47:24 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Mon, 23 Oct 2023 14:47:24 +0200 Subject: [pve-devel] [RFC v2 pve-container pve-manager 0/3] add partial restore In-Reply-To: <20231023111835.238407-1-c.ebner@proxmox.com> References: <20231023111835.238407-1-c.ebner@proxmox.com> Message-ID: <47924ea8-097e-4dbe-979d-021566e564f5@proxmox.com> Am 23.10.23 um 13:18 schrieb Christian Ebner: > This patch series adds functionality to partially restore containers > from backup, by allowing the user to selectively include/exclude > mountpoints for restore. Mountpoints not included in the backup will not > be deleted and recreated but rather attached to the container as unused > disk after the restore. The same is true for mountpoints selected by the > user to be excluded during restore. > FYI, there is an old series [0] adding a similar feature for VMs. It would be nice if we could have the API/UI be not too different between them. Of course, it's not set in stone how it's done there (since it didn't get applied ;)), but it would be great if we could come up with a solution that works for both cases. If you really want, you could even pick up those patches in your next version of the series (just tell me if you need a rebased version). Didn't have time to take a close look yet, just noting that 'exclude-mps' is not specific enough as a parameter name for the 'create' API call. The name really should include the word 'restore' or similar, and you can add a requires => 'restore' to the schema definition of the parameter. [0]: https://lists.proxmox.com/pipermail/pve-devel/2022-April/052720.html From s.lendl at proxmox.com Mon Oct 23 14:52:49 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Mon, 23 Oct 2023 14:52:49 +0200 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: <875y2xab2d.fsf@gmail.com> References: <875y2xab2d.fsf@gmail.com> Message-ID: <87sf61bivy.fsf@gmail.com> PS: Sorry for double posting. this mail was sent with invalid In-Reply-To and References Headers. I sent it again with the correct Headers after I managed to correctly setup my mail client. Stefan Lendl writes: > I am currently working on the SDN feature. This is an initial review of > the patch series and I am trying to make a strong case against ephemeral > DHCP IP reservation. > > The current state of the patch series invokes the IPAM on every VM/CT > start/stop to add or remove the IP from the IPAM. > This triggers the dnsmasq config generation on the specific host with > only the MAC/IP mapping of that particular host. > > From reading the discussion of the v1 patch series I understand this > approach tries to implement the ephemeral IP reservation strategy. From > off-list conversations with Stefan Hanreich, I agree that having > ephemeral IP reservation coordinated by the IPAM requires us to > re-implement DHCP functionality in the IPAM and heavily rely on syncing > between the different services. > > To maintain reliable sync we need to hook into many different places > where the IPAM need to be queried. Any issues with the implementation > may lead to IPAM and DHCP local config state running out of sync causing > network issues duplicate multiple IPs. > > Furthermore, every interaction with the IPAM requires a cluster-wide > lock on the IPAM. Having a central cluster-wide lock on every VM > start/stop/migrate will significantly limit parallel operations. Event > starting two VMs in parallel will be limited by this central lock. At > boot trying to start many VMs (ideally as much in parallel as possible) > is limited by the central IPAM lock even further. > > I argue that we shall not support ephemeral IPs altogether. > The alternative is to make all IPAM reservations persistent. > > Using persistent IPs only reduces the interactions of VM/CTs with the > IPAM to a minimum of NIC joining a subnet and NIC leaving a subnet. I am > deliberately not referring to VMs because a VM may be part of multiple > VNets or even multiple times in the same VNet (regardless if that is > sensible). > > Cases the IPAM needs to be involved: > > - NIC with DHCP enabled VNet is added to VM config > - NIC with DHCP enabled VNet is removed from VM config > - NIC is assigned to another Bridge > can be treated as individual leave + join events > > Cases that are explicitly not covered but may be added if desired: > > - Manually assign an IP address on a NIC > will not be automatically visible in the IPAM > - Manually change the MAC on a NIC > don't do that > you are on your own. > Not handled > change in IPAM manually > > Once an IP is reserved via IPAM, the dnsmasq config can be generated > stateless and idempotent from the pve IPAM and is identical on all nodes > regardless if a VM/CT actually resides on that node or is running or > stopped. This is especially useful for VM migration because the IP > stays consistent without spacial considering. > > Snapshot/revert, backup/restore, suspend/hibernate/resume cases are > automatically covered because the IP will already be reserved for that > MAC. > > If the admin wants to change, the IP of a VM this can be done via the > IPAM API/UI which will have to be implemented separately. > > A limitation of this approach vs dynamic IP reservation is that the IP > range on the subnet needs to be large enough to hold all IPs of all, > even stopped, VMs in that subnet. This is in contrast to default DHCP > functionality where only the number of actively running VMs is limited. > It should be enough to mention this in the docs. > > I will further review the code an try to implement the aforementioned > approach. > > Best regards, > Stefan Lendl > > Stefan Hanreich writes: > >> This is a WIP patch series, since I will be gone for 3 weeks and wanted to >> share my current progress with the DHCP support for SDN. >> >> This patch series adds support for automatically deploying dnsmasq as a DHCP >> server to a simple SDN Zone. >> >> While certainly not 100% polished on some ends (looking at restarting systemd >> services in particular), the general idea behind the mechanism shows. I wanted >> to gather some feedback on how I approached designing the plugins and the >> config regeneration process before comitting to this design by creating an API >> and UI around it. >> >> You need to install dnsmasq (and disable it afterwards): >> >> apt install dnsmasq && systemctl disable --now dnsmasq >> >> >> You can use the following example configuration for deploying a DHCP server in >> a SDN subnet: >> >> /etc/pve/sdn/dhcp.cfg: >> >> dnsmasq: nat >> >> >> /etc/pve/sdn/zones.cfg: >> >> simple: DHCPNAT >> ipam pve >> >> >> /etc/pve/sdn/vnets.cfg: >> >> vnet: dhcpnat >> zone DHCPNAT >> >> >> /etc/pve/sdn/subnets.cfg: >> >> subnet: DHCPNAT-10.1.0.0-16 >> vnet dhcpnat >> dhcp-dns-server 10.1.0.1 >> dhcp-range server=nat,start-address=10.1.0.100,end-address=10.1.0.200 >> gateway 10.1.0.1 >> snat 1 >> >> >> Then apply the SDN configuration: >> >> pvesh set /cluster/sdn >> >> You need to apply the SDN configuration once after adding the dhcp-range lines >> to the configuration, since the running configuration is used for managing >> DHCP. It will not work otherwise! >> >> For testing it can be helpful to monitor the following files (e.g. with watch) >> to find out what is happening >> * /etc/dnsmasq.d//ethers (on each node) >> * /etc/pve/priv/ipam.db >> >> Changes from v1 -> v2: >> * added hooks for handling DHCP when starting / stopping / .. VMs and CTs >> * Get an IP from IPAM and register that IP in the DHCP server >> (pve only for now) >> * remove lease-time, since it is now infinite and managed by the VM lifecycle >> * add hooks for setting & deleting DHCP mappings to DHCP plugins >> * modified interface of the abstract class to reflect new requirements >> * added helpers in existing SDN classes >> * simplified DHCP configuration settings >> >> >> >> pve-cluster: >> >> Stefan Hanreich (1): >> cluster files: add dhcp.cfg >> >> src/PVE/Cluster.pm | 1 + >> src/pmxcfs/status.c | 1 + >> 2 files changed, 2 insertions(+) >> >> >> pve-network: >> >> Stefan Hanreich (6): >> subnets: vnets: preparations for DHCP plugins >> dhcp: add abstract class for DHCP plugins >> dhcp: subnet: add DHCP options to subnet configuration >> dhcp: add DHCP plugin for dnsmasq >> ipam: Add helper methods for DHCP to PVE IPAM >> dhcp: regenerate config for DHCP servers on reload >> >> debian/control | 1 + >> src/PVE/Network/SDN.pm | 11 +- >> src/PVE/Network/SDN/Dhcp.pm | 192 +++++++++++++++++++++++++ >> src/PVE/Network/SDN/Dhcp/Dnsmasq.pm | 186 ++++++++++++++++++++++++ >> src/PVE/Network/SDN/Dhcp/Makefile | 8 ++ >> src/PVE/Network/SDN/Dhcp/Plugin.pm | 83 +++++++++++ >> src/PVE/Network/SDN/Ipams/PVEPlugin.pm | 64 +++++++++ >> src/PVE/Network/SDN/Makefile | 3 +- >> src/PVE/Network/SDN/SubnetPlugin.pm | 32 +++++ >> src/PVE/Network/SDN/Subnets.pm | 43 ++++-- >> src/PVE/Network/SDN/Vnets.pm | 27 ++-- >> 11 files changed, 622 insertions(+), 28 deletions(-) >> create mode 100644 src/PVE/Network/SDN/Dhcp.pm >> create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm >> create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile >> create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm >> >> >> pve-manager: >> >> Stefan Hanreich (1): >> sdn: regenerate DHCP config on reload >> >> PVE/API2/Network.pm | 1 + >> 1 file changed, 1 insertion(+) >> >> >> qemu-server: >> >> Stefan Hanreich (1): >> sdn: dhcp: add DHCP setup to vm-network-scripts >> >> PVE/QemuServer.pm | 14 ++++++++++++++ >> vm-network-scripts/pve-bridge | 3 +++ >> vm-network-scripts/pve-bridgedown | 19 +++++++++++++++++++ >> 3 files changed, 36 insertions(+) >> >> >> pve-container: >> >> Stefan Hanreich (1): >> sdn: dhcp: setup DHCP mappings in LXC hooks >> >> src/PVE/LXC.pm | 10 ++++++++++ >> src/lxc-pve-poststop-hook | 1 + >> src/lxc-pve-prestart-hook | 9 +++++++++ >> 3 files changed, 20 insertions(+) >> >> >> Summary over all repositories: >> 20 files changed, 681 insertions(+), 28 deletions(-) >> >> -- >> murpp v0.4.0 >> >> >> _______________________________________________ >> pve-devel mailing list >> pve-devel at lists.proxmox.com >> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel From c.ebner at proxmox.com Mon Oct 23 15:03:11 2023 From: c.ebner at proxmox.com (Christian Ebner) Date: Mon, 23 Oct 2023 15:03:11 +0200 (CEST) Subject: [pve-devel] [RFC v2 pve-container pve-manager 0/3] add partial restore In-Reply-To: <47924ea8-097e-4dbe-979d-021566e564f5@proxmox.com> References: <20231023111835.238407-1-c.ebner@proxmox.com> <47924ea8-097e-4dbe-979d-021566e564f5@proxmox.com> Message-ID: <260764959.1312.1698066191830@webmail.proxmox.com> Oh, okay! I was not aware of these patches, thanks for letting me know. Yes, being consistent with the UI/API for both VM and LXC restores is desired. I was planning on doing the VM part next, but since there already is a patch series for this I will definitely work on a common denominator for these. Thanks also for the first comments, will include changes regarding this into an updated version. I will also take you up on the offer to pick-up a rebased version of these patches. Cheers, Chris > On 23.10.2023 14:47 CEST Fiona Ebner wrote: > > > Am 23.10.23 um 13:18 schrieb Christian Ebner: > > This patch series adds functionality to partially restore containers > > from backup, by allowing the user to selectively include/exclude > > mountpoints for restore. Mountpoints not included in the backup will not > > be deleted and recreated but rather attached to the container as unused > > disk after the restore. The same is true for mountpoints selected by the > > user to be excluded during restore. > > > > FYI, there is an old series [0] adding a similar feature for VMs. It > would be nice if we could have the API/UI be not too different between > them. Of course, it's not set in stone how it's done there (since it > didn't get applied ;)), but it would be great if we could come up with a > solution that works for both cases. If you really want, you could even > pick up those patches in your next version of the series (just tell me > if you need a rebased version). > > Didn't have time to take a close look yet, just noting that > 'exclude-mps' is not specific enough as a parameter name for the > 'create' API call. The name really should include the word 'restore' or > similar, and you can add a requires => 'restore' to the schema > definition of the parameter. > > [0]: https://lists.proxmox.com/pipermail/pve-devel/2022-April/052720.html From f.gleumes at proxmox.com Mon Oct 23 15:18:05 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Mon, 23 Oct 2023 15:18:05 +0200 Subject: [pve-devel] [PATCH manager 2/5] fix #4497: acme: add support for external account bindings In-Reply-To: <20231023131808.172494-1-f.gleumes@proxmox.com> References: <20231023131808.172494-1-f.gleumes@proxmox.com> Message-ID: <20231023131808.172494-3-f.gleumes@proxmox.com> Signed-off-by: Folke Gleumes --- PVE/API2/ACMEAccount.pm | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/PVE/API2/ACMEAccount.pm b/PVE/API2/ACMEAccount.pm index b790843a..daae18d8 100644 --- a/PVE/API2/ACMEAccount.pm +++ b/PVE/API2/ACMEAccount.pm @@ -115,6 +115,16 @@ __PACKAGE__->register_method ({ default => $acme_default_directory_url, optional => 1, }), + eab_kid => { + type => 'string', + description => 'Key Identifier for External Account Binding.', + optional => 1, + }, + eab_hmac_key => { + type => 'string', + description => 'HMAC key for External Account Binding.', + optional => 1, + }, }, }, returns => { @@ -130,8 +140,15 @@ __PACKAGE__->register_method ({ my $account_file = "${acme_account_dir}/${account_name}"; mkdir $acme_account_dir if ! -e $acme_account_dir; + my $eab_kid = extract_param($param, 'eab_kid'); + my $eab_hmac_key = extract_param($param, 'eab_hmac_key'); + raise_param_exc({'name' => "ACME account config file '${account_name}' already exists."}) if -e $account_file; + raise_param_exc({'eab_kid' => "'eab_hmac_key' must be defined if 'eab_kid' is set."}) + if defined($eab_kid) and not defined($eab_hmac_key); + raise_param_exc({'eab_hmac_key' => "'eab_kid' must be defined if 'eab_hmac_key' is set."}) + if defined($eab_hmac_key) and not defined($eab_kid); my $directory = extract_param($param, 'directory') // $acme_default_directory_url; my $contact = $account_contact_from_param->($param); @@ -145,7 +162,15 @@ __PACKAGE__->register_method ({ print "Generating ACME account key..\n"; $acme->init(4096); print "Registering ACME account..\n"; - eval { $acme->new_account($param->{tos_url}, contact => $contact); }; + my $info = {contact => $contact}; + if (defined($eab_kid) and defined($eab_hmac_key)) { + $info->{eab} = { + kid => $eab_kid, + hmac_key => $eab_hmac_key + }; + } + + eval { $acme->new_account($param->{tos_url}, $info); }; if (my $err = $@) { unlink $account_file; die "Registration failed: $err\n"; -- 2.39.2 From f.gleumes at proxmox.com Mon Oct 23 15:18:07 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Mon, 23 Oct 2023 15:18:07 +0200 Subject: [pve-devel] [PATCH manager 4/5] fix #4497: cli/acme: detect eab and ask for credentials In-Reply-To: <20231023131808.172494-1-f.gleumes@proxmox.com> References: <20231023131808.172494-1-f.gleumes@proxmox.com> Message-ID: <20231023131808.172494-5-f.gleumes@proxmox.com> Since external account binding is advertised the same way as the ToS, it can be detected when creating an account and asked for if needed. Signed-off-by: Folke Gleumes --- PVE/CLI/pvenode.pm | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/PVE/CLI/pvenode.pm b/PVE/CLI/pvenode.pm index acef6c3b..e3d6b15a 100644 --- a/PVE/CLI/pvenode.pm +++ b/PVE/CLI/pvenode.pm @@ -117,8 +117,9 @@ __PACKAGE__->register_method({ } } print "\nAttempting to fetch Terms of Service from '$param->{directory}'..\n"; - my $tos = PVE::API2::ACMEAccount->get_tos({ directory => $param->{directory} }); - if ($tos) { + my $meta = PVE::API2::ACMEAccount->get_meta({ directory => $param->{directory} }); + if ($meta->{termsOfService}) { + my $tos = $meta->{termsOfService}; print "Terms of Service: $tos\n"; my $term = Term::ReadLine->new('pvenode'); my $agreed = $term->readline('Do you agree to the above terms? [y|N]: '); @@ -129,6 +130,17 @@ __PACKAGE__->register_method({ } else { print "No Terms of Service found, proceeding.\n"; } + if ($meta->{externalAccountRequired}) { + print "The ACME Directory uses External Account Binding\n"; + my $term = Term::ReadLine->new('pvenode'); + my $eab_kid = $term->readline('Enter EAB kid: '); + my $eab_hmac_key = $term->readline('Enter EAB HMAC key: '); + + $param->{eab_kid} = $eab_kid; + $param->{eab_hmac_key} = $eab_hmac_key; + } else { + print "No EAB required, proceeding.\n"; + } print "\nAttempting to register account with '$param->{directory}'..\n"; $upid_exit->(PVE::API2::ACMEAccount->register_account($param)); -- 2.39.2 From f.gleumes at proxmox.com Mon Oct 23 15:18:03 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Mon, 23 Oct 2023 15:18:03 +0200 Subject: [pve-devel] [PATCH acme/manager 0/5] fix #4497: add external account binding support Message-ID: <20231023131808.172494-1-f.gleumes@proxmox.com> This patch series adds functionality to use acme directiories that require the use of external account binding, as specified in rfc 8555 section 7.3.4. To avoid code duplication and redundant calls to the CA, the `/cluster/acme/tos` endpoint has been deprecated and it's function will be covered by the new `/cluster/acme/meta` endpoint, which exposes all meta information provided by the CA, including the flag indicating that EAB needs to be used. The underlying call to the CA remains the same. The CLI interface will only ask for the EAB credentials if needed, similar to how it works for the ToS. The patches have been tested to work with and without EAB by using pebble [0] as the CA. [0] https://github.com/letsencrypt/pebble acme: Folke Gleumes (1): fix #4497: add support for external account bindings src/PVE/ACME.pm | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) manager: Folke Gleumes (4): fix #4497: acme: add support for external account bindings fix #4497: api/acme: deprecate tos endpoint in favor of meta fix #4497: cli/acme: detect eab and ask for credentials fix #4497: ui/acme: switch to new meta endpoint PVE/API2/ACMEAccount.pm | 73 +++++++++++++++++++++++++++++++++++++-- PVE/CLI/pvenode.pm | 16 +++++++-- www/manager6/node/ACME.js | 12 ++++--- 3 files changed, 93 insertions(+), 8 deletions(-) -- 2.39.2 From f.gleumes at proxmox.com Mon Oct 23 15:18:04 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Mon, 23 Oct 2023 15:18:04 +0200 Subject: [pve-devel] [PATCH acme 1/5] fix #4497: add support for external account bindings In-Reply-To: <20231023131808.172494-1-f.gleumes@proxmox.com> References: <20231023131808.172494-1-f.gleumes@proxmox.com> Message-ID: <20231023131808.172494-2-f.gleumes@proxmox.com> implementation acording to rfc855 section 7.3.4 Signed-off-by: Folke Gleumes --- src/PVE/ACME.pm | 43 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/PVE/ACME.pm b/src/PVE/ACME.pm index 3f66182..f65729a 100644 --- a/src/PVE/ACME.pm +++ b/src/PVE/ACME.pm @@ -7,10 +7,10 @@ use POSIX; use Data::Dumper; use Date::Parse; -use MIME::Base64 qw(encode_base64url); +use MIME::Base64 qw(encode_base64url decode_base64); use File::Path qw(make_path); use JSON; -use Digest::SHA qw(sha256 sha256_hex); +use Digest::SHA qw(sha256 sha256_hex hmac_sha256); use HTTP::Request; use LWP::UserAgent; @@ -251,6 +251,28 @@ sub jws { }; } +# EAB signing using the HS256 alg (HMAC/SHA256). +sub eab { + my ($self, $eab_kid, $eab_hmac_key, $url) = @_; + + my $protected = { + alg => 'HS256', + kid => $eab_kid, + url => $url, + }; + $protected = encode(tojs($protected)); + + my $payload = encode(tojs($self->jwk())); + my $signdata = "$protected.$payload"; + my $signature = encode(hmac_sha256($signdata, $eab_hmac_key)); + + return { + protected => $protected, + payload => $payload, + signature => $signature, + }; +} + sub __get_result { my ($resp, $code, $plain) = @_; @@ -300,8 +322,8 @@ sub list_methods { } # return (optional) meta directory entry. -# this is public because it might contain the ToS, which should be displayed -# and agreed to before creating an account +# this is public because it might contain the ToS and EAB requirements, +# which have to be considered before creating an account sub get_meta { my ($self) = @_; my $methods = $self->__get_methods(); @@ -329,21 +351,26 @@ sub __new_account { return $self->{account}; } -# Create a new account using data in %info. +# Create a new account using data in $info. # Optionally pass $tos_url to agree to the given Terms of Service # POST to newAccount endpoint # Expects a '201 Created' reply # Saves and returns the account data sub new_account { - my ($self, $tos_url, %info) = @_; + my ($self, $tos_url, $info) = @_; my $url = $self->_method('newAccount'); + if ($info->{'eab'}) { + my $eab_hmac_key = decode_base64($info->{'eab'}->{hmac_key}); + $info->{externalAccountBinding} = $self->eab($info->{'eab'}->{kid}, $eab_hmac_key, $url); + } + if ($tos_url) { $self->{tos} = $tos_url; - $info{termsOfServiceAgreed} = JSON::true; + $info->{termsOfServiceAgreed} = JSON::true; } - return $self->__new_account(201, $url, 1, %info); + return $self->__new_account(201, $url, 1, %{$info}); } # Update existing account with new %info -- 2.39.2 From f.gleumes at proxmox.com Mon Oct 23 15:18:06 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Mon, 23 Oct 2023 15:18:06 +0200 Subject: [pve-devel] [PATCH manager 3/5] fix #4497: api/acme: deprecate tos endpoint in favor of meta In-Reply-To: <20231023131808.172494-1-f.gleumes@proxmox.com> References: <20231023131808.172494-1-f.gleumes@proxmox.com> Message-ID: <20231023131808.172494-4-f.gleumes@proxmox.com> The ToS endpoint ignored data that is needed to detect if EAB needs to be used. Instead of adding a new endpoint that does the same request, the tos endpoint is deprecated and replaced by the meta endpoint, that returns all information returned by the directory. Signed-off-by: Folke Gleumes --- PVE/API2/ACMEAccount.pm | 46 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/PVE/API2/ACMEAccount.pm b/PVE/API2/ACMEAccount.pm index daae18d8..bfe76734 100644 --- a/PVE/API2/ACMEAccount.pm +++ b/PVE/API2/ACMEAccount.pm @@ -62,6 +62,7 @@ __PACKAGE__->register_method ({ return [ { name => 'account' }, { name => 'tos' }, + { name => 'meta' }, { name => 'directories' }, { name => 'plugins' }, { name => 'challenge-schema' }, @@ -333,11 +334,12 @@ __PACKAGE__->register_method ({ return $update_account->($param, 'deactivate', status => 'deactivated'); }}); +# TODO: deprecated, remove with pve 9 __PACKAGE__->register_method ({ name => 'get_tos', path => 'tos', method => 'GET', - description => "Retrieve ACME TermsOfService URL from CA.", + description => "Retrieve ACME TermsOfService URL from CA. Deprecated, please use /cluster/acme/meta.", permissions => { user => 'all' }, parameters => { additionalProperties => 0, @@ -364,6 +366,48 @@ __PACKAGE__->register_method ({ return $meta ? $meta->{termsOfService} : undef; }}); +__PACKAGE__->register_method ({ + name => 'get_meta', + path => 'meta', + method => 'GET', + description => "Retrieve ACME Directory Meta Information", + permissions => { user => 'all' }, + parameters => { + additionalProperties => 0, + properties => { + directory => get_standard_option('pve-acme-directory-url', { + default => $acme_default_directory_url, + optional => 1, + }), + }, + }, + returns => { + type => 'object', + additionalProperties => 0, + properties => { + termsOfService => { + type => 'string', + optional => 1, + description => 'ACME TermsOfService URL.', + }, + externalAccountRequired => { + type => 'boolean', + optional => 1, + description => 'EAB Required' + }, + }, + }, + code => sub { + my ($param) = @_; + + my $directory = extract_param($param, 'directory') // $acme_default_directory_url; + + my $acme = PVE::ACME->new(undef, $directory); + my $meta = $acme->get_meta(); + + return $meta; + }}); + __PACKAGE__->register_method ({ name => 'get_directories', path => 'directories', -- 2.39.2 From f.gleumes at proxmox.com Mon Oct 23 15:18:08 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Mon, 23 Oct 2023 15:18:08 +0200 Subject: [pve-devel] [PATCH manager 5/5] fix #4497: ui/acme: switch to new meta endpoint In-Reply-To: <20231023131808.172494-1-f.gleumes@proxmox.com> References: <20231023131808.172494-1-f.gleumes@proxmox.com> Message-ID: <20231023131808.172494-6-f.gleumes@proxmox.com> Besides the switch from tos to meta endpoint, this fixes a visual bug, where the 'Accept TOS' button would show up, even if no ToS was needed. Signed-off-by: Folke Gleumes --- www/manager6/node/ACME.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/www/manager6/node/ACME.js b/www/manager6/node/ACME.js index 9f1dabce..d64ac36f 100644 --- a/www/manager6/node/ACME.js +++ b/www/manager6/node/ACME.js @@ -79,15 +79,19 @@ Ext.define('PVE.node.ACMEAccountCreate', { checkbox.setHidden(true); Proxmox.Utils.API2Request({ - url: '/cluster/acme/tos', + url: '/cluster/acme/meta', method: 'GET', params: { directory: value, }, success: function(response, opt) { - field.setValue(response.result.data); - disp.setValue(response.result.data); - checkbox.setHidden(false); + if (response.result.data.termsOfService) { + field.setValue(response.result.data.termsOfService); + disp.setValue(response.result.data.termsOfService); + checkbox.setHidden(false); + } else { + disp.setValue(undefined); + } }, failure: function(response, opt) { Ext.Msg.alert(gettext('Error'), response.htmlStatus); -- 2.39.2 From t.lamprecht at proxmox.com Mon Oct 23 17:09:55 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Mon, 23 Oct 2023 17:09:55 +0200 Subject: [pve-devel] applied: [PATCH qemu-server] vzdump: assemble: improve error messages In-Reply-To: <20231023085908.22902-1-f.ebner@proxmox.com> References: <20231023085908.22902-1-f.ebner@proxmox.com> Message-ID: <69afc119-f7d4-4ae5-a7dc-4fd01b455b1d@proxmox.com> Am 23/10/2023 um 10:59 schrieb Fiona Ebner: > by including the errno. Might make it clearer what the issue is in > cases like: https://forum.proxmox.com/threads/135261/ > > Also add the missing newlines, the missing "to" in the second message, > switch to the more common "or die" and avoid line bloat while at it. > > Signed-off-by: Fiona Ebner > --- > PVE/VZDump/QemuServer.pm | 6 ++---- > 1 file changed, 2 insertions(+), 4 deletions(-) > > applied, thanks! From m.sandoval at proxmox.com Mon Oct 23 17:11:05 2023 From: m.sandoval at proxmox.com (Maximiliano Sandoval R) Date: Mon, 23 Oct 2023 17:11:05 +0200 Subject: [pve-devel] [PATCH docs] installation: document UP indicator in network setup Message-ID: <20231023151105.152434-1-m.sandoval@proxmox.com> See https://git.proxmox.com/?p=pve-installer.git;a=commit;h=124ca33f9fb3780a76f0b5fd57952e69823852c8. Signed-off-by: Maximiliano Sandoval R --- pve-installation.adoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pve-installation.adoc b/pve-installation.adoc index aa4e4c9..44b59c0 100644 --- a/pve-installation.adoc +++ b/pve-installation.adoc @@ -172,9 +172,10 @@ For example: [thumbnail="screenshot/pve-setup-network.png"] -The last step is the network configuration. Please note that during installation -you can either use an IPv4 or IPv6 address, but not both. To configure a dual -stack node, add additional IP addresses after the installation. +The last step is the network configuration. Network interfaces that are UP show +a filled circle in front of their name in the drop down menu. Please note that +during installation you can either use an IPv4 or IPv6 address, but not both. To +configure a dual stack node, add additional IP addresses after the installation. [thumbnail="screenshot/pve-installation.png", float="left"] -- 2.39.2 From a.zeidler at proxmox.com Mon Oct 23 17:31:45 2023 From: a.zeidler at proxmox.com (Alexander Zeidler) Date: Mon, 23 Oct 2023 17:31:45 +0200 Subject: [pve-devel] [PATCH docs] faq: change to inline anchors to land on the actual questions Message-ID: <20231023153145.176237-1-a.zeidler@proxmox.com> Signed-off-by: Alexander Zeidler --- pve-faq.adoc | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/pve-faq.adoc b/pve-faq.adoc index 93fef32..2388673 100644 --- a/pve-faq.adoc +++ b/pve-faq.adoc @@ -77,8 +77,7 @@ CPU. It is not limited to Linux guests but allows arbitrary operating systems to run. -[[faq-support-table]] -How long will my {pve} version be supported?:: +[[faq-support-table]] How long will my {pve} version be supported?:: {pve} versions are supported at least as long as the corresponding Debian Version is @@ -99,8 +98,7 @@ recommended. | {pve} 1 | Debian 5 (Lenny) | 2008-10 | 2012-03 | 2013-01 |=============================================================================== -[[faq-upgrade]] -How can I upgrade {pve} to the next point release?:: +[[faq-upgrade]] How can I upgrade {pve} to the next point release?:: Minor version upgrades, for example upgrading from {pve} in version 7.1 to 7.2 or 7.3, can be done just like any normal update. @@ -119,8 +117,7 @@ NOTE: Always ensure you correctly setup the xref:sysadmin_package_repositories[package repositories] and only continue with the actual upgrade if `apt update` did not hit any error. -[[faq-upgrade-major]] -How can I upgrade {pve} to the next major release?:: +[[faq-upgrade-major]] How can I upgrade {pve} to the next major release?:: Major version upgrades, for example going from {pve} 4.4 to 5.0, are also supported. -- 2.39.2 From t.lamprecht at proxmox.com Mon Oct 23 17:32:56 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Mon, 23 Oct 2023 17:32:56 +0200 Subject: [pve-devel] applied-series: [PATCH font-logos/manager v2 0/5] fix #2435: lxc: show distro and privileged status in summary In-Reply-To: <58d30e37-be45-42bf-82f6-1e9e57a53bb1@proxmox.com> References: <20230705111257.759836-1-c.heiss@proxmox.com> <58d30e37-be45-42bf-82f6-1e9e57a53bb1@proxmox.com> Message-ID: <0d7459d1-0b7a-4ee5-9462-b7812859fad2@proxmox.com> Am 20/10/2023 um 15:33 schrieb Dominik Csapak: > the series looks mostly fine to me, i wrote two small > comments in the relevant patches, but both > are not super important to fix IMO > > aside from that consider this series > > Reviewed-by: Dominik Csapak > Tested-by: Dominik Csapak With your tags, and the review comments addressed and squashed into: applied, thanks! From t.lamprecht at proxmox.com Mon Oct 23 17:35:26 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Mon, 23 Oct 2023 17:35:26 +0200 Subject: [pve-devel] applied: [PATCH docs] installation: document UP indicator in network setup In-Reply-To: <20231023151105.152434-1-m.sandoval@proxmox.com> References: <20231023151105.152434-1-m.sandoval@proxmox.com> Message-ID: Am 23/10/2023 um 17:11 schrieb Maximiliano Sandoval R: > See > https://git.proxmox.com/?p=pve-installer.git;a=commit;h=124ca33f9fb3780a76f0b5fd57952e69823852c8. > > Signed-off-by: Maximiliano Sandoval R > --- > pve-installation.adoc | 7 ++++--- > 1 file changed, 4 insertions(+), 3 deletions(-) > > applied, thanks! From t.lamprecht at proxmox.com Mon Oct 23 17:39:59 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Mon, 23 Oct 2023 17:39:59 +0200 Subject: [pve-devel] [PATCH docs] faq: change to inline anchors to land on the actual questions In-Reply-To: <20231023153145.176237-1-a.zeidler@proxmox.com> References: <20231023153145.176237-1-a.zeidler@proxmox.com> Message-ID: Am 23/10/2023 um 17:31 schrieb Alexander Zeidler: > Signed-off-by: Alexander Zeidler > --- > pve-faq.adoc | 9 +++------ > 1 file changed, 3 insertions(+), 6 deletions(-) > > diff --git a/pve-faq.adoc b/pve-faq.adoc > index 93fef32..2388673 100644 > --- a/pve-faq.adoc > +++ b/pve-faq.adoc > @@ -77,8 +77,7 @@ CPU. > It is not limited to Linux guests but allows arbitrary operating systems > to run. > > -[[faq-support-table]] > -How long will my {pve} version be supported?:: > +[[faq-support-table]] How long will my {pve} version be supported?:: > This breaks the package build here though: unable to resolve chapter link (xref:faq-support-table) our tooling probably needs to adapt detecting these inline refs too From ykonotopov at gnome.org Mon Oct 23 19:37:36 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Mon, 23 Oct 2023 21:37:36 +0400 Subject: [PATCH v4 storage 1/1] fix #254: iscsi: add support for multipath iscsi targets In-Reply-To: References: <20231021093731.17753-1-ykonotopov@gnome.org> <20231021093731.17753-2-ykonotopov@gnome.org> Message-ID: <16c0707e-e6ed-4b4b-b525-9dcd15b139e1@gnome.org> 23.10.2023 12:11, Dominik Csapak ?????: > Hi, Hi, Dominik! > sorry but I just noticed that it seems you did not create this patch > on top of our current master? > > at least here it does not apply cleanly, since the files got moved to > src/ > (in may already) > > so could you please rebase your patches on the current master branch > and send it again? > > (i did not get around to check the v4 yet, but a rebase shouldn't be a > problem) Sure, I will send rebased v5 now -- Best regards, Yuri Konotopov From ykonotopov at gnome.org Mon Oct 23 19:45:08 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Mon, 23 Oct 2023 21:45:08 +0400 Subject: [PATCH v5 storage 1/1] fix #254: iscsi: add support for multipath iscsi targets In-Reply-To: <20231023174508.385682-1-ykonotopov@gnome.org> References: <20231023174508.385682-1-ykonotopov@gnome.org> Message-ID: <20231023174508.385682-2-ykonotopov@gnome.org> With this patch Proxmox now tries to login to all discovered portals in case some of them are not logged yet. In case of multipath configuration when initially configured portal is missing for some reason Proxmox don't lose iscsi storage now and can succesfully restore iscsi connection between reboots. Signed-off-by: Yuri Konotopov --- src/PVE/Storage.pm | 2 +- src/PVE/Storage/ISCSIPlugin.pm | 117 +++++++++++++++++++++++++-------- 2 files changed, 89 insertions(+), 30 deletions(-) diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm index a4d85e1..ec5f5bd 100755 --- a/src/PVE/Storage.pm +++ b/src/PVE/Storage.pm @@ -1432,7 +1432,7 @@ sub scan_iscsi { die "unable to parse/resolve portal address '${portal_in}'\n"; } - return PVE::Storage::ISCSIPlugin::iscsi_discovery($portal); + return PVE::Storage::ISCSIPlugin::iscsi_discovery([ $portal ]); } sub storage_default_format { diff --git a/src/PVE/Storage/ISCSIPlugin.pm b/src/PVE/Storage/ISCSIPlugin.pm index a79fcb0..b4ab1dd 100644 --- a/src/PVE/Storage/ISCSIPlugin.pm +++ b/src/PVE/Storage/ISCSIPlugin.pm @@ -18,6 +18,9 @@ use base qw(PVE::Storage::Plugin); my $ISCSIADM = '/usr/bin/iscsiadm'; $ISCSIADM = undef if ! -X $ISCSIADM; +# Example: 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f +my $ISCSI_TARGET_RE = qr/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/; + sub check_iscsi_support { my $noerr = shift; @@ -45,11 +48,12 @@ sub iscsi_session_list { eval { run_command($cmd, errmsg => 'iscsi session scan failed', outfunc => sub { my $line = shift; - - if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)(\s+\S+)?\s*$/) { - my ($session, $target) = ($1, $2); + # example: tcp: [1] 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f (non-flash) + if ($line =~ m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s+\S+?\s*$/) { + my ($session_id, $portal, $target) = ($1, $2, $3); # there can be several sessions per target (multipath) - push @{$res->{$target}}, $session; + my %session = ( session_id => $session_id, portal => $portal ); + push @{$res->{$target}}, \%session; } }); }; @@ -68,42 +72,77 @@ sub iscsi_test_portal { return PVE::Network::tcp_ping($server, $port || 3260, 2); } -sub iscsi_discovery { - my ($portal) = @_; +sub iscsi_portals { + my ($target, $portal_in) = @_; check_iscsi_support (); - my $res = {}; - return $res if !iscsi_test_portal($portal); # fixme: raise exception here? + my $res = []; + my $cmd = [$ISCSIADM, '--mode', 'node']; + eval { + run_command($cmd, outfunc => sub { + my $line = shift; - my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; - run_command($cmd, outfunc => sub { - my $line = shift; + if ($line =~ $ISCSI_TARGET_RE) { + my ($portal, $portal_target) = ($1, $2); + if ($portal_target eq $target) { + push @{$res}, $portal; + } + } + }); + }; - if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { - my $portal = $1; - my $target = $2; - # one target can have more than one portal (multipath). - push @{$res->{$target}}, $portal; - } - }); + if ($@) { + warn $@; + return [ $portal_in ]; + } + + return $res; +} + +sub iscsi_discovery { + my ($portals) = @_; + + check_iscsi_support (); + + my $res = {}; + for my $portal ($portals->@*) { + next if !iscsi_test_portal($portal); # fixme: raise exception here? + + my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; + eval { + run_command($cmd, outfunc => sub { + my $line = shift; + + if ($line =~ $ISCSI_TARGET_RE) { + my ($portal, $target) = ($1, $2); + # one target can have more than one portal (multipath) + # and sendtargets should return all of them in single call + push @{$res->{$target}}, $portal; + } + }); + }; + + # In case of multipath we can stop after receiving targets from any available portal + last if scalar(keys %$res) > 0; + } return $res; } sub iscsi_login { - my ($target, $portal_in) = @_; + my ($target, $portals) = @_; check_iscsi_support(); - eval { iscsi_discovery($portal_in); }; + eval { iscsi_discovery($portals); }; warn $@ if $@; run_command([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login']); } sub iscsi_logout { - my ($target, $portal) = @_; + my ($target) = @_; check_iscsi_support(); @@ -133,7 +172,7 @@ sub iscsi_session_rescan { } foreach my $session (@$session_list) { - my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session, '--rescan']; + my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session->{session_id}, '--rescan']; eval { run_command($cmd, outfunc => sub {}); }; warn $@ if $@; } @@ -379,14 +418,28 @@ sub activate_storage { return if !check_iscsi_support(1); - my $session = iscsi_session($cache, $scfg->{target}); + my $sessions = iscsi_session($cache, $scfg->{target}); + my $portals = iscsi_portals($scfg->{target}, $scfg->{portal}); + my $do_login = !defined($sessions); - if (!defined ($session)) { - eval { iscsi_login($scfg->{target}, $scfg->{portal}); }; + if (!$do_login) { + # We should check that sessions for all portals are available + my $session_portals = [ map { $_->{portal} } (@$sessions) ]; + + for my $portal (@$portals) { + if (!grep(/^\Q$portal\E$/, @$session_portals)) { + $do_login = 1; + last; + } + } + } + + if ($do_login) { + eval { iscsi_login($scfg->{target}, $portals); }; warn $@ if $@; } else { # make sure we get all devices - iscsi_session_rescan($session); + iscsi_session_rescan($sessions); } } @@ -396,15 +449,21 @@ sub deactivate_storage { return if !check_iscsi_support(1); if (defined(iscsi_session($cache, $scfg->{target}))) { - iscsi_logout($scfg->{target}, $scfg->{portal}); + iscsi_logout($scfg->{target}); } } sub check_connection { my ($class, $storeid, $scfg) = @_; - my $portal = $scfg->{portal}; - return iscsi_test_portal($portal); + my $portals = iscsi_portals($scfg->{target}, $scfg->{portal}); + + for my $portal (@$portals) { + my $result = iscsi_test_portal($portal); + return $result if $result; + } + + return 0; } sub volume_resize { -- 2.41.0 From ykonotopov at gnome.org Mon Oct 23 19:45:07 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Mon, 23 Oct 2023 21:45:07 +0400 Subject: [PATCH v5 storage 0/1] fix #254: iscsi: add support for multipath iscsi targets Message-ID: <20231023174508.385682-1-ykonotopov@gnome.org> Changes since v4: * rebased on top of master branch Changes since v3: * drop redundant `m` modifier from `ISCSI_TARGET_RE` regexp * drop redundant group from `iscsi_session_list` regexp * improve stop loop condition in `iscsi_discovery` Changes since v2: * custom configuration file is removed in favor of open-iscsi query * restored `portal` property format since we should not rely on initial configuration but should use discovered configuration instead * changed check_connection() to query all available portals * style fixes Changes since v1: * added configuration file that stores discovered portals * implemented iscsi login in case there missing sessions for any of portals * style fixes * use existent `-list` property format instead of custom one * use PVE::Tools::split_list() instead of custom split function Yuri Konotopov (1): fix #254: iscsi: add support for multipath iscsi targets Yuri Konotopov (1): fix #254: iscsi: add support for multipath iscsi targets src/PVE/Storage.pm | 2 +- src/PVE/Storage/ISCSIPlugin.pm | 117 +++++++++++++++++++++++++-------- 2 files changed, 89 insertions(+), 30 deletions(-) -- 2.41.0 From alexandre.derumier at groupe-cyllene.com Mon Oct 23 20:03:18 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Mon, 23 Oct 2023 18:03:18 +0000 Subject: [pve-devel] [PATCH v4 qemu-server 2/2] remote-migration: add target-cpu && target-reboot params In-Reply-To: <73e0a3a6-f978-ac24-5f6b-16af759ee209@proxmox.com> References: <20230928144556.2023558-1-aderumier@odiso.com> <20230928144556.2023558-3-aderumier@odiso.com> <5ecfa7d0-4525-5f1e-75a2-a6ae1a93356b@proxmox.com> <73e0a3a6-f978-ac24-5f6b-16af759ee209@proxmox.com> Message-ID: Hi Fiona, >>In case of 'restart' migration, we do want to start the VM anyways, >>so >>it's actually better, because we can catch config issues early :) Now >>that I think about it, can we also just start the target VM in >>prelaunch >>mode (instead of incoming migration mode), do the NBD migration, shut >>down the source VM, stop the NBD server and then resume the target? >>That >>would avoid the need to stop and start the target again. And >>therefore >>might be quite a bit less downtime. I have done some tests, It's not possible currently to write to the remote nbd without the --incoming migration flag and only -S https://lists.gnu.org/archive/html/qemu-devel/2017-11/msg05700.html nbd_add is throwing an error like 2023-10-23 18:45:51 [formationkvm1] VM 111 qmp command 'block-export- add' failed - Permission conflict on node '#block524': permissions 'write' are both required by an unnamed block device (uses node '#block524' as 'root' child) and unshared by block device 'drive-scsi0' (uses node '#block524' as 'root' child). Looking at the qemu code, the are some specific codepath in block when the incoming flag is setup. So I think the best way for now is to restart the target vm. From alexandre.derumier at groupe-cyllene.com Mon Oct 23 23:51:22 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Mon, 23 Oct 2023 21:51:22 +0000 Subject: [pve-devel] [PATCH qemu-server] Fix: cpu hotplug feature can't be changed online In-Reply-To: <1a8ae488-7f5e-4cf3-b63c-d7ba384c98a7@proxmox.com> References: <20231010153717.2282543-1-aderumier@odiso.com> <1a8ae488-7f5e-4cf3-b63c-d7ba384c98a7@proxmox.com> Message-ID: <8b4319cdd09cacf13b089206c1d34995aa66205d.camel@groupe-cyllene.com> Am 10.10.23 um 17:37 schrieb Alexandre Derumier: > The cpus are passed as devices with specific id only when cpu hotplug > is enable > at start. > We can't enable/disable it online or vcpu hotplug api will thrown > errors > not finding core id. >>When removing cores after enabling the option this is true, but I can >>1. start a VM without CPU hotplug and fewer than max vCPUs >>2. enable CPU hotplug >>3. add more cores >>And doing the disable of the option online also doesn't seem >>problematic >>at a first glance. So I thought this would technically be a breaking >>change. Note that is also breaking unplug of cores present before enable hotplug >>1. start a VM without CPU hotplug and fewer than max vCPUs >>2. enable CPU hotplug >>3. remove cores (But yes, the more important is migration breaking) From c.heiss at proxmox.com Tue Oct 24 09:45:01 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 24 Oct 2023 09:45:01 +0200 Subject: [pve-devel] applied-series: [PATCH installer 0/5] use hostname from DHCP lease if available In-Reply-To: References: <20231020094651.432513-1-c.heiss@proxmox.com> Message-ID: On Fri, Oct 20, 2023 at 05:21:41PM +0200, Thomas Lamprecht wrote: > > Am 20/10/2023 um 11:46 schrieb Christoph Heiss: > > DHCP servers can set option 12 ("host-name") for client leases [0], > > telling them about their hostname. It's very much non-invasive and falls > > back to the default values as done currently. > > > > This came up while talking to Aaron, which he noticed (esp. during > > trainings) that this would be a very useful feature too have. > > > > I have tested this with the "host-name" entry set and unset, as well as > > any combinations of that with the domain name being set or unset. > > > > [0] https://datatracker.ietf.org/doc/html/rfc2132#section-3.14 > > [..] > > applied series, squashed in a trivial fix to fix the tests though, you > probably based of an older git state where the Interface struct doesn't > have the "state" member yet, thanks! Yeah, exactly. Sorry about that - thanks for fixing it up & applying! From f.ebner at proxmox.com Tue Oct 24 10:11:09 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Tue, 24 Oct 2023 10:11:09 +0200 Subject: [pve-devel] [PATCH v4 qemu-server 2/2] remote-migration: add target-cpu && target-reboot params In-Reply-To: References: <20230928144556.2023558-1-aderumier@odiso.com> <20230928144556.2023558-3-aderumier@odiso.com> <5ecfa7d0-4525-5f1e-75a2-a6ae1a93356b@proxmox.com> <73e0a3a6-f978-ac24-5f6b-16af759ee209@proxmox.com> Message-ID: <016bd4b8-7502-48fe-9208-a075e8aea02b@proxmox.com> Am 23.10.23 um 20:03 schrieb DERUMIER, Alexandre: > Hi Fiona, > > >>> In case of 'restart' migration, we do want to start the VM anyways, >>> so >>> it's actually better, because we can catch config issues early :) Now >>> that I think about it, can we also just start the target VM in >>> prelaunch >>> mode (instead of incoming migration mode), do the NBD migration, shut >>> down the source VM, stop the NBD server and then resume the target? >>> That >>> would avoid the need to stop and start the target again. And >>> therefore >>> might be quite a bit less downtime. > > > I have done some tests, It's not possible currently to write to the > remote nbd without the --incoming migration flag and only -S > > > > https://lists.gnu.org/archive/html/qemu-devel/2017-11/msg05700.html > > > > nbd_add is throwing an error like > > 2023-10-23 18:45:51 [formationkvm1] VM 111 qmp command 'block-export- > add' failed - Permission conflict on node '#block524': permissions > 'write' are both required by an unnamed block device (uses node > '#block524' as 'root' child) and unshared by block device 'drive-scsi0' > (uses node '#block524' as 'root' child). > > > Looking at the qemu code, the are some specific codepath in block when > the incoming flag is setup. > That is unfortunate. But thanks for giving it a shot! I guess if we'd really wanted to go this route, we'd need to add some kind of "empty" migration type without any state, but would be hard to get right and feels like a hack. > > So I think the best way for now is to restart the target vm. > Sure! Going with that is a much cleaner approach then. From f.gruenbichler at proxmox.com Tue Oct 24 10:32:20 2023 From: f.gruenbichler at proxmox.com (Fabian =?iso-8859-1?q?Gr=FCnbichler?=) Date: Tue, 24 Oct 2023 10:32:20 +0200 Subject: [pve-devel] [PATCH manager 4/5] fix #4497: cli/acme: detect eab and ask for credentials In-Reply-To: <20231023131808.172494-5-f.gleumes@proxmox.com> References: <20231023131808.172494-1-f.gleumes@proxmox.com> <20231023131808.172494-5-f.gleumes@proxmox.com> Message-ID: <1698135215.wi12ac6f4w.astroid@yuna.none> On October 23, 2023 3:18 pm, Folke Gleumes wrote: > Since external account binding is advertised the same way as the ToS, > it can be detected when creating an account and asked for if needed. > > Signed-off-by: Folke Gleumes > --- > PVE/CLI/pvenode.pm | 16 ++++++++++++++-- > 1 file changed, 14 insertions(+), 2 deletions(-) > > diff --git a/PVE/CLI/pvenode.pm b/PVE/CLI/pvenode.pm > index acef6c3b..e3d6b15a 100644 > --- a/PVE/CLI/pvenode.pm > +++ b/PVE/CLI/pvenode.pm > @@ -117,8 +117,9 @@ __PACKAGE__->register_method({ > } > } > print "\nAttempting to fetch Terms of Service from '$param->{directory}'..\n"; > - my $tos = PVE::API2::ACMEAccount->get_tos({ directory => $param->{directory} }); > - if ($tos) { > + my $meta = PVE::API2::ACMEAccount->get_meta({ directory => $param->{directory} }); > + if ($meta->{termsOfService}) { > + my $tos = $meta->{termsOfService}; > print "Terms of Service: $tos\n"; > my $term = Term::ReadLine->new('pvenode'); > my $agreed = $term->readline('Do you agree to the above terms? [y|N]: '); > @@ -129,6 +130,17 @@ __PACKAGE__->register_method({ > } else { > print "No Terms of Service found, proceeding.\n"; > } > + if ($meta->{externalAccountRequired}) { > + print "The ACME Directory uses External Account Binding\n"; s/uses/requires and maybe s/Directory/CA/ since "directory" is just the name for the entrypoint of the API :) > + my $term = Term::ReadLine->new('pvenode'); since this is the "interactive" user friendly mode, we might want to add another line here to indicate that the requested values should have been given to the user by the CA? > + my $eab_kid = $term->readline('Enter EAB kid: '); might be worth to s/kid/key identifer ("kid")/ to make it more understandable for users who haven't already learned the ACME spec by heart ;) > + my $eab_hmac_key = $term->readline('Enter EAB HMAC key: '); > + > + $param->{eab_kid} = $eab_kid; > + $param->{eab_hmac_key} = $eab_hmac_key; maybe: } elsif ($directory_is_custom) { # ask for optional EAB parameters } > + } else { > + print "No EAB required, proceeding.\n"; > + } > print "\nAttempting to register account with '$param->{directory}'..\n"; > > $upid_exit->(PVE::API2::ACMEAccount->register_account($param)); > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > From f.gruenbichler at proxmox.com Tue Oct 24 10:32:09 2023 From: f.gruenbichler at proxmox.com (Fabian =?iso-8859-1?q?Gr=FCnbichler?=) Date: Tue, 24 Oct 2023 10:32:09 +0200 Subject: [pve-devel] [PATCH acme/manager 0/5] fix #4497: add external account binding support In-Reply-To: <20231023131808.172494-1-f.gleumes@proxmox.com> References: <20231023131808.172494-1-f.gleumes@proxmox.com> Message-ID: <1698136010.p0w6p0jvfp.astroid@yuna.none> On October 23, 2023 3:18 pm, Folke Gleumes wrote: > This patch series adds functionality to use acme directiories > that require the use of external account binding, as specified > in rfc 8555 section 7.3.4. > > To avoid code duplication and redundant calls to the CA, > the `/cluster/acme/tos` endpoint has been deprecated and > it's function will be covered by the new `/cluster/acme/meta` > endpoint, which exposes all meta information provided by the CA, > including the flag indicating that EAB needs to be used. > The underlying call to the CA remains the same. > > The CLI interface will only ask for the EAB credentials if needed, > similar to how it works for the ToS. > > The patches have been tested to work with and without EAB > by using pebble [0] as the CA. > > [0] https://github.com/letsencrypt/pebble this already looks quite good, some comments on the individual patches, mainly about the interface change in proxmox-acme and the meta endpoint. there might be some additional follow-up work needed once a subsequent version has been applied - in my experience pebble and production CAs often don't handle all corner cases identical (even pebble and boulder, where the divergence is often intentional to force client devs to implement those corner cases right ;)), or commercial CAs requiring some special attention. > > acme: Folke Gleumes (1): > fix #4497: add support for external account bindings > > src/PVE/ACME.pm | 43 +++++++++++++++++++++++++++++++++++-------- > 1 file changed, 35 insertions(+), 8 deletions(-) > > manager: Folke Gleumes (4): > fix #4497: acme: add support for external account bindings > fix #4497: api/acme: deprecate tos endpoint in favor of meta > fix #4497: cli/acme: detect eab and ask for credentials > fix #4497: ui/acme: switch to new meta endpoint nit: we usually only add the fixes tag to the patch/commit the makes the fix user-visible. not always clear cut, and having more than one can be okay. in this case, the tos/meta patches are only indirectly related to the fix, and not needed to use EAB, so they likely can drop the prefix. > > PVE/API2/ACMEAccount.pm | 73 +++++++++++++++++++++++++++++++++++++-- > PVE/CLI/pvenode.pm | 16 +++++++-- > www/manager6/node/ACME.js | 12 ++++--- > 3 files changed, 93 insertions(+), 8 deletions(-) > > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > From f.gruenbichler at proxmox.com Tue Oct 24 10:32:42 2023 From: f.gruenbichler at proxmox.com (Fabian =?iso-8859-1?q?Gr=FCnbichler?=) Date: Tue, 24 Oct 2023 10:32:42 +0200 Subject: [pve-devel] [PATCH manager 2/5] fix #4497: acme: add support for external account bindings In-Reply-To: <20231023131808.172494-3-f.gleumes@proxmox.com> References: <20231023131808.172494-1-f.gleumes@proxmox.com> <20231023131808.172494-3-f.gleumes@proxmox.com> Message-ID: <1698134500.8d6ows44rm.astroid@yuna.none> On October 23, 2023 3:18 pm, Folke Gleumes wrote: > Signed-off-by: Folke Gleumes > --- > PVE/API2/ACMEAccount.pm | 27 ++++++++++++++++++++++++++- > 1 file changed, 26 insertions(+), 1 deletion(-) > > diff --git a/PVE/API2/ACMEAccount.pm b/PVE/API2/ACMEAccount.pm > index b790843a..daae18d8 100644 > --- a/PVE/API2/ACMEAccount.pm > +++ b/PVE/API2/ACMEAccount.pm > @@ -115,6 +115,16 @@ __PACKAGE__->register_method ({ > default => $acme_default_directory_url, > optional => 1, > }), > + eab_kid => { > + type => 'string', > + description => 'Key Identifier for External Account Binding.', > + optional => 1, > + }, > + eab_hmac_key => { > + type => 'string', > + description => 'HMAC key for External Account Binding.', > + optional => 1, > + }, Nit: s/_/-/ for new parameters :) > }, > }, > returns => { > @@ -130,8 +140,15 @@ __PACKAGE__->register_method ({ > my $account_file = "${acme_account_dir}/${account_name}"; > mkdir $acme_account_dir if ! -e $acme_account_dir; > > + my $eab_kid = extract_param($param, 'eab_kid'); > + my $eab_hmac_key = extract_param($param, 'eab_hmac_key'); > + > raise_param_exc({'name' => "ACME account config file '${account_name}' already exists."}) > if -e $account_file; > + raise_param_exc({'eab_kid' => "'eab_hmac_key' must be defined if 'eab_kid' is set."}) > + if defined($eab_kid) and not defined($eab_hmac_key); > + raise_param_exc({'eab_hmac_key' => "'eab_kid' must be defined if 'eab_hmac_key' is set."}) > + if defined($eab_hmac_key) and not defined($eab_kid); these two checks can be encoded directly in the schema by adding requires => "name-of-require-parameter" to both definitions, pointing at the other one. if a caller only provides either of them and not both (or none), the schema check will error: eab_hmac_key: missing property - 'eab_kid' requires this property without needing any manual handling in the API endpoint handler sub. > > my $directory = extract_param($param, 'directory') // $acme_default_directory_url; > my $contact = $account_contact_from_param->($param); > @@ -145,7 +162,15 @@ __PACKAGE__->register_method ({ > print "Generating ACME account key..\n"; > $acme->init(4096); > print "Registering ACME account..\n"; > - eval { $acme->new_account($param->{tos_url}, contact => $contact); }; > + my $info = {contact => $contact}; > + if (defined($eab_kid) and defined($eab_hmac_key)) { > + $info->{eab} = { > + kid => $eab_kid, > + hmac_key => $eab_hmac_key > + }; > + } > + > + eval { $acme->new_account($param->{tos_url}, $info); }; if you switch this line to %$info or $info->%*, the new_account sub can still take the hash directly instead of a reference, but see comments on the proxmox-acme patch for possibly nicer signatures. > if (my $err = $@) { > unlink $account_file; > die "Registration failed: $err\n"; > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > From f.gruenbichler at proxmox.com Tue Oct 24 10:32:53 2023 From: f.gruenbichler at proxmox.com (Fabian =?iso-8859-1?q?Gr=FCnbichler?=) Date: Tue, 24 Oct 2023 10:32:53 +0200 Subject: [pve-devel] [PATCH acme 1/5] fix #4497: add support for external account bindings In-Reply-To: <20231023131808.172494-2-f.gleumes@proxmox.com> References: <20231023131808.172494-1-f.gleumes@proxmox.com> <20231023131808.172494-2-f.gleumes@proxmox.com> Message-ID: <1698133256.b0dljkont3.astroid@yuna.none> On October 23, 2023 3:18 pm, Folke Gleumes wrote: > implementation acording to rfc855 section 7.3.4 > > Signed-off-by: Folke Gleumes > --- > src/PVE/ACME.pm | 43 +++++++++++++++++++++++++++++++++++-------- > 1 file changed, 35 insertions(+), 8 deletions(-) > > diff --git a/src/PVE/ACME.pm b/src/PVE/ACME.pm > index 3f66182..f65729a 100644 > --- a/src/PVE/ACME.pm > +++ b/src/PVE/ACME.pm > @@ -7,10 +7,10 @@ use POSIX; > > use Data::Dumper; > use Date::Parse; > -use MIME::Base64 qw(encode_base64url); > +use MIME::Base64 qw(encode_base64url decode_base64); > use File::Path qw(make_path); > use JSON; > -use Digest::SHA qw(sha256 sha256_hex); > +use Digest::SHA qw(sha256 sha256_hex hmac_sha256); > > use HTTP::Request; > use LWP::UserAgent; > @@ -251,6 +251,28 @@ sub jws { > }; > } > > +# EAB signing using the HS256 alg (HMAC/SHA256). > +sub eab { > + my ($self, $eab_kid, $eab_hmac_key, $url) = @_; > + > + my $protected = { > + alg => 'HS256', > + kid => $eab_kid, > + url => $url, > + }; > + $protected = encode(tojs($protected)); > + > + my $payload = encode(tojs($self->jwk())); > + my $signdata = "$protected.$payload"; > + my $signature = encode(hmac_sha256($signdata, $eab_hmac_key)); > + > + return { > + protected => $protected, > + payload => $payload, > + signature => $signature, > + }; > +} > + > sub __get_result { > my ($resp, $code, $plain) = @_; > > @@ -300,8 +322,8 @@ sub list_methods { > } > > # return (optional) meta directory entry. > -# this is public because it might contain the ToS, which should be displayed > -# and agreed to before creating an account > +# this is public because it might contain the ToS and EAB requirements, > +# which have to be considered before creating an account > sub get_meta { > my ($self) = @_; > my $methods = $self->__get_methods(); > @@ -329,21 +351,26 @@ sub __new_account { > return $self->{account}; > } > > -# Create a new account using data in %info. > +# Create a new account using data in $info. this change (and the related ones below) are actually not required (and would be inconsistent with the signature of the other account methods here) - see comment in the patch for pve-manager. > # Optionally pass $tos_url to agree to the given Terms of Service > # POST to newAccount endpoint > # Expects a '201 Created' reply > # Saves and returns the account data > sub new_account { > - my ($self, $tos_url, %info) = @_; > + my ($self, $tos_url, $info) = @_; > my $url = $self->_method('newAccount'); > > + if ($info->{'eab'}) { > + my $eab_hmac_key = decode_base64($info->{'eab'}->{hmac_key}); > + $info->{externalAccountBinding} = $self->eab($info->{'eab'}->{kid}, $eab_hmac_key, $url); this means that `info` now contains both the binding, but also the input including the KID (okay, this is contained in the binding as well, so just duplicate info) and the HMAC key, which is supposed to be secret. granted, it is a secret given to the user by the CA over some channel, and we only send it back to the CA, but some ACME implementations might still reject the request because of the unexpected contents. and if the user ever mixes up the CAs they are talking to, they might accidentally leak the secret to the wrong entitiy. since `info` is directly translated to the new_account request contents, it might be better to pass in the EAB parameters on their own, and make this sub take my ($self, $tos_url, $eab, %info) = @_; if $eab is undef -> no EAB. if it is set, generate the binding and put it into %info for further passing to the ACME provider. alternatively, it would also work to combine $tos_url and $eab into a new $account_params or $params hash, if we think that more parameters might be added in the future, or if we plan on merging new_account and init, like we do in PMG/PBS. or, as third option, we could switch the public sub new_account to take my ($self, $tos_url, $contact, $eab) = @_; or my ($self, $tos_url, $contact, $eab, $rsa_bits) = @_; which would be more aligned with how PMG and PBS look like. almost all of the variants are a breaking change (except if we keep the signature as is, and properly extract the eab member if it is set) that require a double check for any reverse dependencies. there's at least one internal tool that uses this as well that would need to be updated, for example. > + } > + > if ($tos_url) { > $self->{tos} = $tos_url; > - $info{termsOfServiceAgreed} = JSON::true; > + $info->{termsOfServiceAgreed} = JSON::true; > } > > - return $self->__new_account(201, $url, 1, %info); > + return $self->__new_account(201, $url, 1, %{$info}); > } > > # Update existing account with new %info > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > From f.gruenbichler at proxmox.com Tue Oct 24 10:32:28 2023 From: f.gruenbichler at proxmox.com (Fabian =?iso-8859-1?q?Gr=FCnbichler?=) Date: Tue, 24 Oct 2023 10:32:28 +0200 Subject: [pve-devel] [PATCH manager 3/5] fix #4497: api/acme: deprecate tos endpoint in favor of meta In-Reply-To: <20231023131808.172494-4-f.gleumes@proxmox.com> References: <20231023131808.172494-1-f.gleumes@proxmox.com> <20231023131808.172494-4-f.gleumes@proxmox.com> Message-ID: <1698134740.taq5fv2f4f.astroid@yuna.none> On October 23, 2023 3:18 pm, Folke Gleumes wrote: > The ToS endpoint ignored data that is needed to detect if EAB needs to > be used. Instead of adding a new endpoint that does the same request, > the tos endpoint is deprecated and replaced by the meta endpoint, > that returns all information returned by the directory. not opposed to this, but we could also get away for the time being without this patch and just make users that want to use EAB to explicitly opt-in. any CA that requires EABs has to error out if an attempt to register is made without a binding. the spec also allows CAs to optionally allow bindings without requiring them (e.g., to support both short-lived auto-validated, and longer-lived manually pre-validated certs), or to ignore the EAB part of account registrations with (unwanted or invalid) EABs by just discarding the binding/not persisting it in the created account. so basically every combination of CA indicating EAB requirements and EAB being provided upon registration *might* work, except for EAB being required and not provided. > > Signed-off-by: Folke Gleumes > --- > PVE/API2/ACMEAccount.pm | 46 ++++++++++++++++++++++++++++++++++++++++- > 1 file changed, 45 insertions(+), 1 deletion(-) > > diff --git a/PVE/API2/ACMEAccount.pm b/PVE/API2/ACMEAccount.pm > index daae18d8..bfe76734 100644 > --- a/PVE/API2/ACMEAccount.pm > +++ b/PVE/API2/ACMEAccount.pm > @@ -62,6 +62,7 @@ __PACKAGE__->register_method ({ > return [ > { name => 'account' }, > { name => 'tos' }, > + { name => 'meta' }, > { name => 'directories' }, > { name => 'plugins' }, > { name => 'challenge-schema' }, > @@ -333,11 +334,12 @@ __PACKAGE__->register_method ({ > return $update_account->($param, 'deactivate', status => 'deactivated'); > }}); > > +# TODO: deprecated, remove with pve 9 > __PACKAGE__->register_method ({ > name => 'get_tos', > path => 'tos', > method => 'GET', > - description => "Retrieve ACME TermsOfService URL from CA.", > + description => "Retrieve ACME TermsOfService URL from CA. Deprecated, please use /cluster/acme/meta.", > permissions => { user => 'all' }, > parameters => { > additionalProperties => 0, > @@ -364,6 +366,48 @@ __PACKAGE__->register_method ({ > return $meta ? $meta->{termsOfService} : undef; > }}); > > +__PACKAGE__->register_method ({ > + name => 'get_meta', > + path => 'meta', > + method => 'GET', > + description => "Retrieve ACME Directory Meta Information", > + permissions => { user => 'all' }, > + parameters => { > + additionalProperties => 0, > + properties => { > + directory => get_standard_option('pve-acme-directory-url', { > + default => $acme_default_directory_url, > + optional => 1, > + }), > + }, > + }, > + returns => { > + type => 'object', > + additionalProperties => 0, > + properties => { > + termsOfService => { > + type => 'string', > + optional => 1, > + description => 'ACME TermsOfService URL.', > + }, > + externalAccountRequired => { > + type => 'boolean', > + optional => 1, > + description => 'EAB Required' > + }, the RFC has a few more, so we should maybe add the known ones here as well, and for sure set additionalProperties to 1. > + }, > + }, > + code => sub { > + my ($param) = @_; > + > + my $directory = extract_param($param, 'directory') // $acme_default_directory_url; > + > + my $acme = PVE::ACME->new(undef, $directory); > + my $meta = $acme->get_meta(); > + > + return $meta; > + }}); > + > __PACKAGE__->register_method ({ > name => 'get_directories', > path => 'directories', > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > From t.lamprecht at proxmox.com Tue Oct 24 11:07:14 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 24 Oct 2023 11:07:14 +0200 Subject: [pve-devel] [PATCH acme 1/5] fix #4497: add support for external account bindings In-Reply-To: <20231023131808.172494-2-f.gleumes@proxmox.com> References: <20231023131808.172494-1-f.gleumes@proxmox.com> <20231023131808.172494-2-f.gleumes@proxmox.com> Message-ID: mostly stylistic nits inline, but also a comment w.r.t. FWICT needles ABI breakage. Am 23/10/2023 um 15:18 schrieb Folke Gleumes: > implementation acording to rfc855 section 7.3.4 s/acording/according/ > > Signed-off-by: Folke Gleumes > --- > src/PVE/ACME.pm | 43 +++++++++++++++++++++++++++++++++++-------- > 1 file changed, 35 insertions(+), 8 deletions(-) > > @@ -251,6 +251,28 @@ sub jws { > }; > } > > +# EAB signing using the HS256 alg (HMAC/SHA256). At least write out the acronym "external account binding" out once here, or maybe expand the method name "external_account_binding", for clarity. > +sub eab { > + my ($self, $eab_kid, $eab_hmac_key, $url) = @_; > + > @@ -329,21 +351,26 @@ sub __new_account { > return $self->{account}; > } > > -# Create a new account using data in %info. > +# Create a new account using data in $info. > # Optionally pass $tos_url to agree to the given Terms of Service > # POST to newAccount endpoint > # Expects a '201 Created' reply > # Saves and returns the account data > sub new_account { > - my ($self, $tos_url, %info) = @_; > + my ($self, $tos_url, $info) = @_; This needlessly breaks older call sites though? Why not keep the plain hash here, you do not really win anything by switching to a hash ref, or? Let's avoid churn w.r.t. breaking older package dependencies, if not really required.. > my $url = $self->_method('newAccount'); > > + if ($info->{'eab'}) { nit: eab doesn't need quoting And if you keep the method signature to accept a hash (not a hash reference) here you could do something like: if (my $eab = $info{eab}) { my $eab_hmac_key = decode_base64($eab->{hmac_key}); # .. } > + my $eab_hmac_key = decode_base64($info->{'eab'}->{hmac_key}); nit: inconsistent hash key quoting in the same statement > + $info->{externalAccountBinding} = $self->eab($info->{'eab'}->{kid}, $eab_hmac_key, $url); > + } > + > if ($tos_url) { > $self->{tos} = $tos_url; > - $info{termsOfServiceAgreed} = JSON::true;> + $info->{termsOfServiceAgreed} = JSON::true; can be avoided by keeping the hash > } > > - return $self->__new_account(201, $url, 1, %info); > + return $self->__new_account(201, $url, 1, %{$info}); like above, can be avoided > } > > # Update existing account with new %info From t.lamprecht at proxmox.com Tue Oct 24 11:47:41 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 24 Oct 2023 11:47:41 +0200 Subject: [pve-devel] applied: [PATCH qemu-server] Fix: cpu hotplug feature can't be changed online In-Reply-To: <20231010153717.2282543-1-aderumier@odiso.com> References: <20231010153717.2282543-1-aderumier@odiso.com> Message-ID: <7b0c7c34-9a9c-4737-a9fe-7bf848412846@proxmox.com> Am 10/10/2023 um 17:37 schrieb Alexandre Derumier: > The cpus are passed as devices with specific id only when cpu hotplug is enable > at start. > We can't enable/disable it online or vcpu hotplug api will thrown errors > not finding core id. > > Signed-off-by: Alexandre Derumier > --- > PVE/QemuServer.pm | 3 ++- > 1 file changed, 2 insertions(+), 1 deletion(-) > > applied, thanks! From t.lamprecht at proxmox.com Tue Oct 24 11:53:33 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 24 Oct 2023 11:53:33 +0200 Subject: [pve-devel] [PATCH manager 2/3] ui: tags: prevent pasting non plain-text content In-Reply-To: References: <20231019133607.1416999-1-d.csapak@proxmox.com> <20231019133607.1416999-2-d.csapak@proxmox.com> Message-ID: <1e3aaf6e-efcf-4033-a017-ed2ee8bb8e0d@proxmox.com> Am 19/10/2023 um 15:59 schrieb Dominik Csapak: > sry disregard this patch only, that property value is not supported in firefox :( > We could set it only to "plaintext-only" for non-firefox then though? From d.csapak at proxmox.com Tue Oct 24 11:57:27 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Tue, 24 Oct 2023 11:57:27 +0200 Subject: [pve-devel] [PATCH manager 2/3] ui: tags: prevent pasting non plain-text content In-Reply-To: <1e3aaf6e-efcf-4033-a017-ed2ee8bb8e0d@proxmox.com> References: <20231019133607.1416999-1-d.csapak@proxmox.com> <20231019133607.1416999-2-d.csapak@proxmox.com> <1e3aaf6e-efcf-4033-a017-ed2ee8bb8e0d@proxmox.com> Message-ID: <562e6c04-18b1-4ef7-ae74-c14cfd4f1f9b@proxmox.com> On 10/24/23 11:53, Thomas Lamprecht wrote: > Am 19/10/2023 um 15:59 schrieb Dominik Csapak: >> sry disregard this patch only, that property value is not supported in firefox :( >> > > We could set it only to "plaintext-only" for non-firefox then though? sure not a problem, i just generally try to avoid browser specific stuff if there is a way to prevent pasting html in a sane way for all browsers i'd prefer that, but did not have the time to look into that.. From t.lamprecht at proxmox.com Tue Oct 24 12:07:10 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 24 Oct 2023 12:07:10 +0200 Subject: [pve-devel] [PATCH manager 2/3] ui: tags: prevent pasting non plain-text content In-Reply-To: <562e6c04-18b1-4ef7-ae74-c14cfd4f1f9b@proxmox.com> References: <20231019133607.1416999-1-d.csapak@proxmox.com> <20231019133607.1416999-2-d.csapak@proxmox.com> <1e3aaf6e-efcf-4033-a017-ed2ee8bb8e0d@proxmox.com> <562e6c04-18b1-4ef7-ae74-c14cfd4f1f9b@proxmox.com> Message-ID: <5db9a4ea-fa58-4ecc-afff-f808a29b3b87@proxmox.com> Am 24/10/2023 um 11:57 schrieb Dominik Csapak: > On 10/24/23 11:53, Thomas Lamprecht wrote: >> Am 19/10/2023 um 15:59 schrieb Dominik Csapak: >>> sry disregard this patch only, that property value is not supported in firefox :( >>> >> >> We could set it only to "plaintext-only" for non-firefox then though? > > sure not a problem, i just generally try to avoid browser specific stuff > > if there is a way to prevent pasting html in a sane way for all browsers > i'd prefer that, but did not have the time to look into that.. > OK, let's make "no special cases for browsers" a rule that should have as few as possible exceptions, that can only benefit users and devs reproducing issues. I subscribed to the enhancement request [0] at Mozilla's Bugzilla, where they state that it's only blocked on the spec being to vague and lacking test cases, so once it's done and released in an ESR we can switch to this unconditionally [0]: https://bugzilla.mozilla.org/show_bug.cgi?id=1291467#c22 From d.csapak at proxmox.com Tue Oct 24 12:51:39 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Tue, 24 Oct 2023 12:51:39 +0200 Subject: [pve-devel] [PATCH manager 2/3] ui: tags: prevent pasting non plain-text content In-Reply-To: <5db9a4ea-fa58-4ecc-afff-f808a29b3b87@proxmox.com> References: <20231019133607.1416999-1-d.csapak@proxmox.com> <20231019133607.1416999-2-d.csapak@proxmox.com> <1e3aaf6e-efcf-4033-a017-ed2ee8bb8e0d@proxmox.com> <562e6c04-18b1-4ef7-ae74-c14cfd4f1f9b@proxmox.com> <5db9a4ea-fa58-4ecc-afff-f808a29b3b87@proxmox.com> Message-ID: <8185c74e-d3a3-4c6b-bdfa-e4446b9aa949@proxmox.com> On 10/24/23 12:07, Thomas Lamprecht wrote: > Am 24/10/2023 um 11:57 schrieb Dominik Csapak: >> On 10/24/23 11:53, Thomas Lamprecht wrote: >>> Am 19/10/2023 um 15:59 schrieb Dominik Csapak: >>>> sry disregard this patch only, that property value is not supported in firefox :( >>>> >>> >>> We could set it only to "plaintext-only" for non-firefox then though? >> >> sure not a problem, i just generally try to avoid browser specific stuff >> >> if there is a way to prevent pasting html in a sane way for all browsers >> i'd prefer that, but did not have the time to look into that.. >> > > OK, let's make "no special cases for browsers" a rule that should have as > few as possible exceptions, that can only benefit users and devs reproducing > issues. that makes sense, i'll add a section to the javascript style guide, is that fine with you? > > I subscribed to the enhancement request [0] at Mozilla's Bugzilla, where > they state that it's only blocked on the spec being to vague and lacking test > cases, so once it's done and released in an ESR we can switch to this > unconditionally > > [0]: https://bugzilla.mozilla.org/show_bug.cgi?id=1291467#c22 i'll subscribe too, thanks for researching that From t.lamprecht at proxmox.com Tue Oct 24 12:52:23 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 24 Oct 2023 12:52:23 +0200 Subject: [pve-devel] [PATCH manager 2/3] ui: tags: prevent pasting non plain-text content In-Reply-To: <8185c74e-d3a3-4c6b-bdfa-e4446b9aa949@proxmox.com> References: <20231019133607.1416999-1-d.csapak@proxmox.com> <20231019133607.1416999-2-d.csapak@proxmox.com> <1e3aaf6e-efcf-4033-a017-ed2ee8bb8e0d@proxmox.com> <562e6c04-18b1-4ef7-ae74-c14cfd4f1f9b@proxmox.com> <5db9a4ea-fa58-4ecc-afff-f808a29b3b87@proxmox.com> <8185c74e-d3a3-4c6b-bdfa-e4446b9aa949@proxmox.com> Message-ID: Am 24/10/2023 um 12:51 schrieb Dominik Csapak: > On 10/24/23 12:07, Thomas Lamprecht wrote: >> OK, let's make "no special cases for browsers" a rule that should have as >> few as possible exceptions, that can only benefit users and devs reproducing >> issues. > > that makes sense, i'll add a section to the javascript style guide, is that > fine with you? yeah, that's a good idea. From c.heiss at proxmox.com Tue Oct 24 13:55:16 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 24 Oct 2023 13:55:16 +0200 Subject: [pve-devel] [PATCH installer v2 0/6] fix #4829: set up lower default limit for ZFS ARC in installer Message-ID: <20231024115530.1101733-1-c.heiss@proxmox.com> Fixes #4829. Introduces a new ZFS install option `arc_max` (aptly named after the corresponding module option `zfs_arc_max`). For PVE installations, this can be adjusted when creating a ZFS RAID under "Advanced Options". The default value is choosen as 10% of system memory, clamped to between 64 MiB as lower limit and 16 GiB as upper limit. For PBS and PMG, the option is (currently) hidden. If the option is set to a non-zero value, a new file /etc/modprobe.d/zfs.conf gets written during install, setting the `zfs_arc_max` module option as appropriate. Tested by installing PVE, PBS and PMG. For PVE, checked that the `zfs` module option gets correctly written & applied, the latter by looking at the output of `arc_summary`. For PBS and PMG, verified that no modprobe options file is created and the ARC size is set to default. v1: https://lists.proxmox.com/pipermail/pve-devel/2023-August/058830.html Notable changes v1 -> v2: * Rebased on latest master * Fix arc_max value set in TUI not being applied correctly Christoph Heiss (6): fix #4829: install: add new ZFS `arc_max` setup option fix #4829: proxinstall: expose new `arc_max` ZFS option for PVE installations fix #4829: test: add tests for new zfs_arc_max calculations tui: views: add optional suffix label for `NumericEditView`s fix #4829: tui: setup: add new ZFS `arc_max` option fix #4829: tui: bootdisk: expose new `arc_max` ZFS option for PVE installations Makefile | 3 + Proxmox/Install.pm | 4 + Proxmox/Install/Config.pm | 1 + Proxmox/Install/RunEnv.pm | 38 +++++++ debian/control | 1 + proxinstall | 15 +++ proxmox-tui-installer/src/main.rs | 2 +- proxmox-tui-installer/src/options.rs | 50 +++++++++- proxmox-tui-installer/src/setup.rs | 2 + proxmox-tui-installer/src/views/bootdisk.rs | 61 +++++++++--- proxmox-tui-installer/src/views/mod.rs | 104 +++++++++++++++----- test/Makefile | 10 ++ test/zfs-arc-max.pl | 81 +++++++++++++++ 13 files changed, 329 insertions(+), 43 deletions(-) create mode 100644 test/Makefile create mode 100755 test/zfs-arc-max.pl -- 2.41.0 From c.heiss at proxmox.com Tue Oct 24 13:55:17 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 24 Oct 2023 13:55:17 +0200 Subject: [pve-devel] [PATCH installer v2 1/6] fix #4829: install: add new ZFS `arc_max` setup option In-Reply-To: <20231024115530.1101733-1-c.heiss@proxmox.com> References: <20231024115530.1101733-1-c.heiss@proxmox.com> Message-ID: <20231024115530.1101733-2-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- Changes v1 -> v2: * No changes Proxmox/Install.pm | 4 ++++ Proxmox/Install/Config.pm | 1 + Proxmox/Install/RunEnv.pm | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/Proxmox/Install.pm b/Proxmox/Install.pm index 1117fc4..0c3541f 100644 --- a/Proxmox/Install.pm +++ b/Proxmox/Install.pm @@ -1141,6 +1141,10 @@ _EOD file_write_all("$targetdir/etc/kernel/cmdline", "root=ZFS=$zfs_pool_name/ROOT/$zfs_root_volume_name boot=zfs\n"); + my $arc_max = Proxmox::Install::RunEnv::clamp_zfs_arc_max( + Proxmox::Install::Config::get_zfs_opt('arc_max')) * 1024 * 1024; + file_write_all("$targetdir/etc/modprobe.d/zfs.conf", "options zfs zfs_arc_max=$arc_max\n") + if $arc_max > 0; } diversion_remove($targetdir, "/usr/sbin/update-grub"); diff --git a/Proxmox/Install/Config.pm b/Proxmox/Install/Config.pm index 024f62a..f12ae67 100644 --- a/Proxmox/Install/Config.pm +++ b/Proxmox/Install/Config.pm @@ -72,6 +72,7 @@ my sub init_cfg { compress => 'on', checksum => 'on', copies => 1, + arc_max => Proxmox::Install::RunEnv::default_zfs_arc_max(), }, # TODO: single disk selection config target_hd => undef, diff --git a/Proxmox/Install/RunEnv.pm b/Proxmox/Install/RunEnv.pm index c9d788b..9236030 100644 --- a/Proxmox/Install/RunEnv.pm +++ b/Proxmox/Install/RunEnv.pm @@ -301,6 +301,44 @@ sub query_installation_environment : prototype() { return $output; } +our $ZFS_ARC_MIN_SIZE = 64; # MiB + +# Calculates the default upper limit for the ZFS ARC size. +# See also https://bugzilla.proxmox.com/show_bug.cgi?id=4829 and +# https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max +sub default_zfs_arc_max { + # Use ZFS default on non-PVE + return 0 if Proxmox::Install::ISOEnv::get('product') ne 'pve'; + + my $max = 16 * 1024; # 16 GiB + + my $rounded = int(sprintf('%.0f', get('total_memory') / 10)); + if ($rounded > $max) { + return $max; + } elsif ($rounded < $ZFS_ARC_MIN_SIZE) { + return $ZFS_ARC_MIN_SIZE; + } + + return $rounded; +} + +# Clamps the provided ZFS arc_max value to the accepted bounds. See also +# https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max +sub clamp_zfs_arc_max { + my ($value) = @_; + + return $value if $value == 0; + + my $total_mem = get('total_memory'); + if ($value > $total_mem) { + return $total_mem; + } elsif ($value < $ZFS_ARC_MIN_SIZE) { + return $ZFS_ARC_MIN_SIZE; + } + + return $value; +} + my $_env = undef; sub get { my ($k) = @_; -- 2.42.0 From c.heiss at proxmox.com Tue Oct 24 13:55:20 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 24 Oct 2023 13:55:20 +0200 Subject: [pve-devel] [PATCH installer v2 4/6] tui: views: add optional suffix label for `NumericEditView`s In-Reply-To: <20231024115530.1101733-1-c.heiss@proxmox.com> References: <20231024115530.1101733-1-c.heiss@proxmox.com> Message-ID: <20231024115530.1101733-5-c.heiss@proxmox.com> Most of the churn here is due to changing the inner view from an `EditView` to a `LinearLayout`. Also prompted the introduction of two small helpers .inner() and .inner_mut() to simplify things everywhere else in the view. Signed-off-by: Christoph Heiss --- Changes v1 -> v2: * No changes proxmox-tui-installer/src/views/mod.rs | 104 +++++++++++++++++++------ 1 file changed, 82 insertions(+), 22 deletions(-) diff --git a/proxmox-tui-installer/src/views/mod.rs b/proxmox-tui-installer/src/views/mod.rs index aa24fa4..e997968 100644 --- a/proxmox-tui-installer/src/views/mod.rs +++ b/proxmox-tui-installer/src/views/mod.rs @@ -19,16 +19,37 @@ mod timezone; pub use timezone::*; pub struct NumericEditView { - view: EditView, + view: LinearLayout, max_value: Option, max_content_width: Option, allow_empty: bool, } impl NumericEditView { + /// Creates a new [`NumericEditView`], with the value set to `0`. pub fn new() -> Self { + let view = LinearLayout::horizontal().child(EditView::new().content("0").full_width()); + + Self { + view, + max_value: None, + max_content_width: None, + allow_empty: false, + } + } + + /// Creates a new [`NumericEditView`], with the value set to `0` and a label to the right of it + /// with the given content, separated by a space. + /// + /// # Arguments + /// * `suffix` - Content for the label to the right of it. + pub fn new_with_suffix(suffix: &str) -> Self { + let view = LinearLayout::horizontal() + .child(EditView::new().content("0").full_width()) + .child(TextView::new(format!(" {suffix}"))); + Self { - view: EditView::new().content("0"), + view, max_value: None, max_content_width: None, allow_empty: false, @@ -42,7 +63,7 @@ impl NumericEditView { pub fn max_content_width(mut self, width: usize) -> Self { self.max_content_width = Some(width); - self.view.set_max_content_width(self.max_content_width); + self.inner_mut().set_max_content_width(Some(width)); self } @@ -50,24 +71,25 @@ impl NumericEditView { self.allow_empty = value; if value { - self.view = EditView::new(); + *self.inner_mut() = EditView::new(); } else { - self.view = EditView::new().content("0"); + *self.inner_mut() = EditView::new().content("0"); } - self.view.set_max_content_width(self.max_content_width); + let max_content_width = self.max_content_width; + self.inner_mut().set_max_content_width(max_content_width); self } pub fn get_content(&self) -> Result::Err> { assert!(!self.allow_empty); - self.view.get_content().parse() + self.inner().get_content().parse() } pub fn get_content_maybe(&self) -> Option::Err>> { - let content = self.view.get_content(); + let content = self.inner().get_content(); if !content.is_empty() { - Some(self.view.get_content().parse()) + Some(self.inner().get_content().parse()) } else { None } @@ -83,7 +105,7 @@ impl NumericEditView { if let Ok(val) = self.get_content() { if result.is_consumed() && val > max { // Restore the original value, before the insert - let cb = self.view.set_content((*original).clone()); + let cb = self.inner_mut().set_content((*original).clone()); return EventResult::with_cb_once(move |siv| { result.process(siv); cb(siv); @@ -94,16 +116,54 @@ impl NumericEditView { result } + + /// Provides an immutable reference to the inner [`EditView`]. + fn inner(&self) -> &EditView { + // Safety: Invariant; first child must always exist and be a `EditView` + self.view + .get_child(0) + .unwrap() + .downcast_ref::>() + .unwrap() + .get_inner() + } + + /// Provides a mutable reference to the inner [`EditView`]. + fn inner_mut(&mut self) -> &mut EditView { + // Safety: Invariant; first child must always exist and be a `EditView` + self.view + .get_child_mut(0) + .unwrap() + .downcast_mut::>() + .unwrap() + .get_inner_mut() + } + + /// Sets the content of the inner [`EditView`]. This correctly swaps out the content without + /// modifying the [`EditView`] in any way. + /// + /// Chainable variant. + /// + /// # Arguments + /// * `content` - New, stringified content for the inner [`EditView`]. Must be a valid value + /// according to the containet type `T`. + fn content_inner(mut self, content: &str) -> Self { + let mut inner = EditView::new(); + std::mem::swap(self.inner_mut(), &mut inner); + inner = inner.content(content); + std::mem::swap(self.inner_mut(), &mut inner); + self + } } pub type FloatEditView = NumericEditView; pub type IntegerEditView = NumericEditView; impl ViewWrapper for FloatEditView { - cursive::wrap_impl!(self.view: EditView); + cursive::wrap_impl!(self.view: LinearLayout); fn wrap_on_event(&mut self, event: Event) -> EventResult { - let original = self.view.get_content(); + let original = self.inner_mut().get_content(); let has_decimal_place = original.find('.').is_some(); @@ -114,13 +174,13 @@ impl ViewWrapper for FloatEditView { }; let decimal_places = self - .view + .inner_mut() .get_content() .split_once('.') .map(|(_, s)| s.len()) .unwrap_or_default(); if decimal_places > 2 { - let cb = self.view.set_content((*original).clone()); + let cb = self.inner_mut().set_content((*original).clone()); return EventResult::with_cb_once(move |siv| { result.process(siv); cb(siv); @@ -132,17 +192,17 @@ impl ViewWrapper for FloatEditView { } impl FloatEditView { - pub fn content(mut self, content: f64) -> Self { - self.view = self.view.content(format!("{:.2}", content)); - self + /// Sets the value of the [`FloatEditView`]. + pub fn content(self, content: f64) -> Self { + self.content_inner(&format!("{:.2}", content)) } } impl ViewWrapper for IntegerEditView { - cursive::wrap_impl!(self.view: EditView); + cursive::wrap_impl!(self.view: LinearLayout); fn wrap_on_event(&mut self, event: Event) -> EventResult { - let original = self.view.get_content(); + let original = self.inner_mut().get_content(); let result = match event { // Drop all other characters than numbers; allow dots if not set to integer-only @@ -155,9 +215,9 @@ impl ViewWrapper for IntegerEditView { } impl IntegerEditView { - pub fn content(mut self, content: usize) -> Self { - self.view = self.view.content(content.to_string()); - self + /// Sets the value of the [`IntegerEditView`]. + pub fn content(self, content: usize) -> Self { + self.content_inner(&content.to_string()) } } -- 2.42.0 From c.heiss at proxmox.com Tue Oct 24 13:55:21 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 24 Oct 2023 13:55:21 +0200 Subject: [pve-devel] [PATCH installer v2 5/6] fix #4829: tui: setup: add new ZFS `arc_max` option In-Reply-To: <20231024115530.1101733-1-c.heiss@proxmox.com> References: <20231024115530.1101733-1-c.heiss@proxmox.com> Message-ID: <20231024115530.1101733-6-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- Changes v1 -> v2: * Updated comment for ZfsBootdiskOptions::defaults_from() accordingly proxmox-tui-installer/src/options.rs | 52 +++++++++++++++++++-- proxmox-tui-installer/src/setup.rs | 2 + proxmox-tui-installer/src/views/bootdisk.rs | 8 +++- 3 files changed, 56 insertions(+), 6 deletions(-) diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs index abc8c7b..fbed8d9 100644 --- a/proxmox-tui-installer/src/options.rs +++ b/proxmox-tui-installer/src/options.rs @@ -1,7 +1,7 @@ use std::net::{IpAddr, Ipv4Addr}; use std::{cmp, fmt}; -use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo}; +use crate::setup::{LocaleInfo, NetworkInfo, ProxmoxProduct, RuntimeInfo}; use crate::utils::{CidrAddress, Fqdn}; use crate::SummaryOption; @@ -190,21 +190,23 @@ pub struct ZfsBootdiskOptions { pub compress: ZfsCompressOption, pub checksum: ZfsChecksumOption, pub copies: usize, + pub arc_max: usize, pub disk_size: f64, pub selected_disks: Vec, } impl ZfsBootdiskOptions { - /// This panics if the provided slice is empty. - pub fn defaults_from(disks: &[Disk]) -> Self { - let disk = &disks[0]; + /// Panics if the disk list is empty. + pub fn defaults_from(runinfo: &RuntimeInfo) -> Self { + let disk = &runinfo.disks[0]; Self { ashift: 12, compress: ZfsCompressOption::default(), checksum: ZfsChecksumOption::default(), copies: 1, + arc_max: default_zfs_arc_max(crate::current_product(), runinfo.total_memory), disk_size: disk.size, - selected_disks: (0..disks.len()).collect(), + selected_disks: (0..runinfo.disks.len()).collect(), } } } @@ -451,6 +453,24 @@ impl InstallerOptions { } } +/// Calculates the default upper limit for the ZFS ARC size. +/// See also and +/// https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max +/// +/// # Arguments +/// * `product` - The product to be installed +/// * `total_memory` - Total memory installed in the system, in MiB +fn default_zfs_arc_max(product: ProxmoxProduct, total_memory: usize) -> usize { + if product != ProxmoxProduct::PVE { + // Use ZFS default for non-PVE + 0 + } else { + ((total_memory as f64) / 10.) + .round() + .clamp(64., 16. * 1024.) as usize + } +} + #[cfg(test)] mod tests { use super::*; @@ -460,6 +480,28 @@ mod tests { }; use std::{collections::HashMap, path::PathBuf}; + #[test] + fn zfs_arc_limit() { + const TESTS: &[(usize, usize)] = &[ + (16, 64), // at least 64 MiB + (1024, 102), + (4 * 1024, 410), + (8 * 1024, 819), + (150 * 1024, 15360), + (160 * 1024, 16384), + (1024 * 1024, 16384), // maximum of 16 GiB + ]; + + for (total_memory, expected) in TESTS { + assert_eq!( + default_zfs_arc_max(ProxmoxProduct::PVE, *total_memory), + *expected + ); + assert_eq!(default_zfs_arc_max(ProxmoxProduct::PBS, *total_memory), 0); + assert_eq!(default_zfs_arc_max(ProxmoxProduct::PMG, *total_memory), 0); + } + } + fn fill_setup_info() { crate::init_setup_info(SetupInfo { config: ProductConfig { diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs index 7238131..25504fe 100644 --- a/proxmox-tui-installer/src/setup.rs +++ b/proxmox-tui-installer/src/setup.rs @@ -114,6 +114,7 @@ struct InstallZfsOption { #[serde(serialize_with = "serialize_as_display")] checksum: ZfsChecksumOption, copies: usize, + arc_max: usize, } impl From for InstallZfsOption { @@ -123,6 +124,7 @@ impl From for InstallZfsOption { compress: opts.compress, checksum: opts.checksum, copies: opts.copies, + arc_max: opts.arc_max, } } } diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index dbd13ea..e8322db 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -139,6 +139,11 @@ impl AdvancedBootdiskOptionsView { } fn fstype_on_submit(siv: &mut Cursive, disks: &[Disk], fstype: &FsType) { + let runinfo = siv + .user_data::() + .map(|state| state.runtime_info.clone()) + .unwrap(); + siv.call_on_name("advanced-bootdisk-options-dialog", |view: &mut Dialog| { if let Some(AdvancedBootdiskOptionsView { view }) = view.get_content_mut().downcast_mut() @@ -150,7 +155,7 @@ impl AdvancedBootdiskOptionsView { )), FsType::Zfs(_) => view.add_child(ZfsBootdiskOptionsView::new( disks, - &ZfsBootdiskOptions::defaults_from(disks), + &ZfsBootdiskOptions::defaults_from(&runinfo), )), FsType::Btrfs(_) => view.add_child(BtrfsBootdiskOptionsView::new( disks, @@ -541,6 +546,7 @@ impl ZfsBootdiskOptionsView { compress, checksum, copies, + arc_max: 0, // use built-in ZFS default value disk_size, selected_disks, }, -- 2.42.0 From c.heiss at proxmox.com Tue Oct 24 13:55:18 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 24 Oct 2023 13:55:18 +0200 Subject: [pve-devel] [PATCH installer v2 2/6] fix #4829: proxinstall: expose new `arc_max` ZFS option for PVE installations In-Reply-To: <20231024115530.1101733-1-c.heiss@proxmox.com> References: <20231024115530.1101733-1-c.heiss@proxmox.com> Message-ID: <20231024115530.1101733-3-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- Changes v1 -> v2: * No changes proxinstall | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/proxinstall b/proxinstall index 64c8bab..f1a3c02 100755 --- a/proxinstall +++ b/proxinstall @@ -1162,6 +1162,21 @@ my $create_raid_advanced_grid = sub { $spinbutton_copies->set_value($copies); push @$labeled_widgets, "copies", $spinbutton_copies; + if ($iso_env->{product} eq 'pve') { + my $total_memory = Proxmox::Install::RunEnv::get('total_memory'); + + my $spinbutton_arc_max = Gtk3::SpinButton->new_with_range( + $Proxmox::Install::RunEnv::ZFS_ARC_MIN_SIZE, $total_memory, 1); + $spinbutton_arc_max->set_tooltip_text('Maximum ARC size in megabytes'); + $spinbutton_arc_max->signal_connect('value-changed' => sub { + my $w = shift; + Proxmox::Install::Config::set_zfs_opt('arc_max', $w->get_value_as_int()); + }); + my $arc_max = Proxmox::Install::Config::get_zfs_opt('arc_max'); + $spinbutton_arc_max->set_value($arc_max); + push @$labeled_widgets, "ARC max size", $spinbutton_arc_max; + } + push @$labeled_widgets, "hdsize", $hdsize_btn; return $create_label_widget_grid->($labeled_widgets);; }; -- 2.42.0 From c.heiss at proxmox.com Tue Oct 24 13:55:19 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 24 Oct 2023 13:55:19 +0200 Subject: [pve-devel] [PATCH installer v2 3/6] fix #4829: test: add tests for new zfs_arc_max calculations In-Reply-To: <20231024115530.1101733-1-c.heiss@proxmox.com> References: <20231024115530.1101733-1-c.heiss@proxmox.com> Message-ID: <20231024115530.1101733-4-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- Changes v1 -> v2: * No changes Makefile | 3 ++ debian/control | 1 + test/Makefile | 10 ++++++ test/zfs-arc-max.pl | 81 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 test/Makefile create mode 100755 test/zfs-arc-max.pl diff --git a/Makefile b/Makefile index 111fe4b..13a2b81 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ $(BUILDDIR): proxmox-low-level-installer \ proxmox-tui-installer/ \ spice-vdagent.sh \ + test/ \ unconfigured.sh \ xinitrc \ $@.tmp @@ -75,7 +76,9 @@ $(DSC): $(BUILDDIR) sbuild: $(DSC) sbuild $(DSC) +.PHONY: test test: + $(MAKE) -C test check $(CARGO) test --workspace $(CARGO_BUILD_ARGS) DESTDIR= diff --git a/debian/control b/debian/control index 3d13019..d77b12a 100644 --- a/debian/control +++ b/debian/control @@ -11,6 +11,7 @@ Build-Depends: cargo:native, librust-regex-1+default-dev (>= 1.7~~), librust-serde-1+default-dev, librust-serde-json-1+default-dev, + libtest-mockmodule-perl, perl, rustc:native, Standards-Version: 4.5.1 diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..fb80fc4 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,10 @@ +all: + +export PERLLIB=.. + +.PHONY: check +check: test-zfs-arc-max + +.PHONY: test-zfs-arc-max +test-zfs-arc-max: + ./zfs-arc-max.pl diff --git a/test/zfs-arc-max.pl b/test/zfs-arc-max.pl new file mode 100755 index 0000000..74cb9b5 --- /dev/null +++ b/test/zfs-arc-max.pl @@ -0,0 +1,81 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; +use Test::MockModule qw(strict); + +my $proxmox_install_runenv = Test::MockModule->new('Proxmox::Install::RunEnv'); +my $proxmox_install_isoenv = Test::MockModule->new('Proxmox::Install::ISOEnv'); + +sub mock_product { + my ($product) = @_; + + $proxmox_install_isoenv->redefine( + get => sub { + my ($k) = @_; + return $product if $k eq 'product'; + die "iso environment key $k not mocked!\n"; + }, + ); +} + +my %default_tests = ( + 16 => 64, # at least 64 MiB + 1024 => 102, + 4 * 1024 => 410, + 8 * 1024 => 819, + 150 * 1024 => 15360, + 160 * 1024 => 16384, + 1024 * 1024 => 16384, # maximum of 16 GiB +); + +while (my ($total_mem, $expected) = each %default_tests) { + $proxmox_install_runenv->redefine( + get => sub { + my ($k) = @_; + return $total_mem if $k eq 'total_memory'; + die "runtime environment key $k not mocked!\n"; + }, + ); + + mock_product('pve'); + is(Proxmox::Install::RunEnv::default_zfs_arc_max(), $expected, + "$expected MiB should be zfs_arc_max for PVE with $total_mem MiB system memory"); + + mock_product('pbs'); + is(Proxmox::Install::RunEnv::default_zfs_arc_max(), 0, + "zfs_arc_max should default to `0` for PBS with $total_mem MiB system memory"); + + mock_product('pmg'); + is(Proxmox::Install::RunEnv::default_zfs_arc_max(), 0, + "zfs_arc_max should default to `0` for PMG with $total_mem MiB system memory"); +} + +my @clamp_tests = ( + # input, total system memory, expected + [ 0, 4 * 1024, 0 ], + [ 16, 4 * 1024, 64 ], + [ 4 * 1024, 4 * 1024, 4 * 1024 ], + [ 4 * 1024, 6 * 1024, 4 * 1024 ], + [ 8 * 1024, 4 * 1024, 4 * 1024 ], +); + +mock_product('pve'); +foreach (@clamp_tests) { + my ($input, $total_mem, $expected) = @$_; + + $proxmox_install_runenv->redefine( + get => sub { + my ($k) = @_; + return $total_mem if $k eq 'total_memory'; + die "runtime environment key $k not mocked!\n"; + }, + ); + + is(Proxmox::Install::RunEnv::clamp_zfs_arc_max($input), $expected, + "$input MiB zfs_arc_max should be clamped to $expected MiB with $total_mem MiB system memory"); +} + +done_testing(); -- 2.42.0 From c.heiss at proxmox.com Tue Oct 24 13:55:22 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 24 Oct 2023 13:55:22 +0200 Subject: [pve-devel] [PATCH installer v2 6/6] fix #4829: tui: bootdisk: expose new `arc_max` ZFS option for PVE installations In-Reply-To: <20231024115530.1101733-1-c.heiss@proxmox.com> References: <20231024115530.1101733-1-c.heiss@proxmox.com> Message-ID: <20231024115530.1101733-7-c.heiss@proxmox.com> To set the maximum value for arc_max accordingly, simply pass down `RuntimeInfo` directly instead of the disks array to the views. Signed-off-by: Christoph Heiss --- Changes v1 -> v2: * Fix ZFS_ARC_MIN_SIZE to be MiB rather than bytes proxmox-tui-installer/src/main.rs | 2 +- proxmox-tui-installer/src/views/bootdisk.rs | 55 +++++++++++++++------ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index 0a61e39..44d44c0 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -431,7 +431,7 @@ fn bootdisk_dialog(siv: &mut Cursive) -> InstallerView { InstallerView::new( &state, - BootdiskOptionsView::new(siv, &state.runtime_info.disks, &state.options.bootdisk) + BootdiskOptionsView::new(siv, &state.runtime_info, &state.options.bootdisk) .with_name("bootdisk-options"), Box::new(|siv| { let options = siv.call_on_name("bootdisk-options", BootdiskOptionsView::get_values); diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index e8322db..f7fbea3 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -16,10 +16,12 @@ use crate::{ FsType, LvmBootdiskOptions, ZfsBootdiskOptions, ZfsRaidLevel, FS_TYPES, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS, }, - setup::BootType, + setup::{BootType, RuntimeInfo}, }; use crate::{setup::ProxmoxProduct, InstallerState}; +const ZFS_ARC_MIN_SIZE: usize = 64; // MiB + pub struct BootdiskOptionsView { view: LinearLayout, advanced_options: Rc>, @@ -27,13 +29,13 @@ pub struct BootdiskOptionsView { } impl BootdiskOptionsView { - pub fn new(siv: &mut Cursive, disks: &[Disk], options: &BootdiskOptions) -> Self { + pub fn new(siv: &mut Cursive, runinfo: &RuntimeInfo, options: &BootdiskOptions) -> Self { let bootdisk_form = FormView::new() .child( "Target harddisk", SelectView::new() .popup() - .with_all(disks.iter().map(|d| (d.to_string(), d.clone()))), + .with_all(runinfo.disks.iter().map(|d| (d.to_string(), d.clone()))), ) .with_name("bootdisk-options-target-disk"); @@ -42,10 +44,10 @@ impl BootdiskOptionsView { let advanced_button = LinearLayout::horizontal() .child(DummyView.full_width()) .child(Button::new("Advanced options", { - let disks = disks.to_owned(); + let runinfo = runinfo.clone(); let options = advanced_options.clone(); move |siv| { - siv.add_layer(advanced_options_view(&disks, options.clone())); + siv.add_layer(advanced_options_view(&runinfo, options.clone())); } })); @@ -95,7 +97,7 @@ struct AdvancedBootdiskOptionsView { } impl AdvancedBootdiskOptionsView { - fn new(disks: &[Disk], options: &BootdiskOptions) -> Self { + fn new(runinfo: &RuntimeInfo, options: &BootdiskOptions) -> Self { let enable_btrfs = crate::setup_info().config.enable_btrfs; let filter_btrfs = |fstype: &&FsType| -> bool { enable_btrfs || !fstype.is_btrfs() }; @@ -116,7 +118,7 @@ impl AdvancedBootdiskOptionsView { .unwrap_or_default(), ) .on_submit({ - let disks = disks.to_owned(); + let disks = runinfo.disks.to_owned(); move |siv, fstype| Self::fstype_on_submit(siv, &disks, fstype) }); @@ -128,10 +130,10 @@ impl AdvancedBootdiskOptionsView { match &options.advanced { AdvancedBootdiskOptions::Lvm(lvm) => view.add_child(LvmBootdiskOptionsView::new(lvm)), AdvancedBootdiskOptions::Zfs(zfs) => { - view.add_child(ZfsBootdiskOptionsView::new(disks, zfs)) + view.add_child(ZfsBootdiskOptionsView::new(runinfo, zfs)) } AdvancedBootdiskOptions::Btrfs(btrfs) => { - view.add_child(BtrfsBootdiskOptionsView::new(disks, btrfs)) + view.add_child(BtrfsBootdiskOptionsView::new(&runinfo.disks, btrfs)) } }; @@ -154,7 +156,7 @@ impl AdvancedBootdiskOptionsView { &LvmBootdiskOptions::defaults_from(&disks[0]), )), FsType::Zfs(_) => view.add_child(ZfsBootdiskOptionsView::new( - disks, + &runinfo, &ZfsBootdiskOptions::defaults_from(&runinfo), )), FsType::Btrfs(_) => view.add_child(BtrfsBootdiskOptionsView::new( @@ -491,7 +493,9 @@ struct ZfsBootdiskOptionsView { impl ZfsBootdiskOptionsView { // TODO: Re-apply previous disk selection from `options` correctly - fn new(disks: &[Disk], options: &ZfsBootdiskOptions) -> Self { + fn new(runinfo: &RuntimeInfo, options: &ZfsBootdiskOptions) -> Self { + let is_pve = crate::setup_info().config.product == ProxmoxProduct::PVE; + let inner = FormView::new() .child("ashift", IntegerEditView::new().content(options.ashift)) .child( @@ -519,9 +523,16 @@ impl ZfsBootdiskOptionsView { ), ) .child("copies", IntegerEditView::new().content(options.copies)) + .child_conditional( + is_pve, + "ARC max size", + IntegerEditView::new_with_suffix("MiB") + .max_value(runinfo.total_memory) + .content(options.arc_max), + ) .child("hdsize", DiskSizeEditView::new().content(options.disk_size)); - let view = MultiDiskOptionsView::new(disks, &options.selected_disks, inner) + let view = MultiDiskOptionsView::new(&runinfo.disks, &options.selected_disks, inner) .top_panel(TextView::new( "ZFS is not compatible with hardware RAID controllers, for details see the documentation." ).center()); @@ -532,12 +543,21 @@ impl ZfsBootdiskOptionsView { fn get_values(&mut self) -> Option<(Vec, ZfsBootdiskOptions)> { let (disks, selected_disks) = self.view.get_disks_and_selection()?; let view = self.view.inner_mut()?; + let has_arc_max = view.len() >= 6; + let disk_size_index = if has_arc_max { 5 } else { 4 }; let ashift = view.get_value::(0)?; let compress = view.get_value::, _>(1)?; let checksum = view.get_value::, _>(2)?; let copies = view.get_value::(3)?; - let disk_size = view.get_value::(4)?; + let disk_size = view.get_value::(disk_size_index)?; + + let arc_max = if has_arc_max { + view.get_value::(4)? + .max(ZFS_ARC_MIN_SIZE) + } else { + 0 // use built-in ZFS default value + }; Some(( disks, @@ -546,7 +566,7 @@ impl ZfsBootdiskOptionsView { compress, checksum, copies, - arc_max: 0, // use built-in ZFS default value + arc_max, disk_size, selected_disks, }, @@ -558,9 +578,12 @@ impl ViewWrapper for ZfsBootdiskOptionsView { cursive::wrap_impl!(self.view: MultiDiskOptionsView); } -fn advanced_options_view(disks: &[Disk], options: Rc>) -> impl View { +fn advanced_options_view( + runinfo: &RuntimeInfo, + options: Rc>, +) -> impl View { Dialog::around(AdvancedBootdiskOptionsView::new( - disks, + runinfo, &(*options).borrow(), )) .title("Advanced bootdisk options") -- 2.42.0 From gilberto.nunes32 at gmail.com Tue Oct 24 13:59:36 2023 From: gilberto.nunes32 at gmail.com (Gilberto Ferreira) Date: Tue, 24 Oct 2023 08:59:36 -0300 Subject: [pve-devel] [PATCH installer v2 1/6] fix #4829: install: add new ZFS `arc_max` setup option In-Reply-To: <20231024115530.1101733-2-c.heiss@proxmox.com> References: <20231024115530.1101733-1-c.heiss@proxmox.com> <20231024115530.1101733-2-c.heiss@proxmox.com> Message-ID: Hi there. Now, that's a good option in the installer. I wonder if this option will be available post-intall, like some box in the ZFS manager from WEB GUI! That's would be nice. Em ter., 24 de out. de 2023 ?s 08:55, Christoph Heiss escreveu: > Signed-off-by: Christoph Heiss > --- > Changes v1 -> v2: > * No changes > > Proxmox/Install.pm | 4 ++++ > Proxmox/Install/Config.pm | 1 + > Proxmox/Install/RunEnv.pm | 38 ++++++++++++++++++++++++++++++++++++++ > 3 files changed, 43 insertions(+) > > diff --git a/Proxmox/Install.pm b/Proxmox/Install.pm > index 1117fc4..0c3541f 100644 > --- a/Proxmox/Install.pm > +++ b/Proxmox/Install.pm > @@ -1141,6 +1141,10 @@ _EOD > > file_write_all("$targetdir/etc/kernel/cmdline", > "root=ZFS=$zfs_pool_name/ROOT/$zfs_root_volume_name boot=zfs\n"); > > + my $arc_max = Proxmox::Install::RunEnv::clamp_zfs_arc_max( > + Proxmox::Install::Config::get_zfs_opt('arc_max')) * 1024 * > 1024; > + file_write_all("$targetdir/etc/modprobe.d/zfs.conf", "options > zfs zfs_arc_max=$arc_max\n") > + if $arc_max > 0; > } > > diversion_remove($targetdir, "/usr/sbin/update-grub"); > diff --git a/Proxmox/Install/Config.pm b/Proxmox/Install/Config.pm > index 024f62a..f12ae67 100644 > --- a/Proxmox/Install/Config.pm > +++ b/Proxmox/Install/Config.pm > @@ -72,6 +72,7 @@ my sub init_cfg { > compress => 'on', > checksum => 'on', > copies => 1, > + arc_max => Proxmox::Install::RunEnv::default_zfs_arc_max(), > }, > # TODO: single disk selection config > target_hd => undef, > diff --git a/Proxmox/Install/RunEnv.pm b/Proxmox/Install/RunEnv.pm > index c9d788b..9236030 100644 > --- a/Proxmox/Install/RunEnv.pm > +++ b/Proxmox/Install/RunEnv.pm > @@ -301,6 +301,44 @@ sub query_installation_environment : prototype() { > return $output; > } > > +our $ZFS_ARC_MIN_SIZE = 64; # MiB > + > +# Calculates the default upper limit for the ZFS ARC size. > +# See also https://bugzilla.proxmox.com/show_bug.cgi?id=4829 and > +# > https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max > +sub default_zfs_arc_max { > + # Use ZFS default on non-PVE > + return 0 if Proxmox::Install::ISOEnv::get('product') ne 'pve'; > + > + my $max = 16 * 1024; # 16 GiB > + > + my $rounded = int(sprintf('%.0f', get('total_memory') / 10)); > + if ($rounded > $max) { > + return $max; > + } elsif ($rounded < $ZFS_ARC_MIN_SIZE) { > + return $ZFS_ARC_MIN_SIZE; > + } > + > + return $rounded; > +} > + > +# Clamps the provided ZFS arc_max value to the accepted bounds. See also > +# > https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max > +sub clamp_zfs_arc_max { > + my ($value) = @_; > + > + return $value if $value == 0; > + > + my $total_mem = get('total_memory'); > + if ($value > $total_mem) { > + return $total_mem; > + } elsif ($value < $ZFS_ARC_MIN_SIZE) { > + return $ZFS_ARC_MIN_SIZE; > + } > + > + return $value; > +} > + > my $_env = undef; > sub get { > my ($k) = @_; > -- > 2.42.0 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > From t.lamprecht at proxmox.com Tue Oct 24 14:02:12 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 24 Oct 2023 14:02:12 +0200 Subject: [pve-devel] applied: [PATCH widget-toolkit] text field: add trimValue config Message-ID: <20231024120212.1860444-1-t.lamprecht@proxmox.com> Inspired by a recent bug detected in the subscription key field, where a trailing white space caused verification issues. We might even enable the trimming by default, after checking call sites that is ? most often one wants to trim the text to be submitted Signed-off-by: Thomas Lamprecht --- had that laying around since a while and figured why not, it's opt-in for now anyway. src/form/TextField.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/form/TextField.js b/src/form/TextField.js index 56e5976..7ee95c4 100644 --- a/src/form/TextField.js +++ b/src/form/TextField.js @@ -6,6 +6,8 @@ Ext.define('Proxmox.form.field.Textfield', { skipEmptyText: true, deleteEmpty: false, + + trimValue: false, }, getSubmitData: function() { @@ -29,6 +31,9 @@ Ext.define('Proxmox.form.field.Textfield', { let me = this; let value = this.processRawValue(this.getRawValue()); + if (me.getTrimValue() && typeof value === 'string') { + value = value.trim(); + } if (value !== '') { return value; } -- 2.39.2 From alexandre.derumier at groupe-cyllene.com Tue Oct 24 14:20:46 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Tue, 24 Oct 2023 12:20:46 +0000 Subject: [pve-devel] [PATCH v4 qemu-server 2/2] remote-migration: add target-cpu && target-reboot params In-Reply-To: <016bd4b8-7502-48fe-9208-a075e8aea02b@proxmox.com> References: <20230928144556.2023558-1-aderumier@odiso.com> <20230928144556.2023558-3-aderumier@odiso.com> <5ecfa7d0-4525-5f1e-75a2-a6ae1a93356b@proxmox.com> <73e0a3a6-f978-ac24-5f6b-16af759ee209@proxmox.com> <016bd4b8-7502-48fe-9208-a075e8aea02b@proxmox.com> Message-ID: <283ff207e6065f0ae178410bfa391e9a5369924f.camel@groupe-cyllene.com> >>So I think the best way for now is to restart the target vm. >> >>Sure! Going with that is a much cleaner approach then. I'll try to send a v5 today with you're last comments. I don't manage yet the unused disks, I need to test with blockdev, but if it's work, I think we'll need to add config generation in pve- storage for differents blockdriver like: ?blockdev driver=file,node-name=file0,filename=vm.img ?blockdev driver=rbd,node-name=rbd0,pool=my-pool,image=vm01 So maybe it'll take a little bit more time. (Maybe a second patch serie later to implement it) -------- Message initial -------- De: Fiona Ebner ?: "DERUMIER, Alexandre" , pve- devel at lists.proxmox.com , aderumier at odiso.com Objet: Re: [pve-devel] [PATCH v4 qemu-server 2/2] remote-migration: add target-cpu && target-reboot params Date: 24/10/2023 10:11:09 Am 23.10.23 um 20:03 schrieb DERUMIER, Alexandre: > Hi Fiona, > > > > > In case of 'restart' migration, we do want to start the VM > > > anyways, > > > so > > > it's actually better, because we can catch config issues early :) > > > Now > > > that I think about it, can we also just start the target VM in > > > prelaunch > > > mode (instead of incoming migration mode), do the NBD migration, > > > shut > > > down the source VM, stop the NBD server and then resume the > > > target? > > > That > > > would avoid the need to stop and start the target again. And > > > therefore > > > might be quite a bit less downtime. > > > I have done some tests, It's not possible currently to write to the > remote nbd without the --incoming migration flag and only -S > > > > https://antiphishing.cetsi.fr/proxy/v3?i=SGI0YVJGNmxZNE90Z2thMFYLWSxJ > OfIERJocpmb73Vs&r=SW5LV3JodE9QZkRVZ3JEYaKpfBJeBDlAX9E2aicRCRO3qsFIBX9 > zb4pDqGdxG45MOoGKkZ3R8w3DjSjAvqYgRg&f=bnJjU3hQT3pQSmNQZVE3aOdk6YB- > 6s0kvu35a0_AsxkSltfWi01kMLld3RaPwuBX&u=https%3A//lists.gnu.org/archiv > e/html/qemu-devel/2017-11/msg05700.html&k=dFBm > > > > nbd_add is throwing an error like > > 2023-10-23 18:45:51 [formationkvm1] VM 111 qmp command 'block-export- > add' failed - Permission conflict on node '#block524': permissions > 'write' are both required by an unnamed block device (uses node > '#block524' as 'root' child) and unshared by block device 'drive- > scsi0' > (uses node '#block524' as 'root' child). > > > Looking at the qemu code, the are some specific codepath in block > when > the incoming flag is setup. > That is unfortunate. But thanks for giving it a shot! I guess if we'd really wanted to go this route, we'd need to add some kind of "empty" migration type without any state, but would be hard to get right and feels like a hack. > > So I think the best way for now is to restart the target vm. > Sure! Going with that is a much cleaner approach then. From f.schauer at proxmox.com Tue Oct 24 14:55:53 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Tue, 24 Oct 2023 14:55:53 +0200 Subject: [pve-devel] [PATCH v2 container 1/1] Add device passthrough In-Reply-To: <20231024125554.131800-1-f.schauer@proxmox.com> References: <20231024125554.131800-1-f.schauer@proxmox.com> Message-ID: <20231024125554.131800-2-f.schauer@proxmox.com> Add a dev[n] argument to the container config to pass devices through to a container. A device can be passed by its path. Alternatively a mapped USB device can be passed through with usbmapping=. Signed-off-by: Filip Schauer --- src/PVE/LXC.pm | 34 +++++++++++++++++++++++- src/PVE/LXC/Config.pm | 60 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index c9b5ba7..a3ddb62 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -5,7 +5,8 @@ use warnings; use Cwd qw(); use Errno qw(ELOOP ENOTDIR EROFS ECONNREFUSED EEXIST); -use Fcntl qw(O_RDONLY O_WRONLY O_NOFOLLOW O_DIRECTORY); +use Fcntl qw(O_RDONLY O_WRONLY O_NOFOLLOW O_DIRECTORY :mode); +use File::Basename; use File::Path; use File::Spec; use IO::Poll qw(POLLIN POLLHUP); @@ -639,6 +640,37 @@ sub update_lxc_config { $raw .= "lxc.mount.auto = sys:mixed\n"; } + # Clear passthrough directory from previous run + my $passthrough_dir = "/var/lib/lxc/$vmid/passthrough"; + File::Path::rmtree($passthrough_dir); + + PVE::LXC::Config->foreach_passthrough_device($conf, sub { + my ($key, $sanitized_path) = @_; + + my $absolute_path = "/$sanitized_path"; + my ($mode, $rdev) = (stat($absolute_path))[2, 6]; + die "Could not find major and minor ids of device $absolute_path.\n" + unless ($mode && $rdev); + + my $major = PVE::Tools::dev_t_major($rdev); + my $minor = PVE::Tools::dev_t_minor($rdev); + my $device_type_char = S_ISBLK($mode) ? 'b' : 'c'; + my $passthrough_device_path = "$passthrough_dir/$sanitized_path"; + File::Path::make_path(dirname($passthrough_device_path)); + PVE::Tools::run_command([ + '/usr/bin/mknod', + '-m', '0660', + $passthrough_device_path, + $device_type_char, + $major, + $minor + ]); + chown 100000, 100000, $passthrough_device_path if ($unprivileged); + + $raw .= "lxc.cgroup2.devices.allow = $device_type_char $major:$minor rw\n"; + $raw .= "lxc.mount.entry = $passthrough_device_path $sanitized_path none bind,create=file\n"; + }); + # WARNING: DO NOT REMOVE this without making sure that loop device nodes # cannot be exposed to the container with r/w access (cgroup perms). # When this is enabled mounts will still remain in the monitor's namespace diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm index 56e1f10..edd813e 100644 --- a/src/PVE/LXC/Config.pm +++ b/src/PVE/LXC/Config.pm @@ -29,6 +29,7 @@ mkdir $lockdir; mkdir "/etc/pve/nodes/$nodename/lxc"; my $MAX_MOUNT_POINTS = 256; my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS; +my $MAX_DEVICES = 256; # BEGIN implemented abstract methods from PVE::AbstractConfig @@ -908,6 +909,49 @@ for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) { } } +PVE::JSONSchema::register_format('pve-lxc-dev-string', \&verify_lxc_dev_string); +sub verify_lxc_dev_string { + my ($dev, $noerr) = @_; + + if ( + $dev =~ m@/\.\.?/@ || + $dev =~ m@/\.\.?$@ || + $dev !~ m!^/dev/! + ) { + return undef if $noerr; + die "$dev is not a valid device path\n"; + } + + return $dev; +} + +my $dev_desc = { + path => { + optional => 1, + type => 'string', + default_key => 1, + format => 'pve-lxc-dev-string', + format_description => 'Path', + description => 'Device to pass through to the container', + verbose_description => 'Path to the device to pass through to the container' + }, + usbmapping => { + optional => 1, + type => 'string', + format => 'pve-configid', + format_description => 'mapping-id', + description => 'The ID of a cluster wide USB mapping.' + } +}; + +for (my $i = 0; $i < $MAX_DEVICES; $i++) { + $confdesc->{"dev$i"} = { + optional => 1, + type => 'string', format => $dev_desc, + description => "Device to pass through to the container", + } +} + sub parse_pct_config { my ($filename, $raw, $strict) = @_; @@ -1255,6 +1299,22 @@ sub parse_volume { return; } +sub parse_device { + my ($class, $device_string, $noerr) = @_; + + my $res; + eval { $res = PVE::JSONSchema::parse_property_string($dev_desc, $device_string) }; + if ($@) { + return undef if $noerr; + die $@; + } + + die "Either path or usbmapping has to be defined" + unless (defined($res->{path}) || defined($res->{usbmapping})); + + return $res; +} + sub print_volume { my ($class, $key, $volume) = @_; -- 2.39.2 From f.schauer at proxmox.com Tue Oct 24 14:55:52 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Tue, 24 Oct 2023 14:55:52 +0200 Subject: [pve-devel] [PATCH many v2] Add container device passthrough Message-ID: <20231024125554.131800-1-f.schauer@proxmox.com> Changes since v2: * mknod the devices in /var/lib/lxc/$vmid/passthrough and setup proper permissions instead of bind mounting the devices from /dev directly * Add support for USB mapping * Add foreach_passthrough_device helper function pve-container: Filip Schauer (1): Add device passthrough src/PVE/LXC.pm | 34 +++++++++++++++++++++++- src/PVE/LXC/Config.pm | 60 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) pve-guest-common: Filip Schauer (1): Add foreach_passthrough_device src/PVE/AbstractConfig.pm | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) From f.schauer at proxmox.com Tue Oct 24 14:55:54 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Tue, 24 Oct 2023 14:55:54 +0200 Subject: [pve-devel] [PATCH v2 guest-common 1/1] Add foreach_passthrough_device In-Reply-To: <20231024125554.131800-1-f.schauer@proxmox.com> References: <20231024125554.131800-1-f.schauer@proxmox.com> Message-ID: <20231024125554.131800-3-f.schauer@proxmox.com> Add a function to iterate over passthrough devices of a provided container config. Signed-off-by: Filip Schauer --- src/PVE/AbstractConfig.pm | 44 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/PVE/AbstractConfig.pm b/src/PVE/AbstractConfig.pm index a286b13..ed26e91 100644 --- a/src/PVE/AbstractConfig.pm +++ b/src/PVE/AbstractConfig.pm @@ -484,6 +484,50 @@ sub foreach_volume { return $class->foreach_volume_full($conf, undef, $func, @param); } +sub foreach_passthrough_device { + my ($class, $conf, $func, @param) = @_; + + foreach my $key (keys %$conf) { + next if $key !~ m/^dev(\d+)$/; + + my $device = $class->parse_device($conf->{$key}); + + if (defined($device->{path})) { + die "Device path $device->{path} does not start with /dev/\n" + if $device->{path} !~ m!^/dev/!; + + my $sanitized_path = substr($conf->{$key}, 1); + die "Device /$sanitized_path does not exist\n" unless (-e "/$sanitized_path"); + + $func->($key, $sanitized_path, @param); + } elsif (defined($device->{usbmapping})) { + my $mapping = $device->{usbmapping}; + my $map_devices = PVE::Mapping::USB::find_on_current_node($mapping); + + die "USB device mapping not found for '$mapping'\n" + if !$map_devices || !scalar($map_devices->@*); + + die "More than one USB mapping per host not supported\n" + if scalar($map_devices->@*) > 1; + + eval { + PVE::Mapping::USB::assert_valid($mapping, $map_devices->[0]); + }; + if (my $err = $@) { + die "USB Mapping invalid (hardware probably changed): $err\n"; + } + + my $map_device = $map_devices->[0]; + my $lsusb_output = `/usr/bin/lsusb -d $map_device->{id}`; + my ($bus_id, $device_id) = $lsusb_output =~ /Bus (\d+) Device (\d+)/; + + $func->($key, "dev/bus/usb/$bus_id/$device_id", @param); + } else { + die "Either path or usbmapping has to be defined for $key"; + } + } +} + # $volume_map is a hash of 'old_volid' => 'new_volid' pairs. # This method replaces 'old_volid' by 'new_volid' throughout the config including snapshots, pending # changes, unused volumes and vmstate volumes. -- 2.39.2 From f.schauer at proxmox.com Tue Oct 24 15:00:08 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Tue, 24 Oct 2023 15:00:08 +0200 Subject: [pve-devel] [PATCH RFC container] Add device passthrough In-Reply-To: <61041ddd-5475-48fd-a7ef-d1816bed25a2@proxmox.com> References: <20231019121856.379185-1-f.schauer@proxmox.com> <82d37443-ca7f-4cf3-aae0-36444e4769af@proxmox.com> <54225ac2-b494-4c11-9150-1ad647a65eaf@proxmox.com> <61041ddd-5475-48fd-a7ef-d1816bed25a2@proxmox.com> Message-ID: <47130fdb-2f51-afbe-8e5d-103a7ae6b838@proxmox.com> A patch v2 is available: https://lists.proxmox.com/pipermail/pve-devel/2023-October/059617.html On 20/10/2023 10:39, Dominik Csapak wrote: > On 10/20/23 10:29, Thomas Lamprecht wrote: >> Am 20/10/2023 um 09:51 schrieb Dominik Csapak: >>> On 10/20/23 09:08, Wolfgang Bumiller wrote: >>>> Also, Dominik recently added resource mappings for qemu for USB & PCI. >>>> PCI might be tricky, but for USB we may be able to use these >>>> mappings as >>>> well. >>>> That said, "raw" `/dev` node pass-through still makes sense as a >>>> separate thing for containers anyway since raw `lxc....` entries in >>>> the >>>> container config can currently be very inconvenient to deal with >>>> particularly with unprivileged containers (read on below for why...) >>> >>> just to chime in here, i don't think it'll be easily possible to reuse >>> the pci/usb maps as is since we'd have to map from pciid >>> /usb-vendor/device >>> (or path) to a device node? i don't think thats generally possible, >>> since >>> the driver does not always make that info easily available >>> (e.g. multi gpu setup and /dev/dri/cardX, or usb-to-serial adapters >>> and /dev/ttySX ?) i guess it could work, but we probably would have >>> to implement that for every driver out there? >>> >> >> USB should be workable via resolving to /dev/bus/usb/*, PCI could be, >> theoretically, but isn't now and probably won't be anytime soon ? i.e., >> as Wolfgang mentioned off list, there's a reason that there's no >> /dev/bus/pci/ >> >>> what i would like to see however is to integrate a new type of mapping >>> for container devices specifically so that the ux is the same >>> (create mappings for whole cluster, assigning privileges, etc) >> >> I'd try hard to re-use the USB mappings, those seem to be one of the >> most common pass-through setups for containers (e.g., for those >> home automation zigbee/matter/... adapters, or in some countries DVB-T >> ones, be it for TV or ADS-B plane tracking). > > i guess, but sadly the /dev/bus/usb endpoint is mostly not what you want > to pass-through but the driver specific /dev/ttySX /dev/dvb/X and so on > (there are situations where the /dev/bus/usb path is the wanted one, > but there are many where it isn't) > > and while we can map from those to the usb device/vendor/path the reverse > mapping is not so easy (at least when i tried i did not found a > generic way > via udev or similar > > i would welcome it though if there is a way to do that of course > >> >> If we can make USB work then we'd have the basic concept of attaching >> a mapping done, adding a new type of (block/char) device mapping could >> then be an independent task for later to keep scope a bit smaller. >> >> Fixing Wolfgang's comments for workable pass-through for unprivileged >> containers is probably the most important change needed for now. >> >> I'd then even be open to apply this with (root at pam only!) absolute >> path to /dev support only, but IMO resolving the mapping itself should >> not be too hard, so if using /dev/bus/usb/ works having that in there >> from the start would be definitively nice. > > From t.lamprecht at proxmox.com Tue Oct 24 16:48:34 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 24 Oct 2023 16:48:34 +0200 Subject: [pve-devel] [PATCH manager 3/3] ui: wizards: allow adding tags in the qemu/lxc create wizard In-Reply-To: <20231019133607.1416999-3-d.csapak@proxmox.com> References: <20231019133607.1416999-1-d.csapak@proxmox.com> <20231019133607.1416999-3-d.csapak@proxmox.com> Message-ID: <261471e2-5423-4aeb-a0cf-9891a2b2a154@proxmox.com> Am 19/10/2023 um 15:36 schrieb Dominik Csapak: > in the general tab in the advanced section. > > For that to work, we introduce a new option for the TagEditContainer > named 'editOnly', which controls now the cancel/finish buttons, > automatically enter edit mode and disable enter/escape keypresses. > > We also prevent now the loading of tags while in edit mode, so the tags > don't change while editing (this can be jarring and unexpected). > > In the wizard, we override the layout such that the tags wrap when there > are too many, and make the field scrollable and set a height, so that > the user can enter as many tags as he wants without having the field > overflow or cut off. > > To properly align the input with the '+' button, we have to add a custom > css class there. (In the hbox we could set the alignment, but this is > not possible in the 'column' layout) > I'd wrap this in a fieldset with Tags as legend (well, still "title" in ExtJS), ideally in its own small PVE.form.TagEditFieldSet module, that extends the Ext.form.FieldSet class? That could then also contain the getValue handling, further reducing the wizard specific changes. Allowing the height to grow until a max-height (i.e., fills out wizard panel) and only then get scrollable would be still nice though. From t.lamprecht at proxmox.com Tue Oct 24 16:49:27 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 24 Oct 2023 16:49:27 +0200 Subject: [pve-devel] applied: [PATCH manager 1/3] ui: tags: fix focus for edit mode In-Reply-To: <20231019133607.1416999-1-d.csapak@proxmox.com> References: <20231019133607.1416999-1-d.csapak@proxmox.com> Message-ID: <691fbb22-f634-4d95-9c12-39492485ccc3@proxmox.com> Am 19/10/2023 um 15:36 schrieb Dominik Csapak: > such that one can tab through the editable tag fields. > We have to handle that manually, since ExtJs does not expect > contenteditable html tags for focus handling. > > Signed-off-by: Dominik Csapak > --- > www/manager6/form/Tag.js | 9 +++++++++ > 1 file changed, 9 insertions(+) > > applied this one already, thanks! From t.lamprecht at proxmox.com Tue Oct 24 17:05:15 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 24 Oct 2023 17:05:15 +0200 Subject: [pve-devel] [PATCH installer v2 1/6] fix #4829: install: add new ZFS `arc_max` setup option In-Reply-To: <20231024115530.1101733-2-c.heiss@proxmox.com> References: <20231024115530.1101733-1-c.heiss@proxmox.com> <20231024115530.1101733-2-c.heiss@proxmox.com> Message-ID: Am 24/10/2023 um 13:55 schrieb Christoph Heiss: > Signed-off-by: Christoph Heiss > --- > Changes v1 -> v2: > * No changes > > Proxmox/Install.pm | 4 ++++ > Proxmox/Install/Config.pm | 1 + > Proxmox/Install/RunEnv.pm | 38 ++++++++++++++++++++++++++++++++++++++ > 3 files changed, 43 insertions(+) > > diff --git a/Proxmox/Install.pm b/Proxmox/Install.pm > index 1117fc4..0c3541f 100644 > --- a/Proxmox/Install.pm > +++ b/Proxmox/Install.pm > @@ -1141,6 +1141,10 @@ _EOD > > file_write_all("$targetdir/etc/kernel/cmdline", "root=ZFS=$zfs_pool_name/ROOT/$zfs_root_volume_name boot=zfs\n"); > > + my $arc_max = Proxmox::Install::RunEnv::clamp_zfs_arc_max( > + Proxmox::Install::Config::get_zfs_opt('arc_max')) * 1024 * 1024; Mixing multiplication and method call this way is a bit hard to read. Maybe move the clamping into out to its own local method, something like: my sub setup_zfs_conf { my ($target_dir) = @_; my $arc_max_mb = Proxmox::Install::Config::get_zfs_opt('arc_max'); my $arc_max = Proxmox::Install::RunEnv::clamp_zfs_arc_max($arc_max_mb) * 1024 * 1024; if ($arc_max > 0) { file_write_all("${target_dir}/etc/modprobe.d/zfs.conf", "options zfs zfs_arc_max=$arc_max\n"); } } > + file_write_all("$targetdir/etc/modprobe.d/zfs.conf", "options zfs zfs_arc_max=$arc_max\n") > + if $arc_max > 0; > } > > diversion_remove($targetdir, "/usr/sbin/update-grub"); > diff --git a/Proxmox/Install/Config.pm b/Proxmox/Install/Config.pm > index 024f62a..f12ae67 100644 > --- a/Proxmox/Install/Config.pm > +++ b/Proxmox/Install/Config.pm > @@ -72,6 +72,7 @@ my sub init_cfg { > compress => 'on', > checksum => 'on', > copies => 1, > + arc_max => Proxmox::Install::RunEnv::default_zfs_arc_max(), > }, > # TODO: single disk selection config > target_hd => undef, > diff --git a/Proxmox/Install/RunEnv.pm b/Proxmox/Install/RunEnv.pm > index c9d788b..9236030 100644 > --- a/Proxmox/Install/RunEnv.pm > +++ b/Proxmox/Install/RunEnv.pm > @@ -301,6 +301,44 @@ sub query_installation_environment : prototype() { > return $output; > } > > +our $ZFS_ARC_MIN_SIZE = 64; # MiB > + > +# Calculates the default upper limit for the ZFS ARC size. > +# See also https://bugzilla.proxmox.com/show_bug.cgi?id=4829 and > +# https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max > +sub default_zfs_arc_max { > + # Use ZFS default on non-PVE > + return 0 if Proxmox::Install::ISOEnv::get('product') ne 'pve'; > + > + my $max = 16 * 1024; # 16 GiB > + > + my $rounded = int(sprintf('%.0f', get('total_memory') / 10)); knowing in what units this is calculating/returning would be great here (via comment and/or variable name) > + if ($rounded > $max) { > + return $max; > + } elsif ($rounded < $ZFS_ARC_MIN_SIZE) { > + return $ZFS_ARC_MIN_SIZE; > + } > + > + return $rounded; > +} > + > +# Clamps the provided ZFS arc_max value to the accepted bounds. See also > +# https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max > +sub clamp_zfs_arc_max { > + my ($value) = @_; please at least comment, or better, name variables accordingly, in what units your dealing here, I think MB? When I worked on the partition sizing code it was a PITA to trace what unit was used when. From t.lamprecht at proxmox.com Tue Oct 24 17:07:19 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 24 Oct 2023 17:07:19 +0200 Subject: [pve-devel] [PATCH installer v2 0/6] fix #4829: set up lower default limit for ZFS ARC in installer In-Reply-To: <20231024115530.1101733-1-c.heiss@proxmox.com> References: <20231024115530.1101733-1-c.heiss@proxmox.com> Message-ID: <3e69d24b-f731-4b65-aa2f-77a041463c5e@proxmox.com> Am 24/10/2023 um 13:55 schrieb Christoph Heiss: > Fixes #4829. Introduces a new ZFS install option `arc_max` (aptly named > after the corresponding module option `zfs_arc_max`). > > For PVE installations, this can be adjusted when creating a ZFS RAID > under "Advanced Options". The default value is choosen as 10% of system > memory, clamped to between 64 MiB as lower limit and 16 GiB as upper > limit. For PBS and PMG, the option is (currently) hidden. > > If the option is set to a non-zero value, a new file > /etc/modprobe.d/zfs.conf gets written during install, setting the > `zfs_arc_max` module option as appropriate. > > Tested by installing PVE, PBS and PMG. For PVE, checked that the `zfs` > module option gets correctly written & applied, the latter by looking at > the output of `arc_summary`. For PBS and PMG, verified that no modprobe > options file is created and the ARC size is set to default. > > v1: https://lists.proxmox.com/pipermail/pve-devel/2023-August/058830.html > > Notable changes v1 -> v2: > * Rebased on latest master > * Fix arc_max value set in TUI not being applied correctly > > Christoph Heiss (6): > fix #4829: install: add new ZFS `arc_max` setup option > fix #4829: proxinstall: expose new `arc_max` ZFS option for PVE > installations > fix #4829: test: add tests for new zfs_arc_max calculations > tui: views: add optional suffix label for `NumericEditView`s > fix #4829: tui: setup: add new ZFS `arc_max` option > fix #4829: tui: bootdisk: expose new `arc_max` ZFS option for PVE > installations > looks OK bis of a few comments in the first patch, I can fix those up too, but maybe it's quicker if just send a v3, if you agree with them. From c.heiss at proxmox.com Wed Oct 25 10:17:08 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 25 Oct 2023 10:17:08 +0200 Subject: [pve-devel] [PATCH installer v2 1/6] fix #4829: install: add new ZFS `arc_max` setup option In-Reply-To: References: <20231024115530.1101733-1-c.heiss@proxmox.com> <20231024115530.1101733-2-c.heiss@proxmox.com> Message-ID: Thanks for the review! I will refactor all the points mentioned and send a v2 soon. On Tue, Oct 24, 2023 at 05:05:15PM +0200, Thomas Lamprecht wrote: > > Am 24/10/2023 um 13:55 schrieb Christoph Heiss: > > Signed-off-by: Christoph Heiss > > --- > > Changes v1 -> v2: > > * No changes > > > > Proxmox/Install.pm | 4 ++++ > > Proxmox/Install/Config.pm | 1 + > > Proxmox/Install/RunEnv.pm | 38 ++++++++++++++++++++++++++++++++++++++ > > 3 files changed, 43 insertions(+) > > > > diff --git a/Proxmox/Install.pm b/Proxmox/Install.pm > > index 1117fc4..0c3541f 100644 > > --- a/Proxmox/Install.pm > > +++ b/Proxmox/Install.pm > > @@ -1141,6 +1141,10 @@ _EOD > > > > file_write_all("$targetdir/etc/kernel/cmdline", "root=ZFS=$zfs_pool_name/ROOT/$zfs_root_volume_name boot=zfs\n"); > > > > + my $arc_max = Proxmox::Install::RunEnv::clamp_zfs_arc_max( > > + Proxmox::Install::Config::get_zfs_opt('arc_max')) * 1024 * 1024; > > Mixing multiplication and method call this way is a bit hard to read. > > Maybe move the clamping into out to its own local method, something like: > > my sub setup_zfs_conf { > my ($target_dir) = @_; > > my $arc_max_mb = Proxmox::Install::Config::get_zfs_opt('arc_max'); > my $arc_max = Proxmox::Install::RunEnv::clamp_zfs_arc_max($arc_max_mb) * 1024 * 1024; > > if ($arc_max > 0) { > file_write_all("${target_dir}/etc/modprobe.d/zfs.conf", "options zfs zfs_arc_max=$arc_max\n"); > } > } Seems like a good idea, the extract_data() sub is _way_ to overloaded already anyway. > > > + file_write_all("$targetdir/etc/modprobe.d/zfs.conf", "options zfs zfs_arc_max=$arc_max\n") > > + if $arc_max > 0; > > > > } > > > > diversion_remove($targetdir, "/usr/sbin/update-grub"); > > diff --git a/Proxmox/Install/Config.pm b/Proxmox/Install/Config.pm > > index 024f62a..f12ae67 100644 > > --- a/Proxmox/Install/Config.pm > > +++ b/Proxmox/Install/Config.pm > > @@ -72,6 +72,7 @@ my sub init_cfg { > > compress => 'on', > > checksum => 'on', > > copies => 1, > > + arc_max => Proxmox::Install::RunEnv::default_zfs_arc_max(), > > }, > > # TODO: single disk selection config > > target_hd => undef, > > diff --git a/Proxmox/Install/RunEnv.pm b/Proxmox/Install/RunEnv.pm > > index c9d788b..9236030 100644 > > --- a/Proxmox/Install/RunEnv.pm > > +++ b/Proxmox/Install/RunEnv.pm > > @@ -301,6 +301,44 @@ sub query_installation_environment : prototype() { > > return $output; > > } > > > > +our $ZFS_ARC_MIN_SIZE = 64; # MiB > > + > > +# Calculates the default upper limit for the ZFS ARC size. > > +# See also https://bugzilla.proxmox.com/show_bug.cgi?id=4829 and > > +# https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max > > +sub default_zfs_arc_max { > > + # Use ZFS default on non-PVE > > + return 0 if Proxmox::Install::ISOEnv::get('product') ne 'pve'; > > + > > + my $max = 16 * 1024; # 16 GiB > > + > > + my $rounded = int(sprintf('%.0f', get('total_memory') / 10)); > > knowing in what units this is calculating/returning would be great here > (via comment and/or variable name) Definitely sensible, I will rename + expand the comments a bit more. > > > + if ($rounded > $max) { > > + return $max; > > + } elsif ($rounded < $ZFS_ARC_MIN_SIZE) { > > + return $ZFS_ARC_MIN_SIZE; > > + } > > + > > + return $rounded; > > +} > > + > > +# Clamps the provided ZFS arc_max value to the accepted bounds. See also > > +# https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max > > +sub clamp_zfs_arc_max { > > + my ($value) = @_; > > please at least comment, or better, name variables accordingly, in what > units your dealing here, I think MB? > > When I worked on the partition sizing code it was a PITA to trace what > unit was used when. From c.heiss at proxmox.com Wed Oct 25 10:28:02 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 25 Oct 2023 10:28:02 +0200 Subject: [pve-devel] [PATCH installer v2 1/6] fix #4829: install: add new ZFS `arc_max` setup option In-Reply-To: References: <20231024115530.1101733-1-c.heiss@proxmox.com> <20231024115530.1101733-2-c.heiss@proxmox.com> Message-ID: <7l5h7ecjzznfpagtvnk7xcp7s2cu7njnnext2dp4zcwgyxbk3p@r5nlqvepijry> On Tue, Oct 24, 2023 at 08:59:36AM -0300, Gilberto Ferreira via pve-devel wrote: > Date: Tue, 24 Oct 2023 08:59:36 -0300 > From: Gilberto Ferreira > To: Proxmox VE development discussion > Subject: Re: [pve-devel] [PATCH installer v2 1/6] fix #4829: install: add > new ZFS `arc_max` setup option > > > Hi there. > Now, that's a good option in the installer. > I wonder if this option will be available post-intall, like some box in the > ZFS manager from WEB GUI! > That's would be nice. > Currently, this isn't planned, although - since that setting is exposed after all in the installer in the future - it would be kind of sensible to add it to the GUI as well, I guess. But as a separate series from this one, of course. CC @Thomas - what do you think? If you think this is something worth doing, I'd create a proper bugzilla ticket with some more details. My thinking here (roughly) would be: * Move the {default,clamp}_zfs_arc_max() stuff to pve-common * As for the API, probably shove it under `/nodes/{node}/config`? It's a per-node configuration after all, it seems to fit there best without introducing another endpoint just for this * GUI-wise; if we do the above, it probably should also go under node -> system -> options .. Otherwise, add a small, separate panel for options to node -> disks -> zfs maybe? From f.ebner at proxmox.com Wed Oct 25 10:30:48 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Wed, 25 Oct 2023 10:30:48 +0200 Subject: [pve-devel] [PATCH v4 qemu-server 2/2] remote-migration: add target-cpu && target-reboot params In-Reply-To: <283ff207e6065f0ae178410bfa391e9a5369924f.camel@groupe-cyllene.com> References: <20230928144556.2023558-1-aderumier@odiso.com> <20230928144556.2023558-3-aderumier@odiso.com> <5ecfa7d0-4525-5f1e-75a2-a6ae1a93356b@proxmox.com> <73e0a3a6-f978-ac24-5f6b-16af759ee209@proxmox.com> <016bd4b8-7502-48fe-9208-a075e8aea02b@proxmox.com> <283ff207e6065f0ae178410bfa391e9a5369924f.camel@groupe-cyllene.com> Message-ID: <8d06d2f6-b831-45b3-ac1b-2cc3f1721b85@proxmox.com> Am 24.10.23 um 14:20 schrieb DERUMIER, Alexandre: >>> So I think the best way for now is to restart the target vm. >>> >>> Sure! Going with that is a much cleaner approach then. > > I'll try to send a v5 today with you're last comments. > > I don't manage yet the unused disks, I need to test with blockdev, > Is it required for this series? Unused disks can just be migrated offline via storage_migrate(), or? If we want to switch to migrating disks offline via QEMU instead of our current storage_migrate(), going for QEMU storage daemon + NBD seems the most natural to me. If it's not too complicated to temporarily attach the disks to the VM, that can be done too, but is less re-usable (e.g. pure offline migration won't benefit from that). > but if it's work, I think we'll need to add config generation in pve- > storage for differents blockdriver > > > like: > > ?blockdev driver=file,node-name=file0,filename=vm.img > > ?blockdev driver=rbd,node-name=rbd0,pool=my-pool,image=vm01 > What other special cases besides (non-krbd) RBD are there? If it's just that, I'd much rather keep the special handling in QEMU itself then burden all other storage plugins with implementing something specific to VMs. Or is there a way to use the path from the storage plugin somehow like we do at the moment, i.e. "rbd:rbd/vm-111-disk-1:conf=/etc/pve/ceph.conf:id=admin:keyring=/etc/pve/priv/ceph/rbd.keyring"? > So maybe it'll take a little bit more time. > > (Maybe a second patch serie later to implement it) > Yes, I think that makes sense as a dedicated series. From c.heiss at proxmox.com Wed Oct 25 10:56:24 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 25 Oct 2023 10:56:24 +0200 Subject: [pve-devel] [PATCH installer v2 3/3] tui: remove obsolete, global `SetupInfo` state In-Reply-To: <20231025085634.171618-1-c.heiss@proxmox.com> References: <20231025085634.171618-1-c.heiss@proxmox.com> Message-ID: <20231025085634.171618-4-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/main.rs | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index a342a08..81fe3ca 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -1,3 +1,5 @@ +#![forbid(unsafe_code)] + use std::{ collections::HashMap, env, @@ -50,24 +52,6 @@ const PROXMOX_LOGO: &str = r#" | __/| | | (_) > <| | | | | | (_) > < |_| |_| \___/_/\_\_| |_| |_|\___/_/\_\ "#; -/// ISO information is available globally. -static mut SETUP_INFO: Option = None; - -pub fn setup_info() -> &'static SetupInfo { - unsafe { SETUP_INFO.as_ref().unwrap() } -} - -fn init_setup_info(info: SetupInfo) { - unsafe { - SETUP_INFO = Some(info); - } -} - -#[inline] -pub fn current_product() -> setup::ProxmoxProduct { - setup_info().config.product -} - struct InstallerView { view: ResizedView, } @@ -223,7 +207,6 @@ fn installer_setup(in_test_mode: bool) -> Result<(SetupInfo, LocaleInfo, Runtime setup::read_json(&path).map_err(|err| format!("Failed to retrieve setup info: {err}"))? }; - init_setup_info(installer_info.clone()); let locale_info = { let mut path = path.clone(); -- 2.42.0 From c.heiss at proxmox.com Wed Oct 25 10:56:21 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 25 Oct 2023 10:56:21 +0200 Subject: [pve-devel] [PATCH installer v2 0/3] tui: remove global, unsafe setup info Message-ID: <20231025085634.171618-1-c.heiss@proxmox.com> Removes the `static mut` for holding a `SetupInfo` instance. This is done by either passing the needed info as parameter, or in some cases, the needed information is already available through other means. Not only does it get rid of some ugly, unsafe code, it is needed anyway as a prerequisite by Aaron for pulling out non-TUI-related code into a separate, shared crate. No functional changes overall. v1: https://lists.proxmox.com/pipermail/pve-devel/2023-October/059335.html Changes v1 -> v2: * Rebased on latest master, no actual changes otherwise Christoph Heiss (3): tui: refactor `NetworkOptions` to have a `defaults_from()` function tui: bootdisk: pass down product info to advanced dialog tui: remove obsolete, global `SetupInfo` state proxmox-tui-installer/src/main.rs | 34 +++------- proxmox-tui-installer/src/options.rs | 75 ++++++++++----------- proxmox-tui-installer/src/setup.rs | 10 ++- proxmox-tui-installer/src/views/bootdisk.rs | 74 +++++++++++++------- 4 files changed, 95 insertions(+), 98 deletions(-) -- 2.41.0 From c.heiss at proxmox.com Wed Oct 25 10:56:23 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 25 Oct 2023 10:56:23 +0200 Subject: [pve-devel] [PATCH installer v2 2/3] tui: bootdisk: pass down product info to advanced dialog In-Reply-To: <20231025085634.171618-1-c.heiss@proxmox.com> References: <20231025085634.171618-1-c.heiss@proxmox.com> Message-ID: <20231025085634.171618-3-c.heiss@proxmox.com> Enables the advanced and LVM dialog to determine what options (max root/data size and Btrfs RAIDs) by itself, without needing to resort to the global `setup_info()` function. Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/views/bootdisk.rs | 74 ++++++++++++++------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index dbd13ea..ba08c8b 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -16,7 +16,7 @@ use crate::{ FsType, LvmBootdiskOptions, ZfsBootdiskOptions, ZfsRaidLevel, FS_TYPES, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS, }, - setup::BootType, + setup::{BootType, ProductConfig}, }; use crate::{setup::ProxmoxProduct, InstallerState}; @@ -37,6 +37,11 @@ impl BootdiskOptionsView { ) .with_name("bootdisk-options-target-disk"); + let product_conf = siv + .user_data::() + .map(|state| state.setup_info.config.clone()) + .unwrap(); // Safety: InstallerState must always be set + let advanced_options = Rc::new(RefCell::new(options.clone())); let advanced_button = LinearLayout::horizontal() @@ -45,7 +50,11 @@ impl BootdiskOptionsView { let disks = disks.to_owned(); let options = advanced_options.clone(); move |siv| { - siv.add_layer(advanced_options_view(&disks, options.clone())); + siv.add_layer(advanced_options_view( + &disks, + options.clone(), + product_conf.clone(), + )); } })); @@ -95,10 +104,9 @@ struct AdvancedBootdiskOptionsView { } impl AdvancedBootdiskOptionsView { - fn new(disks: &[Disk], options: &BootdiskOptions) -> Self { - let enable_btrfs = crate::setup_info().config.enable_btrfs; - - let filter_btrfs = |fstype: &&FsType| -> bool { enable_btrfs || !fstype.is_btrfs() }; + fn new(disks: &[Disk], options: &BootdiskOptions, product_conf: ProductConfig) -> Self { + let filter_btrfs = + |fstype: &&FsType| -> bool { product_conf.enable_btrfs || !fstype.is_btrfs() }; let fstype_select = SelectView::new() .popup() @@ -126,7 +134,10 @@ impl AdvancedBootdiskOptionsView { .child(DummyView.full_width()); match &options.advanced { - AdvancedBootdiskOptions::Lvm(lvm) => view.add_child(LvmBootdiskOptionsView::new(lvm)), + AdvancedBootdiskOptions::Lvm(lvm) => view.add_child(LvmBootdiskOptionsView::new( + lvm, + product_conf.product == ProxmoxProduct::PVE, + )), AdvancedBootdiskOptions::Zfs(zfs) => { view.add_child(ZfsBootdiskOptionsView::new(disks, zfs)) } @@ -139,6 +150,11 @@ impl AdvancedBootdiskOptionsView { } fn fstype_on_submit(siv: &mut Cursive, disks: &[Disk], fstype: &FsType) { + let is_pve = siv + .user_data::() + .map(|state| state.setup_info.config.product == ProxmoxProduct::PVE) + .unwrap_or_default(); + siv.call_on_name("advanced-bootdisk-options-dialog", |view: &mut Dialog| { if let Some(AdvancedBootdiskOptionsView { view }) = view.get_content_mut().downcast_mut() @@ -147,6 +163,7 @@ impl AdvancedBootdiskOptionsView { match fstype { FsType::Ext4 | FsType::Xfs => view.add_child(LvmBootdiskOptionsView::new( &LvmBootdiskOptions::defaults_from(&disks[0]), + is_pve, )), FsType::Zfs(_) => view.add_child(ZfsBootdiskOptionsView::new( disks, @@ -240,11 +257,11 @@ impl ViewWrapper for AdvancedBootdiskOptionsView { struct LvmBootdiskOptionsView { view: FormView, + has_extra_fields: bool, } impl LvmBootdiskOptionsView { - fn new(options: &LvmBootdiskOptions) -> Self { - let is_pve = crate::setup_info().config.product == ProxmoxProduct::PVE; + fn new(options: &LvmBootdiskOptions, show_extra_fields: bool) -> Self { // TODO: Set maximum accordingly to disk size let view = FormView::new() .child( @@ -258,12 +275,12 @@ impl LvmBootdiskOptionsView { DiskSizeEditView::new_emptyable().content_maybe(options.swap_size), ) .child_conditional( - is_pve, + show_extra_fields, "Maximum root volume size", DiskSizeEditView::new_emptyable().content_maybe(options.max_root_size), ) .child_conditional( - is_pve, + show_extra_fields, "Maximum data volume size", DiskSizeEditView::new_emptyable().content_maybe(options.max_data_size), ) @@ -272,22 +289,24 @@ impl LvmBootdiskOptionsView { DiskSizeEditView::new_emptyable().content_maybe(options.min_lvm_free), ); - Self { view } + Self { + view, + has_extra_fields: show_extra_fields, + } } fn get_values(&mut self) -> Option { - let is_pve = crate::setup_info().config.product == ProxmoxProduct::PVE; - let min_lvm_free_id = if is_pve { 4 } else { 2 }; - let max_root_size = if is_pve { - self.view.get_value::(2) - } else { - None - }; - let max_data_size = if is_pve { - self.view.get_value::(3) - } else { - None - }; + let min_lvm_free_id = if self.has_extra_fields { 4 } else { 2 }; + + let max_root_size = self + .has_extra_fields + .then(|| self.view.get_value::(2)) + .flatten(); + let max_data_size = self + .has_extra_fields + .then(|| self.view.get_value::(3)) + .flatten(); + Some(LvmBootdiskOptions { total_size: self.view.get_value::(0)?, swap_size: self.view.get_value::(1), @@ -552,10 +571,15 @@ impl ViewWrapper for ZfsBootdiskOptionsView { cursive::wrap_impl!(self.view: MultiDiskOptionsView); } -fn advanced_options_view(disks: &[Disk], options: Rc>) -> impl View { +fn advanced_options_view( + disks: &[Disk], + options: Rc>, + product_conf: ProductConfig, +) -> impl View { Dialog::around(AdvancedBootdiskOptionsView::new( disks, &(*options).borrow(), + product_conf, )) .title("Advanced bootdisk options") .button("Ok", { -- 2.42.0 From c.heiss at proxmox.com Wed Oct 25 10:56:22 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Wed, 25 Oct 2023 10:56:22 +0200 Subject: [pve-devel] [PATCH installer v2 1/3] tui: refactor `NetworkOptions` to have a `defaults_from()` function In-Reply-To: <20231025085634.171618-1-c.heiss@proxmox.com> References: <20231025085634.171618-1-c.heiss@proxmox.com> Message-ID: <20231025085634.171618-2-c.heiss@proxmox.com> This aligns it with the other constructors for options struct by introducing a `::defaults_from()` function. Signed-off-by: Christoph Heiss --- proxmox-tui-installer/src/main.rs | 15 +++--- proxmox-tui-installer/src/options.rs | 75 +++++++++++++--------------- proxmox-tui-installer/src/setup.rs | 10 ++-- 3 files changed, 45 insertions(+), 55 deletions(-) diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index 0a61e39..a342a08 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -166,7 +166,6 @@ enum InstallerStep { #[derive(Clone)] struct InstallerState { options: InstallerOptions, - /// FIXME: Remove: setup_info: SetupInfo, runtime_info: RuntimeInfo, locales: LocaleInfo, @@ -184,7 +183,7 @@ fn main() { _ => cfg!(debug_assertions), }; - let (locales, runtime_info) = match installer_setup(in_test_mode) { + let (setup_info, locales, runtime_info) = match installer_setup(in_test_mode) { Ok(result) => result, Err(err) => initial_setup_error(&mut siv, &err), }; @@ -197,10 +196,10 @@ fn main() { bootdisk: BootdiskOptions::defaults_from(&runtime_info.disks[0]), timezone: TimezoneOptions::defaults_from(&runtime_info, &locales), password: Default::default(), - network: NetworkOptions::from(&runtime_info.network), + network: NetworkOptions::defaults_from(&setup_info, &runtime_info.network), autoreboot: false, }, - setup_info: setup_info().clone(), // FIXME: REMOVE + setup_info, runtime_info, locales, steps: HashMap::new(), @@ -211,20 +210,20 @@ fn main() { siv.run(); } -fn installer_setup(in_test_mode: bool) -> Result<(LocaleInfo, RuntimeInfo), String> { +fn installer_setup(in_test_mode: bool) -> Result<(SetupInfo, LocaleInfo, RuntimeInfo), String> { let base_path = if in_test_mode { "./testdir" } else { "/" }; let mut path = PathBuf::from(base_path); path.push("run"); path.push("proxmox-installer"); - let installer_info = { + let installer_info: SetupInfo = { let mut path = path.clone(); path.push("iso-info.json"); setup::read_json(&path).map_err(|err| format!("Failed to retrieve setup info: {err}"))? }; - init_setup_info(installer_info); + init_setup_info(installer_info.clone()); let locale_info = { let mut path = path.clone(); @@ -245,7 +244,7 @@ fn installer_setup(in_test_mode: bool) -> Result<(LocaleInfo, RuntimeInfo), Stri if runtime_info.disks.is_empty() { Err("The installer could not find any supported hard disks.".to_owned()) } else { - Ok((locale_info, runtime_info)) + Ok((installer_info, locale_info, runtime_info)) } } diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs index abc8c7b..85b39b8 100644 --- a/proxmox-tui-installer/src/options.rs +++ b/proxmox-tui-installer/src/options.rs @@ -1,7 +1,7 @@ use std::net::{IpAddr, Ipv4Addr}; use std::{cmp, fmt}; -use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo}; +use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo, SetupInfo}; use crate::utils::{CidrAddress, Fqdn}; use crate::SummaryOption; @@ -339,49 +339,25 @@ pub struct NetworkOptions { impl NetworkOptions { const DEFAULT_DOMAIN: &str = "example.invalid"; -} -impl Default for NetworkOptions { - fn default() -> Self { - let fqdn = format!( - "{}.{}", - crate::current_product().default_hostname(), - Self::DEFAULT_DOMAIN - ); - // TODO: Retrieve automatically - Self { + pub fn defaults_from(setup: &SetupInfo, network: &NetworkInfo) -> Self { + let mut this = Self { ifname: String::new(), - fqdn: fqdn.parse().unwrap(), + fqdn: Self::construct_fqdn(network, setup.config.product.default_hostname()), // Safety: The provided mask will always be valid. address: CidrAddress::new(Ipv4Addr::UNSPECIFIED, 0).unwrap(), gateway: Ipv4Addr::UNSPECIFIED.into(), dns_server: Ipv4Addr::UNSPECIFIED.into(), - } - } -} - -impl From<&NetworkInfo> for NetworkOptions { - fn from(info: &NetworkInfo) -> Self { - let mut this = Self::default(); + }; - if let Some(ip) = info.dns.dns.first() { + if let Some(ip) = network.dns.dns.first() { this.dns_server = *ip; } - let hostname = info - .hostname - .as_deref() - .unwrap_or_else(|| crate::current_product().default_hostname()); - let domain = info.dns.domain.as_deref().unwrap_or(Self::DEFAULT_DOMAIN); - - if let Ok(fqdn) = Fqdn::from(&format!("{hostname}.{domain}")) { - this.fqdn = fqdn; - } - - if let Some(routes) = &info.routes { + if let Some(routes) = &network.routes { let mut filled = false; if let Some(gw) = &routes.gateway4 { - if let Some(iface) = info.interfaces.get(&gw.dev) { + if let Some(iface) = network.interfaces.get(&gw.dev) { this.ifname = iface.name.clone(); if let Some(addresses) = &iface.addresses { if let Some(addr) = addresses.iter().find(|addr| addr.is_ipv4()) { @@ -394,7 +370,7 @@ impl From<&NetworkInfo> for NetworkOptions { } if !filled { if let Some(gw) = &routes.gateway6 { - if let Some(iface) = info.interfaces.get(&gw.dev) { + if let Some(iface) = network.interfaces.get(&gw.dev) { if let Some(addresses) = &iface.addresses { if let Some(addr) = addresses.iter().find(|addr| addr.is_ipv6()) { this.ifname = iface.name.clone(); @@ -409,6 +385,23 @@ impl From<&NetworkInfo> for NetworkOptions { this } + + fn construct_fqdn(network: &NetworkInfo, default_hostname: &str) -> Fqdn { + let hostname = network.hostname.as_deref().unwrap_or(default_hostname); + + let domain = network + .dns + .domain + .as_deref() + .unwrap_or(Self::DEFAULT_DOMAIN); + + Fqdn::from(&format!("{hostname}.{domain}")).unwrap_or_else(|_| { + // Safety: This will always result in a valid FQDN, as we control & know + // the values of default_hostname (one of "pve", "pmg" or "pbs") and + // constant-defined DEFAULT_DOMAIN. + Fqdn::from(&format!("{}.{}", default_hostname, Self::DEFAULT_DOMAIN)).unwrap() + }) + } } #[derive(Clone, Debug)] @@ -460,8 +453,8 @@ mod tests { }; use std::{collections::HashMap, path::PathBuf}; - fn fill_setup_info() { - crate::init_setup_info(SetupInfo { + fn dummy_setup_info() -> SetupInfo { + SetupInfo { config: ProductConfig { fullname: "Proxmox VE".to_owned(), product: ProxmoxProduct::PVE, @@ -474,12 +467,12 @@ mod tests { locations: IsoLocations { iso: PathBuf::new(), }, - }); + } } #[test] fn network_options_from_setup_network_info() { - fill_setup_info(); + let setup = dummy_setup_info(); let mut interfaces = HashMap::new(); interfaces.insert( @@ -512,7 +505,7 @@ mod tests { }; assert_eq!( - NetworkOptions::from(&info), + NetworkOptions::defaults_from(&setup, &info), NetworkOptions { ifname: "eth0".to_owned(), fqdn: Fqdn::from("foo.bar.com").unwrap(), @@ -524,7 +517,7 @@ mod tests { info.hostname = None; assert_eq!( - NetworkOptions::from(&info), + NetworkOptions::defaults_from(&setup, &info), NetworkOptions { ifname: "eth0".to_owned(), fqdn: Fqdn::from("pve.bar.com").unwrap(), @@ -536,7 +529,7 @@ mod tests { info.dns.domain = None; assert_eq!( - NetworkOptions::from(&info), + NetworkOptions::defaults_from(&setup, &info), NetworkOptions { ifname: "eth0".to_owned(), fqdn: Fqdn::from("pve.example.invalid").unwrap(), @@ -548,7 +541,7 @@ mod tests { info.hostname = Some("foo".to_owned()); assert_eq!( - NetworkOptions::from(&info), + NetworkOptions::defaults_from(&setup, &info), NetworkOptions { ifname: "eth0".to_owned(), fqdn: Fqdn::from("foo.example.invalid").unwrap(), diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs index 7238131..5575759 100644 --- a/proxmox-tui-installer/src/setup.rs +++ b/proxmox-tui-installer/src/setup.rs @@ -196,12 +196,10 @@ impl From for InstallConfig { mngmt_nic: options.network.ifname, - hostname: options - .network - .fqdn - .host() - .unwrap_or_else(|| crate::current_product().default_hostname()) - .to_owned(), + // Safety: At this point, it is know that we have a valid FQDN, as + // this is set by the TUI network panel, which only lets the user + // continue if a valid FQDN is provided. + hostname: options.network.fqdn.host().expect("valid FQDN").to_owned(), domain: options.network.fqdn.domain(), cidr: options.network.address, gateway: options.network.gateway, -- 2.42.0 From d.csapak at proxmox.com Wed Oct 25 11:08:59 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Wed, 25 Oct 2023 11:08:59 +0200 Subject: [pve-devel] [PATCH pve-xtermjs] xtermjs: try to detect hardware support for webgl2 Message-ID: <20231025090859.2235027-1-d.csapak@proxmox.com> with the new webgl renderer, chrome/chromium has buggy software support for emulating this (see [0]), so we have to detect that manually and prevent loading the addon. This fixes the issue that on chrome without hw support, it would not always render every character. Firefox does not have support for a software renderer and the loading/detection throws an exception, falling back to the default renderer. 0: https://github.com/xtermjs/xterm.js/issues/4574 Signed-off-by: Dominik Csapak --- tested with and without webgl hardware support on firefox/chrome/chromium xterm.js/src/main.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/xterm.js/src/main.js b/xterm.js/src/main.js index 85cc399..1575697 100644 --- a/xterm.js/src/main.js +++ b/xterm.js/src/main.js @@ -154,9 +154,15 @@ function createTerminal() { term = new Terminal(getTerminalSettings()); term.open(terminalContainer); term.loadAddon(fitAddon); + let loadedWebgl = false; try { - term.loadAddon(webglAddon); - } catch (_e) { + if (detectWebgl()) { + term.loadAddon(webglAddon); + loadedWebgl = true; + } + } catch (_e) { } + + if (!loadedWebgl) { console.warn("webgl-addon loading failed, falling back to regular dom renderer"); } @@ -384,3 +390,15 @@ function errorTerminal(event) { term.dispose(); updateState(states.disconnected, event.msg, event.code); } + +// try to detect hardware acceleration, can throw an exception if there is no webgl support +// +// for chrome we have to give the parameter, otherwise it'll use its software renderer +// which is buggy: https://github.com/xtermjs/xterm.js/issues/4574 +// +// firefox will fail on the getContext anyway if there is not hardware support +function detectWebgl() { + const canvas = document.createElement("canvas"); + const gl = canvas.getContext("webgl2", { failIfMajorPerformanceCaveat: true }); + return !!gl; +} -- 2.30.2 From m.sandoval at proxmox.com Wed Oct 25 11:55:32 2023 From: m.sandoval at proxmox.com (Maximiliano Sandoval R) Date: Wed, 25 Oct 2023 11:55:32 +0200 Subject: [pve-devel] [PATCH docs] ha: Explicitly mention that migrations only affect HA guests Message-ID: <20231025095532.54953-1-m.sandoval@proxmox.com> Signed-off-by: Maximiliano Sandoval R --- ha-manager.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ha-manager.adoc b/ha-manager.adoc index 6bbd267..7f1ba3d 100644 --- a/ha-manager.adoc +++ b/ha-manager.adoc @@ -854,7 +854,7 @@ Maintenance Mode ~~~~~~~~~~~~~~~~ Enabling the manual maintenance mode will mark the node as unavailable for -operation, this in turn will migrate away all services to other nodes, which +operation, this in turn will migrate away all HA services to other nodes, which are selected through the configured cluster resource scheduler (CRS) mode. During migration the original node will be recorded, so that the service can be moved back to to that node as soon as the maintenance mode is disabled, and it -- 2.39.2 From d.csapak at proxmox.com Wed Oct 25 13:09:38 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Wed, 25 Oct 2023 13:09:38 +0200 Subject: [pve-devel] [PATCH manager v2] ui: wizards: allow adding tags in the qemu/lxc create wizard Message-ID: <20231025110938.3854872-1-d.csapak@proxmox.com> in the general tab in the advanced section. For that to work, we introduce a new option for the TagEditContainer named 'editOnly', which controls now the cancel/finish buttons, automatically enter edit mode and disable enter/escape keypresses. We also prevent now the loading of tags while in edit mode, so the tags don't change while editing (this can be jarring and unexpected). Then we wrap that all in a FieldSet that implements the Field mixin, so we can easily use that in the wizard. There we set a maxHeight so that the field can grow so that it still fits in the wizard. To properly align the input with the '+' button, we have to add a custom css class there. (In the hbox we could set the alignment, but this is not possible in the 'column' layout) Signed-off-by: Dominik Csapak --- changes from v1: * rebase on master * drop unnecessary/applied patches * wrap the tagedit in a fieldset to better emphasize that its an (unconventional) field, and move the submit logic there, keeping the wizard a bit cleaner www/css/ext6-pve.css | 5 +++ www/manager6/Makefile | 1 + www/manager6/form/TagEdit.js | 35 +++++++++++++++-- www/manager6/form/TagFieldSet.js | 64 +++++++++++++++++++++++++++++++ www/manager6/lxc/CreateWizard.js | 7 ++++ www/manager6/qemu/CreateWizard.js | 9 +++++ 6 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 www/manager6/form/TagFieldSet.js diff --git a/www/css/ext6-pve.css b/www/css/ext6-pve.css index 85cf4039..105adc45 100644 --- a/www/css/ext6-pve.css +++ b/www/css/ext6-pve.css @@ -721,3 +721,8 @@ table.osds td:first-of-type { .pmx-opacity-75 { opacity: 0.75; } + +/* tag edit fields must be aligned manually in the fieldset */ +.proxmox-tag-fieldset.proxmox-tags-full .x-component.x-column { + margin: 2px; +} diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 17e0ad05..fa110035 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -87,6 +87,7 @@ JSSRC= \ form/Tag.js \ form/TagEdit.js \ form/MultiFileButton.js \ + form/TagFieldSet.js \ grid/BackupView.js \ grid/FirewallAliases.js \ grid/FirewallOptions.js \ diff --git a/www/manager6/form/TagEdit.js b/www/manager6/form/TagEdit.js index 094f4462..7d5b19ec 100644 --- a/www/manager6/form/TagEdit.js +++ b/www/manager6/form/TagEdit.js @@ -9,6 +9,7 @@ Ext.define('PVE.panel.TagEditContainer', { // set to false to hide the 'no tags' field and the edit button canEdit: true, + editOnly: false, controller: { xclass: 'Ext.app.ViewController', @@ -216,6 +217,9 @@ Ext.define('PVE.panel.TagEditContainer', { me.tagsChanged(); }, keypress: function(key) { + if (vm.get('hideFinishButtons')) { + return; + } if (key === 'Enter') { me.editClick(); } else if (key === 'Escape') { @@ -253,20 +257,40 @@ Ext.define('PVE.panel.TagEditContainer', { me.loadTags(view.tags); } me.getViewModel().set('canEdit', view.canEdit); + me.getViewModel().set('editOnly', view.editOnly); me.mon(Ext.GlobalEvents, 'loadedUiOptions', () => { + let vm = me.getViewModel(); view.toggleCls('hide-handles', PVE.UIOptions.shouldSortTags()); - me.loadTags(me.oldTags, true); // refresh tag colors and order + me.loadTags(me.oldTags, !vm.get('editMode')); // refresh tag colors and order }); + + if (view.editOnly) { + me.toggleEdit(); + } }, }, + getTags: function() { + let me =this; + let controller = me.getController(); + let tags = []; + controller.forEachTag((cmp) => { + if (cmp.tag.length) { + tags.push(cmp.tag); + } + }); + + return tags; + }, + viewModel: { data: { tagCount: 0, editMode: false, canEdit: true, isDirty: false, + editOnly: true, }, formulas: { @@ -276,6 +300,9 @@ Ext.define('PVE.panel.TagEditContainer', { hideEditBtn: function(get) { return get('editMode') || !get('canEdit'); }, + hideFinishButtons: function(get) { + return !get('editMode') || get('editOnly'); + }, }, }, @@ -311,7 +338,7 @@ Ext.define('PVE.panel.TagEditContainer', { xtype: 'tbseparator', ui: 'horizontal', bind: { - hidden: '{!editMode}', + hidden: '{hideFinishButtons}', }, hidden: true, }, @@ -320,7 +347,7 @@ Ext.define('PVE.panel.TagEditContainer', { iconCls: 'fa fa-times', tooltip: gettext('Cancel Edit'), bind: { - hidden: '{!editMode}', + hidden: '{hideFinishButtons}', }, hidden: true, margin: '0 5 0 0', @@ -332,7 +359,7 @@ Ext.define('PVE.panel.TagEditContainer', { iconCls: 'fa fa-check', tooltip: gettext('Finish Edit'), bind: { - hidden: '{!editMode}', + hidden: '{hideFinishButtons}', disabled: '{!isDirty}', }, hidden: true, diff --git a/www/manager6/form/TagFieldSet.js b/www/manager6/form/TagFieldSet.js new file mode 100644 index 00000000..132b215c --- /dev/null +++ b/www/manager6/form/TagFieldSet.js @@ -0,0 +1,64 @@ +Ext.define('PVE.form.TagFieldSet', { + extend: 'Ext.form.FieldSet', + alias: 'widget.pveTagFieldSet', + mixins: ['Ext.form.field.Field'], + + title: gettext('Tags'), + padding: '0 5 5 5', + + getValue: function() { + let me = this; + let tags = me.down('pveTagEditContainer').getTags().filter(t => t !== ''); + return tags.join(';'); + }, + + setValue: function(value) { + let me = this; + value ??= []; + if (!Ext.isArray(value)) { + value = value.split(/[;, ]/).filter(t => t !== ''); + } + me.down('pveTagEditContainer').loadTags(value.join(';')); + }, + + getErrors: function(value) { + value ??= []; + if (!Ext.isArray(value)) { + value = value.split(/[;, ]/).filter(t => t !== ''); + } + if (value.some(t => !t.match(PVE.Utils.tagCharRegex))) { + return [gettext("Tags contain invalid characters.")]; + } + return []; + }, + + getSubmitData: function() { + let me = this; + let value = me.getValue(); + if (me.disabled || !me.submitValue || value === '') { + return null; + } + let data = {}; + data[me.getName()] = value; + return data; + }, + + layout: 'fit', + + items: [ + { + xtype: 'pveTagEditContainer', + userCls: 'proxmox-tags-full proxmox-tag-fieldset', + editOnly: true, + allowBlank: true, + layout: 'column', + scrollable: true, + }, + ], + + initComponent: function() { + let me = this; + me.callParent(); + me.initField(); + }, +}); diff --git a/www/manager6/lxc/CreateWizard.js b/www/manager6/lxc/CreateWizard.js index e3635297..b57b3050 100644 --- a/www/manager6/lxc/CreateWizard.js +++ b/www/manager6/lxc/CreateWizard.js @@ -178,6 +178,13 @@ Ext.define('PVE.lxc.CreateWizard', { }, }, ], + advancedColumnB: [ + { + xtype: 'pveTagFieldSet', + name: 'tags', + maxHeight: 150, + }, + ], }, { xtype: 'inputpanel', diff --git a/www/manager6/qemu/CreateWizard.js b/www/manager6/qemu/CreateWizard.js index a65067ea..74b1feb6 100644 --- a/www/manager6/qemu/CreateWizard.js +++ b/www/manager6/qemu/CreateWizard.js @@ -108,6 +108,15 @@ Ext.define('PVE.qemu.CreateWizard', { fieldLabel: gettext('Shutdown timeout'), }, ], + + advancedColumnB: [ + { + xtype: 'pveTagFieldSet', + name: 'tags', + maxHeight: 150, + }, + ], + onGetValues: function(values) { ['name', 'pool', 'onboot', 'agent'].forEach(function(field) { if (!values[field]) { -- 2.30.2 From t.lamprecht at proxmox.com Wed Oct 25 13:21:35 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Wed, 25 Oct 2023 13:21:35 +0200 Subject: [pve-devel] applied-series: [PATCH-SERIES v3 qemu] update to QEMU 8.1.2 In-Reply-To: <20231017121012.132636-1-f.ebner@proxmox.com> References: <20231017121012.132636-1-f.ebner@proxmox.com> Message-ID: <27e60ed6-97de-4b7b-8d38-7015f7cca52d@proxmox.com> Am 17/10/2023 um 14:10 schrieb Fiona Ebner: > Patch changes: > > For backup, opening the backup dump block driver needed to be adapted, > because of coroutine context changes. > > Block graph locking was disabled, because of deadlocks. > > Snapshot code has a huge performance regression which required a > workaround. > > > Meta-changes: > > Use --disable-download options to avoid automatic downloads during > build, but require the user to do so once themselves. Also done when > initializing the submodule in the Makefile > > Switch back to using QEMU's keycodemapdb instead of splitting it out > in our build-dir. Wasn't updated since QEMU 6.0 anymore and the reason > for the split is not known. If anything pops up, we can re-do it and > document the reason this time. > > Versioned Breaks for qemu-server required, because an upstream change > would prevent VM boot with most configurations (including default > one). > > > Changes in v3: > * Rebase on QEMU 8.1.2 which includes a few more TCG and > migration-related fixes and... > * ...remove previously picked-up patches that are included there. > > Changes in v2: > * Add stable fix for guest-triggerable SCSI crash. > * Make reverting graph locking complete. > > > Fiona Ebner (7): > d/rules: use disable-download option instead of git-submodules=ignore > buildsys: fixup submodule target > buildsys: use QEMU's keycodemapdb again > update submodule and patches to QEMU 8.1.2 > add patch to disable graph locking > add patch to avoid huge snapshot performance regression > d/control: add versioned Breaks for qemu-server <= 8.0.6 > applied series, many thanks for all your efforts on the QEMU front! From f.gruenbichler at proxmox.com Wed Oct 25 13:22:27 2023 From: f.gruenbichler at proxmox.com (Fabian =?iso-8859-1?q?Gr=FCnbichler?=) Date: Wed, 25 Oct 2023 13:22:27 +0200 Subject: [pve-devel] applied-series: [PATCH-SERIES pve-network/pve-manager/pve-docs] sdn: add isis controller In-Reply-To: <20230913113845.1066417-1-aderumier@odiso.com> References: <20230913113845.1066417-1-aderumier@odiso.com> Message-ID: <1698232937.bg0jipazpg.astroid@yuna.none> On September 13, 2023 1:38 pm, Alexandre Derumier wrote: > Hi, > This patch serie add support for the isis routing protocol for underlay network, > instead bgp. > > This was a request of a proxmox user, the implementation has been tested > https://forum.proxmox.com/threads/integrating-proxmox-sdn-with-existing-sdn-network.131946 > > > pve-network: > > Alexandre Derumier (3): > controller: evpn: fix find_bgp_controller > controllers: frr: add parsing of "interfaces" section > controllers: add isis router plugin > > src/PVE/Network/SDN/Controllers.pm | 2 + > src/PVE/Network/SDN/Controllers/EvpnPlugin.pm | 57 ++++++-- > src/PVE/Network/SDN/Controllers/IsisPlugin.pm | 123 ++++++++++++++++++ > src/PVE/Network/SDN/Controllers/Makefile | 2 +- > src/PVE/Network/SDN/Zones/EvpnPlugin.pm | 10 +- > .../evpn/isis/expected_controller_config | 58 +++++++++ > .../zones/evpn/isis/expected_sdn_interfaces | 41 ++++++ > src/test/zones/evpn/isis/interfaces | 7 + > src/test/zones/evpn/isis/sdn_config | 49 +++++++ > .../isis_loopback/expected_controller_config | 59 +++++++++ > .../isis_loopback/expected_sdn_interfaces | 41 ++++++ > src/test/zones/evpn/isis_loopback/interfaces | 12 ++ > src/test/zones/evpn/isis_loopback/sdn_config | 50 +++++++ > 13 files changed, 499 insertions(+), 12 deletions(-) > create mode 100644 src/PVE/Network/SDN/Controllers/IsisPlugin.pm > create mode 100644 src/test/zones/evpn/isis/expected_controller_config > create mode 100644 src/test/zones/evpn/isis/expected_sdn_interfaces > create mode 100644 src/test/zones/evpn/isis/interfaces > create mode 100644 src/test/zones/evpn/isis/sdn_config > create mode 100644 src/test/zones/evpn/isis_loopback/expected_controller_config > create mode 100644 src/test/zones/evpn/isis_loopback/expected_sdn_interfaces > create mode 100644 src/test/zones/evpn/isis_loopback/interfaces > create mode 100644 src/test/zones/evpn/isis_loopback/sdn_config > > > pve-manager: > > Alexandre Derumier (1): > sdn: controllers: add isis controller > > www/manager6/Makefile | 1 + > www/manager6/Utils.js | 5 ++ > www/manager6/sdn/controllers/IsisEdit.js | 61 ++++++++++++++++++++++++ > 3 files changed, 67 insertions(+) > create mode 100644 www/manager6/sdn/controllers/IsisEdit.js > > > pve-docs: > > Alexandre Derumier (2): > sdn: add notes about bgp controller > sdn: add isis controller documentation > > pvesdn.adoc | 25 +++++++++++++++++++++++++ > 1 file changed, 25 insertions(+) > > -- > 2.39.2 > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > From f.gruenbichler at proxmox.com Wed Oct 25 13:22:46 2023 From: f.gruenbichler at proxmox.com (Fabian =?iso-8859-1?q?Gr=FCnbichler?=) Date: Wed, 25 Oct 2023 13:22:46 +0200 Subject: [pve-devel] applied-series: [PATCH-SERIES pve-network/qemu-server/pve-container] cleanup for static mac registration In-Reply-To: <20230926073942.3212260-1-aderumier@odiso.com> References: <20230926073942.3212260-1-aderumier@odiso.com> Message-ID: <1698232955.0gvltghwu2.astroid@yuna.none> On September 26, 2023 9:39 am, Alexandre Derumier wrote: > - add|del_bridge_fdb: remove unused firewall param > - sdn: move add|del_bridge_fdb to plugin to be overridable > - sdn: evpn : add disable-bridge-learning option > > pve-network : > > Alexandre Derumier (3): > zones: add|del_bridge_fdb : remove firewall param > zones: add add|del_bridge_fdb to plugins > zones: evpn: add disable-bridge-learning > > src/PVE/Network/SDN/Zones.pm | 12 ++++++------ > src/PVE/Network/SDN/Zones/EvpnPlugin.pm | 2 +- > src/PVE/Network/SDN/Zones/Plugin.pm | 12 ++++++++++++ > 3 files changed, 19 insertions(+), 7 deletions(-) > > qemu-server: > > Alexandre Derumier (1): > add|del_bridge_fdb: remove unused firewall param > > PVE/QemuServer.pm | 8 ++++---- > 1 file changed, 4 insertions(+), 4 deletions(-) > > pve-container: > > Alexandre Derumier (1): > add_bridge_fbd: remove unused firewall param > > -- > 2.39.2 > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > > From h.duerr at proxmox.com Wed Oct 25 14:37:19 2023 From: h.duerr at proxmox.com (Hannes Duerr) Date: Wed, 25 Oct 2023 14:37:19 +0200 Subject: [pve-devel] [PATCH qemu-server] fix #4957: add vendor and product information passthrough for SCSI-Disks Message-ID: <20231025123719.38036-1-h.duerr@proxmox.com> Signed-off-by: Hannes Duerr --- PVE/QemuServer.pm | 12 ++++++++++++ PVE/QemuServer/Drive.pm | 26 ++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 2cd8948..69be3af 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -1482,6 +1482,18 @@ sub print_drivedevice_full { } $device .= ",wwn=$drive->{wwn}" if $drive->{wwn}; + # only scsi-hd supports passing vendor and product information + if ($devicetype eq 'hd') { + if (my $vendor = $drive->{vendor}) { + $vendor = URI::Escape::uri_unescape($vendor); + $device .= ",vendor=$vendor"; + } + if (my $product = $drive->{product}) { + $product = URI::Escape::uri_unescape($product); + $device .= ",product=$product"; + } + } + } elsif ($drive->{interface} eq 'ide' || $drive->{interface} eq 'sata') { my $maxdev = ($drive->{interface} eq 'sata') ? $PVE::QemuServer::Drive::MAX_SATA_DISKS : 2; my $controller = int($drive->{index} / $maxdev); diff --git a/PVE/QemuServer/Drive.pm b/PVE/QemuServer/Drive.pm index e24ba12..20efc2f 100644 --- a/PVE/QemuServer/Drive.pm +++ b/PVE/QemuServer/Drive.pm @@ -159,6 +159,28 @@ my %iothread_fmt = ( iothread => { optional => 1, }); +my %product_fmt = ( + product => { + type => 'string', + format => 'urlencoded', + format_description => 'product', + maxLength => 40*3, # *3 since it's %xx url enoded + description => "The drive's product name, url-encoded, up to 40 bytes long.", + optional => 1, + }, +); + +my %vendor_fmt = ( + vendor => { + type => 'string', + format => 'urlencoded', + format_description => 'vendor', + maxLength => 40*3, # *3 since it's %xx url enoded + description => "The drive's vendor name, url-encoded, up to 40 bytes long.", + optional => 1, + }, +); + my %model_fmt = ( model => { type => 'string', @@ -281,6 +303,8 @@ my $scsi_fmt = { %scsiblock_fmt, %ssd_fmt, %wwn_fmt, + %vendor_fmt, + %product_fmt, }; my $scsidesc = { optional => 1, @@ -404,6 +428,8 @@ my $alldrive_fmt = { %readonly_fmt, %scsiblock_fmt, %ssd_fmt, + %vendor_fmt, + %product_fmt, %wwn_fmt, %tpmversion_fmt, %efitype_fmt, -- 2.39.2 From f.gleumes at proxmox.com Wed Oct 25 15:07:19 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Wed, 25 Oct 2023 15:07:19 +0200 Subject: [pve-devel] [PATCH manager v2 4/5] fix #4497: cli/acme: detect eab and ask for credentials In-Reply-To: <20231025130720.195478-1-f.gleumes@proxmox.com> References: <20231025130720.195478-1-f.gleumes@proxmox.com> Message-ID: <20231025130720.195478-5-f.gleumes@proxmox.com> Since external account binding is advertised the same way as the ToS, it can be detected when creating an account and asked for if needed. Signed-off-by: Folke Gleumes --- Changes v1 -> v2: * If a custom directory is used, ask if EAB should be used, even when not required by the CA. PVE/CLI/pvenode.pm | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/PVE/CLI/pvenode.pm b/PVE/CLI/pvenode.pm index acef6c3b..a6fbb34e 100644 --- a/PVE/CLI/pvenode.pm +++ b/PVE/CLI/pvenode.pm @@ -83,6 +83,7 @@ __PACKAGE__->register_method({ code => sub { my ($param) = @_; + my $custom_directory = 0; if (!$param->{directory}) { my $directories = PVE::API2::ACMEAccount->get_directories({}); print "Directory endpoints:\n"; @@ -100,6 +101,7 @@ __PACKAGE__->register_method({ $selection = $1; if ($selection == $i) { $param->{directory} = $term->readline("Enter custom URL: "); + $custom_directory = 1; return; } elsif ($selection < $i && $selection >= 0) { $param->{directory} = $directories->[$selection]->{url}; @@ -117,8 +119,9 @@ __PACKAGE__->register_method({ } } print "\nAttempting to fetch Terms of Service from '$param->{directory}'..\n"; - my $tos = PVE::API2::ACMEAccount->get_tos({ directory => $param->{directory} }); - if ($tos) { + my $meta = PVE::API2::ACMEAccount->get_meta({ directory => $param->{directory} }); + if ($meta->{termsOfService}) { + my $tos = $meta->{termsOfService}; print "Terms of Service: $tos\n"; my $term = Term::ReadLine->new('pvenode'); my $agreed = $term->readline('Do you agree to the above terms? [y|N]: '); @@ -129,6 +132,25 @@ __PACKAGE__->register_method({ } else { print "No Terms of Service found, proceeding.\n"; } + + my $eab_enabled = $meta->{externalAccountRequired}; + if (!$eab_enabled && $custom_directory) { + my $term = Term::ReadLine->new('pvenode'); + my $agreed = $term->readline('Do you want to use external account binding? [y|N]: '); + $eab_enabled = ($agreed =~ /^y$/i); + } elsif ($eab_enabled) { + print "The CA requires external account binding.\n"; + } + if ($eab_enabled) { + print "You should have received a key id and a key from your CA.\n"; + my $term = Term::ReadLine->new('pvenode'); + my $eab_kid = $term->readline('Enter EAB key id: '); + my $eab_hmac_key = $term->readline('Enter EAB key: '); + + $param->{'eab-kid'} = $eab_kid; + $param->{'eab-hmac-key'} = $eab_hmac_key; + } + print "\nAttempting to register account with '$param->{directory}'..\n"; $upid_exit->(PVE::API2::ACMEAccount->register_account($param)); -- 2.39.2 From f.gleumes at proxmox.com Wed Oct 25 15:07:15 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Wed, 25 Oct 2023 15:07:15 +0200 Subject: [pve-devel] [PATCH acme/manager v2 0/5] fix #4497: add external account binding support Message-ID: <20231025130720.195478-1-f.gleumes@proxmox.com> Changes since v1: * fixed nit's * expanded meta endpoint by all return values defined in the rfc * expanded new_account signature by field for eab credentials * allow for eab even if not required This patch series adds functionality to use acme directiories that require the use of external account binding, as specified in rfc 8555 section 7.3.4. To avoid code duplication and redundant calls to the CA, the `/cluster/acme/tos` endpoint has been deprecated and it's function will be covered by the new `/cluster/acme/meta` endpoint, which exposes all meta information provided by the CA, including the flag indicating that EAB needs to be used. The underlying call to the CA remains the same. The CLI interface will only ask for the EAB credentials if needed, similar to how it works for the ToS. The patches have been tested to work with and without EAB by using pebble [0] as the CA. [0] https://github.com/letsencrypt/pebble acme: Folke Gleumes (1): fix #4497: add support for external account bindings src/PVE/ACME.pm | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) manager: Folke Gleumes (4): fix #4497: acme: add support for external account bindings api/acme: deprecate tos endpoint in favor of meta fix #4497: cli/acme: detect eab and ask for credentials ui/acme: switch to new meta endpoint PVE/API2/ACMEAccount.pm | 79 ++++++++++++++++++++++++++++++++++++++- PVE/CLI/pvenode.pm | 26 ++++++++++++- www/manager6/node/ACME.js | 12 ++++-- 3 files changed, 109 insertions(+), 8 deletions(-) -- 2.39.2 From f.gleumes at proxmox.com Wed Oct 25 15:07:20 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Wed, 25 Oct 2023 15:07:20 +0200 Subject: [pve-devel] [PATCH manager v2 5/5] ui/acme: switch to new meta endpoint In-Reply-To: <20231025130720.195478-1-f.gleumes@proxmox.com> References: <20231025130720.195478-1-f.gleumes@proxmox.com> Message-ID: <20231025130720.195478-6-f.gleumes@proxmox.com> Besides the switch from tos to meta endpoint, this fixes a visual bug, where the 'Accept TOS' button would show up, even if no ToS was needed. Signed-off-by: Folke Gleumes --- No changes in v2 www/manager6/node/ACME.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/www/manager6/node/ACME.js b/www/manager6/node/ACME.js index 9f1dabce..d64ac36f 100644 --- a/www/manager6/node/ACME.js +++ b/www/manager6/node/ACME.js @@ -79,15 +79,19 @@ Ext.define('PVE.node.ACMEAccountCreate', { checkbox.setHidden(true); Proxmox.Utils.API2Request({ - url: '/cluster/acme/tos', + url: '/cluster/acme/meta', method: 'GET', params: { directory: value, }, success: function(response, opt) { - field.setValue(response.result.data); - disp.setValue(response.result.data); - checkbox.setHidden(false); + if (response.result.data.termsOfService) { + field.setValue(response.result.data.termsOfService); + disp.setValue(response.result.data.termsOfService); + checkbox.setHidden(false); + } else { + disp.setValue(undefined); + } }, failure: function(response, opt) { Ext.Msg.alert(gettext('Error'), response.htmlStatus); -- 2.39.2 From f.gleumes at proxmox.com Wed Oct 25 15:07:17 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Wed, 25 Oct 2023 15:07:17 +0200 Subject: [pve-devel] [PATCH manager v2 2/5] fix #4497: acme: add support for external account bindings In-Reply-To: <20231025130720.195478-1-f.gleumes@proxmox.com> References: <20231025130720.195478-1-f.gleumes@proxmox.com> Message-ID: <20231025130720.195478-3-f.gleumes@proxmox.com> Signed-off-by: Folke Gleumes --- Changes v1 -> v2: * renamed api methods so they use '-' instead of '_' * use 'requires' in api to declare dependency instead of manual check PVE/API2/ACMEAccount.pm | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/PVE/API2/ACMEAccount.pm b/PVE/API2/ACMEAccount.pm index b790843a..994d2b49 100644 --- a/PVE/API2/ACMEAccount.pm +++ b/PVE/API2/ACMEAccount.pm @@ -115,6 +115,18 @@ __PACKAGE__->register_method ({ default => $acme_default_directory_url, optional => 1, }), + 'eab-kid' => { + type => 'string', + description => 'Key Identifier for External Account Binding.', + requires => 'eab-hmac-key', + optional => 1, + }, + 'eab-hmac-key' => { + type => 'string', + description => 'HMAC key for External Account Binding.', + requires => 'eab-kid', + optional => 1, + }, }, }, returns => { @@ -130,6 +142,9 @@ __PACKAGE__->register_method ({ my $account_file = "${acme_account_dir}/${account_name}"; mkdir $acme_account_dir if ! -e $acme_account_dir; + my $eab_kid = extract_param($param, 'eab-kid'); + my $eab_hmac_key = extract_param($param, 'eab-hmac-key'); + raise_param_exc({'name' => "ACME account config file '${account_name}' already exists."}) if -e $account_file; @@ -145,7 +160,13 @@ __PACKAGE__->register_method ({ print "Generating ACME account key..\n"; $acme->init(4096); print "Registering ACME account..\n"; - eval { $acme->new_account($param->{tos_url}, contact => $contact); }; + my $eab = (defined($eab_kid) and defined($eab_hmac_key)) ? { + kid => $eab_kid, + hmac_key => $eab_hmac_key + } : undef; + + eval { $acme->new_account($param->{tos_url}, $eab, contact => $contact); }; + if (my $err = $@) { unlink $account_file; die "Registration failed: $err\n"; -- 2.39.2 From f.gleumes at proxmox.com Wed Oct 25 15:07:16 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Wed, 25 Oct 2023 15:07:16 +0200 Subject: [pve-devel] [PATCH acme v2 1/5] fix #4497: add support for external account bindings In-Reply-To: <20231025130720.195478-1-f.gleumes@proxmox.com> References: <20231025130720.195478-1-f.gleumes@proxmox.com> Message-ID: <20231025130720.195478-2-f.gleumes@proxmox.com> implementation acording to rfc855 section 7.3.4 Signed-off-by: Folke Gleumes --- Changes v1 -> v2: Switched from including the eab credentials in the info hash, to passing them in their own variable. This still unfortunately still breaks the api, but doesn't potentially expose secrets and is cleaner then purging the values from the hash afterwards. src/PVE/ACME.pm | 42 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/src/PVE/ACME.pm b/src/PVE/ACME.pm index 3f66182..7b3840b 100644 --- a/src/PVE/ACME.pm +++ b/src/PVE/ACME.pm @@ -7,10 +7,10 @@ use POSIX; use Data::Dumper; use Date::Parse; -use MIME::Base64 qw(encode_base64url); +use MIME::Base64 qw(encode_base64url decode_base64); use File::Path qw(make_path); use JSON; -use Digest::SHA qw(sha256 sha256_hex); +use Digest::SHA qw(sha256 sha256_hex hmac_sha256); use HTTP::Request; use LWP::UserAgent; @@ -251,6 +251,28 @@ sub jws { }; } +# EAB signing using the HS256 alg (HMAC/SHA256). +sub external_account_binding_jws { + my ($self, $eab_kid, $eab_hmac_key, $url) = @_; + + my $protected = { + alg => 'HS256', + kid => $eab_kid, + url => $url, + }; + $protected = encode(tojs($protected)); + + my $payload = encode(tojs($self->jwk())); + my $signdata = "$protected.$payload"; + my $signature = encode(hmac_sha256($signdata, $eab_hmac_key)); + + return { + protected => $protected, + payload => $payload, + signature => $signature, + }; +} + sub __get_result { my ($resp, $code, $plain) = @_; @@ -300,8 +322,8 @@ sub list_methods { } # return (optional) meta directory entry. -# this is public because it might contain the ToS, which should be displayed -# and agreed to before creating an account +# this is public because it might contain the ToS and EAB requirements, +# which have to be considered before creating an account sub get_meta { my ($self) = @_; my $methods = $self->__get_methods(); @@ -331,13 +353,23 @@ sub __new_account { # Create a new account using data in %info. # Optionally pass $tos_url to agree to the given Terms of Service +# Optionally pass $eab to use External Account Binding # POST to newAccount endpoint # Expects a '201 Created' reply # Saves and returns the account data sub new_account { - my ($self, $tos_url, %info) = @_; + my ($self, $tos_url, $eab, %info) = @_; my $url = $self->_method('newAccount'); + if ($eab) { + my $eab_hmac_key = decode_base64($eab->{hmac_key}); + $info{externalAccountBinding} = $self->external_account_binding_jws( + $eab->{kid}, + $eab_hmac_key, + $url + ); + } + if ($tos_url) { $self->{tos} = $tos_url; $info{termsOfServiceAgreed} = JSON::true; -- 2.39.2 From f.gleumes at proxmox.com Wed Oct 25 15:07:18 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Wed, 25 Oct 2023 15:07:18 +0200 Subject: [pve-devel] [PATCH manager v2 3/5] api/acme: deprecate tos endpoint in favor of meta In-Reply-To: <20231025130720.195478-1-f.gleumes@proxmox.com> References: <20231025130720.195478-1-f.gleumes@proxmox.com> Message-ID: <20231025130720.195478-4-f.gleumes@proxmox.com> The ToS endpoint ignored data that is needed to detect if EAB needs to be used. Instead of adding a new endpoint that does the same request, the tos endpoint is deprecated and replaced by the meta endpoint, that returns all information returned by the directory. Signed-off-by: Folke Gleumes --- Changes v1 -> v2: * Include additional possible return values in api definition PVE/API2/ACMEAccount.pm | 56 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/PVE/API2/ACMEAccount.pm b/PVE/API2/ACMEAccount.pm index 994d2b49..aac3f527 100644 --- a/PVE/API2/ACMEAccount.pm +++ b/PVE/API2/ACMEAccount.pm @@ -62,6 +62,7 @@ __PACKAGE__->register_method ({ return [ { name => 'account' }, { name => 'tos' }, + { name => 'meta' }, { name => 'directories' }, { name => 'plugins' }, { name => 'challenge-schema' }, @@ -329,11 +330,12 @@ __PACKAGE__->register_method ({ return $update_account->($param, 'deactivate', status => 'deactivated'); }}); +# TODO: deprecated, remove with pve 9 __PACKAGE__->register_method ({ name => 'get_tos', path => 'tos', method => 'GET', - description => "Retrieve ACME TermsOfService URL from CA.", + description => "Retrieve ACME TermsOfService URL from CA. Deprecated, please use /cluster/acme/meta.", permissions => { user => 'all' }, parameters => { additionalProperties => 0, @@ -360,6 +362,58 @@ __PACKAGE__->register_method ({ return $meta ? $meta->{termsOfService} : undef; }}); +__PACKAGE__->register_method ({ + name => 'get_meta', + path => 'meta', + method => 'GET', + description => "Retrieve ACME Directory Meta Information", + permissions => { user => 'all' }, + parameters => { + additionalProperties => 0, + properties => { + directory => get_standard_option('pve-acme-directory-url', { + default => $acme_default_directory_url, + optional => 1, + }), + }, + }, + returns => { + type => 'object', + additionalProperties => 1, + properties => { + termsOfService => { + type => 'string', + optional => 1, + description => 'ACME TermsOfService URL.', + }, + externalAccountRequired => { + type => 'boolean', + optional => 1, + description => 'EAB Required' + }, + website => { + type => 'string', + optional => 1, + description => 'URL to more information about the ACME server.' + }, + caaIdentities => { + type => 'string', + optional => 1, + description => 'Hostnames referring to the ACME servers.' + }, + }, + }, + code => sub { + my ($param) = @_; + + my $directory = extract_param($param, 'directory') // $acme_default_directory_url; + + my $acme = PVE::ACME->new(undef, $directory); + my $meta = $acme->get_meta(); + + return $meta; + }}); + __PACKAGE__->register_method ({ name => 'get_directories', path => 'directories', -- 2.39.2 From f.gruenbichler at proxmox.com Wed Oct 25 15:34:35 2023 From: f.gruenbichler at proxmox.com (=?UTF-8?q?Fabian=20Gr=C3=BCnbichler?=) Date: Wed, 25 Oct 2023 15:34:35 +0200 Subject: [pve-devel] [PATCH manager] subscription: remove ceph APT auth if invalid Message-ID: <20231025133435.3217455-1-f.gruenbichler@proxmox.com> like we do for the main APT auth file(s) in proxmox-subscription. Signed-off-by: Fabian Gr?nbichler --- PVE/API2/Subscription.pm | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/PVE/API2/Subscription.pm b/PVE/API2/Subscription.pm index 7c1e300ba..836e7a86f 100644 --- a/PVE/API2/Subscription.pm +++ b/PVE/API2/Subscription.pm @@ -94,14 +94,18 @@ sub write_etc_subscription { Proxmox::RS::Subscription::write_subscription( $filename, "/etc/apt/auth.conf.d/pve.conf", "enterprise.proxmox.com/debian/pve", $info); - # FIXME: improve this, especially the selection of valid ceph-releases - # NOTE: currently we should add future ceph releases as early as possible, to ensure that - my $ceph_auth = ''; - for my $ceph_release ('quincy', 'reef') { - $ceph_auth .= "machine enterprise.proxmox.com/debian/ceph-${ceph_release}" + if (!(defined($info->{key}) && defined($info->{serverid}))) { + unlink "/etc/apt/auth.conf.d/ceph.conf"; + } else { + # FIXME: improve this, especially the selection of valid ceph-releases + # NOTE: currently we should add future ceph releases as early as possible, to ensure that + my $ceph_auth = ''; + for my $ceph_release ('quincy', 'reef') { + $ceph_auth .= "machine enterprise.proxmox.com/debian/ceph-${ceph_release}" ." login $info->{key} password $info->{serverid}\n" + } + PVE::Tools::file_set_contents("/etc/apt/auth.conf.d/ceph.conf", $ceph_auth); } - PVE::Tools::file_set_contents("/etc/apt/auth.conf.d/ceph.conf", $ceph_auth); } __PACKAGE__->register_method ({ -- 2.39.2 From d.csapak at proxmox.com Wed Oct 25 15:45:36 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Wed, 25 Oct 2023 15:45:36 +0200 Subject: [pve-devel] [PATCH v5 storage 1/1] fix #254: iscsi: add support for multipath iscsi targets In-Reply-To: <20231023174508.385682-2-ykonotopov@gnome.org> References: <20231023174508.385682-1-ykonotopov@gnome.org> <20231023174508.385682-2-ykonotopov@gnome.org> Message-ID: One nit inline, but that could be fixed up when applying too probably? Also, and that was previously also the case, if the first (or any) portal is not reachable, we get a big warning about iscsiadm --login not being able to login every 10 seconds in pvestatd. It's nothing your patch changes, but now it also can happen when the storage works, making it more unlikely that the admin will check the logs for an error. We could extend the storage status from a simple binary result to an enum/list with e.g. ok/warning/fail and that could be shown in the ui for such cases. (Again, this has nothing directly to do with your patch, i just wanted to mention it, since this change emphasizes the need for it IMO) Otherwise consider this patch: Reviewed-By: Dominik Csapak Tested-By: Dominik Csapak On 10/23/23 19:45, Yuri Konotopov wrote: > With this patch Proxmox now tries to login to all discovered portals in > case some of them are not logged yet. > In case of multipath configuration when initially configured portal is > missing for some reason Proxmox don't lose iscsi storage now and can > succesfully restore iscsi connection between reboots. > > Signed-off-by: Yuri Konotopov > --- > src/PVE/Storage.pm | 2 +- > src/PVE/Storage/ISCSIPlugin.pm | 117 +++++++++++++++++++++++++-------- > 2 files changed, 89 insertions(+), 30 deletions(-) > > diff --git a/src/PVE/Storage.pm b/src/PVE/Storage.pm > index a4d85e1..ec5f5bd 100755 > --- a/src/PVE/Storage.pm > +++ b/src/PVE/Storage.pm > @@ -1432,7 +1432,7 @@ sub scan_iscsi { > die "unable to parse/resolve portal address '${portal_in}'\n"; > } > > - return PVE::Storage::ISCSIPlugin::iscsi_discovery($portal); > + return PVE::Storage::ISCSIPlugin::iscsi_discovery([ $portal ]); > } > > sub storage_default_format { > diff --git a/src/PVE/Storage/ISCSIPlugin.pm b/src/PVE/Storage/ISCSIPlugin.pm > index a79fcb0..b4ab1dd 100644 > --- a/src/PVE/Storage/ISCSIPlugin.pm > +++ b/src/PVE/Storage/ISCSIPlugin.pm > @@ -18,6 +18,9 @@ use base qw(PVE::Storage::Plugin); > my $ISCSIADM = '/usr/bin/iscsiadm'; > $ISCSIADM = undef if ! -X $ISCSIADM; > > +# Example: 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f > +my $ISCSI_TARGET_RE = qr/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/; > + > sub check_iscsi_support { > my $noerr = shift; > > @@ -45,11 +48,12 @@ sub iscsi_session_list { > eval { > run_command($cmd, errmsg => 'iscsi session scan failed', outfunc => sub { > my $line = shift; > - > - if ($line =~ m/^tcp:\s+\[(\S+)\]\s+\S+\s+(\S+)(\s+\S+)?\s*$/) { > - my ($session, $target) = ($1, $2); > + # example: tcp: [1] 192.168.122.252:3260,1 iqn.2003-01.org.linux-iscsi.proxmox-nfs.x8664:sn.00567885ba8f (non-flash) > + if ($line =~ m/^tcp:\s+\[(\S+)\]\s+((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s+\S+?\s*$/) { > + my ($session_id, $portal, $target) = ($1, $2, $3); > # there can be several sessions per target (multipath) > - push @{$res->{$target}}, $session; > + my %session = ( session_id => $session_id, portal => $portal ); > + push @{$res->{$target}}, \%session; nit: since you don't need the hash afterwards anymore, you could inline that with: --- push @${res->{$target}}, { session_id => $session_id, portal => $portal }; --- bot no hard feelings for that, and could potentially be fixed up. > } > }); > }; > @@ -68,42 +72,77 @@ sub iscsi_test_portal { > return PVE::Network::tcp_ping($server, $port || 3260, 2); > } > > -sub iscsi_discovery { > - my ($portal) = @_; > +sub iscsi_portals { > + my ($target, $portal_in) = @_; > > check_iscsi_support (); > > - my $res = {}; > - return $res if !iscsi_test_portal($portal); # fixme: raise exception here? > + my $res = []; > + my $cmd = [$ISCSIADM, '--mode', 'node']; > + eval { > + run_command($cmd, outfunc => sub { > + my $line = shift; > > - my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; > - run_command($cmd, outfunc => sub { > - my $line = shift; > + if ($line =~ $ISCSI_TARGET_RE) { > + my ($portal, $portal_target) = ($1, $2); > + if ($portal_target eq $target) { > + push @{$res}, $portal; > + } > + } > + }); > + }; > > - if ($line =~ m/^((?:$IPV4RE|\[$IPV6RE\]):\d+)\,\S+\s+(\S+)\s*$/) { > - my $portal = $1; > - my $target = $2; > - # one target can have more than one portal (multipath). > - push @{$res->{$target}}, $portal; > - } > - }); > + if ($@) { > + warn $@; > + return [ $portal_in ]; > + } > + > + return $res; > +} > + > +sub iscsi_discovery { > + my ($portals) = @_; > + > + check_iscsi_support (); > + > + my $res = {}; > + for my $portal ($portals->@*) { > + next if !iscsi_test_portal($portal); # fixme: raise exception here? > + > + my $cmd = [$ISCSIADM, '--mode', 'discovery', '--type', 'sendtargets', '--portal', $portal]; > + eval { > + run_command($cmd, outfunc => sub { > + my $line = shift; > + > + if ($line =~ $ISCSI_TARGET_RE) { > + my ($portal, $target) = ($1, $2); > + # one target can have more than one portal (multipath) > + # and sendtargets should return all of them in single call > + push @{$res->{$target}}, $portal; > + } > + }); > + }; > + > + # In case of multipath we can stop after receiving targets from any available portal > + last if scalar(keys %$res) > 0; > + } > > return $res; > } > > sub iscsi_login { > - my ($target, $portal_in) = @_; > + my ($target, $portals) = @_; > > check_iscsi_support(); > > - eval { iscsi_discovery($portal_in); }; > + eval { iscsi_discovery($portals); }; > warn $@ if $@; > > run_command([$ISCSIADM, '--mode', 'node', '--targetname', $target, '--login']); > } > > sub iscsi_logout { > - my ($target, $portal) = @_; > + my ($target) = @_; > > check_iscsi_support(); > > @@ -133,7 +172,7 @@ sub iscsi_session_rescan { > } > > foreach my $session (@$session_list) { > - my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session, '--rescan']; > + my $cmd = [$ISCSIADM, '--mode', 'session', '--sid', $session->{session_id}, '--rescan']; > eval { run_command($cmd, outfunc => sub {}); }; > warn $@ if $@; > } > @@ -379,14 +418,28 @@ sub activate_storage { > > return if !check_iscsi_support(1); > > - my $session = iscsi_session($cache, $scfg->{target}); > + my $sessions = iscsi_session($cache, $scfg->{target}); > + my $portals = iscsi_portals($scfg->{target}, $scfg->{portal}); > + my $do_login = !defined($sessions); > > - if (!defined ($session)) { > - eval { iscsi_login($scfg->{target}, $scfg->{portal}); }; > + if (!$do_login) { > + # We should check that sessions for all portals are available > + my $session_portals = [ map { $_->{portal} } (@$sessions) ]; > + > + for my $portal (@$portals) { > + if (!grep(/^\Q$portal\E$/, @$session_portals)) { > + $do_login = 1; > + last; > + } > + } > + } > + > + if ($do_login) { > + eval { iscsi_login($scfg->{target}, $portals); }; > warn $@ if $@; > } else { > # make sure we get all devices > - iscsi_session_rescan($session); > + iscsi_session_rescan($sessions); > } > } > > @@ -396,15 +449,21 @@ sub deactivate_storage { > return if !check_iscsi_support(1); > > if (defined(iscsi_session($cache, $scfg->{target}))) { > - iscsi_logout($scfg->{target}, $scfg->{portal}); > + iscsi_logout($scfg->{target}); > } > } > > sub check_connection { > my ($class, $storeid, $scfg) = @_; > > - my $portal = $scfg->{portal}; > - return iscsi_test_portal($portal); > + my $portals = iscsi_portals($scfg->{target}, $scfg->{portal}); > + > + for my $portal (@$portals) { > + my $result = iscsi_test_portal($portal); > + return $result if $result; > + } > + > + return 0; > } > > sub volume_resize { From a.lauterer at proxmox.com Wed Oct 25 18:00:03 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 18:00:03 +0200 Subject: [pve-devel] [PATCH 04/12] common: make InstallZfsOption public In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231025160011.3617524-5-a.lauterer@proxmox.com> Signed-off-by: Aaron Lauterer --- proxmox-installer-common/src/setup.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/proxmox-installer-common/src/setup.rs b/proxmox-installer-common/src/setup.rs index a4947f1..a55f059 100644 --- a/proxmox-installer-common/src/setup.rs +++ b/proxmox-installer-common/src/setup.rs @@ -104,7 +104,7 @@ pub struct LocaleInfo { } #[derive(Serialize)] -struct InstallZfsOption { +pub struct InstallZfsOption { ashift: usize, #[serde(serialize_with = "serialize_as_display")] compress: ZfsCompressOption, -- 2.39.2 From a.lauterer at proxmox.com Wed Oct 25 18:00:05 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 18:00:05 +0200 Subject: [pve-devel] [PATCH 06/12] tui-installer: add dependency for new common crate In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231025160011.3617524-7-a.lauterer@proxmox.com> Signed-off-by: Aaron Lauterer --- proxmox-tui-installer/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/proxmox-tui-installer/Cargo.toml b/proxmox-tui-installer/Cargo.toml index 8a6eba8..fc653f0 100644 --- a/proxmox-tui-installer/Cargo.toml +++ b/proxmox-tui-installer/Cargo.toml @@ -12,3 +12,4 @@ cursive = { version = "0.20.0", default-features = false, features = ["termion-b serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" regex = "1.7" +proxmox-installer-common = { path = "../proxmox-installer-common" } -- 2.39.2 From a.lauterer at proxmox.com Wed Oct 25 18:00:11 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 18:00:11 +0200 Subject: [pve-devel] [PATCH 12/12] tui: remove unused read_json function In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231025160011.3617524-13-a.lauterer@proxmox.com> Signed-off-by: Aaron Lauterer --- proxmox-tui-installer/src/setup.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs index 211a96b..efcabed 100644 --- a/proxmox-tui-installer/src/setup.rs +++ b/proxmox-tui-installer/src/setup.rs @@ -1,13 +1,10 @@ use std::{ collections::HashMap, fmt, - fs::File, - io::BufReader, net::IpAddr, - path::Path, }; -use serde::{Deserialize, Serialize, Serializer}; +use serde::{Serialize, Serializer}; use crate::options::InstallerOptions; use proxmox_installer_common::{ @@ -129,13 +126,6 @@ impl From for InstallConfig { } } -pub fn read_json Deserialize<'de>, P: AsRef>(path: P) -> Result { - let file = File::open(path).map_err(|err| err.to_string())?; - let reader = BufReader::new(file); - - serde_json::from_reader(reader).map_err(|err| format!("failed to parse JSON: {err}")) -} - fn serialize_disk_opt(value: &Option, serializer: S) -> Result where S: Serializer, -- 2.39.2 From a.lauterer at proxmox.com Wed Oct 25 18:00:08 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 18:00:08 +0200 Subject: [pve-devel] [PATCH 09/12] common: add installer_setup method In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231025160011.3617524-10-a.lauterer@proxmox.com> moved over from the TUI installer Signed-off-by: Aaron Lauterer --- proxmox-installer-common/src/setup.rs | 37 +++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/proxmox-installer-common/src/setup.rs b/proxmox-installer-common/src/setup.rs index a55f059..34b00cb 100644 --- a/proxmox-installer-common/src/setup.rs +++ b/proxmox-installer-common/src/setup.rs @@ -103,6 +103,43 @@ pub struct LocaleInfo { pub kmap: HashMap, } +pub fn installer_setup(in_test_mode: bool) -> Result<(SetupInfo, LocaleInfo, RuntimeInfo), String> { + let base_path = if in_test_mode { "./testdir" } else { "/" }; + let mut path = PathBuf::from(base_path); + + path.push("run"); + path.push("proxmox-installer"); + + let installer_info: SetupInfo = { + let mut path = path.clone(); + path.push("iso-info.json"); + + read_json(&path).map_err(|err| format!("Failed to retrieve setup info: {err}"))? + }; + + let locale_info = { + let mut path = path.clone(); + path.push("locales.json"); + + read_json(&path).map_err(|err| format!("Failed to retrieve locale info: {err}"))? + }; + + let mut runtime_info: RuntimeInfo = { + let mut path = path.clone(); + path.push("run-env-info.json"); + + read_json(&path) + .map_err(|err| format!("Failed to retrieve runtime environment info: {err}"))? + }; + + runtime_info.disks.sort(); + if runtime_info.disks.is_empty() { + Err("The installer could not find any supported hard disks.".to_owned()) + } else { + Ok((installer_info, locale_info, runtime_info)) + } +} + #[derive(Serialize)] pub struct InstallZfsOption { ashift: usize, -- 2.39.2 From a.lauterer at proxmox.com Wed Oct 25 18:00:07 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 18:00:07 +0200 Subject: [pve-devel] [PATCH 08/12] tui: remove now unused utils.rs In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231025160011.3617524-9-a.lauterer@proxmox.com> all it did moved to the common crate Signed-off-by: Aaron Lauterer --- proxmox-tui-installer/src/utils.rs | 268 ----------------------------- 1 file changed, 268 deletions(-) delete mode 100644 proxmox-tui-installer/src/utils.rs diff --git a/proxmox-tui-installer/src/utils.rs b/proxmox-tui-installer/src/utils.rs deleted file mode 100644 index 89349ed..0000000 --- a/proxmox-tui-installer/src/utils.rs +++ /dev/null @@ -1,268 +0,0 @@ -use std::{ - fmt, - net::{AddrParseError, IpAddr}, - num::ParseIntError, - str::FromStr, -}; - -use serde::Deserialize; - -/// Possible errors that might occur when parsing CIDR addresses. -#[derive(Debug)] -pub enum CidrAddressParseError { - /// No delimiter for separating address and mask was found. - NoDelimiter, - /// The IP address part could not be parsed. - InvalidAddr(AddrParseError), - /// The mask could not be parsed. - InvalidMask(Option), -} - -/// An IP address (IPv4 or IPv6), including network mask. -/// -/// See the [`IpAddr`] type for more information how IP addresses are handled. -/// The mask is appropriately enforced to be `0 <= mask <= 32` for IPv4 or -/// `0 <= mask <= 128` for IPv6 addresses. -/// -/// # Examples -/// ``` -/// use std::net::{Ipv4Addr, Ipv6Addr}; -/// let ipv4 = CidrAddress::new(Ipv4Addr::new(192, 168, 0, 1), 24).unwrap(); -/// let ipv6 = CidrAddress::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xc0a8, 1), 32).unwrap(); -/// -/// assert_eq!(ipv4.to_string(), "192.168.0.1/24"); -/// assert_eq!(ipv6.to_string(), "2001:db8::c0a8:1/32"); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub struct CidrAddress { - addr: IpAddr, - mask: usize, -} - -impl CidrAddress { - /// Constructs a new CIDR address. - /// - /// It fails if the mask is invalid for the given IP address. - pub fn new>(addr: T, mask: usize) -> Result { - let addr = addr.into(); - - if mask > mask_limit(&addr) { - Err(CidrAddressParseError::InvalidMask(None)) - } else { - Ok(Self { addr, mask }) - } - } - - /// Returns only the IP address part of the address. - pub fn addr(&self) -> IpAddr { - self.addr - } - - /// Returns `true` if this address is an IPv4 address, `false` otherwise. - pub fn is_ipv4(&self) -> bool { - self.addr.is_ipv4() - } - - /// Returns `true` if this address is an IPv6 address, `false` otherwise. - pub fn is_ipv6(&self) -> bool { - self.addr.is_ipv6() - } - - /// Returns only the mask part of the address. - pub fn mask(&self) -> usize { - self.mask - } -} - -impl FromStr for CidrAddress { - type Err = CidrAddressParseError; - - fn from_str(s: &str) -> Result { - let (addr, mask) = s - .split_once('/') - .ok_or(CidrAddressParseError::NoDelimiter)?; - - let addr = addr.parse().map_err(CidrAddressParseError::InvalidAddr)?; - - let mask = mask - .parse() - .map_err(|err| CidrAddressParseError::InvalidMask(Some(err)))?; - - if mask > mask_limit(&addr) { - Err(CidrAddressParseError::InvalidMask(None)) - } else { - Ok(Self { addr, mask }) - } - } -} - -impl fmt::Display for CidrAddress { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}/{}", self.addr, self.mask) - } -} - -fn mask_limit(addr: &IpAddr) -> usize { - if addr.is_ipv4() { - 32 - } else { - 128 - } -} - -/// Possible errors that might occur when parsing FQDNs. -#[derive(Debug, Eq, PartialEq)] -pub enum FqdnParseError { - MissingHostname, - NumericHostname, - InvalidPart(String), -} - -impl fmt::Display for FqdnParseError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - use FqdnParseError::*; - match self { - MissingHostname => write!(f, "missing hostname part"), - NumericHostname => write!(f, "hostname cannot be purely numeric"), - InvalidPart(part) => write!( - f, - "FQDN must only consist of alphanumeric characters and dashes. Invalid part: '{part}'", - ), - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Fqdn { - parts: Vec, -} - -impl Fqdn { - pub fn from(fqdn: &str) -> Result { - let parts = fqdn - .split('.') - .map(ToOwned::to_owned) - .collect::>(); - - for part in &parts { - if !Self::validate_single(part) { - return Err(FqdnParseError::InvalidPart(part.clone())); - } - } - - if parts.len() < 2 { - Err(FqdnParseError::MissingHostname) - } else if parts[0].chars().all(|c| c.is_ascii_digit()) { - // Not allowed/supported on Debian systems. - Err(FqdnParseError::NumericHostname) - } else { - Ok(Self { parts }) - } - } - - pub fn host(&self) -> Option<&str> { - self.has_host().then_some(&self.parts[0]) - } - - pub fn domain(&self) -> String { - let parts = if self.has_host() { - &self.parts[1..] - } else { - &self.parts - }; - - parts.join(".") - } - - /// Checks whether the FQDN has a hostname associated with it, i.e. is has more than 1 part. - fn has_host(&self) -> bool { - self.parts.len() > 1 - } - - fn validate_single(s: &String) -> bool { - !s.is_empty() - // First character must be alphanumeric - && s.chars() - .next() - .map(|c| c.is_ascii_alphanumeric()) - .unwrap_or_default() - // .. last character as well, - && s.chars() - .last() - .map(|c| c.is_ascii_alphanumeric()) - .unwrap_or_default() - // and anything between must be alphanumeric or - - && s.chars() - .skip(1) - .take(s.len().saturating_sub(2)) - .all(|c| c.is_ascii_alphanumeric() || c == '-') - } -} - -impl FromStr for Fqdn { - type Err = FqdnParseError; - - fn from_str(value: &str) -> Result { - Self::from(value) - } -} - -impl fmt::Display for Fqdn { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.parts.join(".")) - } -} - -impl<'de> Deserialize<'de> for Fqdn { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let s: String = Deserialize::deserialize(deserializer)?; - s.parse() - .map_err(|_| serde::de::Error::custom("invalid FQDN")) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn fqdn_construct() { - use FqdnParseError::*; - assert!(Fqdn::from("foo.example.com").is_ok()); - assert!(Fqdn::from("foo-bar.com").is_ok()); - assert!(Fqdn::from("a-b.com").is_ok()); - - assert_eq!(Fqdn::from("foo"), Err(MissingHostname)); - - assert_eq!(Fqdn::from("-foo.com"), Err(InvalidPart("-foo".to_owned()))); - assert_eq!(Fqdn::from("foo-.com"), Err(InvalidPart("foo-".to_owned()))); - assert_eq!(Fqdn::from("foo.com-"), Err(InvalidPart("com-".to_owned()))); - assert_eq!(Fqdn::from("-o-.com"), Err(InvalidPart("-o-".to_owned()))); - - assert_eq!(Fqdn::from("123.com"), Err(NumericHostname)); - assert!(Fqdn::from("foo123.com").is_ok()); - assert!(Fqdn::from("123foo.com").is_ok()); - } - - #[test] - fn fqdn_parts() { - let fqdn = Fqdn::from("pve.example.com").unwrap(); - assert_eq!(fqdn.host().unwrap(), "pve"); - assert_eq!(fqdn.domain(), "example.com"); - assert_eq!( - fqdn.parts, - &["pve".to_owned(), "example".to_owned(), "com".to_owned()] - ); - } - - #[test] - fn fqdn_display() { - assert_eq!( - Fqdn::from("foo.example.com").unwrap().to_string(), - "foo.example.com" - ); - } -} -- 2.39.2 From a.lauterer at proxmox.com Wed Oct 25 18:00:02 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 18:00:02 +0200 Subject: [pve-devel] [PATCH 03/12] common: utils: add dependency for doc test In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231025160011.3617524-4-a.lauterer@proxmox.com> Was probably missed because it used to be in a binary crate where doc tests aren't run automatically. Signed-off-by: Aaron Lauterer --- proxmox-installer-common/src/utils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/proxmox-installer-common/src/utils.rs b/proxmox-installer-common/src/utils.rs index 89349ed..c038524 100644 --- a/proxmox-installer-common/src/utils.rs +++ b/proxmox-installer-common/src/utils.rs @@ -27,6 +27,7 @@ pub enum CidrAddressParseError { /// # Examples /// ``` /// use std::net::{Ipv4Addr, Ipv6Addr}; +/// use proxmox_installer_common::utils::CidrAddress; /// let ipv4 = CidrAddress::new(Ipv4Addr::new(192, 168, 0, 1), 24).unwrap(); /// let ipv6 = CidrAddress::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xc0a8, 1), 32).unwrap(); /// -- 2.39.2 From a.lauterer at proxmox.com Wed Oct 25 18:00:00 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 18:00:00 +0200 Subject: [pve-devel] [PATCH 01/12] add proxmox-installer-common crate In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231025160011.3617524-2-a.lauterer@proxmox.com> It will be used for code shared among the different crates in the installer. For now between the TUI installer and the upcoming auto installer. Signed-off-by: Aaron Lauterer --- Cargo.toml | 1 + proxmox-installer-common/Cargo.toml | 10 ++++++++++ proxmox-installer-common/src/lib.rs | 0 3 files changed, 11 insertions(+) create mode 100644 proxmox-installer-common/Cargo.toml create mode 100644 proxmox-installer-common/src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index fd151ba..c1bd578 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,6 @@ [workspace] members = [ + "proxmox-installer-common", "proxmox-tui-installer", ] diff --git a/proxmox-installer-common/Cargo.toml b/proxmox-installer-common/Cargo.toml new file mode 100644 index 0000000..b8762e8 --- /dev/null +++ b/proxmox-installer-common/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "proxmox-installer-common" +version = "0.1.0" +edition = "2021" +authors = [ "Aaron Lauterer " ] +license = "AGPL-3" +exclude = [ "build", "debian" ] +homepage = "https://www.proxmox.com" + +[dependencies] diff --git a/proxmox-installer-common/src/lib.rs b/proxmox-installer-common/src/lib.rs new file mode 100644 index 0000000..e69de29 -- 2.39.2 From a.lauterer at proxmox.com Wed Oct 25 17:59:59 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 17:59:59 +0200 Subject: [pve-devel] [PATCH 00/12] installer: add crate for common code Message-ID: <20231025160011.3617524-1-a.lauterer@proxmox.com> since work on the auto installer is happenning in parallel, now would be a good point to move commonly used code into its own crate. Otherwise the auto-installer will always have to play catch up with the ongoing development of the tui installer. I tried to split up the commits as much as possible, but there are two larger ones, copying most the code over to the new repo and making the switch. The former because it is difficult to pull apart the parts that belong together. The latter needed to be this big as most local occurences needed to be removed at the same time to avoid dependency conflicts. One last things that is missing, is the "InstallConfig" struct. This should also be part of the common crate, but I need to look further into how to make it possible that it can be created from structs of each crate (tui, auto) as implementing a ::From can only be done within the crate where the struct lives IIUC. This series depends on the patches by Christoph to remove the global unsafe setup info, version 2 [0]. Without those patches applied first, this series will not apply. [0] https://lists.proxmox.com/pipermail/pve-devel/2023-October/059628.html Aaron Lauterer (12): add proxmox-installer-common crate common: copy common code from tui-installer common: utils: add dependency for doc test common: make InstallZfsOption public common: disk_checks: make functions public tui-installer: add dependency for new common crate tui: switch to common crate tui: remove now unused utils.rs common: add installer_setup method common: document installer_setup method tui: use installer_setup from common cate tui: remove unused read_json function Cargo.toml | 1 + proxmox-installer-common/Cargo.toml | 12 + proxmox-installer-common/src/disk_checks.rs | 237 ++++++++++ proxmox-installer-common/src/lib.rs | 4 + proxmox-installer-common/src/options.rs | 387 +++++++++++++++++ proxmox-installer-common/src/setup.rs | 368 ++++++++++++++++ .../src/utils.rs | 1 + proxmox-tui-installer/Cargo.toml | 1 + proxmox-tui-installer/src/main.rs | 51 +-- proxmox-tui-installer/src/options.rs | 403 +----------------- proxmox-tui-installer/src/setup.rs | 324 +------------- proxmox-tui-installer/src/system.rs | 2 +- proxmox-tui-installer/src/views/bootdisk.rs | 248 +---------- proxmox-tui-installer/src/views/mod.rs | 2 +- proxmox-tui-installer/src/views/timezone.rs | 4 +- 15 files changed, 1054 insertions(+), 991 deletions(-) create mode 100644 proxmox-installer-common/Cargo.toml create mode 100644 proxmox-installer-common/src/disk_checks.rs create mode 100644 proxmox-installer-common/src/lib.rs create mode 100644 proxmox-installer-common/src/options.rs create mode 100644 proxmox-installer-common/src/setup.rs rename {proxmox-tui-installer => proxmox-installer-common}/src/utils.rs (99%) -- 2.39.2 From a.lauterer at proxmox.com Wed Oct 25 18:00:09 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 18:00:09 +0200 Subject: [pve-devel] [PATCH 10/12] common: document installer_setup method In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231025160011.3617524-11-a.lauterer@proxmox.com> Signed-off-by: Aaron Lauterer --- proxmox-installer-common/src/setup.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/proxmox-installer-common/src/setup.rs b/proxmox-installer-common/src/setup.rs index 34b00cb..3ef05ae 100644 --- a/proxmox-installer-common/src/setup.rs +++ b/proxmox-installer-common/src/setup.rs @@ -103,6 +103,7 @@ pub struct LocaleInfo { pub kmap: HashMap, } +/// Fetches basic information needed for the installer which is required to work pub fn installer_setup(in_test_mode: bool) -> Result<(SetupInfo, LocaleInfo, RuntimeInfo), String> { let base_path = if in_test_mode { "./testdir" } else { "/" }; let mut path = PathBuf::from(base_path); -- 2.39.2 From a.lauterer at proxmox.com Wed Oct 25 18:00:04 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 18:00:04 +0200 Subject: [pve-devel] [PATCH 05/12] common: disk_checks: make functions public In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231025160011.3617524-6-a.lauterer@proxmox.com> Signed-off-by: Aaron Lauterer --- proxmox-installer-common/src/disk_checks.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/proxmox-installer-common/src/disk_checks.rs b/proxmox-installer-common/src/disk_checks.rs index 15b5928..bcf2e21 100644 --- a/proxmox-installer-common/src/disk_checks.rs +++ b/proxmox-installer-common/src/disk_checks.rs @@ -8,7 +8,7 @@ use crate::setup::BootType; /// # Arguments /// /// * `disks` - A list of disks to check for duplicates. -fn check_for_duplicate_disks(disks: &[Disk]) -> Result<(), &Disk> { +pub fn check_for_duplicate_disks(disks: &[Disk]) -> Result<(), &Disk> { let mut set = HashSet::new(); for disk in disks { @@ -26,7 +26,7 @@ fn check_for_duplicate_disks(disks: &[Disk]) -> Result<(), &Disk> { /// /// * `disks` - A list of disks to check the lenght of. /// * `min` - Minimum number of disks -fn check_raid_min_disks(disks: &[Disk], min: usize) -> Result<(), String> { +pub fn check_raid_min_disks(disks: &[Disk], min: usize) -> Result<(), String> { if disks.len() < min { Err(format!("Need at least {min} disks")) } else { @@ -41,7 +41,7 @@ fn check_raid_min_disks(disks: &[Disk], min: usize) -> Result<(), String> { /// /// * `runinfo` - `RuntimeInfo` instance of currently running system /// * `disks` - List of disks designated as bootdisk targets. -fn check_disks_4kn_legacy_boot(boot_type: BootType, disks: &[Disk]) -> Result<(), &str> { +pub fn check_disks_4kn_legacy_boot(boot_type: BootType, disks: &[Disk]) -> Result<(), &str> { let is_blocksize_4096 = |disk: &Disk| disk.block_size.map(|s| s == 4096).unwrap_or(false); if boot_type == BootType::Bios && disks.iter().any(is_blocksize_4096) { @@ -58,7 +58,7 @@ fn check_disks_4kn_legacy_boot(boot_type: BootType, disks: &[Disk]) -> Result<() /// /// * `level` - The targeted ZFS RAID level by the user. /// * `disks` - List of disks designated as RAID targets. -fn check_zfs_raid_config(level: ZfsRaidLevel, disks: &[Disk]) -> Result<(), String> { +pub fn check_zfs_raid_config(level: ZfsRaidLevel, disks: &[Disk]) -> Result<(), String> { // See also Proxmox/Install.pm:get_zfs_raid_setup() let check_mirror_size = |disk1: &Disk, disk2: &Disk| { @@ -117,7 +117,7 @@ fn check_zfs_raid_config(level: ZfsRaidLevel, disks: &[Disk]) -> Result<(), Stri /// /// * `level` - The targeted Btrfs RAID level by the user. /// * `disks` - List of disks designated as RAID targets. -fn check_btrfs_raid_config(level: BtrfsRaidLevel, disks: &[Disk]) -> Result<(), String> { +pub fn check_btrfs_raid_config(level: BtrfsRaidLevel, disks: &[Disk]) -> Result<(), String> { // See also Proxmox/Install.pm:get_btrfs_raid_setup() match level { -- 2.39.2 From a.lauterer at proxmox.com Wed Oct 25 18:00:10 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 18:00:10 +0200 Subject: [pve-devel] [PATCH 11/12] tui: use installer_setup from common cate In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231025160011.3617524-12-a.lauterer@proxmox.com> Signed-off-by: Aaron Lauterer --- proxmox-tui-installer/src/main.rs | 40 +------------------------------ 1 file changed, 1 insertion(+), 39 deletions(-) diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index 875a33a..3216868 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -5,7 +5,6 @@ use std::{ env, io::{BufRead, BufReader, Write}, net::IpAddr, - path::PathBuf, str::FromStr, sync::{Arc, Mutex}, thread, @@ -32,7 +31,7 @@ use options::InstallerOptions; use proxmox_installer_common::{ options::{BootdiskOptions, NetworkOptions, PasswordOptions, TimezoneOptions}, - setup::{LocaleInfo, ProxmoxProduct, RuntimeInfo, SetupInfo}, + setup::{installer_setup, LocaleInfo, ProxmoxProduct, RuntimeInfo, SetupInfo}, utils::Fqdn, }; @@ -197,43 +196,6 @@ fn main() { siv.run(); } -fn installer_setup(in_test_mode: bool) -> Result<(SetupInfo, LocaleInfo, RuntimeInfo), String> { - let base_path = if in_test_mode { "./testdir" } else { "/" }; - let mut path = PathBuf::from(base_path); - - path.push("run"); - path.push("proxmox-installer"); - - let installer_info: SetupInfo = { - let mut path = path.clone(); - path.push("iso-info.json"); - - setup::read_json(&path).map_err(|err| format!("Failed to retrieve setup info: {err}"))? - }; - - let locale_info = { - let mut path = path.clone(); - path.push("locales.json"); - - setup::read_json(&path).map_err(|err| format!("Failed to retrieve locale info: {err}"))? - }; - - let mut runtime_info: RuntimeInfo = { - let mut path = path.clone(); - path.push("run-env-info.json"); - - setup::read_json(&path) - .map_err(|err| format!("Failed to retrieve runtime environment info: {err}"))? - }; - - runtime_info.disks.sort(); - if runtime_info.disks.is_empty() { - Err("The installer could not find any supported hard disks.".to_owned()) - } else { - Ok((installer_info, locale_info, runtime_info)) - } -} - /// Anything that can be done late in the setup and will not result in fatal errors. fn installer_setup_late(siv: &mut Cursive) { let state = siv.user_data::().cloned().unwrap(); -- 2.39.2 From a.lauterer at proxmox.com Wed Oct 25 18:00:06 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 18:00:06 +0200 Subject: [pve-devel] [PATCH 07/12] tui: switch to common crate In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231025160011.3617524-8-a.lauterer@proxmox.com> by switching dependencies and deleting doubled code to avoid ambiguities within the same scope. Signed-off-by: Aaron Lauterer --- proxmox-tui-installer/src/main.rs | 13 +- proxmox-tui-installer/src/options.rs | 403 +------------------- proxmox-tui-installer/src/setup.rs | 316 +-------------- proxmox-tui-installer/src/system.rs | 2 +- proxmox-tui-installer/src/views/bootdisk.rs | 248 +----------- proxmox-tui-installer/src/views/mod.rs | 2 +- proxmox-tui-installer/src/views/timezone.rs | 4 +- 7 files changed, 44 insertions(+), 944 deletions(-) diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index 81fe3ca..875a33a 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -28,16 +28,19 @@ use cursive::{ use regex::Regex; mod options; -use options::*; +use options::InstallerOptions; + +use proxmox_installer_common::{ + options::{BootdiskOptions, NetworkOptions, PasswordOptions, TimezoneOptions}, + setup::{LocaleInfo, ProxmoxProduct, RuntimeInfo, SetupInfo}, + utils::Fqdn, +}; mod setup; -use setup::{InstallConfig, LocaleInfo, ProxmoxProduct, RuntimeInfo, SetupInfo}; +use setup::InstallConfig; mod system; -mod utils; -use utils::Fqdn; - mod views; use views::{ BootdiskOptionsView, CidrAddressEditView, FormView, TableView, TableViewItem, diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs index 85b39b8..221ad01 100644 --- a/proxmox-tui-installer/src/options.rs +++ b/proxmox-tui-installer/src/options.rs @@ -1,77 +1,12 @@ -use std::net::{IpAddr, Ipv4Addr}; -use std::{cmp, fmt}; - -use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo, SetupInfo}; -use crate::utils::{CidrAddress, Fqdn}; use crate::SummaryOption; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum BtrfsRaidLevel { - Raid0, - Raid1, - Raid10, -} - -impl fmt::Display for BtrfsRaidLevel { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use BtrfsRaidLevel::*; - match self { - Raid0 => write!(f, "RAID0"), - Raid1 => write!(f, "RAID1"), - Raid10 => write!(f, "RAID10"), - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum ZfsRaidLevel { - Raid0, - Raid1, - Raid10, - RaidZ, - RaidZ2, - RaidZ3, -} - -impl fmt::Display for ZfsRaidLevel { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use ZfsRaidLevel::*; - match self { - Raid0 => write!(f, "RAID0"), - Raid1 => write!(f, "RAID1"), - Raid10 => write!(f, "RAID10"), - RaidZ => write!(f, "RAIDZ-1"), - RaidZ2 => write!(f, "RAIDZ-2"), - RaidZ3 => write!(f, "RAIDZ-3"), - } - } -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum FsType { - Ext4, - Xfs, - Zfs(ZfsRaidLevel), - Btrfs(BtrfsRaidLevel), -} - -impl FsType { - pub fn is_btrfs(&self) -> bool { - matches!(self, FsType::Btrfs(_)) - } -} - -impl fmt::Display for FsType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use FsType::*; - match self { - Ext4 => write!(f, "ext4"), - Xfs => write!(f, "XFS"), - Zfs(level) => write!(f, "ZFS ({level})"), - Btrfs(level) => write!(f, "Btrfs ({level})"), - } - } -} +use proxmox_installer_common::{ + setup::LocaleInfo, + options::{ + BootdiskOptions, BtrfsRaidLevel, FsType, NetworkOptions, TimezoneOptions, + PasswordOptions, ZfsRaidLevel, + }, +}; pub const FS_TYPES: &[FsType] = { use FsType::*; @@ -90,320 +25,6 @@ pub const FS_TYPES: &[FsType] = { ] }; -#[derive(Clone, Debug)] -pub struct LvmBootdiskOptions { - pub total_size: f64, - pub swap_size: Option, - pub max_root_size: Option, - pub max_data_size: Option, - pub min_lvm_free: Option, -} - -impl LvmBootdiskOptions { - pub fn defaults_from(disk: &Disk) -> Self { - Self { - total_size: disk.size, - swap_size: None, - max_root_size: None, - max_data_size: None, - min_lvm_free: None, - } - } -} - -#[derive(Clone, Debug)] -pub struct BtrfsBootdiskOptions { - pub disk_size: f64, - pub selected_disks: Vec, -} - -impl BtrfsBootdiskOptions { - /// This panics if the provided slice is empty. - pub fn defaults_from(disks: &[Disk]) -> Self { - let disk = &disks[0]; - Self { - disk_size: disk.size, - selected_disks: (0..disks.len()).collect(), - } - } -} - -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -pub enum ZfsCompressOption { - #[default] - On, - Off, - Lzjb, - Lz4, - Zle, - Gzip, - Zstd, -} - -impl fmt::Display for ZfsCompressOption { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", format!("{self:?}").to_lowercase()) - } -} - -impl From<&ZfsCompressOption> for String { - fn from(value: &ZfsCompressOption) -> Self { - value.to_string() - } -} - -pub const ZFS_COMPRESS_OPTIONS: &[ZfsCompressOption] = { - use ZfsCompressOption::*; - &[On, Off, Lzjb, Lz4, Zle, Gzip, Zstd] -}; - -#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] -pub enum ZfsChecksumOption { - #[default] - On, - Off, - Fletcher2, - Fletcher4, - Sha256, -} - -impl fmt::Display for ZfsChecksumOption { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", format!("{self:?}").to_lowercase()) - } -} - -impl From<&ZfsChecksumOption> for String { - fn from(value: &ZfsChecksumOption) -> Self { - value.to_string() - } -} - -pub const ZFS_CHECKSUM_OPTIONS: &[ZfsChecksumOption] = { - use ZfsChecksumOption::*; - &[On, Off, Fletcher2, Fletcher4, Sha256] -}; - -#[derive(Clone, Debug)] -pub struct ZfsBootdiskOptions { - pub ashift: usize, - pub compress: ZfsCompressOption, - pub checksum: ZfsChecksumOption, - pub copies: usize, - pub disk_size: f64, - pub selected_disks: Vec, -} - -impl ZfsBootdiskOptions { - /// This panics if the provided slice is empty. - pub fn defaults_from(disks: &[Disk]) -> Self { - let disk = &disks[0]; - Self { - ashift: 12, - compress: ZfsCompressOption::default(), - checksum: ZfsChecksumOption::default(), - copies: 1, - disk_size: disk.size, - selected_disks: (0..disks.len()).collect(), - } - } -} - -#[derive(Clone, Debug)] -pub enum AdvancedBootdiskOptions { - Lvm(LvmBootdiskOptions), - Zfs(ZfsBootdiskOptions), - Btrfs(BtrfsBootdiskOptions), -} - -#[derive(Clone, Debug, PartialEq)] -pub struct Disk { - pub index: String, - pub path: String, - pub model: Option, - pub size: f64, - pub block_size: Option, -} - -impl fmt::Display for Disk { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - // TODO: Format sizes properly with `proxmox-human-byte` once merged - // https://lists.proxmox.com/pipermail/pbs-devel/2023-May/006125.html - f.write_str(&self.path)?; - if let Some(model) = &self.model { - // FIXME: ellipsize too-long names? - write!(f, " ({model})")?; - } - write!(f, " ({:.2} GiB)", self.size) - } -} - -impl From<&Disk> for String { - fn from(value: &Disk) -> Self { - value.to_string() - } -} - -impl cmp::Eq for Disk {} - -impl cmp::PartialOrd for Disk { - fn partial_cmp(&self, other: &Self) -> Option { - self.index.partial_cmp(&other.index) - } -} - -impl cmp::Ord for Disk { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.index.cmp(&other.index) - } -} - -#[derive(Clone, Debug)] -pub struct BootdiskOptions { - pub disks: Vec, - pub fstype: FsType, - pub advanced: AdvancedBootdiskOptions, -} - -impl BootdiskOptions { - pub fn defaults_from(disk: &Disk) -> Self { - Self { - disks: vec![disk.clone()], - fstype: FsType::Ext4, - advanced: AdvancedBootdiskOptions::Lvm(LvmBootdiskOptions::defaults_from(disk)), - } - } -} - -#[derive(Clone, Debug)] -pub struct TimezoneOptions { - pub country: String, - pub timezone: String, - pub kb_layout: String, -} - -impl TimezoneOptions { - pub fn defaults_from(runtime: &RuntimeInfo, locales: &LocaleInfo) -> Self { - let country = runtime.country.clone().unwrap_or_else(|| "at".to_owned()); - - let timezone = locales - .cczones - .get(&country) - .and_then(|zones| zones.get(0)) - .cloned() - .unwrap_or_else(|| "UTC".to_owned()); - - let kb_layout = locales - .countries - .get(&country) - .and_then(|c| { - if c.kmap.is_empty() { - None - } else { - Some(c.kmap.clone()) - } - }) - .unwrap_or_else(|| "en-us".to_owned()); - - Self { - country, - timezone, - kb_layout, - } - } -} - -#[derive(Clone, Debug)] -pub struct PasswordOptions { - pub email: String, - pub root_password: String, -} - -impl Default for PasswordOptions { - fn default() -> Self { - Self { - email: "mail at example.invalid".to_string(), - root_password: String::new(), - } - } -} - -#[derive(Clone, Debug, PartialEq)] -pub struct NetworkOptions { - pub ifname: String, - pub fqdn: Fqdn, - pub address: CidrAddress, - pub gateway: IpAddr, - pub dns_server: IpAddr, -} - -impl NetworkOptions { - const DEFAULT_DOMAIN: &str = "example.invalid"; - - pub fn defaults_from(setup: &SetupInfo, network: &NetworkInfo) -> Self { - let mut this = Self { - ifname: String::new(), - fqdn: Self::construct_fqdn(network, setup.config.product.default_hostname()), - // Safety: The provided mask will always be valid. - address: CidrAddress::new(Ipv4Addr::UNSPECIFIED, 0).unwrap(), - gateway: Ipv4Addr::UNSPECIFIED.into(), - dns_server: Ipv4Addr::UNSPECIFIED.into(), - }; - - if let Some(ip) = network.dns.dns.first() { - this.dns_server = *ip; - } - - if let Some(routes) = &network.routes { - let mut filled = false; - if let Some(gw) = &routes.gateway4 { - if let Some(iface) = network.interfaces.get(&gw.dev) { - this.ifname = iface.name.clone(); - if let Some(addresses) = &iface.addresses { - if let Some(addr) = addresses.iter().find(|addr| addr.is_ipv4()) { - this.gateway = gw.gateway; - this.address = addr.clone(); - filled = true; - } - } - } - } - if !filled { - if let Some(gw) = &routes.gateway6 { - if let Some(iface) = network.interfaces.get(&gw.dev) { - if let Some(addresses) = &iface.addresses { - if let Some(addr) = addresses.iter().find(|addr| addr.is_ipv6()) { - this.ifname = iface.name.clone(); - this.gateway = gw.gateway; - this.address = addr.clone(); - } - } - } - } - } - } - - this - } - - fn construct_fqdn(network: &NetworkInfo, default_hostname: &str) -> Fqdn { - let hostname = network.hostname.as_deref().unwrap_or(default_hostname); - - let domain = network - .dns - .domain - .as_deref() - .unwrap_or(Self::DEFAULT_DOMAIN); - - Fqdn::from(&format!("{hostname}.{domain}")).unwrap_or_else(|_| { - // Safety: This will always result in a valid FQDN, as we control & know - // the values of default_hostname (one of "pve", "pmg" or "pbs") and - // constant-defined DEFAULT_DOMAIN. - Fqdn::from(&format!("{}.{}", default_hostname, Self::DEFAULT_DOMAIN)).unwrap() - }) - } -} - #[derive(Clone, Debug)] pub struct InstallerOptions { pub bootdisk: BootdiskOptions, @@ -447,11 +68,15 @@ impl InstallerOptions { #[cfg(test)] mod tests { use super::*; - use crate::setup::{ - Dns, Gateway, Interface, InterfaceState, IsoInfo, IsoLocations, NetworkInfo, ProductConfig, - ProxmoxProduct, Routes, SetupInfo, + use proxmox_installer_common::{ + setup::{ + Dns, Gateway, Interface, InterfaceState, IsoInfo, IsoLocations, NetworkInfo, ProductConfig, + ProxmoxProduct, Routes, SetupInfo, + }, + utils::{CidrAddress, Fqdn}, }; use std::{collections::HashMap, path::PathBuf}; + use std::net::{IpAddr, Ipv4Addr}; fn dummy_setup_info() -> SetupInfo { SetupInfo { diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs index 5575759..211a96b 100644 --- a/proxmox-tui-installer/src/setup.rs +++ b/proxmox-tui-installer/src/setup.rs @@ -1,131 +1,20 @@ use std::{ - cmp, collections::HashMap, fmt, fs::File, io::BufReader, net::IpAddr, - path::{Path, PathBuf}, + path::Path, }; -use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use serde::{Deserialize, Serialize, Serializer}; -use crate::{ - options::{ - AdvancedBootdiskOptions, BtrfsRaidLevel, Disk, FsType, InstallerOptions, - ZfsBootdiskOptions, ZfsChecksumOption, ZfsCompressOption, ZfsRaidLevel, - }, - utils::CidrAddress, -}; - -#[allow(clippy::upper_case_acronyms)] -#[derive(Clone, Copy, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum ProxmoxProduct { - PVE, - PBS, - PMG, -} - -impl ProxmoxProduct { - pub fn default_hostname(self) -> &'static str { - match self { - Self::PVE => "pve", - Self::PMG => "pmg", - Self::PBS => "pbs", - } - } -} - -#[derive(Clone, Deserialize)] -pub struct ProductConfig { - pub fullname: String, - pub product: ProxmoxProduct, - #[serde(deserialize_with = "deserialize_bool_from_int")] - pub enable_btrfs: bool, -} - -#[derive(Clone, Deserialize)] -pub struct IsoInfo { - pub release: String, - pub isorelease: String, -} - -/// Paths in the ISO environment containing installer data. -#[derive(Clone, Deserialize)] -pub struct IsoLocations { - pub iso: PathBuf, -} - -#[derive(Clone, Deserialize)] -pub struct SetupInfo { - #[serde(rename = "product-cfg")] - pub config: ProductConfig, - #[serde(rename = "iso-info")] - pub iso_info: IsoInfo, - pub locations: IsoLocations, -} - -#[derive(Clone, Deserialize)] -pub struct CountryInfo { - pub name: String, - #[serde(default)] - pub zone: String, - pub kmap: String, -} - -#[derive(Clone, Deserialize, Eq, PartialEq)] -pub struct KeyboardMapping { - pub name: String, - #[serde(rename = "kvm")] - pub id: String, - #[serde(rename = "x11")] - pub xkb_layout: String, - #[serde(rename = "x11var")] - pub xkb_variant: String, -} - -impl cmp::PartialOrd for KeyboardMapping { - fn partial_cmp(&self, other: &Self) -> Option { - self.name.partial_cmp(&other.name) - } -} - -impl cmp::Ord for KeyboardMapping { - fn cmp(&self, other: &Self) -> cmp::Ordering { - self.name.cmp(&other.name) - } -} - -#[derive(Clone, Deserialize)] -pub struct LocaleInfo { - #[serde(deserialize_with = "deserialize_cczones_map")] - pub cczones: HashMap>, - #[serde(rename = "country")] - pub countries: HashMap, - pub kmap: HashMap, -} - -#[derive(Serialize)] -struct InstallZfsOption { - ashift: usize, - #[serde(serialize_with = "serialize_as_display")] - compress: ZfsCompressOption, - #[serde(serialize_with = "serialize_as_display")] - checksum: ZfsChecksumOption, - copies: usize, -} - -impl From for InstallZfsOption { - fn from(opts: ZfsBootdiskOptions) -> Self { - InstallZfsOption { - ashift: opts.ashift, - compress: opts.compress, - checksum: opts.checksum, - copies: opts.copies, - } - } -} +use crate::options::InstallerOptions; +use proxmox_installer_common::{ + options::{AdvancedBootdiskOptions, BtrfsRaidLevel, Disk, FsType, ZfsRaidLevel}, + setup::InstallZfsOption, + utils::CidrAddress, + }; /// See Proxmox::Install::Config #[derive(Serialize)] @@ -247,82 +136,6 @@ pub fn read_json Deserialize<'de>, P: AsRef>(path: P) -> Resul serde_json::from_reader(reader).map_err(|err| format!("failed to parse JSON: {err}")) } -fn deserialize_bool_from_int<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let val: u32 = Deserialize::deserialize(deserializer)?; - Ok(val != 0) -} - -fn deserialize_cczones_map<'de, D>( - deserializer: D, -) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - let map: HashMap> = Deserialize::deserialize(deserializer)?; - - let mut result = HashMap::new(); - for (cc, list) in map.into_iter() { - result.insert(cc, list.into_keys().collect()); - } - - Ok(result) -} - -fn deserialize_disks_map<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let disks = - , String)>>::deserialize(deserializer)?; - Ok(disks - .into_iter() - .map( - |(index, device, size_mb, model, logical_bsize, _syspath)| Disk { - index: index.to_string(), - // Linux always reports the size of block devices in sectors, where one sector is - // defined as being 2^9 = 512 bytes in size. - // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/blk_types.h?h=v6.4#n30 - size: (size_mb * 512.) / 1024. / 1024. / 1024., - block_size: logical_bsize, - path: device, - model: (!model.is_empty()).then_some(model), - }, - ) - .collect()) -} - -fn deserialize_cidr_list<'de, D>(deserializer: D) -> Result>, D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - struct CidrDescriptor { - address: String, - prefix: usize, - // family is implied anyway by parsing the address - } - - let list: Vec = Deserialize::deserialize(deserializer)?; - - let mut result = Vec::with_capacity(list.len()); - for desc in list { - let ip_addr = desc - .address - .parse::() - .map_err(|err| de::Error::custom(format!("{:?}", err)))?; - - result.push( - CidrAddress::new(ip_addr, desc.prefix) - .map_err(|err| de::Error::custom(format!("{:?}", err)))?, - ); - } - - Ok(Some(result)) -} - fn serialize_disk_opt(value: &Option, serializer: S) -> Result where S: Serializer, @@ -366,116 +179,3 @@ where { serializer.collect_str(value) } - -#[derive(Clone, Deserialize)] -pub struct RuntimeInfo { - /// Whether is system was booted in (legacy) BIOS or UEFI mode. - pub boot_type: BootType, - - /// Detected country if available. - pub country: Option, - - /// Maps devices to their information. - #[serde(deserialize_with = "deserialize_disks_map")] - pub disks: Vec, - - /// Network addresses, gateways and DNS info. - pub network: NetworkInfo, - - /// Total memory of the system in MiB. - pub total_memory: usize, - - /// Whether the CPU supports hardware-accelerated virtualization - #[serde(deserialize_with = "deserialize_bool_from_int")] - pub hvm_supported: bool, -} - -#[derive(Copy, Clone, Eq, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum BootType { - Bios, - Efi, -} - -#[derive(Clone, Deserialize)] -pub struct NetworkInfo { - pub dns: Dns, - pub routes: Option, - - /// Maps devices to their configuration, if it has a usable configuration. - /// (Contains no entries for devices with only link-local addresses.) - #[serde(default)] - pub interfaces: HashMap, - - /// The hostname of this machine, if set by the DHCP server. - pub hostname: Option, -} - -#[derive(Clone, Deserialize)] -pub struct Dns { - pub domain: Option, - - /// List of stringified IP addresses. - #[serde(default)] - pub dns: Vec, -} - -#[derive(Clone, Deserialize)] -pub struct Routes { - /// Ipv4 gateway. - pub gateway4: Option, - - /// Ipv6 gateway. - pub gateway6: Option, -} - -#[derive(Clone, Deserialize)] -pub struct Gateway { - /// Outgoing network device. - pub dev: String, - - /// Stringified gateway IP address. - pub gateway: IpAddr, -} - -#[derive(Clone, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -pub enum InterfaceState { - Up, - Down, - #[serde(other)] - Unknown, -} - -impl InterfaceState { - // avoid display trait as this is not the string representation for a serializer - pub fn render(&self) -> String { - match self { - Self::Up => "\u{25CF}", - Self::Down | Self::Unknown => " ", - } - .into() - } -} - -#[derive(Clone, Deserialize)] -pub struct Interface { - pub name: String, - - pub index: usize, - - pub mac: String, - - pub state: InterfaceState, - - #[serde(default)] - #[serde(deserialize_with = "deserialize_cidr_list")] - pub addresses: Option>, -} - -impl Interface { - // avoid display trait as this is not the string representation for a serializer - pub fn render(&self) -> String { - format!("{} {}", self.state.render(), self.name) - } -} diff --git a/proxmox-tui-installer/src/system.rs b/proxmox-tui-installer/src/system.rs index bbf13b8..d1675a9 100644 --- a/proxmox-tui-installer/src/system.rs +++ b/proxmox-tui-installer/src/system.rs @@ -1,6 +1,6 @@ use std::{fs::OpenOptions, io::Write, process::Command}; -use crate::setup::KeyboardMapping; +use proxmox_installer_common::setup::KeyboardMapping; pub fn set_keyboard_layout(kmap: &KeyboardMapping) -> Result<(), String> { Command::new("setxkbmap") diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index ba08c8b..38a6521 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -1,4 +1,4 @@ -use std::{cell::RefCell, collections::HashSet, marker::PhantomData, rc::Rc}; +use std::{cell::RefCell, marker::PhantomData, rc::Rc}; use cursive::{ view::{Nameable, Resizable, ViewWrapper}, @@ -10,15 +10,18 @@ use cursive::{ }; use super::{DiskSizeEditView, FormView, IntegerEditView}; -use crate::{ +use crate::options::FS_TYPES; +use crate::InstallerState; + +use proxmox_installer_common::{ options::{ - AdvancedBootdiskOptions, BootdiskOptions, BtrfsBootdiskOptions, BtrfsRaidLevel, Disk, - FsType, LvmBootdiskOptions, ZfsBootdiskOptions, ZfsRaidLevel, FS_TYPES, + AdvancedBootdiskOptions, BootdiskOptions, BtrfsBootdiskOptions, Disk, + FsType, LvmBootdiskOptions, ZfsBootdiskOptions, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS, }, - setup::{BootType, ProductConfig}, + setup::{BootType, ProductConfig, ProxmoxProduct}, + disk_checks::{check_btrfs_raid_config, check_for_duplicate_disks, check_disks_4kn_legacy_boot, check_zfs_raid_config}, }; -use crate::{setup::ProxmoxProduct, InstallerState}; pub struct BootdiskOptionsView { view: LinearLayout, @@ -619,236 +622,3 @@ fn advanced_options_view( .with_name("advanced-bootdisk-options-dialog") .max_size((120, 40)) } - -/// Checks a list of disks for duplicate entries, using their index as key. -/// -/// # Arguments -/// -/// * `disks` - A list of disks to check for duplicates. -fn check_for_duplicate_disks(disks: &[Disk]) -> Result<(), &Disk> { - let mut set = HashSet::new(); - - for disk in disks { - if !set.insert(&disk.index) { - return Err(disk); - } - } - - Ok(()) -} - -/// Simple wrapper which returns an descriptive error if the list of disks is too short. -/// -/// # Arguments -/// -/// * `disks` - A list of disks to check the lenght of. -/// * `min` - Minimum number of disks -fn check_raid_min_disks(disks: &[Disk], min: usize) -> Result<(), String> { - if disks.len() < min { - Err(format!("Need at least {min} disks")) - } else { - Ok(()) - } -} - -/// Checks all disks for legacy BIOS boot compatibility and reports an error as appropriate. 4Kn -/// disks are generally broken with legacy BIOS and cannot be booted from. -/// -/// # Arguments -/// -/// * `runinfo` - `RuntimeInfo` instance of currently running system -/// * `disks` - List of disks designated as bootdisk targets. -fn check_disks_4kn_legacy_boot(boot_type: BootType, disks: &[Disk]) -> Result<(), &str> { - let is_blocksize_4096 = |disk: &Disk| disk.block_size.map(|s| s == 4096).unwrap_or(false); - - if boot_type == BootType::Bios && disks.iter().any(is_blocksize_4096) { - return Err("Booting from 4Kn drive in legacy BIOS mode is not supported."); - } - - Ok(()) -} - -/// Checks whether a user-supplied ZFS RAID setup is valid or not, such as disk sizes andminimum -/// number of disks. -/// -/// # Arguments -/// -/// * `level` - The targeted ZFS RAID level by the user. -/// * `disks` - List of disks designated as RAID targets. -fn check_zfs_raid_config(level: ZfsRaidLevel, disks: &[Disk]) -> Result<(), String> { - // See also Proxmox/Install.pm:get_zfs_raid_setup() - - let check_mirror_size = |disk1: &Disk, disk2: &Disk| { - if (disk1.size - disk2.size).abs() > disk1.size / 10. { - Err(format!( - "Mirrored disks must have same size:\n\n * {disk1}\n * {disk2}" - )) - } else { - Ok(()) - } - }; - - match level { - ZfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?, - ZfsRaidLevel::Raid1 => { - check_raid_min_disks(disks, 2)?; - for disk in disks { - check_mirror_size(&disks[0], disk)?; - } - } - ZfsRaidLevel::Raid10 => { - check_raid_min_disks(disks, 4)?; - // Pairs need to have the same size - for i in (0..disks.len()).step_by(2) { - check_mirror_size(&disks[i], &disks[i + 1])?; - } - } - // For RAID-Z: minimum disks number is level + 2 - ZfsRaidLevel::RaidZ => { - check_raid_min_disks(disks, 3)?; - for disk in disks { - check_mirror_size(&disks[0], disk)?; - } - } - ZfsRaidLevel::RaidZ2 => { - check_raid_min_disks(disks, 4)?; - for disk in disks { - check_mirror_size(&disks[0], disk)?; - } - } - ZfsRaidLevel::RaidZ3 => { - check_raid_min_disks(disks, 5)?; - for disk in disks { - check_mirror_size(&disks[0], disk)?; - } - } - } - - Ok(()) -} - -/// Checks whether a user-supplied Btrfs RAID setup is valid or not, such as minimum -/// number of disks. -/// -/// # Arguments -/// -/// * `level` - The targeted Btrfs RAID level by the user. -/// * `disks` - List of disks designated as RAID targets. -fn check_btrfs_raid_config(level: BtrfsRaidLevel, disks: &[Disk]) -> Result<(), String> { - // See also Proxmox/Install.pm:get_btrfs_raid_setup() - - match level { - BtrfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?, - BtrfsRaidLevel::Raid1 => check_raid_min_disks(disks, 2)?, - BtrfsRaidLevel::Raid10 => check_raid_min_disks(disks, 4)?, - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - fn dummy_disk(index: usize) -> Disk { - Disk { - index: index.to_string(), - path: format!("/dev/dummy{index}"), - model: Some("Dummy disk".to_owned()), - size: 1024. * 1024. * 1024. * 8., - block_size: Some(512), - } - } - - fn dummy_disks(num: usize) -> Vec { - (0..num).map(dummy_disk).collect() - } - - #[test] - fn duplicate_disks() { - assert!(check_for_duplicate_disks(&dummy_disks(2)).is_ok()); - assert_eq!( - check_for_duplicate_disks(&[ - dummy_disk(0), - dummy_disk(1), - dummy_disk(2), - dummy_disk(2), - dummy_disk(3), - ]), - Err(&dummy_disk(2)), - ); - } - - #[test] - fn raid_min_disks() { - let disks = dummy_disks(10); - - assert!(check_raid_min_disks(&disks[..1], 2).is_err()); - assert!(check_raid_min_disks(&disks[..1], 1).is_ok()); - assert!(check_raid_min_disks(&disks, 1).is_ok()); - } - - #[test] - fn bios_boot_compat_4kn() { - for i in 0..10 { - let mut disks = dummy_disks(10); - disks[i].block_size = Some(4096); - - // Must fail if /any/ of the disks are 4Kn - assert!(check_disks_4kn_legacy_boot(BootType::Bios, &disks).is_err()); - // For UEFI, we allow it for every configuration - assert!(check_disks_4kn_legacy_boot(BootType::Efi, &disks).is_ok()); - } - } - - #[test] - fn btrfs_raid() { - let disks = dummy_disks(10); - - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &[]).is_err()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks[..1]).is_ok()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks).is_ok()); - - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &[]).is_err()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..1]).is_err()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..2]).is_ok()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks).is_ok()); - - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &[]).is_err()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..3]).is_err()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..4]).is_ok()); - assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks).is_ok()); - } - - #[test] - fn zfs_raid() { - let disks = dummy_disks(10); - - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &[]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks[..1]).is_ok()); - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks).is_ok()); - - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &[]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks[..2]).is_ok()); - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks).is_ok()); - - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &[]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &dummy_disks(4)).is_ok()); - assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &disks).is_ok()); - - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &[]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..2]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..3]).is_ok()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks).is_ok()); - - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &[]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..3]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..4]).is_ok()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks).is_ok()); - - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &[]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..4]).is_err()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..5]).is_ok()); - assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks).is_ok()); - } -} diff --git a/proxmox-tui-installer/src/views/mod.rs b/proxmox-tui-installer/src/views/mod.rs index aa24fa4..aabae0e 100644 --- a/proxmox-tui-installer/src/views/mod.rs +++ b/proxmox-tui-installer/src/views/mod.rs @@ -7,7 +7,7 @@ use cursive::{ Rect, Vec2, View, }; -use crate::utils::CidrAddress; +use proxmox_installer_common::utils::CidrAddress; mod bootdisk; pub use bootdisk::*; diff --git a/proxmox-tui-installer/src/views/timezone.rs b/proxmox-tui-installer/src/views/timezone.rs index 6732286..77fbb10 100644 --- a/proxmox-tui-installer/src/views/timezone.rs +++ b/proxmox-tui-installer/src/views/timezone.rs @@ -6,9 +6,11 @@ use cursive::{ use super::FormView; use crate::{ + system, InstallerState, +}; +use proxmox_installer_common::{ options::TimezoneOptions, setup::{KeyboardMapping, LocaleInfo}, - system, InstallerState, }; pub struct TimezoneOptionsView { -- 2.39.2 From a.lauterer at proxmox.com Wed Oct 25 18:00:01 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Wed, 25 Oct 2023 18:00:01 +0200 Subject: [pve-devel] [PATCH 02/12] common: copy common code from tui-installer In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231025160011.3617524-3-a.lauterer@proxmox.com> Copy code that is common to its own crate. Signed-off-by: Aaron Lauterer --- proxmox-installer-common/Cargo.toml | 2 + proxmox-installer-common/src/disk_checks.rs | 237 ++++++++++++ proxmox-installer-common/src/lib.rs | 4 + proxmox-installer-common/src/options.rs | 387 ++++++++++++++++++++ proxmox-installer-common/src/setup.rs | 330 +++++++++++++++++ proxmox-installer-common/src/utils.rs | 268 ++++++++++++++ 6 files changed, 1228 insertions(+) create mode 100644 proxmox-installer-common/src/disk_checks.rs create mode 100644 proxmox-installer-common/src/options.rs create mode 100644 proxmox-installer-common/src/setup.rs create mode 100644 proxmox-installer-common/src/utils.rs diff --git a/proxmox-installer-common/Cargo.toml b/proxmox-installer-common/Cargo.toml index b8762e8..bde5457 100644 --- a/proxmox-installer-common/Cargo.toml +++ b/proxmox-installer-common/Cargo.toml @@ -8,3 +8,5 @@ exclude = [ "build", "debian" ] homepage = "https://www.proxmox.com" [dependencies] +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/proxmox-installer-common/src/disk_checks.rs b/proxmox-installer-common/src/disk_checks.rs new file mode 100644 index 0000000..15b5928 --- /dev/null +++ b/proxmox-installer-common/src/disk_checks.rs @@ -0,0 +1,237 @@ +use std::collections::HashSet; + +use crate::options::{BtrfsRaidLevel, Disk, ZfsRaidLevel}; +use crate::setup::BootType; + +/// Checks a list of disks for duplicate entries, using their index as key. +/// +/// # Arguments +/// +/// * `disks` - A list of disks to check for duplicates. +fn check_for_duplicate_disks(disks: &[Disk]) -> Result<(), &Disk> { + let mut set = HashSet::new(); + + for disk in disks { + if !set.insert(&disk.index) { + return Err(disk); + } + } + + Ok(()) +} + +/// Simple wrapper which returns an descriptive error if the list of disks is too short. +/// +/// # Arguments +/// +/// * `disks` - A list of disks to check the lenght of. +/// * `min` - Minimum number of disks +fn check_raid_min_disks(disks: &[Disk], min: usize) -> Result<(), String> { + if disks.len() < min { + Err(format!("Need at least {min} disks")) + } else { + Ok(()) + } +} + +/// Checks all disks for legacy BIOS boot compatibility and reports an error as appropriate. 4Kn +/// disks are generally broken with legacy BIOS and cannot be booted from. +/// +/// # Arguments +/// +/// * `runinfo` - `RuntimeInfo` instance of currently running system +/// * `disks` - List of disks designated as bootdisk targets. +fn check_disks_4kn_legacy_boot(boot_type: BootType, disks: &[Disk]) -> Result<(), &str> { + let is_blocksize_4096 = |disk: &Disk| disk.block_size.map(|s| s == 4096).unwrap_or(false); + + if boot_type == BootType::Bios && disks.iter().any(is_blocksize_4096) { + return Err("Booting from 4Kn drive in legacy BIOS mode is not supported."); + } + + Ok(()) +} + +/// Checks whether a user-supplied ZFS RAID setup is valid or not, such as disk sizes andminimum +/// number of disks. +/// +/// # Arguments +/// +/// * `level` - The targeted ZFS RAID level by the user. +/// * `disks` - List of disks designated as RAID targets. +fn check_zfs_raid_config(level: ZfsRaidLevel, disks: &[Disk]) -> Result<(), String> { + // See also Proxmox/Install.pm:get_zfs_raid_setup() + + let check_mirror_size = |disk1: &Disk, disk2: &Disk| { + if (disk1.size - disk2.size).abs() > disk1.size / 10. { + Err(format!( + "Mirrored disks must have same size:\n\n * {disk1}\n * {disk2}" + )) + } else { + Ok(()) + } + }; + + match level { + ZfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?, + ZfsRaidLevel::Raid1 => { + check_raid_min_disks(disks, 2)?; + for disk in disks { + check_mirror_size(&disks[0], disk)?; + } + } + ZfsRaidLevel::Raid10 => { + check_raid_min_disks(disks, 4)?; + // Pairs need to have the same size + for i in (0..disks.len()).step_by(2) { + check_mirror_size(&disks[i], &disks[i + 1])?; + } + } + // For RAID-Z: minimum disks number is level + 2 + ZfsRaidLevel::RaidZ => { + check_raid_min_disks(disks, 3)?; + for disk in disks { + check_mirror_size(&disks[0], disk)?; + } + } + ZfsRaidLevel::RaidZ2 => { + check_raid_min_disks(disks, 4)?; + for disk in disks { + check_mirror_size(&disks[0], disk)?; + } + } + ZfsRaidLevel::RaidZ3 => { + check_raid_min_disks(disks, 5)?; + for disk in disks { + check_mirror_size(&disks[0], disk)?; + } + } + } + + Ok(()) +} + +/// Checks whether a user-supplied Btrfs RAID setup is valid or not, such as minimum +/// number of disks. +/// +/// # Arguments +/// +/// * `level` - The targeted Btrfs RAID level by the user. +/// * `disks` - List of disks designated as RAID targets. +fn check_btrfs_raid_config(level: BtrfsRaidLevel, disks: &[Disk]) -> Result<(), String> { + // See also Proxmox/Install.pm:get_btrfs_raid_setup() + + match level { + BtrfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?, + BtrfsRaidLevel::Raid1 => check_raid_min_disks(disks, 2)?, + BtrfsRaidLevel::Raid10 => check_raid_min_disks(disks, 4)?, + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn dummy_disk(index: usize) -> Disk { + Disk { + index: index.to_string(), + path: format!("/dev/dummy{index}"), + model: Some("Dummy disk".to_owned()), + size: 1024. * 1024. * 1024. * 8., + block_size: Some(512), + } + } + + fn dummy_disks(num: usize) -> Vec { + (0..num).map(dummy_disk).collect() + } + + #[test] + fn duplicate_disks() { + assert!(check_for_duplicate_disks(&dummy_disks(2)).is_ok()); + assert_eq!( + check_for_duplicate_disks(&[ + dummy_disk(0), + dummy_disk(1), + dummy_disk(2), + dummy_disk(2), + dummy_disk(3), + ]), + Err(&dummy_disk(2)), + ); + } + + #[test] + fn raid_min_disks() { + let disks = dummy_disks(10); + + assert!(check_raid_min_disks(&disks[..1], 2).is_err()); + assert!(check_raid_min_disks(&disks[..1], 1).is_ok()); + assert!(check_raid_min_disks(&disks, 1).is_ok()); + } + + #[test] + fn bios_boot_compat_4kn() { + for i in 0..10 { + let mut disks = dummy_disks(10); + disks[i].block_size = Some(4096); + + // Must fail if /any/ of the disks are 4Kn + assert!(check_disks_4kn_legacy_boot(BootType::Bios, &disks).is_err()); + // For UEFI, we allow it for every configuration + assert!(check_disks_4kn_legacy_boot(BootType::Efi, &disks).is_ok()); + } + } + + #[test] + fn btrfs_raid() { + let disks = dummy_disks(10); + + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &[]).is_err()); + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks[..1]).is_ok()); + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks).is_ok()); + + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &[]).is_err()); + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..1]).is_err()); + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..2]).is_ok()); + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks).is_ok()); + + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &[]).is_err()); + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..3]).is_err()); + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..4]).is_ok()); + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks).is_ok()); + } + + #[test] + fn zfs_raid() { + let disks = dummy_disks(10); + + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &[]).is_err()); + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks[..1]).is_ok()); + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks).is_ok()); + + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &[]).is_err()); + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks[..2]).is_ok()); + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks).is_ok()); + + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &[]).is_err()); + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &dummy_disks(4)).is_ok()); + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &disks).is_ok()); + + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &[]).is_err()); + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..2]).is_err()); + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..3]).is_ok()); + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks).is_ok()); + + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &[]).is_err()); + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..3]).is_err()); + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..4]).is_ok()); + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks).is_ok()); + + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &[]).is_err()); + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..4]).is_err()); + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..5]).is_ok()); + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks).is_ok()); + } +} diff --git a/proxmox-installer-common/src/lib.rs b/proxmox-installer-common/src/lib.rs index e69de29..f0093f5 100644 --- a/proxmox-installer-common/src/lib.rs +++ b/proxmox-installer-common/src/lib.rs @@ -0,0 +1,4 @@ +pub mod disk_checks; +pub mod options; +pub mod setup; +pub mod utils; diff --git a/proxmox-installer-common/src/options.rs b/proxmox-installer-common/src/options.rs new file mode 100644 index 0000000..185be2e --- /dev/null +++ b/proxmox-installer-common/src/options.rs @@ -0,0 +1,387 @@ +use std::net::{IpAddr, Ipv4Addr}; +use std::{cmp, fmt}; + +use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo, SetupInfo}; +use crate::utils::{CidrAddress, Fqdn}; + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum BtrfsRaidLevel { + Raid0, + Raid1, + Raid10, +} + +impl fmt::Display for BtrfsRaidLevel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use BtrfsRaidLevel::*; + match self { + Raid0 => write!(f, "RAID0"), + Raid1 => write!(f, "RAID1"), + Raid10 => write!(f, "RAID10"), + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum ZfsRaidLevel { + Raid0, + Raid1, + Raid10, + RaidZ, + RaidZ2, + RaidZ3, +} + +impl fmt::Display for ZfsRaidLevel { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use ZfsRaidLevel::*; + match self { + Raid0 => write!(f, "RAID0"), + Raid1 => write!(f, "RAID1"), + Raid10 => write!(f, "RAID10"), + RaidZ => write!(f, "RAIDZ-1"), + RaidZ2 => write!(f, "RAIDZ-2"), + RaidZ3 => write!(f, "RAIDZ-3"), + } + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum FsType { + Ext4, + Xfs, + Zfs(ZfsRaidLevel), + Btrfs(BtrfsRaidLevel), +} + +impl FsType { + pub fn is_btrfs(&self) -> bool { + matches!(self, FsType::Btrfs(_)) + } +} + +impl fmt::Display for FsType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use FsType::*; + match self { + Ext4 => write!(f, "ext4"), + Xfs => write!(f, "XFS"), + Zfs(level) => write!(f, "ZFS ({level})"), + Btrfs(level) => write!(f, "Btrfs ({level})"), + } + } +} + +#[derive(Clone, Debug)] +pub struct LvmBootdiskOptions { + pub total_size: f64, + pub swap_size: Option, + pub max_root_size: Option, + pub max_data_size: Option, + pub min_lvm_free: Option, +} + +impl LvmBootdiskOptions { + pub fn defaults_from(disk: &Disk) -> Self { + Self { + total_size: disk.size, + swap_size: None, + max_root_size: None, + max_data_size: None, + min_lvm_free: None, + } + } +} + +#[derive(Clone, Debug)] +pub struct BtrfsBootdiskOptions { + pub disk_size: f64, + pub selected_disks: Vec, +} + +impl BtrfsBootdiskOptions { + /// This panics if the provided slice is empty. + pub fn defaults_from(disks: &[Disk]) -> Self { + let disk = &disks[0]; + Self { + disk_size: disk.size, + selected_disks: (0..disks.len()).collect(), + } + } +} + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub enum ZfsCompressOption { + #[default] + On, + Off, + Lzjb, + Lz4, + Zle, + Gzip, + Zstd, +} + +impl fmt::Display for ZfsCompressOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", format!("{self:?}").to_lowercase()) + } +} + +impl From<&ZfsCompressOption> for String { + fn from(value: &ZfsCompressOption) -> Self { + value.to_string() + } +} + +pub const ZFS_COMPRESS_OPTIONS: &[ZfsCompressOption] = { + use ZfsCompressOption::*; + &[On, Off, Lzjb, Lz4, Zle, Gzip, Zstd] +}; + +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] +pub enum ZfsChecksumOption { + #[default] + On, + Off, + Fletcher2, + Fletcher4, + Sha256, +} + +impl fmt::Display for ZfsChecksumOption { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", format!("{self:?}").to_lowercase()) + } +} + +impl From<&ZfsChecksumOption> for String { + fn from(value: &ZfsChecksumOption) -> Self { + value.to_string() + } +} + +pub const ZFS_CHECKSUM_OPTIONS: &[ZfsChecksumOption] = { + use ZfsChecksumOption::*; + &[On, Off, Fletcher2, Fletcher4, Sha256] +}; + +#[derive(Clone, Debug)] +pub struct ZfsBootdiskOptions { + pub ashift: usize, + pub compress: ZfsCompressOption, + pub checksum: ZfsChecksumOption, + pub copies: usize, + pub disk_size: f64, + pub selected_disks: Vec, +} + +impl ZfsBootdiskOptions { + /// This panics if the provided slice is empty. + pub fn defaults_from(disks: &[Disk]) -> Self { + let disk = &disks[0]; + Self { + ashift: 12, + compress: ZfsCompressOption::default(), + checksum: ZfsChecksumOption::default(), + copies: 1, + disk_size: disk.size, + selected_disks: (0..disks.len()).collect(), + } + } +} + +#[derive(Clone, Debug)] +pub enum AdvancedBootdiskOptions { + Lvm(LvmBootdiskOptions), + Zfs(ZfsBootdiskOptions), + Btrfs(BtrfsBootdiskOptions), +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Disk { + pub index: String, + pub path: String, + pub model: Option, + pub size: f64, + pub block_size: Option, +} + +impl fmt::Display for Disk { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // TODO: Format sizes properly with `proxmox-human-byte` once merged + // https://lists.proxmox.com/pipermail/pbs-devel/2023-May/006125.html + f.write_str(&self.path)?; + if let Some(model) = &self.model { + // FIXME: ellipsize too-long names? + write!(f, " ({model})")?; + } + write!(f, " ({:.2} GiB)", self.size) + } +} + +impl From<&Disk> for String { + fn from(value: &Disk) -> Self { + value.to_string() + } +} + +impl cmp::Eq for Disk {} + +impl cmp::PartialOrd for Disk { + fn partial_cmp(&self, other: &Self) -> Option { + self.index.partial_cmp(&other.index) + } +} + +impl cmp::Ord for Disk { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.index.cmp(&other.index) + } +} + +#[derive(Clone, Debug)] +pub struct BootdiskOptions { + pub disks: Vec, + pub fstype: FsType, + pub advanced: AdvancedBootdiskOptions, +} + +impl BootdiskOptions { + pub fn defaults_from(disk: &Disk) -> Self { + Self { + disks: vec![disk.clone()], + fstype: FsType::Ext4, + advanced: AdvancedBootdiskOptions::Lvm(LvmBootdiskOptions::defaults_from(disk)), + } + } +} + +#[derive(Clone, Debug)] +pub struct TimezoneOptions { + pub country: String, + pub timezone: String, + pub kb_layout: String, +} + +impl TimezoneOptions { + pub fn defaults_from(runtime: &RuntimeInfo, locales: &LocaleInfo) -> Self { + let country = runtime.country.clone().unwrap_or_else(|| "at".to_owned()); + + let timezone = locales + .cczones + .get(&country) + .and_then(|zones| zones.get(0)) + .cloned() + .unwrap_or_else(|| "UTC".to_owned()); + + let kb_layout = locales + .countries + .get(&country) + .and_then(|c| { + if c.kmap.is_empty() { + None + } else { + Some(c.kmap.clone()) + } + }) + .unwrap_or_else(|| "en-us".to_owned()); + + Self { + country, + timezone, + kb_layout, + } + } +} + +#[derive(Clone, Debug)] +pub struct PasswordOptions { + pub email: String, + pub root_password: String, +} + +impl Default for PasswordOptions { + fn default() -> Self { + Self { + email: "mail at example.invalid".to_string(), + root_password: String::new(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct NetworkOptions { + pub ifname: String, + pub fqdn: Fqdn, + pub address: CidrAddress, + pub gateway: IpAddr, + pub dns_server: IpAddr, +} + +impl NetworkOptions { + const DEFAULT_DOMAIN: &str = "example.invalid"; + + pub fn defaults_from(setup: &SetupInfo, network: &NetworkInfo) -> Self { + let mut this = Self { + ifname: String::new(), + fqdn: Self::construct_fqdn(network, setup.config.product.default_hostname()), + // Safety: The provided mask will always be valid. + address: CidrAddress::new(Ipv4Addr::UNSPECIFIED, 0).unwrap(), + gateway: Ipv4Addr::UNSPECIFIED.into(), + dns_server: Ipv4Addr::UNSPECIFIED.into(), + }; + + if let Some(ip) = network.dns.dns.first() { + this.dns_server = *ip; + } + + if let Some(routes) = &network.routes { + let mut filled = false; + if let Some(gw) = &routes.gateway4 { + if let Some(iface) = network.interfaces.get(&gw.dev) { + this.ifname = iface.name.clone(); + if let Some(addresses) = &iface.addresses { + if let Some(addr) = addresses.iter().find(|addr| addr.is_ipv4()) { + this.gateway = gw.gateway; + this.address = addr.clone(); + filled = true; + } + } + } + } + if !filled { + if let Some(gw) = &routes.gateway6 { + if let Some(iface) = network.interfaces.get(&gw.dev) { + if let Some(addresses) = &iface.addresses { + if let Some(addr) = addresses.iter().find(|addr| addr.is_ipv6()) { + this.ifname = iface.name.clone(); + this.gateway = gw.gateway; + this.address = addr.clone(); + } + } + } + } + } + } + + this + } + + fn construct_fqdn(network: &NetworkInfo, default_hostname: &str) -> Fqdn { + let hostname = network.hostname.as_deref().unwrap_or(default_hostname); + + let domain = network + .dns + .domain + .as_deref() + .unwrap_or(Self::DEFAULT_DOMAIN); + + Fqdn::from(&format!("{hostname}.{domain}")).unwrap_or_else(|_| { + // Safety: This will always result in a valid FQDN, as we control & know + // the values of default_hostname (one of "pve", "pmg" or "pbs") and + // constant-defined DEFAULT_DOMAIN. + Fqdn::from(&format!("{}.{}", default_hostname, Self::DEFAULT_DOMAIN)).unwrap() + }) + } +} diff --git a/proxmox-installer-common/src/setup.rs b/proxmox-installer-common/src/setup.rs new file mode 100644 index 0000000..a4947f1 --- /dev/null +++ b/proxmox-installer-common/src/setup.rs @@ -0,0 +1,330 @@ +use std::{ + cmp, + collections::HashMap, + fmt, + fs::File, + io::BufReader, + net::IpAddr, + path::{Path, PathBuf}, +}; + +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; + +use crate::{ + options::{Disk, ZfsBootdiskOptions, ZfsChecksumOption, ZfsCompressOption}, + utils::CidrAddress, +}; + +#[allow(clippy::upper_case_acronyms)] +#[derive(Clone, Copy, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum ProxmoxProduct { + PVE, + PBS, + PMG, +} + +impl ProxmoxProduct { + pub fn default_hostname(self) -> &'static str { + match self { + Self::PVE => "pve", + Self::PMG => "pmg", + Self::PBS => "pbs", + } + } +} + +#[derive(Clone, Deserialize)] +pub struct ProductConfig { + pub fullname: String, + pub product: ProxmoxProduct, + #[serde(deserialize_with = "deserialize_bool_from_int")] + pub enable_btrfs: bool, +} + +#[derive(Clone, Deserialize)] +pub struct IsoInfo { + pub release: String, + pub isorelease: String, +} + +/// Paths in the ISO environment containing installer data. +#[derive(Clone, Deserialize)] +pub struct IsoLocations { + pub iso: PathBuf, +} + +#[derive(Clone, Deserialize)] +pub struct SetupInfo { + #[serde(rename = "product-cfg")] + pub config: ProductConfig, + #[serde(rename = "iso-info")] + pub iso_info: IsoInfo, + pub locations: IsoLocations, +} + +#[derive(Clone, Deserialize)] +pub struct CountryInfo { + pub name: String, + #[serde(default)] + pub zone: String, + pub kmap: String, +} + +#[derive(Clone, Deserialize, Eq, PartialEq)] +pub struct KeyboardMapping { + pub name: String, + #[serde(rename = "kvm")] + pub id: String, + #[serde(rename = "x11")] + pub xkb_layout: String, + #[serde(rename = "x11var")] + pub xkb_variant: String, +} + +impl cmp::PartialOrd for KeyboardMapping { + fn partial_cmp(&self, other: &Self) -> Option { + self.name.partial_cmp(&other.name) + } +} + +impl cmp::Ord for KeyboardMapping { + fn cmp(&self, other: &Self) -> cmp::Ordering { + self.name.cmp(&other.name) + } +} + +#[derive(Clone, Deserialize)] +pub struct LocaleInfo { + #[serde(deserialize_with = "deserialize_cczones_map")] + pub cczones: HashMap>, + #[serde(rename = "country")] + pub countries: HashMap, + pub kmap: HashMap, +} + +#[derive(Serialize)] +struct InstallZfsOption { + ashift: usize, + #[serde(serialize_with = "serialize_as_display")] + compress: ZfsCompressOption, + #[serde(serialize_with = "serialize_as_display")] + checksum: ZfsChecksumOption, + copies: usize, +} + +impl From for InstallZfsOption { + fn from(opts: ZfsBootdiskOptions) -> Self { + InstallZfsOption { + ashift: opts.ashift, + compress: opts.compress, + checksum: opts.checksum, + copies: opts.copies, + } + } +} + +pub fn read_json Deserialize<'de>, P: AsRef>(path: P) -> Result { + let file = File::open(path).map_err(|err| err.to_string())?; + let reader = BufReader::new(file); + + serde_json::from_reader(reader).map_err(|err| format!("failed to parse JSON: {err}")) +} + +fn deserialize_bool_from_int<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let val: u32 = Deserialize::deserialize(deserializer)?; + Ok(val != 0) +} + +fn deserialize_cczones_map<'de, D>( + deserializer: D, +) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + let map: HashMap> = Deserialize::deserialize(deserializer)?; + + let mut result = HashMap::new(); + for (cc, list) in map.into_iter() { + result.insert(cc, list.into_keys().collect()); + } + + Ok(result) +} + +fn deserialize_disks_map<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let disks = + , String)>>::deserialize(deserializer)?; + Ok(disks + .into_iter() + .map( + |(index, device, size_mb, model, logical_bsize, _syspath)| Disk { + index: index.to_string(), + // Linux always reports the size of block devices in sectors, where one sector is + // defined as being 2^9 = 512 bytes in size. + // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/blk_types.h?h=v6.4#n30 + size: (size_mb * 512.) / 1024. / 1024. / 1024., + block_size: logical_bsize, + path: device, + model: (!model.is_empty()).then_some(model), + }, + ) + .collect()) +} + +fn deserialize_cidr_list<'de, D>(deserializer: D) -> Result>, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + struct CidrDescriptor { + address: String, + prefix: usize, + // family is implied anyway by parsing the address + } + + let list: Vec = Deserialize::deserialize(deserializer)?; + + let mut result = Vec::with_capacity(list.len()); + for desc in list { + let ip_addr = desc + .address + .parse::() + .map_err(|err| de::Error::custom(format!("{:?}", err)))?; + + result.push( + CidrAddress::new(ip_addr, desc.prefix) + .map_err(|err| de::Error::custom(format!("{:?}", err)))?, + ); + } + + Ok(Some(result)) +} + +fn serialize_as_display(value: &T, serializer: S) -> Result +where + S: Serializer, + T: fmt::Display, +{ + serializer.collect_str(value) +} + +#[derive(Clone, Deserialize)] +pub struct RuntimeInfo { + /// Whether is system was booted in (legacy) BIOS or UEFI mode. + pub boot_type: BootType, + + /// Detected country if available. + pub country: Option, + + /// Maps devices to their information. + #[serde(deserialize_with = "deserialize_disks_map")] + pub disks: Vec, + + /// Network addresses, gateways and DNS info. + pub network: NetworkInfo, + + /// Total memory of the system in MiB. + pub total_memory: usize, + + /// Whether the CPU supports hardware-accelerated virtualization + #[serde(deserialize_with = "deserialize_bool_from_int")] + pub hvm_supported: bool, +} + +#[derive(Copy, Clone, Eq, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum BootType { + Bios, + Efi, +} + +#[derive(Clone, Deserialize)] +pub struct NetworkInfo { + pub dns: Dns, + pub routes: Option, + + /// Maps devices to their configuration, if it has a usable configuration. + /// (Contains no entries for devices with only link-local addresses.) + #[serde(default)] + pub interfaces: HashMap, + + /// The hostname of this machine, if set by the DHCP server. + pub hostname: Option, +} + +#[derive(Clone, Deserialize)] +pub struct Dns { + pub domain: Option, + + /// List of stringified IP addresses. + #[serde(default)] + pub dns: Vec, +} + +#[derive(Clone, Deserialize)] +pub struct Routes { + /// Ipv4 gateway. + pub gateway4: Option, + + /// Ipv6 gateway. + pub gateway6: Option, +} + +#[derive(Clone, Deserialize)] +pub struct Gateway { + /// Outgoing network device. + pub dev: String, + + /// Stringified gateway IP address. + pub gateway: IpAddr, +} + +#[derive(Clone, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum InterfaceState { + Up, + Down, + #[serde(other)] + Unknown, +} + +impl InterfaceState { + // avoid display trait as this is not the string representation for a serializer + pub fn render(&self) -> String { + match self { + Self::Up => "\u{25CF}", + Self::Down | Self::Unknown => " ", + } + .into() + } +} + +#[derive(Clone, Deserialize)] +pub struct Interface { + pub name: String, + + pub index: usize, + + pub mac: String, + + pub state: InterfaceState, + + #[serde(default)] + #[serde(deserialize_with = "deserialize_cidr_list")] + pub addresses: Option>, +} + +impl Interface { + // avoid display trait as this is not the string representation for a serializer + pub fn render(&self) -> String { + format!("{} {}", self.state.render(), self.name) + } +} + diff --git a/proxmox-installer-common/src/utils.rs b/proxmox-installer-common/src/utils.rs new file mode 100644 index 0000000..89349ed --- /dev/null +++ b/proxmox-installer-common/src/utils.rs @@ -0,0 +1,268 @@ +use std::{ + fmt, + net::{AddrParseError, IpAddr}, + num::ParseIntError, + str::FromStr, +}; + +use serde::Deserialize; + +/// Possible errors that might occur when parsing CIDR addresses. +#[derive(Debug)] +pub enum CidrAddressParseError { + /// No delimiter for separating address and mask was found. + NoDelimiter, + /// The IP address part could not be parsed. + InvalidAddr(AddrParseError), + /// The mask could not be parsed. + InvalidMask(Option), +} + +/// An IP address (IPv4 or IPv6), including network mask. +/// +/// See the [`IpAddr`] type for more information how IP addresses are handled. +/// The mask is appropriately enforced to be `0 <= mask <= 32` for IPv4 or +/// `0 <= mask <= 128` for IPv6 addresses. +/// +/// # Examples +/// ``` +/// use std::net::{Ipv4Addr, Ipv6Addr}; +/// let ipv4 = CidrAddress::new(Ipv4Addr::new(192, 168, 0, 1), 24).unwrap(); +/// let ipv6 = CidrAddress::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xc0a8, 1), 32).unwrap(); +/// +/// assert_eq!(ipv4.to_string(), "192.168.0.1/24"); +/// assert_eq!(ipv6.to_string(), "2001:db8::c0a8:1/32"); +/// ``` +#[derive(Clone, Debug, PartialEq)] +pub struct CidrAddress { + addr: IpAddr, + mask: usize, +} + +impl CidrAddress { + /// Constructs a new CIDR address. + /// + /// It fails if the mask is invalid for the given IP address. + pub fn new>(addr: T, mask: usize) -> Result { + let addr = addr.into(); + + if mask > mask_limit(&addr) { + Err(CidrAddressParseError::InvalidMask(None)) + } else { + Ok(Self { addr, mask }) + } + } + + /// Returns only the IP address part of the address. + pub fn addr(&self) -> IpAddr { + self.addr + } + + /// Returns `true` if this address is an IPv4 address, `false` otherwise. + pub fn is_ipv4(&self) -> bool { + self.addr.is_ipv4() + } + + /// Returns `true` if this address is an IPv6 address, `false` otherwise. + pub fn is_ipv6(&self) -> bool { + self.addr.is_ipv6() + } + + /// Returns only the mask part of the address. + pub fn mask(&self) -> usize { + self.mask + } +} + +impl FromStr for CidrAddress { + type Err = CidrAddressParseError; + + fn from_str(s: &str) -> Result { + let (addr, mask) = s + .split_once('/') + .ok_or(CidrAddressParseError::NoDelimiter)?; + + let addr = addr.parse().map_err(CidrAddressParseError::InvalidAddr)?; + + let mask = mask + .parse() + .map_err(|err| CidrAddressParseError::InvalidMask(Some(err)))?; + + if mask > mask_limit(&addr) { + Err(CidrAddressParseError::InvalidMask(None)) + } else { + Ok(Self { addr, mask }) + } + } +} + +impl fmt::Display for CidrAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", self.addr, self.mask) + } +} + +fn mask_limit(addr: &IpAddr) -> usize { + if addr.is_ipv4() { + 32 + } else { + 128 + } +} + +/// Possible errors that might occur when parsing FQDNs. +#[derive(Debug, Eq, PartialEq)] +pub enum FqdnParseError { + MissingHostname, + NumericHostname, + InvalidPart(String), +} + +impl fmt::Display for FqdnParseError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use FqdnParseError::*; + match self { + MissingHostname => write!(f, "missing hostname part"), + NumericHostname => write!(f, "hostname cannot be purely numeric"), + InvalidPart(part) => write!( + f, + "FQDN must only consist of alphanumeric characters and dashes. Invalid part: '{part}'", + ), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Fqdn { + parts: Vec, +} + +impl Fqdn { + pub fn from(fqdn: &str) -> Result { + let parts = fqdn + .split('.') + .map(ToOwned::to_owned) + .collect::>(); + + for part in &parts { + if !Self::validate_single(part) { + return Err(FqdnParseError::InvalidPart(part.clone())); + } + } + + if parts.len() < 2 { + Err(FqdnParseError::MissingHostname) + } else if parts[0].chars().all(|c| c.is_ascii_digit()) { + // Not allowed/supported on Debian systems. + Err(FqdnParseError::NumericHostname) + } else { + Ok(Self { parts }) + } + } + + pub fn host(&self) -> Option<&str> { + self.has_host().then_some(&self.parts[0]) + } + + pub fn domain(&self) -> String { + let parts = if self.has_host() { + &self.parts[1..] + } else { + &self.parts + }; + + parts.join(".") + } + + /// Checks whether the FQDN has a hostname associated with it, i.e. is has more than 1 part. + fn has_host(&self) -> bool { + self.parts.len() > 1 + } + + fn validate_single(s: &String) -> bool { + !s.is_empty() + // First character must be alphanumeric + && s.chars() + .next() + .map(|c| c.is_ascii_alphanumeric()) + .unwrap_or_default() + // .. last character as well, + && s.chars() + .last() + .map(|c| c.is_ascii_alphanumeric()) + .unwrap_or_default() + // and anything between must be alphanumeric or - + && s.chars() + .skip(1) + .take(s.len().saturating_sub(2)) + .all(|c| c.is_ascii_alphanumeric() || c == '-') + } +} + +impl FromStr for Fqdn { + type Err = FqdnParseError; + + fn from_str(value: &str) -> Result { + Self::from(value) + } +} + +impl fmt::Display for Fqdn { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.parts.join(".")) + } +} + +impl<'de> Deserialize<'de> for Fqdn { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let s: String = Deserialize::deserialize(deserializer)?; + s.parse() + .map_err(|_| serde::de::Error::custom("invalid FQDN")) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn fqdn_construct() { + use FqdnParseError::*; + assert!(Fqdn::from("foo.example.com").is_ok()); + assert!(Fqdn::from("foo-bar.com").is_ok()); + assert!(Fqdn::from("a-b.com").is_ok()); + + assert_eq!(Fqdn::from("foo"), Err(MissingHostname)); + + assert_eq!(Fqdn::from("-foo.com"), Err(InvalidPart("-foo".to_owned()))); + assert_eq!(Fqdn::from("foo-.com"), Err(InvalidPart("foo-".to_owned()))); + assert_eq!(Fqdn::from("foo.com-"), Err(InvalidPart("com-".to_owned()))); + assert_eq!(Fqdn::from("-o-.com"), Err(InvalidPart("-o-".to_owned()))); + + assert_eq!(Fqdn::from("123.com"), Err(NumericHostname)); + assert!(Fqdn::from("foo123.com").is_ok()); + assert!(Fqdn::from("123foo.com").is_ok()); + } + + #[test] + fn fqdn_parts() { + let fqdn = Fqdn::from("pve.example.com").unwrap(); + assert_eq!(fqdn.host().unwrap(), "pve"); + assert_eq!(fqdn.domain(), "example.com"); + assert_eq!( + fqdn.parts, + &["pve".to_owned(), "example".to_owned(), "com".to_owned()] + ); + } + + #[test] + fn fqdn_display() { + assert_eq!( + Fqdn::from("foo.example.com").unwrap().to_string(), + "foo.example.com" + ); + } +} -- 2.39.2 From alexandre.derumier at groupe-cyllene.com Wed Oct 25 18:01:30 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Wed, 25 Oct 2023 16:01:30 +0000 Subject: [pve-devel] [PATCH v4 qemu-server 2/2] remote-migration: add target-cpu && target-reboot params In-Reply-To: <8d06d2f6-b831-45b3-ac1b-2cc3f1721b85@proxmox.com> References: <20230928144556.2023558-1-aderumier@odiso.com> <20230928144556.2023558-3-aderumier@odiso.com> <5ecfa7d0-4525-5f1e-75a2-a6ae1a93356b@proxmox.com> <73e0a3a6-f978-ac24-5f6b-16af759ee209@proxmox.com> <016bd4b8-7502-48fe-9208-a075e8aea02b@proxmox.com> <283ff207e6065f0ae178410bfa391e9a5369924f.camel@groupe-cyllene.com> <8d06d2f6-b831-45b3-ac1b-2cc3f1721b85@proxmox.com> Message-ID: <188c296857bc3ae42f0a5150770e8c3942ec74f0.camel@groupe-cyllene.com> > >>Is it required for this series? for this series, no.? It's only focus on migrating to remote with different cpu without too much downtime. >> Unused disks can just be migrated >>offline via storage_migrate(), or?? currently unused disk can't be migrate through the http tunnel for remote-migration 2023-10-25 17:51:38 ERROR: error - tunnel command '{"format":"raw","migration_snapshot":"1","export_formats":"zfs","allow _rename":"1","snapshot":"__migration__","volname":"vm-1112-disk- 1","cmd":"disk-import","storage":"targetstorage","with_snapshots":1}' failed - failed to handle 'disk-import' command - no matching import/export format found for storage 'preprodkvm' 2023-10-25 17:51:38 aborting phase 1 - cleanup resources tunnel: -> sending command "quit" to remote tunnel: <- got reply tunnel: CMD channel closed, shutting down 2023-10-25 17:51:39 ERROR: migration aborted (duration 00:00:01): error - tunnel command '{"format":"raw","migration_snapshot":"1","export_formats":"zfs","allow _rename":"1","snapshot":"__migration__","volname":"vm-1112-disk- 1","cmd":"disk-import","storage":"targetstorage","with_snapshots":1}' failed - failed to handle 'disk-import' command - no matching import/export format found for storage 'preprodkvm' migration aborted >>If we want to switch to migrating >>disks offline via QEMU instead of our current storage_migrate(), >>going >>for QEMU storage daemon + NBD seems the most natural to me. Yes, I more for this solution. >>If it's not too complicated to temporarily attach the disks to the >>VM, >>that can be done too, but is less re-usable (e.g. pure offline >>migration >>won't benefit from that). No sure about attach/detach temporary once by once, or attach all devices (but this need enough controllers slot). qemu storage daemon seem to be a less hacky solution ^_^ > but if it's work, I think we'll need to add config generation in pv > storage for differents blockdriver > > > like: > > ?blockdev driver=file,node-name=file0,filename=vm.img > > ?blockdev driver=rbd,node-name=rbd0,pool=my-pool,image=vm01 > >>What other special cases besides (non-krbd) RBD are there? If it's >>just >>that, I'd much rather keep the special handling in QEMU itself then >>burden all other storage plugins with implementing something specific >>to >>VMs. not sure, maybe glusterfs, .raw (should works for block device like lvm,zfs), .qcow2 >>Or is there a way to use the path from the storage plugin somehow >>like >>we do at the moment, i.e. >>"rbd:rbd/vm-111-disk- >>1:conf=/etc/pve/ceph.conf:id=admin:keyring=/etc/pve/priv/ceph/rbd.key >>ring"? I don't think it's possible just like this.I need to do more test, looking at libvirt before they are not too much doc about it. > So maybe it'll take a little bit more time. > > (Maybe a second patch serie later to implement it) > >>Yes, I think that makes sense as a dedicated series. From t.lamprecht at proxmox.com Wed Oct 25 18:46:04 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Wed, 25 Oct 2023 18:46:04 +0200 Subject: [pve-devel] applied: [PATCH manager] subscription: remove ceph APT auth if invalid In-Reply-To: <20231025133435.3217455-1-f.gruenbichler@proxmox.com> References: <20231025133435.3217455-1-f.gruenbichler@proxmox.com> Message-ID: Am 25/10/2023 um 15:34 schrieb Fabian Gr?nbichler: > like we do for the main APT auth file(s) in proxmox-subscription. > > Signed-off-by: Fabian Gr?nbichler > --- > PVE/API2/Subscription.pm | 16 ++++++++++------ > 1 file changed, 10 insertions(+), 6 deletions(-) > > applied, thanks! From t.lamprecht at proxmox.com Wed Oct 25 19:02:39 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Wed, 25 Oct 2023 19:02:39 +0200 Subject: [pve-devel] applied: [PATCH pve-xtermjs] xtermjs: try to detect hardware support for webgl2 In-Reply-To: <20231025090859.2235027-1-d.csapak@proxmox.com> References: <20231025090859.2235027-1-d.csapak@proxmox.com> Message-ID: Am 25/10/2023 um 11:08 schrieb Dominik Csapak: > with the new webgl renderer, chrome/chromium has buggy software support for > emulating this (see [0]), so we have to detect that manually and prevent > loading the addon. This fixes the issue that on chrome without hw > support, it would not always render every character. > > Firefox does not have support for a software renderer and the > loading/detection throws an exception, falling back to the default > renderer. > > 0: https://github.com/xtermjs/xterm.js/issues/4574 > > Signed-off-by: Dominik Csapak > --- > tested with and without webgl hardware support on firefox/chrome/chromium > > xterm.js/src/main.js | 22 ++++++++++++++++++++-- > 1 file changed, 20 insertions(+), 2 deletions(-) > > applied, thanks! From t.lamprecht at proxmox.com Wed Oct 25 19:04:23 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Wed, 25 Oct 2023 19:04:23 +0200 Subject: [pve-devel] applied-series: [PATCH installer v2 0/3] tui: remove global, unsafe setup info In-Reply-To: <20231025085634.171618-1-c.heiss@proxmox.com> References: <20231025085634.171618-1-c.heiss@proxmox.com> Message-ID: <334abe4b-f548-4e10-bf89-6eb89d3e6c9f@proxmox.com> Am 25/10/2023 um 10:56 schrieb Christoph Heiss: > Removes the `static mut` for holding a `SetupInfo` instance. > > This is done by either passing the needed info as parameter, or in some > cases, the needed information is already available through other means. > > Not only does it get rid of some ugly, unsafe code, it is needed anyway > as a prerequisite by Aaron for pulling out non-TUI-related code into a > separate, shared crate. > > No functional changes overall. > > v1: https://lists.proxmox.com/pipermail/pve-devel/2023-October/059335.html > > Changes v1 -> v2: > * Rebased on latest master, no actual changes otherwise > > Christoph Heiss (3): > tui: refactor `NetworkOptions` to have a `defaults_from()` function > tui: bootdisk: pass down product info to advanced dialog > tui: remove obsolete, global `SetupInfo` state applied series, thanks! From t.lamprecht at proxmox.com Wed Oct 25 19:09:00 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Wed, 25 Oct 2023 19:09:00 +0200 Subject: [pve-devel] [PATCH installer v2 1/6] fix #4829: install: add new ZFS `arc_max` setup option In-Reply-To: <7l5h7ecjzznfpagtvnk7xcp7s2cu7njnnext2dp4zcwgyxbk3p@r5nlqvepijry> References: <20231024115530.1101733-1-c.heiss@proxmox.com> <20231024115530.1101733-2-c.heiss@proxmox.com> <7l5h7ecjzznfpagtvnk7xcp7s2cu7njnnext2dp4zcwgyxbk3p@r5nlqvepijry> Message-ID: <97055168-3ed7-4338-8890-05bbe8c893ee@proxmox.com> Am 25/10/2023 um 10:28 schrieb Christoph Heiss: > On Tue, Oct 24, 2023 at 08:59:36AM -0300, Gilberto Ferreira via pve-devel wrote: >> Date: Tue, 24 Oct 2023 08:59:36 -0300 >> From: Gilberto Ferreira >> To: Proxmox VE development discussion >> Subject: Re: [pve-devel] [PATCH installer v2 1/6] fix #4829: install: add >> new ZFS `arc_max` setup option >> >> >> Hi there. >> Now, that's a good option in the installer. >> I wonder if this option will be available post-intall, like some box in the >> ZFS manager from WEB GUI! >> That's would be nice. >> > > Currently, this isn't planned, although - since that setting is exposed > after all in the installer in the future - it would be kind of sensible > to add it to the GUI as well, I guess. > But as a separate series from this one, of course. > > CC @Thomas - what do you think? We would need to either have a separate config entry, that can easily get out of sync with the actual configured values, or parse all possible ways to set this, e.g., modprobe configs, which isn't too nice. With a few trade-offs/limitations one could probably work around that, but IMO it can be a bit too much hassle for something that one normally changes only once or twice (or once this change is in, probably never for new setups), so for now I'd keep this manual. From aderumier at odiso.com Thu Oct 26 10:57:07 2023 From: aderumier at odiso.com (Alexandre Derumier) Date: Thu, 26 Oct 2023 10:57:07 +0200 Subject: [pve-devel] [PATCH v5 qemu-server 0/3] remote-migration: migration with different cpu Message-ID: <20231026085710.1611413-1-aderumier@odiso.com> This patch series allow remote migration between cluster with different cpu model. 2 new params are introduced: "target-cpu" && "restart" If target-cpu is defined, this will replace the cpu model of the target vm. If vm is online/running, an extra "target-reboot" safeguard option is needed. Indeed, as the target cpu is different, the live migration with memory transfert is skipped (as anyway, the target will die with a different cpu). Then, after the storage copy, we switch source vm disk to the targetvm nbd export, then shutdown the source vm and restart the target vm. (Like a virtual reboot between source/target) We have redone a lot of migration this summer( maybe another 4000vm), 0 corruption, windows or linux guest vms. Changelog v2: The first version was simply shuting down the target vm, wihout doing the block-job-complete. After doing production migration with around 400vms, I had some fs corruption, like some datas was still in buffer. This v2 has been tested with another 400vms batch, without any corruption. Changelog v3: v2 was not perfect, still have some 1 or 2 fs corruption with vms doing a lot of write. This v3 retake idea of the v1 but in a cleaner way - we migrate disk to target vm - source vm is switching disk to the nbd of the target vm. (with a block-job-complete, and not a block-job-cancel with standard disk migration). We are 100% sure it that no pending write is still pending in the migration job. - source vm is shutdown - target with is restart Changelog v4: - bugfix: no not override cpu with empty config if targetcpu is not defined - small cleanups with params Changelov V5: - Fix fiona comments - use "restart" param instead "target-reboot" - split target-cpu param in separated patch Alexandre Derumier (3): migration: move livemigration code in a dedicated sub remote-migration: add restart param add target-cpu param PVE/API2/Qemu.pm | 26 +++ PVE/CLI/qm.pm | 12 ++ PVE/QemuMigrate.pm | 452 ++++++++++++++++++++++++--------------------- 3 files changed, 281 insertions(+), 209 deletions(-) -- 2.39.2 From aderumier at odiso.com Thu Oct 26 10:57:08 2023 From: aderumier at odiso.com (Alexandre Derumier) Date: Thu, 26 Oct 2023 10:57:08 +0200 Subject: [pve-devel] [PATCH v5 qemu-server 1/3] migration: move livemigration code in a dedicated sub In-Reply-To: <20231026085710.1611413-1-aderumier@odiso.com> References: <20231026085710.1611413-1-aderumier@odiso.com> Message-ID: <20231026085710.1611413-2-aderumier@odiso.com> Signed-off-by: Alexandre Derumier --- PVE/QemuMigrate.pm | 420 +++++++++++++++++++++++---------------------- 1 file changed, 214 insertions(+), 206 deletions(-) diff --git a/PVE/QemuMigrate.pm b/PVE/QemuMigrate.pm index 111eeb0..7dd3455 100644 --- a/PVE/QemuMigrate.pm +++ b/PVE/QemuMigrate.pm @@ -728,6 +728,219 @@ sub cleanup_bitmaps { } } +sub live_migration { + my ($self, $vmid, $migrate_uri, $spice_port) = @_; + + my $conf = $self->{vmconf}; + + $self->log('info', "starting online/live migration on $migrate_uri"); + $self->{livemigration} = 1; + + # load_defaults + my $defaults = PVE::QemuServer::load_defaults(); + + $self->log('info', "set migration capabilities"); + eval { PVE::QemuServer::set_migration_caps($vmid) }; + warn $@ if $@; + + my $qemu_migrate_params = {}; + + # migrate speed can be set via bwlimit (datacenter.cfg and API) and via the + # migrate_speed parameter in qm.conf - take the lower of the two. + my $bwlimit = $self->get_bwlimit(); + + my $migrate_speed = $conf->{migrate_speed} // 0; + $migrate_speed *= 1024; # migrate_speed is in MB/s, bwlimit in KB/s + + if ($bwlimit && $migrate_speed) { + $migrate_speed = ($bwlimit < $migrate_speed) ? $bwlimit : $migrate_speed; + } else { + $migrate_speed ||= $bwlimit; + } + $migrate_speed ||= ($defaults->{migrate_speed} || 0) * 1024; + + if ($migrate_speed) { + $migrate_speed *= 1024; # qmp takes migrate_speed in B/s. + $self->log('info', "migration speed limit: ". render_bytes($migrate_speed, 1) ."/s"); + } else { + # always set migrate speed as QEMU default to 128 MiBps == 1 Gbps, use 16 GiBps == 128 Gbps + $migrate_speed = (16 << 30); + } + $qemu_migrate_params->{'max-bandwidth'} = int($migrate_speed); + + my $migrate_downtime = $defaults->{migrate_downtime}; + $migrate_downtime = $conf->{migrate_downtime} if defined($conf->{migrate_downtime}); + # migrate-set-parameters expects limit in ms + $migrate_downtime *= 1000; + $self->log('info', "migration downtime limit: $migrate_downtime ms"); + $qemu_migrate_params->{'downtime-limit'} = int($migrate_downtime); + + # set cachesize to 10% of the total memory + my $memory = get_current_memory($conf->{memory}); + my $cachesize = int($memory * 1048576 / 10); + $cachesize = round_powerof2($cachesize); + + $self->log('info', "migration cachesize: " . render_bytes($cachesize, 1)); + $qemu_migrate_params->{'xbzrle-cache-size'} = int($cachesize); + + $self->log('info', "set migration parameters"); + eval { + mon_cmd($vmid, "migrate-set-parameters", %{$qemu_migrate_params}); + }; + $self->log('info', "migrate-set-parameters error: $@") if $@; + + if (PVE::QemuServer::vga_conf_has_spice($conf->{vga}) && !$self->{opts}->{remote}) { + my $rpcenv = PVE::RPCEnvironment::get(); + my $authuser = $rpcenv->get_user(); + + my (undef, $proxyticket) = PVE::AccessControl::assemble_spice_ticket($authuser, $vmid, $self->{node}); + + my $filename = "/etc/pve/nodes/$self->{node}/pve-ssl.pem"; + my $subject = PVE::AccessControl::read_x509_subject_spice($filename); + + $self->log('info', "spice client_migrate_info"); + + eval { + mon_cmd($vmid, "client_migrate_info", protocol => 'spice', + hostname => $proxyticket, 'port' => 0, 'tls-port' => $spice_port, + 'cert-subject' => $subject); + }; + $self->log('info', "client_migrate_info error: $@") if $@; + + } + + my $start = time(); + + $self->log('info', "start migrate command to $migrate_uri"); + eval { + mon_cmd($vmid, "migrate", uri => $migrate_uri); + }; + my $merr = $@; + $self->log('info', "migrate uri => $migrate_uri failed: $merr") if $merr; + + my $last_mem_transferred = 0; + my $usleep = 1000000; + my $i = 0; + my $err_count = 0; + my $lastrem = undef; + my $downtimecounter = 0; + while (1) { + $i++; + my $avglstat = $last_mem_transferred ? $last_mem_transferred / $i : 0; + + usleep($usleep); + + my $stat = eval { mon_cmd($vmid, "query-migrate") }; + if (my $err = $@) { + $err_count++; + warn "query migrate failed: $err\n"; + $self->log('info', "query migrate failed: $err"); + if ($err_count <= 5) { + usleep(1_000_000); + next; + } + die "too many query migrate failures - aborting\n"; + } + + my $status = $stat->{status}; + if (defined($status) && $status =~ m/^(setup)$/im) { + sleep(1); + next; + } + + if (!defined($status) || $status !~ m/^(active|completed|failed|cancelled)$/im) { + die $merr if $merr; + die "unable to parse migration status '$status' - aborting\n"; + } + $merr = undef; + $err_count = 0; + + my $memstat = $stat->{ram}; + + if ($status eq 'completed') { + my $delay = time() - $start; + if ($delay > 0) { + my $total = $memstat->{total} || 0; + my $avg_speed = render_bytes($total / $delay, 1); + my $downtime = $stat->{downtime} || 0; + $self->log('info', "average migration speed: $avg_speed/s - downtime $downtime ms"); + } + } + + if ($status eq 'failed' || $status eq 'cancelled') { + my $message = $stat->{'error-desc'} ? "$status - $stat->{'error-desc'}" : $status; + $self->log('info', "migration status error: $message"); + die "aborting\n" + } + + if ($status ne 'active') { + $self->log('info', "migration status: $status"); + last; + } + + if ($memstat->{transferred} ne $last_mem_transferred) { + my $trans = $memstat->{transferred} || 0; + my $rem = $memstat->{remaining} || 0; + my $total = $memstat->{total} || 0; + my $speed = ($memstat->{'pages-per-second'} // 0) * ($memstat->{'page-size'} // 0); + my $dirty_rate = ($memstat->{'dirty-pages-rate'} // 0) * ($memstat->{'page-size'} // 0); + + # reduce sleep if remainig memory is lower than the average transfer speed + $usleep = 100_000 if $avglstat && $rem < $avglstat; + + # also reduce loggin if we poll more frequent + my $should_log = $usleep > 100_000 ? 1 : ($i % 10) == 0; + + my $total_h = render_bytes($total, 1); + my $transferred_h = render_bytes($trans, 1); + my $speed_h = render_bytes($speed, 1); + + my $progress = "transferred $transferred_h of $total_h VM-state, ${speed_h}/s"; + + if ($dirty_rate > $speed) { + my $dirty_rate_h = render_bytes($dirty_rate, 1); + $progress .= ", VM dirties lots of memory: $dirty_rate_h/s"; + } + + $self->log('info', "migration $status, $progress") if $should_log; + + my $xbzrle = $stat->{"xbzrle-cache"} || {}; + my ($xbzrlebytes, $xbzrlepages) = $xbzrle->@{'bytes', 'pages'}; + if ($xbzrlebytes || $xbzrlepages) { + my $bytes_h = render_bytes($xbzrlebytes, 1); + + my $msg = "send updates to $xbzrlepages pages in $bytes_h encoded memory"; + + $msg .= sprintf(", cache-miss %.2f%%", $xbzrle->{'cache-miss-rate'} * 100) + if $xbzrle->{'cache-miss-rate'}; + + $msg .= ", overflow $xbzrle->{overflow}" if $xbzrle->{overflow}; + + $self->log('info', "xbzrle: $msg") if $should_log; + } + + if (($lastrem && $rem > $lastrem) || ($rem == 0)) { + $downtimecounter++; + } + $lastrem = $rem; + + if ($downtimecounter > 5) { + $downtimecounter = 0; + $migrate_downtime *= 2; + $self->log('info', "auto-increased downtime to continue migration: $migrate_downtime ms"); + eval { + # migrate-set-parameters does not touch values not + # specified, so this only changes downtime-limit + mon_cmd($vmid, "migrate-set-parameters", 'downtime-limit' => int($migrate_downtime)); + }; + $self->log('info', "migrate-set-parameters error: $@") if $@; + } + } + + $last_mem_transferred = $memstat->{transferred}; + } +} + sub phase1 { my ($self, $vmid) = @_; @@ -1139,212 +1352,7 @@ sub phase2 { } } - $self->log('info', "starting online/live migration on $migrate_uri"); - $self->{livemigration} = 1; - - # load_defaults - my $defaults = PVE::QemuServer::load_defaults(); - - $self->log('info', "set migration capabilities"); - eval { PVE::QemuServer::set_migration_caps($vmid) }; - warn $@ if $@; - - my $qemu_migrate_params = {}; - - # migrate speed can be set via bwlimit (datacenter.cfg and API) and via the - # migrate_speed parameter in qm.conf - take the lower of the two. - my $bwlimit = $self->get_bwlimit(); - - my $migrate_speed = $conf->{migrate_speed} // 0; - $migrate_speed *= 1024; # migrate_speed is in MB/s, bwlimit in KB/s - - if ($bwlimit && $migrate_speed) { - $migrate_speed = ($bwlimit < $migrate_speed) ? $bwlimit : $migrate_speed; - } else { - $migrate_speed ||= $bwlimit; - } - $migrate_speed ||= ($defaults->{migrate_speed} || 0) * 1024; - - if ($migrate_speed) { - $migrate_speed *= 1024; # qmp takes migrate_speed in B/s. - $self->log('info', "migration speed limit: ". render_bytes($migrate_speed, 1) ."/s"); - } else { - # always set migrate speed as QEMU default to 128 MiBps == 1 Gbps, use 16 GiBps == 128 Gbps - $migrate_speed = (16 << 30); - } - $qemu_migrate_params->{'max-bandwidth'} = int($migrate_speed); - - my $migrate_downtime = $defaults->{migrate_downtime}; - $migrate_downtime = $conf->{migrate_downtime} if defined($conf->{migrate_downtime}); - # migrate-set-parameters expects limit in ms - $migrate_downtime *= 1000; - $self->log('info', "migration downtime limit: $migrate_downtime ms"); - $qemu_migrate_params->{'downtime-limit'} = int($migrate_downtime); - - # set cachesize to 10% of the total memory - my $memory = get_current_memory($conf->{memory}); - my $cachesize = int($memory * 1048576 / 10); - $cachesize = round_powerof2($cachesize); - - $self->log('info', "migration cachesize: " . render_bytes($cachesize, 1)); - $qemu_migrate_params->{'xbzrle-cache-size'} = int($cachesize); - - $self->log('info', "set migration parameters"); - eval { - mon_cmd($vmid, "migrate-set-parameters", %{$qemu_migrate_params}); - }; - $self->log('info', "migrate-set-parameters error: $@") if $@; - - if (PVE::QemuServer::vga_conf_has_spice($conf->{vga}) && !$self->{opts}->{remote}) { - my $rpcenv = PVE::RPCEnvironment::get(); - my $authuser = $rpcenv->get_user(); - - my (undef, $proxyticket) = PVE::AccessControl::assemble_spice_ticket($authuser, $vmid, $self->{node}); - - my $filename = "/etc/pve/nodes/$self->{node}/pve-ssl.pem"; - my $subject = PVE::AccessControl::read_x509_subject_spice($filename); - - $self->log('info', "spice client_migrate_info"); - - eval { - mon_cmd($vmid, "client_migrate_info", protocol => 'spice', - hostname => $proxyticket, 'port' => 0, 'tls-port' => $spice_port, - 'cert-subject' => $subject); - }; - $self->log('info', "client_migrate_info error: $@") if $@; - - } - - my $start = time(); - - $self->log('info', "start migrate command to $migrate_uri"); - eval { - mon_cmd($vmid, "migrate", uri => $migrate_uri); - }; - my $merr = $@; - $self->log('info', "migrate uri => $migrate_uri failed: $merr") if $merr; - - my $last_mem_transferred = 0; - my $usleep = 1000000; - my $i = 0; - my $err_count = 0; - my $lastrem = undef; - my $downtimecounter = 0; - while (1) { - $i++; - my $avglstat = $last_mem_transferred ? $last_mem_transferred / $i : 0; - - usleep($usleep); - - my $stat = eval { mon_cmd($vmid, "query-migrate") }; - if (my $err = $@) { - $err_count++; - warn "query migrate failed: $err\n"; - $self->log('info', "query migrate failed: $err"); - if ($err_count <= 5) { - usleep(1_000_000); - next; - } - die "too many query migrate failures - aborting\n"; - } - - my $status = $stat->{status}; - if (defined($status) && $status =~ m/^(setup)$/im) { - sleep(1); - next; - } - - if (!defined($status) || $status !~ m/^(active|completed|failed|cancelled)$/im) { - die $merr if $merr; - die "unable to parse migration status '$status' - aborting\n"; - } - $merr = undef; - $err_count = 0; - - my $memstat = $stat->{ram}; - - if ($status eq 'completed') { - my $delay = time() - $start; - if ($delay > 0) { - my $total = $memstat->{total} || 0; - my $avg_speed = render_bytes($total / $delay, 1); - my $downtime = $stat->{downtime} || 0; - $self->log('info', "average migration speed: $avg_speed/s - downtime $downtime ms"); - } - } - - if ($status eq 'failed' || $status eq 'cancelled') { - my $message = $stat->{'error-desc'} ? "$status - $stat->{'error-desc'}" : $status; - $self->log('info', "migration status error: $message"); - die "aborting\n" - } - - if ($status ne 'active') { - $self->log('info', "migration status: $status"); - last; - } - - if ($memstat->{transferred} ne $last_mem_transferred) { - my $trans = $memstat->{transferred} || 0; - my $rem = $memstat->{remaining} || 0; - my $total = $memstat->{total} || 0; - my $speed = ($memstat->{'pages-per-second'} // 0) * ($memstat->{'page-size'} // 0); - my $dirty_rate = ($memstat->{'dirty-pages-rate'} // 0) * ($memstat->{'page-size'} // 0); - - # reduce sleep if remainig memory is lower than the average transfer speed - $usleep = 100_000 if $avglstat && $rem < $avglstat; - - # also reduce loggin if we poll more frequent - my $should_log = $usleep > 100_000 ? 1 : ($i % 10) == 0; - - my $total_h = render_bytes($total, 1); - my $transferred_h = render_bytes($trans, 1); - my $speed_h = render_bytes($speed, 1); - - my $progress = "transferred $transferred_h of $total_h VM-state, ${speed_h}/s"; - - if ($dirty_rate > $speed) { - my $dirty_rate_h = render_bytes($dirty_rate, 1); - $progress .= ", VM dirties lots of memory: $dirty_rate_h/s"; - } - - $self->log('info', "migration $status, $progress") if $should_log; - - my $xbzrle = $stat->{"xbzrle-cache"} || {}; - my ($xbzrlebytes, $xbzrlepages) = $xbzrle->@{'bytes', 'pages'}; - if ($xbzrlebytes || $xbzrlepages) { - my $bytes_h = render_bytes($xbzrlebytes, 1); - - my $msg = "send updates to $xbzrlepages pages in $bytes_h encoded memory"; - - $msg .= sprintf(", cache-miss %.2f%%", $xbzrle->{'cache-miss-rate'} * 100) - if $xbzrle->{'cache-miss-rate'}; - - $msg .= ", overflow $xbzrle->{overflow}" if $xbzrle->{overflow}; - - $self->log('info', "xbzrle: $msg") if $should_log; - } - - if (($lastrem && $rem > $lastrem) || ($rem == 0)) { - $downtimecounter++; - } - $lastrem = $rem; - - if ($downtimecounter > 5) { - $downtimecounter = 0; - $migrate_downtime *= 2; - $self->log('info', "auto-increased downtime to continue migration: $migrate_downtime ms"); - eval { - # migrate-set-parameters does not touch values not - # specified, so this only changes downtime-limit - mon_cmd($vmid, "migrate-set-parameters", 'downtime-limit' => int($migrate_downtime)); - }; - $self->log('info', "migrate-set-parameters error: $@") if $@; - } - } - - $last_mem_transferred = $memstat->{transferred}; - } + live_migration($self, $vmid, $migrate_uri, $spice_port); if ($self->{storage_migration}) { # finish block-job with block-job-cancel, to disconnect source VM from NBD -- 2.39.2 From aderumier at odiso.com Thu Oct 26 10:57:09 2023 From: aderumier at odiso.com (Alexandre Derumier) Date: Thu, 26 Oct 2023 10:57:09 +0200 Subject: [pve-devel] [PATCH v5 qemu-server 2/3] remote-migration: add restart param In-Reply-To: <20231026085710.1611413-1-aderumier@odiso.com> References: <20231026085710.1611413-1-aderumier@odiso.com> Message-ID: <20231026085710.1611413-3-aderumier@odiso.com> This patch add support for migration without memory transfert. After the optionnal storage migration, we cleanly shutdown source vm and restart the target vm. (like a virtual restart between source/dest) Signed-off-by: Alexandre Derumier --- PVE/API2/Qemu.pm | 19 +++++++++++++++++++ PVE/CLI/qm.pm | 5 +++++ PVE/QemuMigrate.pm | 31 ++++++++++++++++++++++++++++--- 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 38bdaab..c0ae516 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -4583,6 +4583,11 @@ __PACKAGE__->register_method({ optional => 1, default => 0, }, + 'restart' => { + type => 'boolean', + description => "For online migration, skip memory migration and restart the vm.", + optional => 1, + }, 'target-storage' => get_standard_option('pve-targetstorage', { completion => \&PVE::QemuServer::complete_migration_storage, optional => 0, @@ -5729,6 +5734,20 @@ __PACKAGE__->register_method({ PVE::QemuServer::nbd_stop($state->{vmid}); return; }, + 'restart' => sub { + my $nocheck = 1; + my $timeout = 1; + my $shutdown = undef; + my $force = undef; + my $keepactive = 1; + PVE::QemuServer::vm_stop($storecfg, $state->{vmid}, $nocheck, $timeout, undef, undef, $keepactive); + my $info = PVE::QemuServer::vm_start_nolock( + $state->{storecfg}, + $state->{vmid}, + $state->{conf}, + ); + return; + }, 'resume' => sub { if (PVE::QemuServer::Helpers::vm_running_locally($state->{vmid})) { PVE::QemuServer::vm_resume($state->{vmid}, 1, 1); diff --git a/PVE/CLI/qm.pm b/PVE/CLI/qm.pm index b17b4fe..12c5291 100755 --- a/PVE/CLI/qm.pm +++ b/PVE/CLI/qm.pm @@ -189,6 +189,11 @@ __PACKAGE__->register_method({ optional => 1, default => 0, }, + 'restart' => { + type => 'boolean', + description => "For online migration , skip memory migration and restart the vm.", + optional => 1, + }, 'target-storage' => get_standard_option('pve-targetstorage', { completion => \&PVE::QemuServer::complete_migration_storage, optional => 0, diff --git a/PVE/QemuMigrate.pm b/PVE/QemuMigrate.pm index 7dd3455..c801362 100644 --- a/PVE/QemuMigrate.pm +++ b/PVE/QemuMigrate.pm @@ -731,6 +731,11 @@ sub cleanup_bitmaps { sub live_migration { my ($self, $vmid, $migrate_uri, $spice_port) = @_; + if($self->{opts}->{'restart'}){ + $self->log('info', "using restart migration - skipping live migration."); + return; + } + my $conf = $self->{vmconf}; $self->log('info', "starting online/live migration on $migrate_uri"); @@ -1358,7 +1363,14 @@ sub phase2 { # finish block-job with block-job-cancel, to disconnect source VM from NBD # to avoid it trying to re-establish it. We are in blockjob ready state, # thus, this command changes to it to blockjob complete (see qapi docs) - eval { PVE::QemuServer::qemu_drive_mirror_monitor($vmid, undef, $self->{storage_migration_jobs}, 'cancel'); }; + my $finish_cmd = "cancel"; + if ($self->{opts}->{'restart'}) { + # no live migration. + # finish block-job with block-job-complete, the source will switch to remote NDB + # then we cleanly stop the source vm during phase3 + $finish_cmd = "complete"; + } + eval { PVE::QemuServer::qemu_drive_mirror_monitor($vmid, undef, $self->{storage_migration_jobs}, $finish_cmd); }; if (my $err = $@) { die "Failed to complete storage migration: $err\n"; } @@ -1575,7 +1587,17 @@ sub phase3_cleanup { }; # always stop local VM with nocheck, since config is moved already - eval { PVE::QemuServer::vm_stop($self->{storecfg}, $vmid, 1, 1); }; + my $shutdown_timeout = undef; + my $shutdown = undef; + my $force_stop = undef; + if ($self->{opts}->{'restart'}) { + $shutdown_timeout = 180; + $shutdown = 1; + $force_stop = 1; + $self->log('info', "shutting down source vm"); + } + + eval { PVE::QemuServer::vm_stop($self->{storecfg}, $vmid, 1, 1, $shutdown_timeout, $shutdown, $force_stop); }; if (my $err = $@) { $self->log('err', "stopping vm failed - $err"); $self->{errors} = 1; @@ -1609,7 +1631,10 @@ sub phase3_cleanup { # clear migrate lock if ($tunnel && $tunnel->{version} >= 2) { PVE::Tunnel::write_tunnel($tunnel, 10, "unlock"); - + if ($self->{opts}->{'restart'}) { + $self->log('info', "restart target vm"); + PVE::Tunnel::write_tunnel($tunnel, 10, 'restart'); + } PVE::Tunnel::finish_tunnel($tunnel); } else { my $cmd = [ @{$self->{rem_ssh}}, 'qm', 'unlock', $vmid ]; -- 2.39.2 From aderumier at odiso.com Thu Oct 26 10:57:10 2023 From: aderumier at odiso.com (Alexandre Derumier) Date: Thu, 26 Oct 2023 10:57:10 +0200 Subject: [pve-devel] [PATCH v5 qemu-server 3/3] add target-cpu param In-Reply-To: <20231026085710.1611413-1-aderumier@odiso.com> References: <20231026085710.1611413-1-aderumier@odiso.com> Message-ID: <20231026085710.1611413-4-aderumier@odiso.com> Signed-off-by: Alexandre Derumier --- PVE/API2/Qemu.pm | 7 +++++++ PVE/CLI/qm.pm | 7 +++++++ PVE/QemuMigrate.pm | 1 + 3 files changed, 15 insertions(+) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index c0ae516..291eb2b 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -4597,6 +4597,13 @@ __PACKAGE__->register_method({ description => "Mapping from source to target bridges. Providing only a single bridge ID maps all source bridges to that bridge. Providing the special value '1' will map each source bridge to itself.", format => 'bridge-pair-list', }, + 'target-cpu' => { + optional => 1, + description => "Target Emulated CPU model. For online migration, this require restart option", + type => 'string', + requires => 'restart', + format => 'pve-vm-cpu-conf', + }, bwlimit => { description => "Override I/O bandwidth limit (in KiB/s).", optional => 1, diff --git a/PVE/CLI/qm.pm b/PVE/CLI/qm.pm index 12c5291..358ace6 100755 --- a/PVE/CLI/qm.pm +++ b/PVE/CLI/qm.pm @@ -194,6 +194,13 @@ __PACKAGE__->register_method({ description => "For online migration , skip memory migration and restart the vm.", optional => 1, }, + 'target-cpu' => { + optional => 1, + description => "Target Emulated CPU model. For online migration, this require restart option", + type => 'string', + requires => 'restart', + format => 'pve-vm-cpu-conf', + }, 'target-storage' => get_standard_option('pve-targetstorage', { completion => \&PVE::QemuServer::complete_migration_storage, optional => 0, diff --git a/PVE/QemuMigrate.pm b/PVE/QemuMigrate.pm index c801362..a0e3d04 100644 --- a/PVE/QemuMigrate.pm +++ b/PVE/QemuMigrate.pm @@ -998,6 +998,7 @@ sub phase1_remote { my ($self, $vmid) = @_; my $remote_conf = PVE::QemuConfig->load_config($vmid); + $remote_conf->{cpu} = $self->{opts}->{'target-cpu'} if $self->{opts}->{'target-cpu'}; PVE::QemuConfig->update_volume_ids($remote_conf, $self->{volume_map}); my $bridges = map_bridges($remote_conf, $self->{opts}->{bridgemap}); -- 2.39.2 From alexandre.derumier at groupe-cyllene.com Thu Oct 26 14:49:57 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Thu, 26 Oct 2023 12:49:57 +0000 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: <875y2xab2d.fsf@gmail.com> References: <875y2xab2d.fsf@gmail.com> Message-ID: <0ab6a28cb30ebea986f05db86ffb540d44ce2bf5.camel@groupe-cyllene.com> Hi Stefan (Lendl), I'm totally agreed with you, we should have persistent reservation, at vm create/nic plug, nic delete, vm delete. At least , for my usage with multiple cluster on different datacenters, I really can wait to call ipam to api at each start (for scalability or for security if ipam is down) This also allow to simply do reservations in dnsmasq file without any need to restart it. (AFAIK, openstack is using dnsmasq like this too) I'm not sure if true dynamic ephemral ip , changing at each vm stop/start is interesting for a server vm usage. (maybe for desktop vmwhere you share a small pool of ip, but I personnaly don't known any proxmox users using proxmox ve for this) see my proposal here (with handle ephemeral && reserved, but it's even easier with only reserved): https://lists.proxmox.com/pipermail/pve-devel/2023-September/059169.html " I think we could implement ipam call like: create vm or add a new nic --> ----------------------------- qm create ... -net0 bridge=vnet,....,ip=(auto|192.168.0.1|dynamic),ip6=(..) auto : search a free ip in ipam. write the ip address in net0: ...,ip= ip field 192.168.0.1: check if ip is free in ipam && register ip in ipam. write the ip in ip field. dynamic: write "ephemeral" in net0: ....,ip=ephemeral (This is a dynamic ip registered at vm start, and release at vm stop) vm start --------- - if ip=ephemeral, find && register a free ip in ipam, write it in vm net0: ...,ip=192.168.0.10[E] . (maybe with a special flag [E] to indicate it's ephemeral) - read ip from vm config && inject in dhcp vm_stop ------- if ip is ephemeral (netX: ip=192.168.0.10[E]), delete ip from ipam, set ip=ephemeral in vm config vm_destroy or nic remove/unplug ------------------------- if netX: ...,ip=192.168.0.10 , remove ip from ipam nic update when vm is running: ------------------------------ if ip is defined : netX: ip=192.168.0.10, we don't allow bridge change or ip change, as vm is not notified about theses changes, and still use old ip. We can allow nic hot-unplug && hotplug. (guest os will remove the ip on nic removal, and will call dhcp again on nic hotplug) nic hotplug with ip=auto: ------------------------- --> add nic in pending state ----> find ip in ipam && write it in pending ---> do the hotplug in qemu. We need to handle the config revert to remove ip from ipam if the nic hotplug is blocked in pending state(I never see this case until os don't have pci_hotplug module loaded, but it's better to be carefull ) " >>I am currently working on the SDN feature.? This is an initial review >>of >>the patch series and I am trying to make a strong case against >>ephemeral >>DHCP IP reservation. >> >>The current state of the patch series invokes the IPAM on every VM/CT >>start/stop to add or remove the IP from the IPAM. >>This triggers the dnsmasq config generation on the specific host with >>only the MAC/IP mapping of that particular host. From reading the discussion of the v1 patch series I understand this approach tries to implement the ephemeral IP reservation strategy. From off-list conversations with Stefan Hanreich, I agree that having ephemeral IP reservation coordinated by the IPAM requires us to re-implement DHCP functionality in the IPAM and heavily rely on syncing between the different services. To maintain reliable sync we need to hook into many different places where the IPAM need to be queried.? Any issues with the implementation may lead to IPAM and DHCP local config state running out of sync causing network issues duplicate multiple IPs. Furthermore, every interaction with the IPAM requires a cluster-wide lock on the IPAM. Having a central cluster-wide lock on every VM start/stop/migrate will significantly limit parallel operations.? Event starting two VMs in parallel will be limited by this central lock. At boot trying to start many VMs (ideally as much in parallel as possible) is limited by the central IPAM lock even further. I argue that we shall not support ephemeral IPs altogether. The alternative is to make all IPAM reservations persistent. Using persistent IPs only reduces the interactions of VM/CTs with the IPAM to a minimum of NIC joining a subnet and NIC leaving a subnet. I am deliberately not referring to VMs because a VM may be part of multiple VNets or even multiple times in the same VNet (regardless if that is sensible). Cases the IPAM needs to be involved: - NIC with DHCP enabled VNet is added to VM config - NIC with DHCP enabled VNet is removed from VM config - NIC is assigned to another Bridge ? can be treated as individual leave + join events Cases that are explicitly not covered but may be added if desired: - Manually assign an IP address on a NIC ? will not be automatically visible in the IPAM - Manually change the MAC on a NIC ? don't do that > you are on your own. ? Not handled > change in IPAM manually Once an IP is reserved via IPAM, the dnsmasq config can be generated stateless and idempotent from the pve IPAM and is identical on all nodes regardless if a VM/CT actually resides on that node or is running or stopped.? This is especially useful for VM migration because the IP stays consistent without spacial considering. Snapshot/revert, backup/restore, suspend/hibernate/resume cases are automatically covered because the IP will already be reserved for that MAC. If the admin wants to change, the IP of a VM this can be done via the IPAM API/UI which will have to be implemented separately. A limitation of this approach vs dynamic IP reservation is that the IP range on the subnet needs to be large enough to hold all IPs of all, even stopped, VMs in that subnet. This is in contrast to default DHCP functionality where only the number of actively running VMs is limited. It should be enough to mention this in the docs. I will further review the code an try to implement the aforementioned approach. Best regards, Stefan Lendl Stefan Hanreich writes: > This is a WIP patch series, since I will be gone for 3 weeks and > wanted to > share my current progress with the DHCP support for SDN. > > This patch series adds support for automatically deploying dnsmasq as > a DHCP > server to a simple SDN Zone. > > While certainly not 100% polished on some ends (looking at restarting > systemd > services in particular), the general idea behind the mechanism shows. > I wanted > to gather some feedback on how I approached designing the plugins and > the > config regeneration process before comitting to this design by > creating an API > and UI around it. > > You need to install dnsmasq (and disable it afterwards): > > ? apt install dnsmasq && systemctl disable --now dnsmasq > > > You can use the following example configuration for deploying a DHCP > server in > a SDN subnet: > > /etc/pve/sdn/dhcp.cfg: > > ? dnsmasq: nat > > > /etc/pve/sdn/zones.cfg: > > ? simple: DHCPNAT > ????????? ipam pve > > > /etc/pve/sdn/vnets.cfg: > > ? vnet: dhcpnat > ????????? zone DHCPNAT > > > /etc/pve/sdn/subnets.cfg: > > ? subnet: DHCPNAT-10.1.0.0-16 > ????????? vnet dhcpnat > ????????? dhcp-dns-server 10.1.0.1 > ????????? dhcp-range server=nat,start-address=10.1.0.100,end- > address=10.1.0.200 > ????????? gateway 10.1.0.1 > ????????? snat 1 > > > Then apply the SDN configuration: > > ? pvesh set /cluster/sdn > > You need to apply the SDN configuration once after adding the dhcp- > range lines > to the configuration, since the running configuration is used for > managing > DHCP. It will not work otherwise! > > For testing it can be helpful to monitor the following files (e.g. > with watch) > to find out what is happening > ? * /etc/dnsmasq.d//ethers (on each node) > ? * /etc/pve/priv/ipam.db > > Changes from v1 -> v2: > ? * added hooks for handling DHCP when starting / stopping / .. VMs > and CTs > ? * Get an IP from IPAM and register that IP in the DHCP server > ??? (pve only for now) > ? * remove lease-time, since it is now infinite and managed by the VM > lifecycle > ? * add hooks for setting & deleting DHCP mappings to DHCP plugins > ? * modified interface of the abstract class to reflect new > requirements > ? * added helpers in existing SDN classes > ? * simplified DHCP configuration settings > > > > pve-cluster: > > Stefan Hanreich (1): > ? cluster files: add dhcp.cfg > > ?src/PVE/Cluster.pm? | 1 + > ?src/pmxcfs/status.c | 1 + > ?2 files changed, 2 insertions(+) > > > pve-network: > > Stefan Hanreich (6): > ? subnets: vnets: preparations for DHCP plugins > ? dhcp: add abstract class for DHCP plugins > ? dhcp: subnet: add DHCP options to subnet configuration > ? dhcp: add DHCP plugin for dnsmasq > ? ipam: Add helper methods for DHCP to PVE IPAM > ? dhcp: regenerate config for DHCP servers on reload > > ?debian/control???????????????????????? |?? 1 + > ?src/PVE/Network/SDN.pm???????????????? |? 11 +- > ?src/PVE/Network/SDN/Dhcp.pm??????????? | 192 > +++++++++++++++++++++++++ > ?src/PVE/Network/SDN/Dhcp/Dnsmasq.pm??? | 186 > ++++++++++++++++++++++++ > ?src/PVE/Network/SDN/Dhcp/Makefile????? |?? 8 ++ > ?src/PVE/Network/SDN/Dhcp/Plugin.pm???? |? 83 +++++++++++ > ?src/PVE/Network/SDN/Ipams/PVEPlugin.pm |? 64 +++++++++ > ?src/PVE/Network/SDN/Makefile?????????? |?? 3 +- > ?src/PVE/Network/SDN/SubnetPlugin.pm??? |? 32 +++++ > ?src/PVE/Network/SDN/Subnets.pm???????? |? 43 ++++-- > ?src/PVE/Network/SDN/Vnets.pm?????????? |? 27 ++-- > ?11 files changed, 622 insertions(+), 28 deletions(-) > ?create mode 100644 src/PVE/Network/SDN/Dhcp.pm > ?create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm > ?create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile > ?create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm > > > pve-manager: > > Stefan Hanreich (1): > ? sdn: regenerate DHCP config on reload > > ?PVE/API2/Network.pm | 1 + > ?1 file changed, 1 insertion(+) > > > qemu-server: > > Stefan Hanreich (1): > ? sdn: dhcp: add DHCP setup to vm-network-scripts > > ?PVE/QemuServer.pm???????????????? | 14 ++++++++++++++ > ?vm-network-scripts/pve-bridge???? |? 3 +++ > ?vm-network-scripts/pve-bridgedown | 19 +++++++++++++++++++ > ?3 files changed, 36 insertions(+) > > > pve-container: > > Stefan Hanreich (1): > ? sdn: dhcp: setup DHCP mappings in LXC hooks > > ?src/PVE/LXC.pm??????????? | 10 ++++++++++ > ?src/lxc-pve-poststop-hook |? 1 + > ?src/lxc-pve-prestart-hook |? 9 +++++++++ > ?3 files changed, 20 insertions(+) > > > Summary over all repositories: > ? 20 files changed, 681 insertions(+), 28 deletions(-) > > -- > murpp v0.4.0 > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://antiphishing.cetsi.fr/proxy/v3?i=d1l4NXNNaWE4SWZqU0dLWcuTfdxE > d98NfWIp9dma5kY&r=MXJUa0FrUVJqc1UwYWxNZ- > tuXduEO8AMVnCvYVMprCZ3oPilgy3nXcuJTOGH5iK84rVRg8cukFAROdxYRgFTTg&f=c2 > xMdVN4Smh2R2tOZDdIRKCk7WEocHpTPMerT1Q- > Aq5qwr8l2xvAWuOGvFsV3frp2oSAgxNUQCpJDHp2iUmTWg&u=https%3A//lists.prox > mox.com/cgi-bin/mailman/listinfo/pve-devel&k=fjzS _______________________________________________ pve-devel mailing list pve-devel at lists.proxmox.com https://antiphishing.cetsi.fr/proxy/v3?i=d1l4NXNNaWE4SWZqU0dLWcuTfdxEd9 8NfWIp9dma5kY&r=MXJUa0FrUVJqc1UwYWxNZ- tuXduEO8AMVnCvYVMprCZ3oPilgy3nXcuJTOGH5iK84rVRg8cukFAROdxYRgFTTg&f=c2xM dVN4Smh2R2tOZDdIRKCk7WEocHpTPMerT1Q- Aq5qwr8l2xvAWuOGvFsV3frp2oSAgxNUQCpJDHp2iUmTWg&u=https%3A//lists.proxmo x.com/cgi-bin/mailman/listinfo/pve-devel&k=fjzS From alexandre.derumier at groupe-cyllene.com Thu Oct 26 14:53:04 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Thu, 26 Oct 2023 12:53:04 +0000 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: <875y2xab2d.fsf@gmail.com> References: <875y2xab2d.fsf@gmail.com> Message-ID: <0f37f625b50a0e794da81ce960293ed9d42f48d2.camel@groupe-cyllene.com> also, about dhcp-ranges in this patch series, I think it could be great to make it optionnal, as some external couldn't support it. (netbox seem to support it, but I don't have looked at next_free api yet , phpipam don't seem to support it). A lot of external ipam tool only search about free ip in the full subnet. So maybe something like : no dhcp-range = any ip from the subnet. -------- Message initial -------- De: Stefan Lendl R?pondre ?: Proxmox VE development discussion ?: pve-devel at lists.proxmox.com Objet: Re: [pve-devel] [WIP v2 cluster/network/manager/qemu- server/container 00/10] Add support for DHCP servers to SDN Date: 23/10/2023 12:27:06 I am currently working on the SDN feature.? This is an initial review of the patch series and I am trying to make a strong case against ephemeral DHCP IP reservation. The current state of the patch series invokes the IPAM on every VM/CT start/stop to add or remove the IP from the IPAM. This triggers the dnsmasq config generation on the specific host with only the MAC/IP mapping of that particular host. From reading the discussion of the v1 patch series I understand this approach tries to implement the ephemeral IP reservation strategy. From off-list conversations with Stefan Hanreich, I agree that having ephemeral IP reservation coordinated by the IPAM requires us to re-implement DHCP functionality in the IPAM and heavily rely on syncing between the different services. To maintain reliable sync we need to hook into many different places where the IPAM need to be queried.? Any issues with the implementation may lead to IPAM and DHCP local config state running out of sync causing network issues duplicate multiple IPs. Furthermore, every interaction with the IPAM requires a cluster-wide lock on the IPAM. Having a central cluster-wide lock on every VM start/stop/migrate will significantly limit parallel operations.? Event starting two VMs in parallel will be limited by this central lock. At boot trying to start many VMs (ideally as much in parallel as possible) is limited by the central IPAM lock even further. I argue that we shall not support ephemeral IPs altogether. The alternative is to make all IPAM reservations persistent. Using persistent IPs only reduces the interactions of VM/CTs with the IPAM to a minimum of NIC joining a subnet and NIC leaving a subnet. I am deliberately not referring to VMs because a VM may be part of multiple VNets or even multiple times in the same VNet (regardless if that is sensible). Cases the IPAM needs to be involved: - NIC with DHCP enabled VNet is added to VM config - NIC with DHCP enabled VNet is removed from VM config - NIC is assigned to another Bridge ? can be treated as individual leave + join events Cases that are explicitly not covered but may be added if desired: - Manually assign an IP address on a NIC ? will not be automatically visible in the IPAM - Manually change the MAC on a NIC ? don't do that > you are on your own. ? Not handled > change in IPAM manually Once an IP is reserved via IPAM, the dnsmasq config can be generated stateless and idempotent from the pve IPAM and is identical on all nodes regardless if a VM/CT actually resides on that node or is running or stopped.? This is especially useful for VM migration because the IP stays consistent without spacial considering. Snapshot/revert, backup/restore, suspend/hibernate/resume cases are automatically covered because the IP will already be reserved for that MAC. If the admin wants to change, the IP of a VM this can be done via the IPAM API/UI which will have to be implemented separately. A limitation of this approach vs dynamic IP reservation is that the IP range on the subnet needs to be large enough to hold all IPs of all, even stopped, VMs in that subnet. This is in contrast to default DHCP functionality where only the number of actively running VMs is limited. It should be enough to mention this in the docs. I will further review the code an try to implement the aforementioned approach. Best regards, Stefan Lendl Stefan Hanreich writes: > This is a WIP patch series, since I will be gone for 3 weeks and > wanted to > share my current progress with the DHCP support for SDN. > > This patch series adds support for automatically deploying dnsmasq as > a DHCP > server to a simple SDN Zone. > > While certainly not 100% polished on some ends (looking at restarting > systemd > services in particular), the general idea behind the mechanism shows. > I wanted > to gather some feedback on how I approached designing the plugins and > the > config regeneration process before comitting to this design by > creating an API > and UI around it. > > You need to install dnsmasq (and disable it afterwards): > > ? apt install dnsmasq && systemctl disable --now dnsmasq > > > You can use the following example configuration for deploying a DHCP > server in > a SDN subnet: > > /etc/pve/sdn/dhcp.cfg: > > ? dnsmasq: nat > > > /etc/pve/sdn/zones.cfg: > > ? simple: DHCPNAT > ????????? ipam pve > > > /etc/pve/sdn/vnets.cfg: > > ? vnet: dhcpnat > ????????? zone DHCPNAT > > > /etc/pve/sdn/subnets.cfg: > > ? subnet: DHCPNAT-10.1.0.0-16 > ????????? vnet dhcpnat > ????????? dhcp-dns-server 10.1.0.1 > ????????? dhcp-range server=nat,start-address=10.1.0.100,end- > address=10.1.0.200 > ????????? gateway 10.1.0.1 > ????????? snat 1 > > > Then apply the SDN configuration: > > ? pvesh set /cluster/sdn > > You need to apply the SDN configuration once after adding the dhcp- > range lines > to the configuration, since the running configuration is used for > managing > DHCP. It will not work otherwise! > > For testing it can be helpful to monitor the following files (e.g. > with watch) > to find out what is happening > ? * /etc/dnsmasq.d//ethers (on each node) > ? * /etc/pve/priv/ipam.db > > Changes from v1 -> v2: > ? * added hooks for handling DHCP when starting / stopping / .. VMs > and CTs > ? * Get an IP from IPAM and register that IP in the DHCP server > ??? (pve only for now) > ? * remove lease-time, since it is now infinite and managed by the VM > lifecycle > ? * add hooks for setting & deleting DHCP mappings to DHCP plugins > ? * modified interface of the abstract class to reflect new > requirements > ? * added helpers in existing SDN classes > ? * simplified DHCP configuration settings > > > > pve-cluster: > > Stefan Hanreich (1): > ? cluster files: add dhcp.cfg > > ?src/PVE/Cluster.pm? | 1 + > ?src/pmxcfs/status.c | 1 + > ?2 files changed, 2 insertions(+) > > > pve-network: > > Stefan Hanreich (6): > ? subnets: vnets: preparations for DHCP plugins > ? dhcp: add abstract class for DHCP plugins > ? dhcp: subnet: add DHCP options to subnet configuration > ? dhcp: add DHCP plugin for dnsmasq > ? ipam: Add helper methods for DHCP to PVE IPAM > ? dhcp: regenerate config for DHCP servers on reload > > ?debian/control???????????????????????? |?? 1 + > ?src/PVE/Network/SDN.pm???????????????? |? 11 +- > ?src/PVE/Network/SDN/Dhcp.pm??????????? | 192 > +++++++++++++++++++++++++ > ?src/PVE/Network/SDN/Dhcp/Dnsmasq.pm??? | 186 > ++++++++++++++++++++++++ > ?src/PVE/Network/SDN/Dhcp/Makefile????? |?? 8 ++ > ?src/PVE/Network/SDN/Dhcp/Plugin.pm???? |? 83 +++++++++++ > ?src/PVE/Network/SDN/Ipams/PVEPlugin.pm |? 64 +++++++++ > ?src/PVE/Network/SDN/Makefile?????????? |?? 3 +- > ?src/PVE/Network/SDN/SubnetPlugin.pm??? |? 32 +++++ > ?src/PVE/Network/SDN/Subnets.pm???????? |? 43 ++++-- > ?src/PVE/Network/SDN/Vnets.pm?????????? |? 27 ++-- > ?11 files changed, 622 insertions(+), 28 deletions(-) > ?create mode 100644 src/PVE/Network/SDN/Dhcp.pm > ?create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm > ?create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile > ?create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm > > > pve-manager: > > Stefan Hanreich (1): > ? sdn: regenerate DHCP config on reload > > ?PVE/API2/Network.pm | 1 + > ?1 file changed, 1 insertion(+) > > > qemu-server: > > Stefan Hanreich (1): > ? sdn: dhcp: add DHCP setup to vm-network-scripts > > ?PVE/QemuServer.pm???????????????? | 14 ++++++++++++++ > ?vm-network-scripts/pve-bridge???? |? 3 +++ > ?vm-network-scripts/pve-bridgedown | 19 +++++++++++++++++++ > ?3 files changed, 36 insertions(+) > > > pve-container: > > Stefan Hanreich (1): > ? sdn: dhcp: setup DHCP mappings in LXC hooks > > ?src/PVE/LXC.pm??????????? | 10 ++++++++++ > ?src/lxc-pve-poststop-hook |? 1 + > ?src/lxc-pve-prestart-hook |? 9 +++++++++ > ?3 files changed, 20 insertions(+) > > > Summary over all repositories: > ? 20 files changed, 681 insertions(+), 28 deletions(-) > > -- > murpp v0.4.0 > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://antiphishing.cetsi.fr/proxy/v3?i=d1l4NXNNaWE4SWZqU0dLWcuTfdxE > d98NfWIp9dma5kY&r=MXJUa0FrUVJqc1UwYWxNZ- > tuXduEO8AMVnCvYVMprCZ3oPilgy3nXcuJTOGH5iK84rVRg8cukFAROdxYRgFTTg&f=c2 > xMdVN4Smh2R2tOZDdIRKCk7WEocHpTPMerT1Q- > Aq5qwr8l2xvAWuOGvFsV3frp2oSAgxNUQCpJDHp2iUmTWg&u=https%3A//lists.prox > mox.com/cgi-bin/mailman/listinfo/pve-devel&k=fjzS _______________________________________________ pve-devel mailing list pve-devel at lists.proxmox.com https://antiphishing.cetsi.fr/proxy/v3?i=d1l4NXNNaWE4SWZqU0dLWcuTfdxEd9 8NfWIp9dma5kY&r=MXJUa0FrUVJqc1UwYWxNZ- tuXduEO8AMVnCvYVMprCZ3oPilgy3nXcuJTOGH5iK84rVRg8cukFAROdxYRgFTTg&f=c2xM dVN4Smh2R2tOZDdIRKCk7WEocHpTPMerT1Q- Aq5qwr8l2xvAWuOGvFsV3frp2oSAgxNUQCpJDHp2iUmTWg&u=https%3A//lists.proxmo x.com/cgi-bin/mailman/listinfo/pve-devel&k=fjzS From t.lamprecht at proxmox.com Fri Oct 27 08:40:31 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 27 Oct 2023 08:40:31 +0200 Subject: [pve-devel] [PATCH acme 1/5] fix #4497: add support for external account bindings In-Reply-To: <1698133256.b0dljkont3.astroid@yuna.none> References: <20231023131808.172494-1-f.gleumes@proxmox.com> <20231023131808.172494-2-f.gleumes@proxmox.com> <1698133256.b0dljkont3.astroid@yuna.none> Message-ID: <0b46c18c-71cf-4ee0-9044-11221fde0cb7@proxmox.com> Am 24/10/2023 um 10:32 schrieb Fabian Gr?nbichler: >> # Optionally pass $tos_url to agree to the given Terms of Service >> # POST to newAccount endpoint >> # Expects a '201 Created' reply >> # Saves and returns the account data >> sub new_account { >> - my ($self, $tos_url, %info) = @_; >> + my ($self, $tos_url, $info) = @_; >> my $url = $self->_method('newAccount'); >> >> + if ($info->{'eab'}) { >> + my $eab_hmac_key = decode_base64($info->{'eab'}->{hmac_key}); >> + $info->{externalAccountBinding} = $self->eab($info->{'eab'}->{kid}, $eab_hmac_key, $url); > > this means that `info` now contains both the binding, but also the input > including the KID (okay, this is contained in the binding as well, so > just duplicate info) and the HMAC key, which is supposed to be secret. > granted, it is a secret given to the user by the CA over some channel, > and we only send it back to the CA, but some ACME implementations might > still reject the request because of the unexpected contents. and if the > user ever mixes up the CAs they are talking to, they might accidentally > leak the secret to the wrong entitiy. passing %info direct around seems like a bad ABI anyway, so why not stop doing that and construct a new hash here that only takes the properties from info out that we actually care about? > > since `info` is directly translated to the new_account request contents, > it might be better to pass in the EAB parameters on their own, and make > this sub take > > my ($self, $tos_url, $eab, %info) = @_; > > if $eab is undef -> no EAB. if it is set, generate the binding and put > it into %info for further passing to the ACME provider. IMO this is not really a better general API (the unconstrained passing around of %info, while requiring further ABI breakage on any future parameter addition, is still there). > > alternatively, it would also work to combine $tos_url and $eab into a > new $account_params or $params hash, if we think that more parameters > might be added in the future, or if we plan on merging new_account and > init, like we do in PMG/PBS. > > or, as third option, we could switch the public sub new_account to take > > my ($self, $tos_url, $contact, $eab) = @_; > > or > > my ($self, $tos_url, $contact, $eab, $rsa_bits) = @_; > > which would be more aligned with how PMG and PBS look like. Aligning more towards PBS/PMG has it's merits, FWIW, we could also do so with a new method, and then transform the existing one to just transform the parameters as needed and call into that. > > almost all of the variants are a breaking change (except if we keep the > signature as is, and properly extract the eab member if it is set) that > require a double check for any reverse dependencies. there's at least > one internal tool that uses this as well that would need to be updated, > for example. > Yeah, that or adding a new method would be preferred from my side. From t.lamprecht at proxmox.com Fri Oct 27 08:58:31 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 27 Oct 2023 08:58:31 +0200 Subject: [pve-devel] [PATCH acme v2 1/5] fix #4497: add support for external account bindings In-Reply-To: <20231025130720.195478-2-f.gleumes@proxmox.com> References: <20231025130720.195478-1-f.gleumes@proxmox.com> <20231025130720.195478-2-f.gleumes@proxmox.com> Message-ID: <3c97105b-c896-4613-aaaf-6b8395309d5e@proxmox.com> Am 25/10/2023 um 15:07 schrieb Folke Gleumes: > Changes v1 -> v2: > Switched from including the eab credentials in the info hash, > to passing them in their own variable. This still unfortunately still > breaks the api, but doesn't potentially expose secrets and is > cleaner then purging the values from the hash afterwards. yeah, IMO the signature of that method is still not really ideal, i.e., adding that as explicit param is a lateral move and still breaks ABI, so meh, and I'd prefer > src/PVE/ACME.pm | 42 +++++++++++++++++++++++++++++++++++++----- > 1 file changed, 37 insertions(+), 5 deletions(-) > > diff --git a/src/PVE/ACME.pm b/src/PVE/ACME.pm > index 3f66182..7b3840b 100644 > --- a/src/PVE/ACME.pm > +++ b/src/PVE/ACME.pm > @@ -251,6 +251,28 @@ sub jws { > }; > } > > +# EAB signing using the HS256 alg (HMAC/SHA256). > +sub external_account_binding_jws { should this be actually a private method? I.e., some my sub external_account_binding_jws { } or as code ref in a variable: my $external_account_binding_jws = sub { } FYI: For above examples you'd need to pass $self then explicitly either way though. As this is probably not intended to be called from the "outside"? Albeit, that would be yet another way to avoid API breakage: let the caller call this first and place the result into the %info hash, but that would still keep that unchecked passing along of the %info hash. From t.lamprecht at proxmox.com Fri Oct 27 09:12:51 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 27 Oct 2023 09:12:51 +0200 Subject: [pve-devel] [PATCH acme v2 1/5] fix #4497: add support for external account bindings In-Reply-To: <3c97105b-c896-4613-aaaf-6b8395309d5e@proxmox.com> References: <20231025130720.195478-1-f.gleumes@proxmox.com> <20231025130720.195478-2-f.gleumes@proxmox.com> <3c97105b-c896-4613-aaaf-6b8395309d5e@proxmox.com> Message-ID: <6f7752ee-0ef7-4f93-90ed-06a27f5785a5@proxmox.com> Am 27/10/2023 um 08:58 schrieb Thomas Lamprecht: > Am 25/10/2023 um 15:07 schrieb Folke Gleumes: >> Changes v1 -> v2: >> Switched from including the eab credentials in the info hash, >> to passing them in their own variable. This still unfortunately still >> breaks the api, but doesn't potentially expose secrets and is >> cleaner then purging the values from the hash afterwards. > > yeah, IMO the signature of that method is still not really ideal, i.e., > adding that as explicit param is a lateral move and still breaks ABI, so > meh, and I'd prefer argh, sent to early, but bascially what I write in my reply to v1: https://lists.proxmox.com/pipermail/pve-devel/2023-October/059670.html From t.lamprecht at proxmox.com Fri Oct 27 09:39:06 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 27 Oct 2023 09:39:06 +0200 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: <87v8axbjh1.fsf@gmail.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> <87v8axbjh1.fsf@gmail.com> Message-ID: <330b6d23-6a0f-4041-9892-26944fb7e30d@proxmox.com> Am 23/10/2023 um 14:40 schrieb Stefan Lendl: > I am currently working on the SDN feature. This is an initial review of > the patch series and I am trying to make a strong case against ephemeral > DHCP IP reservation. Stefan Hanreich's reply to the cover letter already mentions upserts, those will avoid basically all problems while allowing for some dynamic changes. > The current state of the patch series invokes the IPAM on every VM/CT > start/stop to add or remove the IP from the IPAM. > This triggers the dnsmasq config generation on the specific host with > only the MAC/IP mapping of that particular host. > > From reading the discussion of the v1 patch series I understand this > approach tries to implement the ephemeral IP reservation strategy. From > off-list conversations with Stefan Hanreich, I agree that having > ephemeral IP reservation coordinated by the IPAM requires us to > re-implement DHCP functionality in the IPAM and heavily rely on syncing > between the different services. > > To maintain reliable sync we need to hook into many different places > where the IPAM need to be queried. Any issues with the implementation > may lead to IPAM and DHCP local config state running out of sync causing > network issues duplicate multiple IPs. The same is true for permanent reservations, wherever that reservation is saved needs to be in sync with IPAM, e.g., also on backup restore (into a new env), if subnets change their configured CIDRs, ... > > Furthermore, every interaction with the IPAM requires a cluster-wide > lock on the IPAM. Having a central cluster-wide lock on every VM > start/stop/migrate will significantly limit parallel operations. Event > starting two VMs in parallel will be limited by this central lock. At > boot trying to start many VMs (ideally as much in parallel as possible) > is limited by the central IPAM lock even further. Cluster wide locks are relatively cheap, especially if one avoids having a long critical section, i.e., query IPAM while still unlocked, then read and update the state locked, if the newly received IP is already in there then simply give up lock again and repeat. We also have a clusters wide lock for starting HA guests, to set the wanted ha-resource state, that is no issue at all, you can start/stop many orders of magnitudes more VMs than any HW/Storage could cope with. > > I argue that we shall not support ephemeral IPs altogether. > The alternative is to make all IPAM reservations persistent. > > Using persistent IPs only reduces the interactions of VM/CTs with the > IPAM to a minimum of NIC joining a subnet and NIC leaving a subnet. I am > deliberately not referring to VMs because a VM may be part of multiple > VNets or even multiple times in the same VNet (regardless if that is > sensible). Yeah, talking about vNICs / veth's is the better term here, guests are only indirectly relevant. > > Cases the IPAM needs to be involved: > > - NIC with DHCP enabled VNet is added to VM config > - NIC with DHCP enabled VNet is removed from VM config > - NIC is assigned to another Bridge > can be treated as individual leave + join events and: - subnet config is changed - vNIC changes from SDN-DHCP managed to manual, or vice versa Albeit that can almost be treated like vNet leave/join though > Cases that are explicitly not covered but may be added if desired: > > - Manually assign an IP address on a NIC > will not be automatically visible in the IPAM This sounds like you want to save the state in the VM config, which I'm rather skeptical about, and would try hard to avoid. We also would need to differ between bridges that are part of DHCP-managed SDN and others, as else a user could set some IP but nothing would happen. > - Manually change the MAC on a NIC > don't do that > you are on your own. FWIW, a clone is such a change, and we have to support that, otherwise the MAC field needs to get some warning hints or even become read-only in the UI. > Not handled > change in IPAM manually > > Once an IP is reserved via IPAM, the dnsmasq config can be generated > stateless and idempotent from the pve IPAM and is identical on all nodes > regardless if a VM/CT actually resides on that node or is running or > stopped. This is especially useful for VM migration because the IP > stays consistent without spacial considering. That should be orthogonal to the feature set, if we have all the info saved somewhere else But this also speaks against having it in the VM config, as that would mean that every node needs to parse every guests' config periodically, which is way worse than some cluster lock and breaks with our base axiom that guests are owned by their current node, and only by that, and a node should not really alter behavior dependent on some "foreign" guest. > > Snapshot/revert, backup/restore, suspend/hibernate/resume cases are > automatically covered because the IP will already be reserved for that > MAC. Not really, restore to another setup is broken, one could resume the VM after having changed CIDRs of a subnet, making that broken too, ... > > If the admin wants to change, the IP of a VM this can be done via the > IPAM API/UI which will have to be implemented separately. Providing Overrides can be fine, but IMO that all should be still in the SDN state, not per-VM one, and ideally use a common API. > A limitation of this approach vs dynamic IP reservation is that the IP > range on the subnet needs to be large enough to hold all IPs of all, > even stopped, VMs in that subnet. This is in contrast to default DHCP > functionality where only the number of actively running VMs is limited. > It should be enough to mention this in the docs. In production setups it should not matter _that_ much, but it might be a bit of a PITA if one has a few "archived" VMs or the like, but that alone would > > I will further review the code an try to implement the aforementioned > approach. You can naturally experiment, but I'd also try the upsert proposal from Stefan H., as IMO that sounds like a good balance. From c.heiss at proxmox.com Fri Oct 27 10:29:26 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 27 Oct 2023 10:29:26 +0200 Subject: [pve-devel] [PATCH installer v2 1/6] fix #4829: install: add new ZFS `arc_max` setup option In-Reply-To: <97055168-3ed7-4338-8890-05bbe8c893ee@proxmox.com> References: <20231024115530.1101733-1-c.heiss@proxmox.com> <20231024115530.1101733-2-c.heiss@proxmox.com> <7l5h7ecjzznfpagtvnk7xcp7s2cu7njnnext2dp4zcwgyxbk3p@r5nlqvepijry> <97055168-3ed7-4338-8890-05bbe8c893ee@proxmox.com> Message-ID: On Wed, Oct 25, 2023 at 07:09:00PM +0200, Thomas Lamprecht wrote: > > Am 25/10/2023 um 10:28 schrieb Christoph Heiss: > > On Tue, Oct 24, 2023 at 08:59:36AM -0300, Gilberto Ferreira via pve-devel wrote: > > > > Currently, this isn't planned, although - since that setting is exposed > > after all in the installer in the future - it would be kind of sensible > > to add it to the GUI as well, I guess. > > But as a separate series from this one, of course. > > > > CC @Thomas - what do you think? > > We would need to either have a separate config entry, that can easily > get out of sync with the actual configured values, or parse all possible > ways to set this, e.g., modprobe configs, which isn't too nice. Yeah, that actually seems like a big problem. > > With a few trade-offs/limitations one could probably work around that, > but IMO it can be a bit too much hassle for something that one normally > changes only once or twice (or once this change is in, probably never > for new setups), so for now I'd keep this manual. Then I'd also leave it at this, just being able to change it in the installer and afterwards only "manually". It is more than sufficiently documented in our guides how to change that, let alone searching for that topic on the internet. Thanks for chiming in! From f.ebner at proxmox.com Fri Oct 27 11:19:33 2023 From: f.ebner at proxmox.com (Fiona Ebner) Date: Fri, 27 Oct 2023 11:19:33 +0200 Subject: [pve-devel] [PATCH v4 qemu-server 2/2] remote-migration: add target-cpu && target-reboot params In-Reply-To: <188c296857bc3ae42f0a5150770e8c3942ec74f0.camel@groupe-cyllene.com> References: <20230928144556.2023558-1-aderumier@odiso.com> <20230928144556.2023558-3-aderumier@odiso.com> <5ecfa7d0-4525-5f1e-75a2-a6ae1a93356b@proxmox.com> <73e0a3a6-f978-ac24-5f6b-16af759ee209@proxmox.com> <016bd4b8-7502-48fe-9208-a075e8aea02b@proxmox.com> <283ff207e6065f0ae178410bfa391e9a5369924f.camel@groupe-cyllene.com> <8d06d2f6-b831-45b3-ac1b-2cc3f1721b85@proxmox.com> <188c296857bc3ae42f0a5150770e8c3942ec74f0.camel@groupe-cyllene.com> Message-ID: Am 25.10.23 um 18:01 schrieb DERUMIER, Alexandre: >>> Unused disks can just be migrated >>> offline via storage_migrate(), or?? > > currently unused disk can't be migrate through the http tunnel for > remote-migration > > 2023-10-25 17:51:38 ERROR: error - tunnel command > '{"format":"raw","migration_snapshot":"1","export_formats":"zfs","allow > _rename":"1","snapshot":"__migration__","volname":"vm-1112-disk- > 1","cmd":"disk-import","storage":"targetstorage","with_snapshots":1}' > failed - failed to handle 'disk-import' command - no matching > import/export format found for storage 'preprodkvm' > 2023-10-25 17:51:38 aborting phase 1 - cleanup resources > tunnel: -> sending command "quit" to remote > tunnel: <- got reply > tunnel: CMD channel closed, shutting down > 2023-10-25 17:51:39 ERROR: migration aborted (duration 00:00:01): error > - tunnel command > '{"format":"raw","migration_snapshot":"1","export_formats":"zfs","allow > _rename":"1","snapshot":"__migration__","volname":"vm-1112-disk- > 1","cmd":"disk-import","storage":"targetstorage","with_snapshots":1}' > failed - failed to handle 'disk-import' command - no matching > import/export format found for storage 'preprodkvm' > migration aborted > Well, yes, they can. But there needs to be a common import/export format between the storage types. Which admittedly is a bit limited for certain storage types, e.g. ZFS only supports ZFS and RBD does not implement import/export at all yet (because in a single cluster it wasn't needed). >>> If we want to switch to migrating >>> disks offline via QEMU instead of our current storage_migrate(), >>> going >>> for QEMU storage daemon + NBD seems the most natural to me. > > Yes, I more for this solution. > >>> If it's not too complicated to temporarily attach the disks to the >>> VM, >>> that can be done too, but is less re-usable (e.g. pure offline >>> migration >>> won't benefit from that). > > No sure about attach/detach temporary once by once, or attach all > devices (but this need enough controllers slot). > I think you can attach them to the VM without attaching to a controller by using QMP blockdev-add, but... > qemu storage daemon seem to be a less hacky solution ^_^ > ...sure, this should be nicer and more re-usable. > >> but if it's work, I think we'll need to add config generation in pv >> storage for differents blockdriver >> >> >> like: >> >> ?blockdev driver=file,node-name=file0,filename=vm.img >> >> ?blockdev driver=rbd,node-name=rbd0,pool=my-pool,image=vm01 >> > >>> What other special cases besides (non-krbd) RBD are there? If it's >>> just >>> that, I'd much rather keep the special handling in QEMU itself then >>> burden all other storage plugins with implementing something specific >>> to >>> VMs. > > not sure, maybe glusterfs, .raw (should works for block device like > lvm,zfs), .qcow2 > There's a whole lot of drivers https://qemu.readthedocs.io/en/v8.1.0/interop/qemu-qmp-ref.html#qapidoc-883 But e.g. for NFS, we don't necessarily need it and can just use qcow2/raw. Currently, with -drive we also just treat it like any other file. I'd like to keep the logic for how to construct the -blockdev command line option (mostly) in qemu-server itself. But I guess we can't avoid some amount of coupling. Currently, for -drive we have the coupling in path() which can e.g. return rbd: or gluster: and then QEMU will parse what driver to use from that path. Two approaches that make sense to me (no real preference at the moment): 1. Have a storage plugin method which tells qemu-server about the necessary driver and properties for opening the image. E.g. return the properties as a hash and then have qemu-server join them together and then add the generic properties (e.g. aio,node-name) to construct the full -blockdev option. 2. Do everything in qemu-server and special case for certain storage types that have a dedicated driver. Still needs to get the info like pool name from the RBD storage of course, but that should be possible with existing methods. Happy to hear other suggestions/opinions. > >>> Or is there a way to use the path from the storage plugin somehow >>> like >>> we do at the moment, i.e. >>> "rbd:rbd/vm-111-disk- >>> 1:conf=/etc/pve/ceph.conf:id=admin:keyring=/etc/pve/priv/ceph/rbd.key >>> ring"? > > I don't think it's possible just like this.I need to do more test, > looking at libvirt before they are not too much doc about it. > Probably they decided to get rid of this magic for the newer -blockdev variant. I tried to cheat using driver=file and specify the "rbd:"-path as the filename, but it doesn't work :P From c.heiss at proxmox.com Fri Oct 27 12:59:09 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 27 Oct 2023 12:59:09 +0200 Subject: [pve-devel] [PATCH 01/12] add proxmox-installer-common crate In-Reply-To: <20231025160011.3617524-2-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> <20231025160011.3617524-2-a.lauterer@proxmox.com> Message-ID: <4cj4mysxb3uqno45rftab4ten5rfzanym7mkl3ndyrxlgwsont@hwlcont2swvw> The .deb fails to build with this patch applied, `proxmox-installer-common/` must also be copied to the build directory (see `$(BUILDIR)` target in the Makefile). On Wed, Oct 25, 2023 at 06:00:00PM +0200, Aaron Lauterer wrote: > > It will be used for code shared among the different crates in the > installer. For now between the TUI installer and the upcoming auto > installer. > > Signed-off-by: Aaron Lauterer > --- > Cargo.toml | 1 + > proxmox-installer-common/Cargo.toml | 10 ++++++++++ > proxmox-installer-common/src/lib.rs | 0 > 3 files changed, 11 insertions(+) > create mode 100644 proxmox-installer-common/Cargo.toml > create mode 100644 proxmox-installer-common/src/lib.rs > > diff --git a/Cargo.toml b/Cargo.toml > index fd151ba..c1bd578 100644 > --- a/Cargo.toml > +++ b/Cargo.toml > @@ -1,5 +1,6 @@ > [workspace] > members = [ > + "proxmox-installer-common", > "proxmox-tui-installer", > ] > > diff --git a/proxmox-installer-common/Cargo.toml b/proxmox-installer-common/Cargo.toml > new file mode 100644 > index 0000000..b8762e8 > --- /dev/null > +++ b/proxmox-installer-common/Cargo.toml > @@ -0,0 +1,10 @@ > +[package] > +name = "proxmox-installer-common" > +version = "0.1.0" > +edition = "2021" > +authors = [ "Aaron Lauterer " ] > +license = "AGPL-3" > +exclude = [ "build", "debian" ] > +homepage = "https://www.proxmox.com" > + > +[dependencies] > diff --git a/proxmox-installer-common/src/lib.rs b/proxmox-installer-common/src/lib.rs > new file mode 100644 > index 0000000..e69de29 > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > From c.heiss at proxmox.com Fri Oct 27 13:06:12 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 27 Oct 2023 13:06:12 +0200 Subject: [pve-devel] [PATCH 12/12] tui: remove unused read_json function In-Reply-To: <20231025160011.3617524-13-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> <20231025160011.3617524-13-a.lauterer@proxmox.com> Message-ID: Could be squased into the previous patch IMO if you do send a v2, but not a blocker either for me if not. In any case: Reviewed-by: Christoph Heiss On Wed, Oct 25, 2023 at 06:00:11PM +0200, Aaron Lauterer wrote: > > Signed-off-by: Aaron Lauterer > --- > proxmox-tui-installer/src/setup.rs | 12 +----------- > 1 file changed, 1 insertion(+), 11 deletions(-) > > diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs > index 211a96b..efcabed 100644 > --- a/proxmox-tui-installer/src/setup.rs > +++ b/proxmox-tui-installer/src/setup.rs > @@ -1,13 +1,10 @@ > use std::{ > collections::HashMap, > fmt, > - fs::File, > - io::BufReader, > net::IpAddr, > - path::Path, > }; > > -use serde::{Deserialize, Serialize, Serializer}; > +use serde::{Serialize, Serializer}; > > use crate::options::InstallerOptions; > use proxmox_installer_common::{ > @@ -129,13 +126,6 @@ impl From for InstallConfig { > } > } > > -pub fn read_json Deserialize<'de>, P: AsRef>(path: P) -> Result { > - let file = File::open(path).map_err(|err| err.to_string())?; > - let reader = BufReader::new(file); > - > - serde_json::from_reader(reader).map_err(|err| format!("failed to parse JSON: {err}")) > -} > - > fn serialize_disk_opt(value: &Option, serializer: S) -> Result > where > S: Serializer, > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > From c.heiss at proxmox.com Fri Oct 27 13:14:03 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 27 Oct 2023 13:14:03 +0200 Subject: [pve-devel] [PATCH 02/12] common: copy common code from tui-installer In-Reply-To: <20231025160011.3617524-3-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> <20231025160011.3617524-3-a.lauterer@proxmox.com> Message-ID: Simple code move/copy, so LGTM. Reviewed-by: Christoph Heiss On Wed, Oct 25, 2023 at 06:00:01PM +0200, Aaron Lauterer wrote: > > Copy code that is common to its own crate. > > Signed-off-by: Aaron Lauterer > --- > proxmox-installer-common/Cargo.toml | 2 + > proxmox-installer-common/src/disk_checks.rs | 237 ++++++++++++ > proxmox-installer-common/src/lib.rs | 4 + > proxmox-installer-common/src/options.rs | 387 ++++++++++++++++++++ > proxmox-installer-common/src/setup.rs | 330 +++++++++++++++++ > proxmox-installer-common/src/utils.rs | 268 ++++++++++++++ > 6 files changed, 1228 insertions(+) > create mode 100644 proxmox-installer-common/src/disk_checks.rs > create mode 100644 proxmox-installer-common/src/options.rs > create mode 100644 proxmox-installer-common/src/setup.rs > create mode 100644 proxmox-installer-common/src/utils.rs > > diff --git a/proxmox-installer-common/Cargo.toml b/proxmox-installer-common/Cargo.toml > index b8762e8..bde5457 100644 > --- a/proxmox-installer-common/Cargo.toml > +++ b/proxmox-installer-common/Cargo.toml > @@ -8,3 +8,5 @@ exclude = [ "build", "debian" ] > homepage = "https://www.proxmox.com" > > [dependencies] > +serde = { version = "1.0", features = ["derive"] } > +serde_json = "1.0" > diff --git a/proxmox-installer-common/src/disk_checks.rs b/proxmox-installer-common/src/disk_checks.rs > new file mode 100644 > index 0000000..15b5928 > --- /dev/null > +++ b/proxmox-installer-common/src/disk_checks.rs > @@ -0,0 +1,237 @@ > +use std::collections::HashSet; > + > +use crate::options::{BtrfsRaidLevel, Disk, ZfsRaidLevel}; > +use crate::setup::BootType; > + > +/// Checks a list of disks for duplicate entries, using their index as key. > +/// > +/// # Arguments > +/// > +/// * `disks` - A list of disks to check for duplicates. > +fn check_for_duplicate_disks(disks: &[Disk]) -> Result<(), &Disk> { > + let mut set = HashSet::new(); > + > + for disk in disks { > + if !set.insert(&disk.index) { > + return Err(disk); > + } > + } > + > + Ok(()) > +} > + > +/// Simple wrapper which returns an descriptive error if the list of disks is too short. > +/// > +/// # Arguments > +/// > +/// * `disks` - A list of disks to check the lenght of. > +/// * `min` - Minimum number of disks > +fn check_raid_min_disks(disks: &[Disk], min: usize) -> Result<(), String> { > + if disks.len() < min { > + Err(format!("Need at least {min} disks")) > + } else { > + Ok(()) > + } > +} > + > +/// Checks all disks for legacy BIOS boot compatibility and reports an error as appropriate. 4Kn > +/// disks are generally broken with legacy BIOS and cannot be booted from. > +/// > +/// # Arguments > +/// > +/// * `runinfo` - `RuntimeInfo` instance of currently running system > +/// * `disks` - List of disks designated as bootdisk targets. > +fn check_disks_4kn_legacy_boot(boot_type: BootType, disks: &[Disk]) -> Result<(), &str> { > + let is_blocksize_4096 = |disk: &Disk| disk.block_size.map(|s| s == 4096).unwrap_or(false); > + > + if boot_type == BootType::Bios && disks.iter().any(is_blocksize_4096) { > + return Err("Booting from 4Kn drive in legacy BIOS mode is not supported."); > + } > + > + Ok(()) > +} > + > +/// Checks whether a user-supplied ZFS RAID setup is valid or not, such as disk sizes andminimum > +/// number of disks. > +/// > +/// # Arguments > +/// > +/// * `level` - The targeted ZFS RAID level by the user. > +/// * `disks` - List of disks designated as RAID targets. > +fn check_zfs_raid_config(level: ZfsRaidLevel, disks: &[Disk]) -> Result<(), String> { > + // See also Proxmox/Install.pm:get_zfs_raid_setup() > + > + let check_mirror_size = |disk1: &Disk, disk2: &Disk| { > + if (disk1.size - disk2.size).abs() > disk1.size / 10. { > + Err(format!( > + "Mirrored disks must have same size:\n\n * {disk1}\n * {disk2}" > + )) > + } else { > + Ok(()) > + } > + }; > + > + match level { > + ZfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?, > + ZfsRaidLevel::Raid1 => { > + check_raid_min_disks(disks, 2)?; > + for disk in disks { > + check_mirror_size(&disks[0], disk)?; > + } > + } > + ZfsRaidLevel::Raid10 => { > + check_raid_min_disks(disks, 4)?; > + // Pairs need to have the same size > + for i in (0..disks.len()).step_by(2) { > + check_mirror_size(&disks[i], &disks[i + 1])?; > + } > + } > + // For RAID-Z: minimum disks number is level + 2 > + ZfsRaidLevel::RaidZ => { > + check_raid_min_disks(disks, 3)?; > + for disk in disks { > + check_mirror_size(&disks[0], disk)?; > + } > + } > + ZfsRaidLevel::RaidZ2 => { > + check_raid_min_disks(disks, 4)?; > + for disk in disks { > + check_mirror_size(&disks[0], disk)?; > + } > + } > + ZfsRaidLevel::RaidZ3 => { > + check_raid_min_disks(disks, 5)?; > + for disk in disks { > + check_mirror_size(&disks[0], disk)?; > + } > + } > + } > + > + Ok(()) > +} > + > +/// Checks whether a user-supplied Btrfs RAID setup is valid or not, such as minimum > +/// number of disks. > +/// > +/// # Arguments > +/// > +/// * `level` - The targeted Btrfs RAID level by the user. > +/// * `disks` - List of disks designated as RAID targets. > +fn check_btrfs_raid_config(level: BtrfsRaidLevel, disks: &[Disk]) -> Result<(), String> { > + // See also Proxmox/Install.pm:get_btrfs_raid_setup() > + > + match level { > + BtrfsRaidLevel::Raid0 => check_raid_min_disks(disks, 1)?, > + BtrfsRaidLevel::Raid1 => check_raid_min_disks(disks, 2)?, > + BtrfsRaidLevel::Raid10 => check_raid_min_disks(disks, 4)?, > + } > + > + Ok(()) > +} > + > +#[cfg(test)] > +mod tests { > + use super::*; > + > + fn dummy_disk(index: usize) -> Disk { > + Disk { > + index: index.to_string(), > + path: format!("/dev/dummy{index}"), > + model: Some("Dummy disk".to_owned()), > + size: 1024. * 1024. * 1024. * 8., > + block_size: Some(512), > + } > + } > + > + fn dummy_disks(num: usize) -> Vec { > + (0..num).map(dummy_disk).collect() > + } > + > + #[test] > + fn duplicate_disks() { > + assert!(check_for_duplicate_disks(&dummy_disks(2)).is_ok()); > + assert_eq!( > + check_for_duplicate_disks(&[ > + dummy_disk(0), > + dummy_disk(1), > + dummy_disk(2), > + dummy_disk(2), > + dummy_disk(3), > + ]), > + Err(&dummy_disk(2)), > + ); > + } > + > + #[test] > + fn raid_min_disks() { > + let disks = dummy_disks(10); > + > + assert!(check_raid_min_disks(&disks[..1], 2).is_err()); > + assert!(check_raid_min_disks(&disks[..1], 1).is_ok()); > + assert!(check_raid_min_disks(&disks, 1).is_ok()); > + } > + > + #[test] > + fn bios_boot_compat_4kn() { > + for i in 0..10 { > + let mut disks = dummy_disks(10); > + disks[i].block_size = Some(4096); > + > + // Must fail if /any/ of the disks are 4Kn > + assert!(check_disks_4kn_legacy_boot(BootType::Bios, &disks).is_err()); > + // For UEFI, we allow it for every configuration > + assert!(check_disks_4kn_legacy_boot(BootType::Efi, &disks).is_ok()); > + } > + } > + > + #[test] > + fn btrfs_raid() { > + let disks = dummy_disks(10); > + > + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &[]).is_err()); > + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks[..1]).is_ok()); > + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid0, &disks).is_ok()); > + > + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &[]).is_err()); > + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..1]).is_err()); > + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks[..2]).is_ok()); > + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid1, &disks).is_ok()); > + > + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &[]).is_err()); > + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..3]).is_err()); > + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks[..4]).is_ok()); > + assert!(check_btrfs_raid_config(BtrfsRaidLevel::Raid10, &disks).is_ok()); > + } > + > + #[test] > + fn zfs_raid() { > + let disks = dummy_disks(10); > + > + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &[]).is_err()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks[..1]).is_ok()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid0, &disks).is_ok()); > + > + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &[]).is_err()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks[..2]).is_ok()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid1, &disks).is_ok()); > + > + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &[]).is_err()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &dummy_disks(4)).is_ok()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::Raid10, &disks).is_ok()); > + > + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &[]).is_err()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..2]).is_err()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks[..3]).is_ok()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ, &disks).is_ok()); > + > + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &[]).is_err()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..3]).is_err()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks[..4]).is_ok()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ2, &disks).is_ok()); > + > + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &[]).is_err()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..4]).is_err()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks[..5]).is_ok()); > + assert!(check_zfs_raid_config(ZfsRaidLevel::RaidZ3, &disks).is_ok()); > + } > +} > diff --git a/proxmox-installer-common/src/lib.rs b/proxmox-installer-common/src/lib.rs > index e69de29..f0093f5 100644 > --- a/proxmox-installer-common/src/lib.rs > +++ b/proxmox-installer-common/src/lib.rs > @@ -0,0 +1,4 @@ > +pub mod disk_checks; > +pub mod options; > +pub mod setup; > +pub mod utils; > diff --git a/proxmox-installer-common/src/options.rs b/proxmox-installer-common/src/options.rs > new file mode 100644 > index 0000000..185be2e > --- /dev/null > +++ b/proxmox-installer-common/src/options.rs > @@ -0,0 +1,387 @@ > +use std::net::{IpAddr, Ipv4Addr}; > +use std::{cmp, fmt}; > + > +use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo, SetupInfo}; > +use crate::utils::{CidrAddress, Fqdn}; > + > +#[derive(Copy, Clone, Debug, Eq, PartialEq)] > +pub enum BtrfsRaidLevel { > + Raid0, > + Raid1, > + Raid10, > +} > + > +impl fmt::Display for BtrfsRaidLevel { > + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { > + use BtrfsRaidLevel::*; > + match self { > + Raid0 => write!(f, "RAID0"), > + Raid1 => write!(f, "RAID1"), > + Raid10 => write!(f, "RAID10"), > + } > + } > +} > + > +#[derive(Copy, Clone, Debug, Eq, PartialEq)] > +pub enum ZfsRaidLevel { > + Raid0, > + Raid1, > + Raid10, > + RaidZ, > + RaidZ2, > + RaidZ3, > +} > + > +impl fmt::Display for ZfsRaidLevel { > + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { > + use ZfsRaidLevel::*; > + match self { > + Raid0 => write!(f, "RAID0"), > + Raid1 => write!(f, "RAID1"), > + Raid10 => write!(f, "RAID10"), > + RaidZ => write!(f, "RAIDZ-1"), > + RaidZ2 => write!(f, "RAIDZ-2"), > + RaidZ3 => write!(f, "RAIDZ-3"), > + } > + } > +} > + > +#[derive(Copy, Clone, Debug, Eq, PartialEq)] > +pub enum FsType { > + Ext4, > + Xfs, > + Zfs(ZfsRaidLevel), > + Btrfs(BtrfsRaidLevel), > +} > + > +impl FsType { > + pub fn is_btrfs(&self) -> bool { > + matches!(self, FsType::Btrfs(_)) > + } > +} > + > +impl fmt::Display for FsType { > + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { > + use FsType::*; > + match self { > + Ext4 => write!(f, "ext4"), > + Xfs => write!(f, "XFS"), > + Zfs(level) => write!(f, "ZFS ({level})"), > + Btrfs(level) => write!(f, "Btrfs ({level})"), > + } > + } > +} > + > +#[derive(Clone, Debug)] > +pub struct LvmBootdiskOptions { > + pub total_size: f64, > + pub swap_size: Option, > + pub max_root_size: Option, > + pub max_data_size: Option, > + pub min_lvm_free: Option, > +} > + > +impl LvmBootdiskOptions { > + pub fn defaults_from(disk: &Disk) -> Self { > + Self { > + total_size: disk.size, > + swap_size: None, > + max_root_size: None, > + max_data_size: None, > + min_lvm_free: None, > + } > + } > +} > + > +#[derive(Clone, Debug)] > +pub struct BtrfsBootdiskOptions { > + pub disk_size: f64, > + pub selected_disks: Vec, > +} > + > +impl BtrfsBootdiskOptions { > + /// This panics if the provided slice is empty. > + pub fn defaults_from(disks: &[Disk]) -> Self { > + let disk = &disks[0]; > + Self { > + disk_size: disk.size, > + selected_disks: (0..disks.len()).collect(), > + } > + } > +} > + > +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] > +pub enum ZfsCompressOption { > + #[default] > + On, > + Off, > + Lzjb, > + Lz4, > + Zle, > + Gzip, > + Zstd, > +} > + > +impl fmt::Display for ZfsCompressOption { > + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { > + write!(f, "{}", format!("{self:?}").to_lowercase()) > + } > +} > + > +impl From<&ZfsCompressOption> for String { > + fn from(value: &ZfsCompressOption) -> Self { > + value.to_string() > + } > +} > + > +pub const ZFS_COMPRESS_OPTIONS: &[ZfsCompressOption] = { > + use ZfsCompressOption::*; > + &[On, Off, Lzjb, Lz4, Zle, Gzip, Zstd] > +}; > + > +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq)] > +pub enum ZfsChecksumOption { > + #[default] > + On, > + Off, > + Fletcher2, > + Fletcher4, > + Sha256, > +} > + > +impl fmt::Display for ZfsChecksumOption { > + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { > + write!(f, "{}", format!("{self:?}").to_lowercase()) > + } > +} > + > +impl From<&ZfsChecksumOption> for String { > + fn from(value: &ZfsChecksumOption) -> Self { > + value.to_string() > + } > +} > + > +pub const ZFS_CHECKSUM_OPTIONS: &[ZfsChecksumOption] = { > + use ZfsChecksumOption::*; > + &[On, Off, Fletcher2, Fletcher4, Sha256] > +}; > + > +#[derive(Clone, Debug)] > +pub struct ZfsBootdiskOptions { > + pub ashift: usize, > + pub compress: ZfsCompressOption, > + pub checksum: ZfsChecksumOption, > + pub copies: usize, > + pub disk_size: f64, > + pub selected_disks: Vec, > +} > + > +impl ZfsBootdiskOptions { > + /// This panics if the provided slice is empty. > + pub fn defaults_from(disks: &[Disk]) -> Self { > + let disk = &disks[0]; > + Self { > + ashift: 12, > + compress: ZfsCompressOption::default(), > + checksum: ZfsChecksumOption::default(), > + copies: 1, > + disk_size: disk.size, > + selected_disks: (0..disks.len()).collect(), > + } > + } > +} > + > +#[derive(Clone, Debug)] > +pub enum AdvancedBootdiskOptions { > + Lvm(LvmBootdiskOptions), > + Zfs(ZfsBootdiskOptions), > + Btrfs(BtrfsBootdiskOptions), > +} > + > +#[derive(Clone, Debug, PartialEq)] > +pub struct Disk { > + pub index: String, > + pub path: String, > + pub model: Option, > + pub size: f64, > + pub block_size: Option, > +} > + > +impl fmt::Display for Disk { > + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { > + // TODO: Format sizes properly with `proxmox-human-byte` once merged > + // https://lists.proxmox.com/pipermail/pbs-devel/2023-May/006125.html > + f.write_str(&self.path)?; > + if let Some(model) = &self.model { > + // FIXME: ellipsize too-long names? > + write!(f, " ({model})")?; > + } > + write!(f, " ({:.2} GiB)", self.size) > + } > +} > + > +impl From<&Disk> for String { > + fn from(value: &Disk) -> Self { > + value.to_string() > + } > +} > + > +impl cmp::Eq for Disk {} > + > +impl cmp::PartialOrd for Disk { > + fn partial_cmp(&self, other: &Self) -> Option { > + self.index.partial_cmp(&other.index) > + } > +} > + > +impl cmp::Ord for Disk { > + fn cmp(&self, other: &Self) -> cmp::Ordering { > + self.index.cmp(&other.index) > + } > +} > + > +#[derive(Clone, Debug)] > +pub struct BootdiskOptions { > + pub disks: Vec, > + pub fstype: FsType, > + pub advanced: AdvancedBootdiskOptions, > +} > + > +impl BootdiskOptions { > + pub fn defaults_from(disk: &Disk) -> Self { > + Self { > + disks: vec![disk.clone()], > + fstype: FsType::Ext4, > + advanced: AdvancedBootdiskOptions::Lvm(LvmBootdiskOptions::defaults_from(disk)), > + } > + } > +} > + > +#[derive(Clone, Debug)] > +pub struct TimezoneOptions { > + pub country: String, > + pub timezone: String, > + pub kb_layout: String, > +} > + > +impl TimezoneOptions { > + pub fn defaults_from(runtime: &RuntimeInfo, locales: &LocaleInfo) -> Self { > + let country = runtime.country.clone().unwrap_or_else(|| "at".to_owned()); > + > + let timezone = locales > + .cczones > + .get(&country) > + .and_then(|zones| zones.get(0)) > + .cloned() > + .unwrap_or_else(|| "UTC".to_owned()); > + > + let kb_layout = locales > + .countries > + .get(&country) > + .and_then(|c| { > + if c.kmap.is_empty() { > + None > + } else { > + Some(c.kmap.clone()) > + } > + }) > + .unwrap_or_else(|| "en-us".to_owned()); > + > + Self { > + country, > + timezone, > + kb_layout, > + } > + } > +} > + > +#[derive(Clone, Debug)] > +pub struct PasswordOptions { > + pub email: String, > + pub root_password: String, > +} > + > +impl Default for PasswordOptions { > + fn default() -> Self { > + Self { > + email: "mail at example.invalid".to_string(), > + root_password: String::new(), > + } > + } > +} > + > +#[derive(Clone, Debug, PartialEq)] > +pub struct NetworkOptions { > + pub ifname: String, > + pub fqdn: Fqdn, > + pub address: CidrAddress, > + pub gateway: IpAddr, > + pub dns_server: IpAddr, > +} > + > +impl NetworkOptions { > + const DEFAULT_DOMAIN: &str = "example.invalid"; > + > + pub fn defaults_from(setup: &SetupInfo, network: &NetworkInfo) -> Self { > + let mut this = Self { > + ifname: String::new(), > + fqdn: Self::construct_fqdn(network, setup.config.product.default_hostname()), > + // Safety: The provided mask will always be valid. > + address: CidrAddress::new(Ipv4Addr::UNSPECIFIED, 0).unwrap(), > + gateway: Ipv4Addr::UNSPECIFIED.into(), > + dns_server: Ipv4Addr::UNSPECIFIED.into(), > + }; > + > + if let Some(ip) = network.dns.dns.first() { > + this.dns_server = *ip; > + } > + > + if let Some(routes) = &network.routes { > + let mut filled = false; > + if let Some(gw) = &routes.gateway4 { > + if let Some(iface) = network.interfaces.get(&gw.dev) { > + this.ifname = iface.name.clone(); > + if let Some(addresses) = &iface.addresses { > + if let Some(addr) = addresses.iter().find(|addr| addr.is_ipv4()) { > + this.gateway = gw.gateway; > + this.address = addr.clone(); > + filled = true; > + } > + } > + } > + } > + if !filled { > + if let Some(gw) = &routes.gateway6 { > + if let Some(iface) = network.interfaces.get(&gw.dev) { > + if let Some(addresses) = &iface.addresses { > + if let Some(addr) = addresses.iter().find(|addr| addr.is_ipv6()) { > + this.ifname = iface.name.clone(); > + this.gateway = gw.gateway; > + this.address = addr.clone(); > + } > + } > + } > + } > + } > + } > + > + this > + } > + > + fn construct_fqdn(network: &NetworkInfo, default_hostname: &str) -> Fqdn { > + let hostname = network.hostname.as_deref().unwrap_or(default_hostname); > + > + let domain = network > + .dns > + .domain > + .as_deref() > + .unwrap_or(Self::DEFAULT_DOMAIN); > + > + Fqdn::from(&format!("{hostname}.{domain}")).unwrap_or_else(|_| { > + // Safety: This will always result in a valid FQDN, as we control & know > + // the values of default_hostname (one of "pve", "pmg" or "pbs") and > + // constant-defined DEFAULT_DOMAIN. > + Fqdn::from(&format!("{}.{}", default_hostname, Self::DEFAULT_DOMAIN)).unwrap() > + }) > + } > +} > diff --git a/proxmox-installer-common/src/setup.rs b/proxmox-installer-common/src/setup.rs > new file mode 100644 > index 0000000..a4947f1 > --- /dev/null > +++ b/proxmox-installer-common/src/setup.rs > @@ -0,0 +1,330 @@ > +use std::{ > + cmp, > + collections::HashMap, > + fmt, > + fs::File, > + io::BufReader, > + net::IpAddr, > + path::{Path, PathBuf}, > +}; > + > +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; > + > +use crate::{ > + options::{Disk, ZfsBootdiskOptions, ZfsChecksumOption, ZfsCompressOption}, > + utils::CidrAddress, > +}; > + > +#[allow(clippy::upper_case_acronyms)] > +#[derive(Clone, Copy, Deserialize, PartialEq)] > +#[serde(rename_all = "lowercase")] > +pub enum ProxmoxProduct { > + PVE, > + PBS, > + PMG, > +} > + > +impl ProxmoxProduct { > + pub fn default_hostname(self) -> &'static str { > + match self { > + Self::PVE => "pve", > + Self::PMG => "pmg", > + Self::PBS => "pbs", > + } > + } > +} > + > +#[derive(Clone, Deserialize)] > +pub struct ProductConfig { > + pub fullname: String, > + pub product: ProxmoxProduct, > + #[serde(deserialize_with = "deserialize_bool_from_int")] > + pub enable_btrfs: bool, > +} > + > +#[derive(Clone, Deserialize)] > +pub struct IsoInfo { > + pub release: String, > + pub isorelease: String, > +} > + > +/// Paths in the ISO environment containing installer data. > +#[derive(Clone, Deserialize)] > +pub struct IsoLocations { > + pub iso: PathBuf, > +} > + > +#[derive(Clone, Deserialize)] > +pub struct SetupInfo { > + #[serde(rename = "product-cfg")] > + pub config: ProductConfig, > + #[serde(rename = "iso-info")] > + pub iso_info: IsoInfo, > + pub locations: IsoLocations, > +} > + > +#[derive(Clone, Deserialize)] > +pub struct CountryInfo { > + pub name: String, > + #[serde(default)] > + pub zone: String, > + pub kmap: String, > +} > + > +#[derive(Clone, Deserialize, Eq, PartialEq)] > +pub struct KeyboardMapping { > + pub name: String, > + #[serde(rename = "kvm")] > + pub id: String, > + #[serde(rename = "x11")] > + pub xkb_layout: String, > + #[serde(rename = "x11var")] > + pub xkb_variant: String, > +} > + > +impl cmp::PartialOrd for KeyboardMapping { > + fn partial_cmp(&self, other: &Self) -> Option { > + self.name.partial_cmp(&other.name) > + } > +} > + > +impl cmp::Ord for KeyboardMapping { > + fn cmp(&self, other: &Self) -> cmp::Ordering { > + self.name.cmp(&other.name) > + } > +} > + > +#[derive(Clone, Deserialize)] > +pub struct LocaleInfo { > + #[serde(deserialize_with = "deserialize_cczones_map")] > + pub cczones: HashMap>, > + #[serde(rename = "country")] > + pub countries: HashMap, > + pub kmap: HashMap, > +} > + > +#[derive(Serialize)] > +struct InstallZfsOption { > + ashift: usize, > + #[serde(serialize_with = "serialize_as_display")] > + compress: ZfsCompressOption, > + #[serde(serialize_with = "serialize_as_display")] > + checksum: ZfsChecksumOption, > + copies: usize, > +} > + > +impl From for InstallZfsOption { > + fn from(opts: ZfsBootdiskOptions) -> Self { > + InstallZfsOption { > + ashift: opts.ashift, > + compress: opts.compress, > + checksum: opts.checksum, > + copies: opts.copies, > + } > + } > +} > + > +pub fn read_json Deserialize<'de>, P: AsRef>(path: P) -> Result { > + let file = File::open(path).map_err(|err| err.to_string())?; > + let reader = BufReader::new(file); > + > + serde_json::from_reader(reader).map_err(|err| format!("failed to parse JSON: {err}")) > +} > + > +fn deserialize_bool_from_int<'de, D>(deserializer: D) -> Result > +where > + D: Deserializer<'de>, > +{ > + let val: u32 = Deserialize::deserialize(deserializer)?; > + Ok(val != 0) > +} > + > +fn deserialize_cczones_map<'de, D>( > + deserializer: D, > +) -> Result>, D::Error> > +where > + D: Deserializer<'de>, > +{ > + let map: HashMap> = Deserialize::deserialize(deserializer)?; > + > + let mut result = HashMap::new(); > + for (cc, list) in map.into_iter() { > + result.insert(cc, list.into_keys().collect()); > + } > + > + Ok(result) > +} > + > +fn deserialize_disks_map<'de, D>(deserializer: D) -> Result, D::Error> > +where > + D: Deserializer<'de>, > +{ > + let disks = > + , String)>>::deserialize(deserializer)?; > + Ok(disks > + .into_iter() > + .map( > + |(index, device, size_mb, model, logical_bsize, _syspath)| Disk { > + index: index.to_string(), > + // Linux always reports the size of block devices in sectors, where one sector is > + // defined as being 2^9 = 512 bytes in size. > + // https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/linux/blk_types.h?h=v6.4#n30 > + size: (size_mb * 512.) / 1024. / 1024. / 1024., > + block_size: logical_bsize, > + path: device, > + model: (!model.is_empty()).then_some(model), > + }, > + ) > + .collect()) > +} > + > +fn deserialize_cidr_list<'de, D>(deserializer: D) -> Result>, D::Error> > +where > + D: Deserializer<'de>, > +{ > + #[derive(Deserialize)] > + struct CidrDescriptor { > + address: String, > + prefix: usize, > + // family is implied anyway by parsing the address > + } > + > + let list: Vec = Deserialize::deserialize(deserializer)?; > + > + let mut result = Vec::with_capacity(list.len()); > + for desc in list { > + let ip_addr = desc > + .address > + .parse::() > + .map_err(|err| de::Error::custom(format!("{:?}", err)))?; > + > + result.push( > + CidrAddress::new(ip_addr, desc.prefix) > + .map_err(|err| de::Error::custom(format!("{:?}", err)))?, > + ); > + } > + > + Ok(Some(result)) > +} > + > +fn serialize_as_display(value: &T, serializer: S) -> Result > +where > + S: Serializer, > + T: fmt::Display, > +{ > + serializer.collect_str(value) > +} > + > +#[derive(Clone, Deserialize)] > +pub struct RuntimeInfo { > + /// Whether is system was booted in (legacy) BIOS or UEFI mode. > + pub boot_type: BootType, > + > + /// Detected country if available. > + pub country: Option, > + > + /// Maps devices to their information. > + #[serde(deserialize_with = "deserialize_disks_map")] > + pub disks: Vec, > + > + /// Network addresses, gateways and DNS info. > + pub network: NetworkInfo, > + > + /// Total memory of the system in MiB. > + pub total_memory: usize, > + > + /// Whether the CPU supports hardware-accelerated virtualization > + #[serde(deserialize_with = "deserialize_bool_from_int")] > + pub hvm_supported: bool, > +} > + > +#[derive(Copy, Clone, Eq, Deserialize, PartialEq)] > +#[serde(rename_all = "lowercase")] > +pub enum BootType { > + Bios, > + Efi, > +} > + > +#[derive(Clone, Deserialize)] > +pub struct NetworkInfo { > + pub dns: Dns, > + pub routes: Option, > + > + /// Maps devices to their configuration, if it has a usable configuration. > + /// (Contains no entries for devices with only link-local addresses.) > + #[serde(default)] > + pub interfaces: HashMap, > + > + /// The hostname of this machine, if set by the DHCP server. > + pub hostname: Option, > +} > + > +#[derive(Clone, Deserialize)] > +pub struct Dns { > + pub domain: Option, > + > + /// List of stringified IP addresses. > + #[serde(default)] > + pub dns: Vec, > +} > + > +#[derive(Clone, Deserialize)] > +pub struct Routes { > + /// Ipv4 gateway. > + pub gateway4: Option, > + > + /// Ipv6 gateway. > + pub gateway6: Option, > +} > + > +#[derive(Clone, Deserialize)] > +pub struct Gateway { > + /// Outgoing network device. > + pub dev: String, > + > + /// Stringified gateway IP address. > + pub gateway: IpAddr, > +} > + > +#[derive(Clone, Deserialize)] > +#[serde(rename_all = "UPPERCASE")] > +pub enum InterfaceState { > + Up, > + Down, > + #[serde(other)] > + Unknown, > +} > + > +impl InterfaceState { > + // avoid display trait as this is not the string representation for a serializer > + pub fn render(&self) -> String { > + match self { > + Self::Up => "\u{25CF}", > + Self::Down | Self::Unknown => " ", > + } > + .into() > + } > +} > + > +#[derive(Clone, Deserialize)] > +pub struct Interface { > + pub name: String, > + > + pub index: usize, > + > + pub mac: String, > + > + pub state: InterfaceState, > + > + #[serde(default)] > + #[serde(deserialize_with = "deserialize_cidr_list")] > + pub addresses: Option>, > +} > + > +impl Interface { > + // avoid display trait as this is not the string representation for a serializer > + pub fn render(&self) -> String { > + format!("{} {}", self.state.render(), self.name) > + } > +} > + > diff --git a/proxmox-installer-common/src/utils.rs b/proxmox-installer-common/src/utils.rs > new file mode 100644 > index 0000000..89349ed > --- /dev/null > +++ b/proxmox-installer-common/src/utils.rs > @@ -0,0 +1,268 @@ > +use std::{ > + fmt, > + net::{AddrParseError, IpAddr}, > + num::ParseIntError, > + str::FromStr, > +}; > + > +use serde::Deserialize; > + > +/// Possible errors that might occur when parsing CIDR addresses. > +#[derive(Debug)] > +pub enum CidrAddressParseError { > + /// No delimiter for separating address and mask was found. > + NoDelimiter, > + /// The IP address part could not be parsed. > + InvalidAddr(AddrParseError), > + /// The mask could not be parsed. > + InvalidMask(Option), > +} > + > +/// An IP address (IPv4 or IPv6), including network mask. > +/// > +/// See the [`IpAddr`] type for more information how IP addresses are handled. > +/// The mask is appropriately enforced to be `0 <= mask <= 32` for IPv4 or > +/// `0 <= mask <= 128` for IPv6 addresses. > +/// > +/// # Examples > +/// ``` > +/// use std::net::{Ipv4Addr, Ipv6Addr}; > +/// let ipv4 = CidrAddress::new(Ipv4Addr::new(192, 168, 0, 1), 24).unwrap(); > +/// let ipv6 = CidrAddress::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0xc0a8, 1), 32).unwrap(); > +/// > +/// assert_eq!(ipv4.to_string(), "192.168.0.1/24"); > +/// assert_eq!(ipv6.to_string(), "2001:db8::c0a8:1/32"); > +/// ``` > +#[derive(Clone, Debug, PartialEq)] > +pub struct CidrAddress { > + addr: IpAddr, > + mask: usize, > +} > + > +impl CidrAddress { > + /// Constructs a new CIDR address. > + /// > + /// It fails if the mask is invalid for the given IP address. > + pub fn new>(addr: T, mask: usize) -> Result { > + let addr = addr.into(); > + > + if mask > mask_limit(&addr) { > + Err(CidrAddressParseError::InvalidMask(None)) > + } else { > + Ok(Self { addr, mask }) > + } > + } > + > + /// Returns only the IP address part of the address. > + pub fn addr(&self) -> IpAddr { > + self.addr > + } > + > + /// Returns `true` if this address is an IPv4 address, `false` otherwise. > + pub fn is_ipv4(&self) -> bool { > + self.addr.is_ipv4() > + } > + > + /// Returns `true` if this address is an IPv6 address, `false` otherwise. > + pub fn is_ipv6(&self) -> bool { > + self.addr.is_ipv6() > + } > + > + /// Returns only the mask part of the address. > + pub fn mask(&self) -> usize { > + self.mask > + } > +} > + > +impl FromStr for CidrAddress { > + type Err = CidrAddressParseError; > + > + fn from_str(s: &str) -> Result { > + let (addr, mask) = s > + .split_once('/') > + .ok_or(CidrAddressParseError::NoDelimiter)?; > + > + let addr = addr.parse().map_err(CidrAddressParseError::InvalidAddr)?; > + > + let mask = mask > + .parse() > + .map_err(|err| CidrAddressParseError::InvalidMask(Some(err)))?; > + > + if mask > mask_limit(&addr) { > + Err(CidrAddressParseError::InvalidMask(None)) > + } else { > + Ok(Self { addr, mask }) > + } > + } > +} > + > +impl fmt::Display for CidrAddress { > + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { > + write!(f, "{}/{}", self.addr, self.mask) > + } > +} > + > +fn mask_limit(addr: &IpAddr) -> usize { > + if addr.is_ipv4() { > + 32 > + } else { > + 128 > + } > +} > + > +/// Possible errors that might occur when parsing FQDNs. > +#[derive(Debug, Eq, PartialEq)] > +pub enum FqdnParseError { > + MissingHostname, > + NumericHostname, > + InvalidPart(String), > +} > + > +impl fmt::Display for FqdnParseError { > + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { > + use FqdnParseError::*; > + match self { > + MissingHostname => write!(f, "missing hostname part"), > + NumericHostname => write!(f, "hostname cannot be purely numeric"), > + InvalidPart(part) => write!( > + f, > + "FQDN must only consist of alphanumeric characters and dashes. Invalid part: '{part}'", > + ), > + } > + } > +} > + > +#[derive(Clone, Debug, Eq, PartialEq)] > +pub struct Fqdn { > + parts: Vec, > +} > + > +impl Fqdn { > + pub fn from(fqdn: &str) -> Result { > + let parts = fqdn > + .split('.') > + .map(ToOwned::to_owned) > + .collect::>(); > + > + for part in &parts { > + if !Self::validate_single(part) { > + return Err(FqdnParseError::InvalidPart(part.clone())); > + } > + } > + > + if parts.len() < 2 { > + Err(FqdnParseError::MissingHostname) > + } else if parts[0].chars().all(|c| c.is_ascii_digit()) { > + // Not allowed/supported on Debian systems. > + Err(FqdnParseError::NumericHostname) > + } else { > + Ok(Self { parts }) > + } > + } > + > + pub fn host(&self) -> Option<&str> { > + self.has_host().then_some(&self.parts[0]) > + } > + > + pub fn domain(&self) -> String { > + let parts = if self.has_host() { > + &self.parts[1..] > + } else { > + &self.parts > + }; > + > + parts.join(".") > + } > + > + /// Checks whether the FQDN has a hostname associated with it, i.e. is has more than 1 part. > + fn has_host(&self) -> bool { > + self.parts.len() > 1 > + } > + > + fn validate_single(s: &String) -> bool { > + !s.is_empty() > + // First character must be alphanumeric > + && s.chars() > + .next() > + .map(|c| c.is_ascii_alphanumeric()) > + .unwrap_or_default() > + // .. last character as well, > + && s.chars() > + .last() > + .map(|c| c.is_ascii_alphanumeric()) > + .unwrap_or_default() > + // and anything between must be alphanumeric or - > + && s.chars() > + .skip(1) > + .take(s.len().saturating_sub(2)) > + .all(|c| c.is_ascii_alphanumeric() || c == '-') > + } > +} > + > +impl FromStr for Fqdn { > + type Err = FqdnParseError; > + > + fn from_str(value: &str) -> Result { > + Self::from(value) > + } > +} > + > +impl fmt::Display for Fqdn { > + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { > + write!(f, "{}", self.parts.join(".")) > + } > +} > + > +impl<'de> Deserialize<'de> for Fqdn { > + fn deserialize(deserializer: D) -> Result > + where > + D: serde::Deserializer<'de>, > + { > + let s: String = Deserialize::deserialize(deserializer)?; > + s.parse() > + .map_err(|_| serde::de::Error::custom("invalid FQDN")) > + } > +} > + > +#[cfg(test)] > +mod tests { > + use super::*; > + > + #[test] > + fn fqdn_construct() { > + use FqdnParseError::*; > + assert!(Fqdn::from("foo.example.com").is_ok()); > + assert!(Fqdn::from("foo-bar.com").is_ok()); > + assert!(Fqdn::from("a-b.com").is_ok()); > + > + assert_eq!(Fqdn::from("foo"), Err(MissingHostname)); > + > + assert_eq!(Fqdn::from("-foo.com"), Err(InvalidPart("-foo".to_owned()))); > + assert_eq!(Fqdn::from("foo-.com"), Err(InvalidPart("foo-".to_owned()))); > + assert_eq!(Fqdn::from("foo.com-"), Err(InvalidPart("com-".to_owned()))); > + assert_eq!(Fqdn::from("-o-.com"), Err(InvalidPart("-o-".to_owned()))); > + > + assert_eq!(Fqdn::from("123.com"), Err(NumericHostname)); > + assert!(Fqdn::from("foo123.com").is_ok()); > + assert!(Fqdn::from("123foo.com").is_ok()); > + } > + > + #[test] > + fn fqdn_parts() { > + let fqdn = Fqdn::from("pve.example.com").unwrap(); > + assert_eq!(fqdn.host().unwrap(), "pve"); > + assert_eq!(fqdn.domain(), "example.com"); > + assert_eq!( > + fqdn.parts, > + &["pve".to_owned(), "example".to_owned(), "com".to_owned()] > + ); > + } > + > + #[test] > + fn fqdn_display() { > + assert_eq!( > + Fqdn::from("foo.example.com").unwrap().to_string(), > + "foo.example.com" > + ); > + } > +} > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > From s.lendl at proxmox.com Fri Oct 27 13:19:28 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:19:28 +0200 Subject: [pve-devel] [RFC SDN DHCP] Add and Remove DHCP mappings on vNIC add/remove In-Reply-To: <87v8axbjh1.fsf@gmail.com> References: <87v8axbjh1.fsf@gmail.com> Message-ID: <20231027111934.1956349-1-s.lendl@proxmox.com> Date: Fri, 27 Oct 2023 13:00:49 +0200 >From 96fee0866727a1188b1debd805c625c598816b98 Mon Sep 17 00:00:00 2001 This patch series defines IPs, once allocated by the IPAM as persistant until the vNIC is removed from the VM or the VM is destroyed. The dnsmasq ethers file is a pure product of the IPAM database and does not have it's own state, therefore removing the potential issue of wrong sync state between IPAM and dnsmasq. Updating the ethers file is only done on the effected node. Technically, starting the VM still adds the DHCP mapping. This is needed at the moment because during migration, this triggers generating the ethers file on the target node. If the IPAM already contains the mapping, it returns the existing IP (upsert) Alternative approach would be to trigger an update of the ethers file on all nodes on IPAM update. Stopping a guest does not effect IPAM/DHCP. Destroying a guest removes the associated IPs from the IPAM. -- From s.lendl at proxmox.com Fri Oct 27 13:20:16 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:20:16 +0200 Subject: [pve-devel] [RFC SDN DHCP] Add and Remove DHCP mappings on vNIC add/remove In-Reply-To: <87v8axbjh1.fsf@gmail.com> References: <87v8axbjh1.fsf@gmail.com> Message-ID: <20231027112022.1960451-1-s.lendl@proxmox.com> Date: Fri, 27 Oct 2023 13:00:49 +0200 >From 96fee0866727a1188b1debd805c625c598816b98 Mon Sep 17 00:00:00 2001 This patch series defines IPs, once allocated by the IPAM as persistant until the vNIC is removed from the VM or the VM is destroyed. The dnsmasq ethers file is a pure product of the IPAM database and does not have it's own state, therefore removing the potential issue of wrong sync state between IPAM and dnsmasq. Updating the ethers file is only done on the effected node. Technically, starting the VM still adds the DHCP mapping. This is needed at the moment because during migration, this triggers generating the ethers file on the target node. If the IPAM already contains the mapping, it returns the existing IP (upsert) Alternative approach would be to trigger an update of the ethers file on all nodes on IPAM update. Stopping a guest does not effect IPAM/DHCP. Destroying a guest removes the associated IPs from the IPAM. -- From s.lendl at proxmox.com Fri Oct 27 13:20:17 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:20:17 +0200 Subject: [pve-devel] [RFC pve-network 1/3] dhcp add ip returns IP if already present for MAC In-Reply-To: <20231027112022.1960451-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027112022.1960451-1-s.lendl@proxmox.com> Message-ID: <20231027112022.1960451-2-s.lendl@proxmox.com> Signed-off-by: Stefan Lendl --- src/PVE/Network/SDN/Dhcp/Dnsmasq.pm | 23 +++++++++++++++++++++++ src/PVE/Network/SDN/Ipams/PVEPlugin.pm | 16 ++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm index af109b8..6f8b1c4 100644 --- a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm +++ b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm @@ -18,6 +18,29 @@ sub type { return 'dnsmasq'; } +sub generate_config { + my ($class, $dhcp_config, $ip_mappings) = @_; + + my $ethers_file = "$DNSMASQ_CONFIG_ROOT/$dhcp_config->{id}/ethers"; + my $ethers_tmp_file = "$ethers_file.tmp"; + + open(my $out, '>', $ethers_file) or die "Could not open file '$ethers_file' $!\n"; + + foreach my $ip (keys %$ip_mappings) { + if (exists $ip_mappings->{$ip}{mac}) { + my $mac = $ip_mappings->{$ip}{mac}; + print $out "$mac,$ip\n"; + } + } + + close $out; + + chmod 0644, $ethers_file; + + my $service_name = "dnsmasq\@$dhcp_config->{id}"; + PVE::Tools::run_command(['systemctl', 'reload', $service_name]); +} + sub del_ip_mapping { my ($class, $dhcp_config, $mac) = @_; diff --git a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm index fcc8282..40b4a8f 100644 --- a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm +++ b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm @@ -161,6 +161,8 @@ sub add_dhcp_ip { my $cidr = $subnet->{cidr}; my $zone = $subnet->{zone}; + my $vmid = $data->{vmid}; + my $mac = $data->{mac}; cfs_lock_file($ipamdb_file, undef, sub { my $db = read_db(); @@ -174,11 +176,25 @@ sub add_dhcp_ip { my $ip = new Net::IP ("$dhcp_range->{'start-address'} - $dhcp_range->{'end-address'}") or die "Invalid IP address(es) in DHCP Range!\n"; + do { + my $ip_address = $ip->ip(); + if (exists $dbsubnet->{ips}->{$ip_address} && + exists $dbsubnet->{ips}->{$ip_address}->{mac} && + $dbsubnet->{ips}->{$ip_address}->{mac} eq $mac) { + print "IP '$ip_address' already exist for $mac in $vmid\n"; + + return $ip_address; + } + } while (++$ip); + + $ip = new Net::IP ("$dhcp_range->{'start-address'} - $dhcp_range->{'end-address'}"); + do { my $ip_address = $ip->ip(); if (!$dbsubnet->{ips}->{$ip_address}) { $dbsubnet->{ips}->{$ip_address} = $data; write_db($db); + print "New IP '$ip_address' added for $mac at $vmid\n"; return $ip_address; } -- 2.41.0 From s.lendl at proxmox.com Fri Oct 27 13:20:18 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:20:18 +0200 Subject: [pve-devel] [RFC pve-network 2/3] always generate dnsmasq ethers file In-Reply-To: <20231027112022.1960451-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027112022.1960451-1-s.lendl@proxmox.com> Message-ID: <20231027112022.1960451-3-s.lendl@proxmox.com> Makes dnsmasq stateless and can be generated from the IPAM. On dhcp_add_ip always generate the entire ethers file. Signed-off-by: Stefan Lendl --- src/PVE/Network/SDN/Dhcp.pm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/PVE/Network/SDN/Dhcp.pm b/src/PVE/Network/SDN/Dhcp.pm index b92c73a..b854cce 100644 --- a/src/PVE/Network/SDN/Dhcp.pm +++ b/src/PVE/Network/SDN/Dhcp.pm @@ -87,8 +87,14 @@ sub add_mapping { next if !$ip; + # generates ethers file every time + my $ipam_db = $ipam_plugin->read_db(); + my $dbzone = $ipam_db->{zones}->{$subnet_config->{zone}}; + my $dbsubnet = $dbzone->{subnets}->{$subnet_config->{cidr}}; + my $dbsubnet_ips = $dbsubnet->{ips}; + my $dhcp_plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($dhcp_config->{type}); - $dhcp_plugin->add_ip_mapping($dhcp_config, $mac, $ip); + $dhcp_plugin->generate_config($dhcp_config, $dbsubnet_ips); return $ip; } -- 2.41.0 From s.lendl at proxmox.com Fri Oct 27 13:20:20 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:20:20 +0200 Subject: [pve-devel] [RFC pve-network] do not remove DHCP mapping on stop In-Reply-To: <20231027112022.1960451-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027112022.1960451-1-s.lendl@proxmox.com> Message-ID: <20231027112022.1960451-5-s.lendl@proxmox.com> Signed-off-by: Stefan Lendl --- src/PVE/LXC.pm | 8 -------- src/lxc-pve-poststop-hook | 1 - 2 files changed, 9 deletions(-) diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index bd8eb63..a7de9b8 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -916,14 +916,6 @@ sub vm_stop_cleanup { eval { my $vollist = PVE::LXC::Config->get_vm_volumes($conf); PVE::Storage::deactivate_volumes($storage_cfg, $vollist); - - for my $k (keys %$conf) { - next if $k !~ /^net(\d+)/; - my $net = PVE::LXC::Config->parse_lxc_network($conf->{$k}); - next if $net->{type} ne 'veth'; - - PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{hwaddr}); - } }; warn $@ if $@; # avoid errors - just warn } diff --git a/src/lxc-pve-poststop-hook b/src/lxc-pve-poststop-hook index e7d46c7..2fe97ec 100755 --- a/src/lxc-pve-poststop-hook +++ b/src/lxc-pve-poststop-hook @@ -12,7 +12,6 @@ use PVE::LXC::Config; use PVE::LXC::Tools; use PVE::LXC; use PVE::Network; -use PVE::Network::SDN::Dhcp; use PVE::RESTEnvironment; use PVE::Storage; use PVE::Tools; -- 2.41.0 From s.lendl at proxmox.com Fri Oct 27 13:20:19 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:20:19 +0200 Subject: [pve-devel] [RFC pve-network 3/3] touch the ethers file when creating the dnsmasq config In-Reply-To: <20231027112022.1960451-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027112022.1960451-1-s.lendl@proxmox.com> Message-ID: <20231027112022.1960451-4-s.lendl@proxmox.com> removes an error from journal when newly DHCP server is configured Signed-off-by: Stefan Lendl --- src/PVE/Network/SDN/Dhcp/Dnsmasq.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm index 6f8b1c4..39e4cce 100644 --- a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm +++ b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm @@ -182,6 +182,11 @@ CFG $default_dnsmasq_config ); + # touch the ethers file to avoid errors before creation + my $ethers_file = "$DNSMASQ_CONFIG_ROOT/$dhcp_config->{id}/ethers"; + open(my $e, '>>', $ethers_file) or die "Could not open file '$ethers_file' $!\n"; + close $e; + unlink glob "$config_directory/10-*.conf"; } -- 2.41.0 From s.lendl at proxmox.com Fri Oct 27 13:20:21 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:20:21 +0200 Subject: [pve-devel] [RFC pve-network 4/5] do not remove DHCP mapping on VM stop In-Reply-To: <20231027112022.1960451-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027112022.1960451-1-s.lendl@proxmox.com> Message-ID: <20231027112022.1960451-6-s.lendl@proxmox.com> Signed-off-by: Stefan Lendl --- PVE/QemuServer.pm | 2 -- vm-network-scripts/pve-bridgedown | 4 ---- 2 files changed, 6 deletions(-) diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 6c1e463..710259b 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -6143,8 +6143,6 @@ sub vm_stop_cleanup { cleanup_pci_devices($vmid, $conf); - cleanup_sdn_dhcp($vmid, $conf); - vmconfig_apply_pending($vmid, $conf, $storecfg) if $apply_pending_changes; }; warn $@ if $@; # avoid errors - just warn diff --git a/vm-network-scripts/pve-bridgedown b/vm-network-scripts/pve-bridgedown index a220660..8d58947 100755 --- a/vm-network-scripts/pve-bridgedown +++ b/vm-network-scripts/pve-bridgedown @@ -26,10 +26,6 @@ my $conf = PVE::QemuConfig->load_config($vmid); my $netconf = $conf->{$netid}; my $net = PVE::QemuServer::parse_net($netconf); -if ($have_sdn) { - PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{macaddr}); -} - PVE::Network::tap_unplug($iface); exit 0; -- 2.41.0 From s.lendl at proxmox.com Fri Oct 27 13:20:22 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:20:22 +0200 Subject: [pve-devel] [RFC pve-network 5/5] DHCP mappings on vNIC add/remove In-Reply-To: <20231027112022.1960451-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027112022.1960451-1-s.lendl@proxmox.com> Message-ID: <20231027112022.1960451-7-s.lendl@proxmox.com> add DHCP mapping on vNIC add/update and VM clone (new mac) remove DHCP mapping on vNIC delete and VM destroy Signed-off-by: Stefan Lendl --- PVE/API2/Qemu.pm | 25 +++++++++++++++++++++++++ PVE/QemuServer.pm | 2 ++ 2 files changed, 27 insertions(+) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 38bdaab..1b16fa5 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -49,6 +49,8 @@ use PVE::SSHInfo; use PVE::Replication; use PVE::StorageTunnel; +use PVE::Network::SDN; + BEGIN { if (!$ENV{PVE_GENERATING_DOCS}) { require PVE::HA::Env::PVE2; @@ -1804,6 +1806,9 @@ my $update_vm_api = sub { } PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force); PVE::QemuConfig->write_config($vmid, $conf); + + my $net = PVE::QemuServer::parse_net($conf->{$opt}); + PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{macaddr}); } else { PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force); PVE::QemuConfig->write_config($vmid, $conf); @@ -1881,6 +1886,18 @@ my $update_vm_api = sub { ); } $conf->{pending}->{$opt} = $param->{$opt}; + + my $new_net = PVE::QemuServer::parse_net($param->{$opt}); + if (exists $conf->{$opt}) { + my $old_net = PVE::QemuServer::parse_net($conf->{$opt}); + if ($old_net->{bridge} ne $new_net->{bridge} or + $old_net->{macaddr} ne $new_net->{macaddr}) { + print "Bridge or MAC changed: $conf->{$opt} -> $param->{$opt}\n"; + PVE::Network::SDN::Dhcp::remove_mapping($old_net->{bridge}, $old_net->{macaddr}); + } + } + PVE::Network::SDN::Dhcp::add_mapping($vmid, $new_net->{bridge}, $new_net->{macaddr}); + } else { $conf->{pending}->{$opt} = $param->{$opt}; @@ -3763,6 +3780,14 @@ __PACKAGE__->register_method({ PVE::QemuConfig->write_config($newid, $newconf); + foreach my $opt (keys %$newconf) { + if ($opt =~ m/^net(\d+)$/) { + my $value = $newconf->{$opt}; + my $net = PVE::QemuServer::parse_net($value); + PVE::Network::SDN::Dhcp::add_mapping($newid, $net->{bridge}, $net->{macaddr}); + } + } + if ($target) { # always deactivate volumes - avoid lvm LVs to be active on several nodes PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running; diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 710259b..8a63ec3 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -2337,6 +2337,8 @@ sub destroy_vm { }); } + cleanup_sdn_dhcp($vmid, $conf); + if (defined $replacement_conf) { PVE::QemuConfig->write_config($vmid, $replacement_conf); } else { -- 2.41.0 From ykonotopov at gnome.org Fri Oct 27 13:23:58 2023 From: ykonotopov at gnome.org (Yuri Konotopov) Date: Fri, 27 Oct 2023 15:23:58 +0400 Subject: [PATCH v5 storage 1/1] fix #254: iscsi: add support for multipath iscsi targets In-Reply-To: References: <20231023174508.385682-1-ykonotopov@gnome.org> <20231023174508.385682-2-ykonotopov@gnome.org> Message-ID: <2518e894-e0ff-420d-9d9d-babdfa7b07a6@gnome.org> 25.10.2023 17:45, Dominik Csapak ?????: > One nit inline, but that could be fixed up when applying too probably? Hi, Dominik! Thanks for review! Should I send v6 with the fix for hash? -- Best regards, Yuri Konotopov From s.lendl at proxmox.com Fri Oct 27 13:29:54 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:29:54 +0200 Subject: [pve-devel] [RFC SDN DHCP] Add and Remove DHCP mappings on vNIC add/remove In-Reply-To: <87v8axbjh1.fsf@gmail.com> References: <87v8axbjh1.fsf@gmail.com> Message-ID: <20231027113000.2008166-1-s.lendl@proxmox.com> Sorry, Sending this again because I noticed that I messed up the subject prefixes for the patches. This patch series defines IPs, once allocated by the IPAM as persistant until the vNIC is removed from the VM or the VM is destroyed. The dnsmasq ethers file is a pure product of the IPAM database and does not have it's own state, therefore removing the potential issue of wrong sync state between IPAM and dnsmasq. Updating the ethers file is only done on the effected node. Technically, starting the VM still adds the DHCP mapping. This is needed at the moment because during migration, this triggers generating the ethers file on the target node. If the IPAM already contains the mapping, it returns the existing IP (upsert) Alternative approach would be to trigger an update of the ethers file on all nodes on IPAM update. Stopping a guest does not effect IPAM/DHCP. Destroying a guest removes the associated IPs from the IPAM. -- From s.lendl at proxmox.com Fri Oct 27 13:29:56 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:29:56 +0200 Subject: [pve-devel] [RFC pve-network 2/6] always generate dnsmasq ethers file In-Reply-To: <20231027113000.2008166-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027113000.2008166-1-s.lendl@proxmox.com> Message-ID: <20231027113000.2008166-3-s.lendl@proxmox.com> Makes dnsmasq stateless and can be generated from the IPAM. On dhcp_add_ip always generate the entire ethers file. Signed-off-by: Stefan Lendl --- src/PVE/Network/SDN/Dhcp.pm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/PVE/Network/SDN/Dhcp.pm b/src/PVE/Network/SDN/Dhcp.pm index b92c73a..b854cce 100644 --- a/src/PVE/Network/SDN/Dhcp.pm +++ b/src/PVE/Network/SDN/Dhcp.pm @@ -87,8 +87,14 @@ sub add_mapping { next if !$ip; + # generates ethers file every time + my $ipam_db = $ipam_plugin->read_db(); + my $dbzone = $ipam_db->{zones}->{$subnet_config->{zone}}; + my $dbsubnet = $dbzone->{subnets}->{$subnet_config->{cidr}}; + my $dbsubnet_ips = $dbsubnet->{ips}; + my $dhcp_plugin = PVE::Network::SDN::Dhcp::Plugin->lookup($dhcp_config->{type}); - $dhcp_plugin->add_ip_mapping($dhcp_config, $mac, $ip); + $dhcp_plugin->generate_config($dhcp_config, $dbsubnet_ips); return $ip; } -- 2.41.0 From s.lendl at proxmox.com Fri Oct 27 13:29:57 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:29:57 +0200 Subject: [pve-devel] [RFC pve-network 3/6] touch the ethers file when creating the dnsmasq config In-Reply-To: <20231027113000.2008166-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027113000.2008166-1-s.lendl@proxmox.com> Message-ID: <20231027113000.2008166-4-s.lendl@proxmox.com> removes an error from journal when newly DHCP server is configured Signed-off-by: Stefan Lendl --- src/PVE/Network/SDN/Dhcp/Dnsmasq.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm index 6f8b1c4..39e4cce 100644 --- a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm +++ b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm @@ -182,6 +182,11 @@ CFG $default_dnsmasq_config ); + # touch the ethers file to avoid errors before creation + my $ethers_file = "$DNSMASQ_CONFIG_ROOT/$dhcp_config->{id}/ethers"; + open(my $e, '>>', $ethers_file) or die "Could not open file '$ethers_file' $!\n"; + close $e; + unlink glob "$config_directory/10-*.conf"; } -- 2.41.0 From s.lendl at proxmox.com Fri Oct 27 13:29:58 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:29:58 +0200 Subject: [pve-devel] [RFC pve-container 4/6] do not remove DHCP mapping on stop In-Reply-To: <20231027113000.2008166-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027113000.2008166-1-s.lendl@proxmox.com> Message-ID: <20231027113000.2008166-5-s.lendl@proxmox.com> Signed-off-by: Stefan Lendl --- src/PVE/LXC.pm | 8 -------- src/lxc-pve-poststop-hook | 1 - 2 files changed, 9 deletions(-) diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm index bd8eb63..a7de9b8 100644 --- a/src/PVE/LXC.pm +++ b/src/PVE/LXC.pm @@ -916,14 +916,6 @@ sub vm_stop_cleanup { eval { my $vollist = PVE::LXC::Config->get_vm_volumes($conf); PVE::Storage::deactivate_volumes($storage_cfg, $vollist); - - for my $k (keys %$conf) { - next if $k !~ /^net(\d+)/; - my $net = PVE::LXC::Config->parse_lxc_network($conf->{$k}); - next if $net->{type} ne 'veth'; - - PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{hwaddr}); - } }; warn $@ if $@; # avoid errors - just warn } diff --git a/src/lxc-pve-poststop-hook b/src/lxc-pve-poststop-hook index e7d46c7..2fe97ec 100755 --- a/src/lxc-pve-poststop-hook +++ b/src/lxc-pve-poststop-hook @@ -12,7 +12,6 @@ use PVE::LXC::Config; use PVE::LXC::Tools; use PVE::LXC; use PVE::Network; -use PVE::Network::SDN::Dhcp; use PVE::RESTEnvironment; use PVE::Storage; use PVE::Tools; -- 2.41.0 From s.lendl at proxmox.com Fri Oct 27 13:30:00 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:30:00 +0200 Subject: [pve-devel] [RFC qemu-server 6/6] DHCP mappings on vNIC add/remove In-Reply-To: <20231027113000.2008166-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027113000.2008166-1-s.lendl@proxmox.com> Message-ID: <20231027113000.2008166-7-s.lendl@proxmox.com> add DHCP mapping on vNIC add/update and VM clone (new mac) remove DHCP mapping on vNIC delete and VM destroy Signed-off-by: Stefan Lendl --- PVE/API2/Qemu.pm | 25 +++++++++++++++++++++++++ PVE/QemuServer.pm | 2 ++ 2 files changed, 27 insertions(+) diff --git a/PVE/API2/Qemu.pm b/PVE/API2/Qemu.pm index 38bdaab..1b16fa5 100644 --- a/PVE/API2/Qemu.pm +++ b/PVE/API2/Qemu.pm @@ -49,6 +49,8 @@ use PVE::SSHInfo; use PVE::Replication; use PVE::StorageTunnel; +use PVE::Network::SDN; + BEGIN { if (!$ENV{PVE_GENERATING_DOCS}) { require PVE::HA::Env::PVE2; @@ -1804,6 +1806,9 @@ my $update_vm_api = sub { } PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force); PVE::QemuConfig->write_config($vmid, $conf); + + my $net = PVE::QemuServer::parse_net($conf->{$opt}); + PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{macaddr}); } else { PVE::QemuConfig->add_to_pending_delete($conf, $opt, $force); PVE::QemuConfig->write_config($vmid, $conf); @@ -1881,6 +1886,18 @@ my $update_vm_api = sub { ); } $conf->{pending}->{$opt} = $param->{$opt}; + + my $new_net = PVE::QemuServer::parse_net($param->{$opt}); + if (exists $conf->{$opt}) { + my $old_net = PVE::QemuServer::parse_net($conf->{$opt}); + if ($old_net->{bridge} ne $new_net->{bridge} or + $old_net->{macaddr} ne $new_net->{macaddr}) { + print "Bridge or MAC changed: $conf->{$opt} -> $param->{$opt}\n"; + PVE::Network::SDN::Dhcp::remove_mapping($old_net->{bridge}, $old_net->{macaddr}); + } + } + PVE::Network::SDN::Dhcp::add_mapping($vmid, $new_net->{bridge}, $new_net->{macaddr}); + } else { $conf->{pending}->{$opt} = $param->{$opt}; @@ -3763,6 +3780,14 @@ __PACKAGE__->register_method({ PVE::QemuConfig->write_config($newid, $newconf); + foreach my $opt (keys %$newconf) { + if ($opt =~ m/^net(\d+)$/) { + my $value = $newconf->{$opt}; + my $net = PVE::QemuServer::parse_net($value); + PVE::Network::SDN::Dhcp::add_mapping($newid, $net->{bridge}, $net->{macaddr}); + } + } + if ($target) { # always deactivate volumes - avoid lvm LVs to be active on several nodes PVE::Storage::deactivate_volumes($storecfg, $vollist, $snapname) if !$running; diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 710259b..8a63ec3 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -2337,6 +2337,8 @@ sub destroy_vm { }); } + cleanup_sdn_dhcp($vmid, $conf); + if (defined $replacement_conf) { PVE::QemuConfig->write_config($vmid, $replacement_conf); } else { -- 2.41.0 From s.lendl at proxmox.com Fri Oct 27 13:29:55 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:29:55 +0200 Subject: [pve-devel] [RFC pve-network 1/6] dhcp add ip returns IP if already present for MAC In-Reply-To: <20231027113000.2008166-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027113000.2008166-1-s.lendl@proxmox.com> Message-ID: <20231027113000.2008166-2-s.lendl@proxmox.com> Signed-off-by: Stefan Lendl --- src/PVE/Network/SDN/Dhcp/Dnsmasq.pm | 23 +++++++++++++++++++++++ src/PVE/Network/SDN/Ipams/PVEPlugin.pm | 16 ++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm index af109b8..6f8b1c4 100644 --- a/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm +++ b/src/PVE/Network/SDN/Dhcp/Dnsmasq.pm @@ -18,6 +18,29 @@ sub type { return 'dnsmasq'; } +sub generate_config { + my ($class, $dhcp_config, $ip_mappings) = @_; + + my $ethers_file = "$DNSMASQ_CONFIG_ROOT/$dhcp_config->{id}/ethers"; + my $ethers_tmp_file = "$ethers_file.tmp"; + + open(my $out, '>', $ethers_file) or die "Could not open file '$ethers_file' $!\n"; + + foreach my $ip (keys %$ip_mappings) { + if (exists $ip_mappings->{$ip}{mac}) { + my $mac = $ip_mappings->{$ip}{mac}; + print $out "$mac,$ip\n"; + } + } + + close $out; + + chmod 0644, $ethers_file; + + my $service_name = "dnsmasq\@$dhcp_config->{id}"; + PVE::Tools::run_command(['systemctl', 'reload', $service_name]); +} + sub del_ip_mapping { my ($class, $dhcp_config, $mac) = @_; diff --git a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm index fcc8282..40b4a8f 100644 --- a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm +++ b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm @@ -161,6 +161,8 @@ sub add_dhcp_ip { my $cidr = $subnet->{cidr}; my $zone = $subnet->{zone}; + my $vmid = $data->{vmid}; + my $mac = $data->{mac}; cfs_lock_file($ipamdb_file, undef, sub { my $db = read_db(); @@ -174,11 +176,25 @@ sub add_dhcp_ip { my $ip = new Net::IP ("$dhcp_range->{'start-address'} - $dhcp_range->{'end-address'}") or die "Invalid IP address(es) in DHCP Range!\n"; + do { + my $ip_address = $ip->ip(); + if (exists $dbsubnet->{ips}->{$ip_address} && + exists $dbsubnet->{ips}->{$ip_address}->{mac} && + $dbsubnet->{ips}->{$ip_address}->{mac} eq $mac) { + print "IP '$ip_address' already exist for $mac in $vmid\n"; + + return $ip_address; + } + } while (++$ip); + + $ip = new Net::IP ("$dhcp_range->{'start-address'} - $dhcp_range->{'end-address'}"); + do { my $ip_address = $ip->ip(); if (!$dbsubnet->{ips}->{$ip_address}) { $dbsubnet->{ips}->{$ip_address} = $data; write_db($db); + print "New IP '$ip_address' added for $mac at $vmid\n"; return $ip_address; } -- 2.41.0 From s.lendl at proxmox.com Fri Oct 27 13:29:59 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:29:59 +0200 Subject: [pve-devel] [RFC qemu-server 5/6] do not remove DHCP mapping on VM stop In-Reply-To: <20231027113000.2008166-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027113000.2008166-1-s.lendl@proxmox.com> Message-ID: <20231027113000.2008166-6-s.lendl@proxmox.com> Signed-off-by: Stefan Lendl --- PVE/QemuServer.pm | 2 -- vm-network-scripts/pve-bridgedown | 4 ---- 2 files changed, 6 deletions(-) diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm index 6c1e463..710259b 100644 --- a/PVE/QemuServer.pm +++ b/PVE/QemuServer.pm @@ -6143,8 +6143,6 @@ sub vm_stop_cleanup { cleanup_pci_devices($vmid, $conf); - cleanup_sdn_dhcp($vmid, $conf); - vmconfig_apply_pending($vmid, $conf, $storecfg) if $apply_pending_changes; }; warn $@ if $@; # avoid errors - just warn diff --git a/vm-network-scripts/pve-bridgedown b/vm-network-scripts/pve-bridgedown index a220660..8d58947 100755 --- a/vm-network-scripts/pve-bridgedown +++ b/vm-network-scripts/pve-bridgedown @@ -26,10 +26,6 @@ my $conf = PVE::QemuConfig->load_config($vmid); my $netconf = $conf->{$netid}; my $net = PVE::QemuServer::parse_net($netconf); -if ($have_sdn) { - PVE::Network::SDN::Dhcp::remove_mapping($net->{bridge}, $net->{macaddr}); -} - PVE::Network::tap_unplug($iface); exit 0; -- 2.41.0 From c.heiss at proxmox.com Fri Oct 27 13:41:20 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 27 Oct 2023 13:41:20 +0200 Subject: [pve-devel] [PATCH 00/12] installer: add crate for common code In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: Had a lot of in-person discussions with Aaron over the last few weeks over this. There are no real functional changes here that would impact users, merely a code move. Each inidivdual patch (except #1, see my answer on that) builds cleanly on top of current master. The one dependency (see below) is also already applied. I also did some quick smoke testing to verify everything really still works. I will go over the the rest of the patches afterwards, but from a glance there is nothing that I would consider a blocker. Things like e.g. the `InstallConfig` stuff can be done separately as followups. Also considering this is a great code churn, I would rather have this get applied sooner than later, such that all future work is done on top of this. The only "complaint" here is that the .deb package does not build with this series (see my reply on #1). I will shortly send a quick follow-up patch for that, so you don't have to necessarily re-spin the whole series just for a single, trivial line. LGTM; thus please consider the whole series: Reviewed-by: Christoph Heiss Tested-by: Christoph Heiss On Wed, Oct 25, 2023 at 05:59:59PM +0200, Aaron Lauterer wrote: > > since work on the auto installer is happenning in parallel, now would be > a good point to move commonly used code into its own crate. Otherwise > the auto-installer will always have to play catch up with the ongoing > development of the tui installer. > > I tried to split up the commits as much as possible, but there are two > larger ones, copying most the code over to the new repo and making the > switch. The former because it is difficult to pull apart the parts that > belong together. The latter needed to be this big as most local > occurences needed to be removed at the same time to avoid dependency > conflicts. > > One last things that is missing, is the "InstallConfig" struct. > This should also be part of the common crate, but I need to look further > into how to make it possible that it can be created from structs of each > crate (tui, auto) as implementing a ::From can only be done within the > crate where the struct lives IIUC. > > This series depends on the patches by Christoph to remove the global > unsafe setup info, version 2 [0]. Without those patches applied first, > this series will not apply. > > [0] https://lists.proxmox.com/pipermail/pve-devel/2023-October/059628.html > > Aaron Lauterer (12): > add proxmox-installer-common crate > common: copy common code from tui-installer > common: utils: add dependency for doc test > common: make InstallZfsOption public > common: disk_checks: make functions public > tui-installer: add dependency for new common crate > tui: switch to common crate > tui: remove now unused utils.rs > common: add installer_setup method > common: document installer_setup method > tui: use installer_setup from common cate > tui: remove unused read_json function > > Cargo.toml | 1 + > proxmox-installer-common/Cargo.toml | 12 + > proxmox-installer-common/src/disk_checks.rs | 237 ++++++++++ > proxmox-installer-common/src/lib.rs | 4 + > proxmox-installer-common/src/options.rs | 387 +++++++++++++++++ > proxmox-installer-common/src/setup.rs | 368 ++++++++++++++++ > .../src/utils.rs | 1 + > proxmox-tui-installer/Cargo.toml | 1 + > proxmox-tui-installer/src/main.rs | 51 +-- > proxmox-tui-installer/src/options.rs | 403 +----------------- > proxmox-tui-installer/src/setup.rs | 324 +------------- > proxmox-tui-installer/src/system.rs | 2 +- > proxmox-tui-installer/src/views/bootdisk.rs | 248 +---------- > proxmox-tui-installer/src/views/mod.rs | 2 +- > proxmox-tui-installer/src/views/timezone.rs | 4 +- > 15 files changed, 1054 insertions(+), 991 deletions(-) > create mode 100644 proxmox-installer-common/Cargo.toml > create mode 100644 proxmox-installer-common/src/disk_checks.rs > create mode 100644 proxmox-installer-common/src/lib.rs > create mode 100644 proxmox-installer-common/src/options.rs > create mode 100644 proxmox-installer-common/src/setup.rs > rename {proxmox-tui-installer => proxmox-installer-common}/src/utils.rs (99%) > > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > From c.heiss at proxmox.com Fri Oct 27 13:39:04 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Fri, 27 Oct 2023 13:39:04 +0200 Subject: [pve-devel] [PATCH installer] buildsys: copy over `proxmox-installer-common` crate to build directory In-Reply-To: <20231025160011.3617524-1-a.lauterer@proxmox.com> References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: <20231027113907.765405-1-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 111fe4b..e792e0e 100644 --- a/Makefile +++ b/Makefile @@ -43,6 +43,7 @@ $(BUILDDIR): proxinstall \ proxmox-low-level-installer \ proxmox-tui-installer/ \ + proxmox-installer-common/ \ spice-vdagent.sh \ unconfigured.sh \ xinitrc \ -- 2.42.0 From t.lamprecht at proxmox.com Fri Oct 27 13:47:04 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 27 Oct 2023 13:47:04 +0200 Subject: [pve-devel] applied: [PATCH v5 storage 1/1] fix #254: iscsi: add support for multipath iscsi targets In-Reply-To: <20231023174508.385682-2-ykonotopov@gnome.org> References: <20231023174508.385682-1-ykonotopov@gnome.org> <20231023174508.385682-2-ykonotopov@gnome.org> Message-ID: Am 23/10/2023 um 19:45 schrieb Yuri Konotopov: > With this patch Proxmox now tries to login to all discovered portals in > case some of them are not logged yet. > In case of multipath configuration when initially configured portal is > missing for some reason Proxmox don't lose iscsi storage now and can > succesfully restore iscsi connection between reboots. > > Signed-off-by: Yuri Konotopov > --- > src/PVE/Storage.pm | 2 +- > src/PVE/Storage/ISCSIPlugin.pm | 117 +++++++++++++++++++++++++-------- > 2 files changed, 89 insertions(+), 30 deletions(-) > > applied, with Dominik's R-b and T-b tags, thanks! I also made two small follow-ups, one unrelated to your change, and one for small code cleanups like Dominik suggested. From s.lendl at proxmox.com Fri Oct 27 13:51:50 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:51:50 +0200 Subject: [pve-devel] [WIP v2 pve-network 06/10] ipam: Add helper methods for DHCP to PVE IPAM In-Reply-To: <20231017135507.2220948-7-s.hanreich@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> <20231017135507.2220948-7-s.hanreich@proxmox.com> Message-ID: <87jzr8mgfd.fsf@gmail.com> Stefan Hanreich writes: > Those methods are used by the DHCP plugins to attain the next free > IP address for a given DHCP range, as well as delete all entries with > a certain MAC address. > > Signed-off-by: Stefan Hanreich > --- > src/PVE/Network/SDN/Ipams/PVEPlugin.pm | 64 ++++++++++++++++++++++++++ > 1 file changed, 64 insertions(+) > > diff --git a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm > index 3e8ffc5..fcc8282 100644 > --- a/src/PVE/Network/SDN/Ipams/PVEPlugin.pm > +++ b/src/PVE/Network/SDN/Ipams/PVEPlugin.pm > @@ -156,6 +156,70 @@ sub add_next_freeip { > return "$freeip/$mask"; > } > > +sub add_dhcp_ip { > + my ($class, $subnet, $dhcp_range, $data) = @_; > + > + my $cidr = $subnet->{cidr}; > + my $zone = $subnet->{zone}; > + > + cfs_lock_file($ipamdb_file, undef, sub { > + my $db = read_db(); > + > + my $dbzone = $db->{zones}->{$zone}; > + die "zone '$zone' doesn't exist in IPAM DB\n" if !$dbzone; > + > + my $dbsubnet = $dbzone->{subnets}->{$cidr}; > + die "subnet '$cidr' doesn't exist in IPAM DB\n" if !$dbsubnet; > + > + my $ip = new Net::IP ("$dhcp_range->{'start-address'} - $dhcp_range->{'end-address'}") > + or die "Invalid IP address(es) in DHCP Range!\n"; > + > + do { > + my $ip_address = $ip->ip(); > + if (!$dbsubnet->{ips}->{$ip_address}) { > + $dbsubnet->{ips}->{$ip_address} = $data; > + write_db($db); > + > + return $ip_address; > + } > + } while (++$ip); > + > + die "No free IP left in DHCP Range $dhcp_range->{'start-address'}:$dhcp_range->{'end-address'}}\n"; > + }); > +} This duplicates existing functionality to find a new IP. Regarding the reply from Alexandre, we may not need to implement dhcp_range feature and solely rely on IPAM generating a new IP. > + > +sub del_dhcp_ip { > + my ($class, $subnet, $mac) = @_; > + > + my $cidr = $subnet->{cidr}; > + my $zone = $subnet->{zone}; > + > + my $returned_ip = undef; > + > + cfs_lock_file($ipamdb_file, undef, sub { > + my $db = read_db(); > + > + die "zone $zone don't exist in ipam db" if !$db->{zones}->{$zone}; > + my $dbzone = $db->{zones}->{$zone}; > + > + die "subnet $cidr don't exist in ipam db" if !$dbzone->{subnets}->{$cidr}; > + my $dbsubnet = $dbzone->{subnets}->{$cidr}; > + > + foreach my $ip_address (keys %{$dbsubnet->{ips}}) { > + my $data = $dbsubnet->{ips}->{$ip_address}; > + next if !$data->{mac} || $data->{mac} ne $mac; > + > + delete $dbsubnet->{ips}->{$ip_address}; > + write_db($db); > + > + $returned_ip = $ip_address; > + } > + }); > + die "$@" if $@; > + > + return $returned_ip; > +} > + > sub del_ip { > my ($class, $plugin_config, $subnetid, $subnet, $ip) = @_; From t.lamprecht at proxmox.com Fri Oct 27 13:52:09 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 27 Oct 2023 13:52:09 +0200 Subject: [pve-devel] [RFC SDN DHCP] Add and Remove DHCP mappings on vNIC add/remove In-Reply-To: <20231027113000.2008166-1-s.lendl@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027113000.2008166-1-s.lendl@proxmox.com> Message-ID: <8f1c2587-e260-4a3b-97f5-dcdbcb571dcc@proxmox.com> Am 27/10/2023 um 13:29 schrieb Stefan Lendl: > Sorry, Sending this again because I noticed that I messed up the subject > prefixes for the patches. I'd also prefer having such series as separate thread, not in reply to another (already big) series.. Also, if I didn't overlook something, you nowhere state if this patch series is on top of Stefan Hanreich's, or stand alone.. From aderumier at odiso.com Fri Oct 27 13:53:28 2023 From: aderumier at odiso.com (Alexandre Derumier) Date: Fri, 27 Oct 2023 13:53:28 +0200 Subject: [pve-devel] [PATCH pve-network] Fix #4917: evpn: forbid vlan-aware bridge Message-ID: <20231027115328.654782-1-aderumier@odiso.com> Do it on vnet update instead throwing a warning at config generation. Signed-off-by: Alexandre Derumier --- src/PVE/Network/SDN/Zones/EvpnPlugin.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm index 5e9f8ec..655a9f0 100644 --- a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm +++ b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm @@ -117,7 +117,6 @@ sub generate_sdn_config { die "missing vxlan tag" if !$tag; die "missing controller" if !$controller; - warn "vlan-aware vnet can't be enabled with evpn plugin" if $vnet->{vlanaware}; my @peers = PVE::Tools::split_list($controller->{'peers'}); @@ -309,6 +308,7 @@ sub vnet_update_hook { raise_param_exc({ tag => "missing vxlan tag"}) if !defined($tag); raise_param_exc({ tag => "vxlan tag max value is 16777216"}) if $tag > 16777216; + raise_param_exc({ 'vlan-aware' => "vlan-aware option can't be enabled with evpn"}) if $vnet->{vlanaware}; # verify that tag is not already defined globally (vxlan-id are unique) foreach my $id (keys %{$vnet_cfg->{ids}}) { -- 2.39.2 From s.lendl at proxmox.com Fri Oct 27 13:54:43 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 13:54:43 +0200 Subject: [pve-devel] [RFC SDN DHCP] Add and Remove DHCP mappings on vNIC add/remove In-Reply-To: <8f1c2587-e260-4a3b-97f5-dcdbcb571dcc@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027113000.2008166-1-s.lendl@proxmox.com> <8f1c2587-e260-4a3b-97f5-dcdbcb571dcc@proxmox.com> Message-ID: <87h6mcmgak.fsf@gmail.com> It is on top of Stefan Hanreich's patch series therefore I replied here as I consider it a continuation of his efforts. Thomas Lamprecht writes: > Am 27/10/2023 um 13:29 schrieb Stefan Lendl: >> Sorry, Sending this again because I noticed that I messed up the subject >> prefixes for the patches. > > I'd also prefer having such series as separate thread, not in reply to another > (already big) series.. > > Also, if I didn't overlook something, you nowhere state if this patch series > is on top of Stefan Hanreich's, or stand alone.. From t.lamprecht at proxmox.com Fri Oct 27 13:57:00 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 27 Oct 2023 13:57:00 +0200 Subject: [pve-devel] [RFC SDN DHCP] Add and Remove DHCP mappings on vNIC add/remove In-Reply-To: <8f1c2587-e260-4a3b-97f5-dcdbcb571dcc@proxmox.com> References: <87v8axbjh1.fsf@gmail.com> <20231027113000.2008166-1-s.lendl@proxmox.com> <8f1c2587-e260-4a3b-97f5-dcdbcb571dcc@proxmox.com> Message-ID: <3c721802-f7f4-43d1-8ba1-80259808c085@proxmox.com> Am 27/10/2023 um 13:52 schrieb Thomas Lamprecht: > Am 27/10/2023 um 13:29 schrieb Stefan Lendl: >> Sorry, Sending this again because I noticed that I messed up the subject >> prefixes for the patches. > > I'd also prefer having such series as separate thread, not in reply to another > (already big) series.. > > Also, if I didn't overlook something, you nowhere state if this patch series > is on top of Stefan Hanreich's, or stand alone.. > And if you use the `--cover-letter` option from git send-email, or git format-patch to base off the cover letter, you would automatically get a nice summary over commits and total diffstat here. From t.lamprecht at proxmox.com Fri Oct 27 13:59:06 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 27 Oct 2023 13:59:06 +0200 Subject: [pve-devel] [RFC SDN DHCP] Add and Remove DHCP mappings on vNIC add/remove In-Reply-To: <87h6mcmgak.fsf@gmail.com> References: <87v8axbjh1.fsf@gmail.com> <20231027113000.2008166-1-s.lendl@proxmox.com> <8f1c2587-e260-4a3b-97f5-dcdbcb571dcc@proxmox.com> <87h6mcmgak.fsf@gmail.com> Message-ID: Am 27/10/2023 um 13:54 schrieb Stefan Lendl: > It is on top of Stefan Hanreich's patch series therefore I replied here > as I consider it a continuation of his efforts. Please state that the next time. Also, please avoid top posting on the mailing list and trim your replies! https://git-send-email.io/top-posting.html From s.lendl at proxmox.com Fri Oct 27 14:26:02 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 14:26:02 +0200 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: <330b6d23-6a0f-4041-9892-26944fb7e30d@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> <87v8axbjh1.fsf@gmail.com> <330b6d23-6a0f-4041-9892-26944fb7e30d@proxmox.com> Message-ID: <87edhgmeud.fsf@gmail.com> Thomas Lamprecht writes: > Am 23/10/2023 um 14:40 schrieb Stefan Lendl: >> I am currently working on the SDN feature. This is an initial review of >> the patch series and I am trying to make a strong case against ephemeral >> DHCP IP reservation. > > Stefan Hanreich's reply to the cover letter already mentions upserts, those > will avoid basically all problems while allowing for some dynamic changes. > I totally agree with upserts and my patches add this functionality. >> The current state of the patch series invokes the IPAM on every VM/CT >> start/stop to add or remove the IP from the IPAM. >> This triggers the dnsmasq config generation on the specific host with >> only the MAC/IP mapping of that particular host. >> >> From reading the discussion of the v1 patch series I understand this >> approach tries to implement the ephemeral IP reservation strategy. From >> off-list conversations with Stefan Hanreich, I agree that having >> ephemeral IP reservation coordinated by the IPAM requires us to >> re-implement DHCP functionality in the IPAM and heavily rely on syncing >> between the different services. >> >> To maintain reliable sync we need to hook into many different places >> where the IPAM need to be queried. Any issues with the implementation >> may lead to IPAM and DHCP local config state running out of sync causing >> network issues duplicate multiple IPs. > > The same is true for permanent reservations, wherever that reservation is > saved needs to be in sync with IPAM, e.g., also on backup restore (into a > new env), if subnets change their configured CIDRs, ... > Yes, agreed but it's arguably less states and situation that need to be synced. The current implementation had a different state per node and depended on the online/offline state of the guest. It is currently not allowed to change the CIDR of a subnet. >> >> Furthermore, every interaction with the IPAM requires a cluster-wide >> lock on the IPAM. Having a central cluster-wide lock on every VM >> start/stop/migrate will significantly limit parallel operations. Event >> starting two VMs in parallel will be limited by this central lock. At >> boot trying to start many VMs (ideally as much in parallel as possible) >> is limited by the central IPAM lock even further. > > Cluster wide locks are relatively cheap, especially if one avoids having > a long critical section, i.e., query IPAM while still unlocked, then > read and update the state locked, if the newly received IP is already > in there then simply give up lock again and repeat. > > We also have a clusters wide lock for starting HA guests, to set the > wanted ha-resource state, that is no issue at all, you can start/stop > many orders of magnitudes more VMs than any HW/Storage could cope with. > >> >> I argue that we shall not support ephemeral IPs altogether. >> The alternative is to make all IPAM reservations persistent. > > >> >> Using persistent IPs only reduces the interactions of VM/CTs with the >> IPAM to a minimum of NIC joining a subnet and NIC leaving a subnet. I am >> deliberately not referring to VMs because a VM may be part of multiple >> VNets or even multiple times in the same VNet (regardless if that is >> sensible). > > Yeah, talking about vNICs / veth's is the better term here, guests are > only indirectly relevant. > >> >> Cases the IPAM needs to be involved: >> >> - NIC with DHCP enabled VNet is added to VM config >> - NIC with DHCP enabled VNet is removed from VM config >> - NIC is assigned to another Bridge >> can be treated as individual leave + join events > > and: > > - subnet config is changed > - vNIC changes from SDN-DHCP managed to manual, or vice versa > Albeit that can almost be treated like vNet leave/join though > > >> Cases that are explicitly not covered but may be added if desired: >> >> - Manually assign an IP address on a NIC >> will not be automatically visible in the IPAM > > This sounds like you want to save the state in the VM config, which I'm > rather skeptical about, and would try hard to avoid. We also would need > to differ between bridges that are part of DHCP-managed SDN and others, > as else a user could set some IP but nothing would happen. > I am sorry, my explanation was not clear here. I do not want to store IP inside the VM config. I agree that this would not be ideal. If a user configures an IP from inside the VM, we have no way of tracking that IP. For now, every added vNIC gets an IP from the IPAM, and if the guest is configured to use DHCP, it will get this IP from the DHCP server. If the user decides to manually configure the IP, he will have to reserve it in the IPAM, and mark the IP as "manual". This will prevent the IPAM from allocating the IP again and keep the IP/MAC mapping even if the VM is destroyed. This is not implemented yet, but sketched out with Mira off-list. >> - Manually change the MAC on a NIC >> don't do that > you are on your own. > > FWIW, a clone is such a change, and we have to support that, otherwise > the MAC field needs to get some warning hints or even become read-only > in the UI. > >> Not handled > change in IPAM manually >> >> Once an IP is reserved via IPAM, the dnsmasq config can be generated >> stateless and idempotent from the pve IPAM and is identical on all nodes >> regardless if a VM/CT actually resides on that node or is running or >> stopped. This is especially useful for VM migration because the IP >> stays consistent without spacial considering. > > That should be orthogonal to the feature set, if we have all the info > saved somewhere else > > But this also speaks against having it in the VM config, as that would > mean that every node needs to parse every guests' config periodically, > which is way worse than some cluster lock and breaks with our base > axiom that guests are owned by their current node, and only by that, > and a node should not really alter behavior dependent on some "foreign" > guest. > >> >> Snapshot/revert, backup/restore, suspend/hibernate/resume cases are >> automatically covered because the IP will already be reserved for that >> MAC. > > Not really, restore to another setup is broken, one could resume the > VM after having changed CIDRs of a subnet, making that broken too, ... > >> >> If the admin wants to change, the IP of a VM this can be done via the >> IPAM API/UI which will have to be implemented separately. > > Providing Overrides can be fine, but IMO that all should be still in > the SDN state, not per-VM one, and ideally use a common API. > > >> A limitation of this approach vs dynamic IP reservation is that the IP >> range on the subnet needs to be large enough to hold all IPs of all, >> even stopped, VMs in that subnet. This is in contrast to default DHCP >> functionality where only the number of actively running VMs is limited. >> It should be enough to mention this in the docs. > > In production setups it should not matter _that_ much, but it might > be a bit of a PITA if one has a few "archived" VMs or the like, but > that alone would > >> >> I will further review the code an try to implement the aforementioned >> approach. > > You can naturally experiment, but I'd also try the upsert proposal from > Stefan H., as IMO that sounds like a good balance. From alexandre.derumier at groupe-cyllene.com Fri Oct 27 14:36:14 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Fri, 27 Oct 2023 12:36:14 +0000 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: <330b6d23-6a0f-4041-9892-26944fb7e30d@proxmox.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> <87v8axbjh1.fsf@gmail.com> <330b6d23-6a0f-4041-9892-26944fb7e30d@proxmox.com> Message-ID: > > Furthermore, every interaction with the IPAM requires a cluster-wide > lock on the IPAM. Having a central cluster-wide lock on every VM > start/stop/migrate will significantly limit parallel operations.? > Event > starting two VMs in parallel will be limited by this central lock. At > boot trying to start many VMs (ideally as much in parallel as > possible) > is limited by the central IPAM lock even further. >>Cluster wide locks are relatively cheap, especially if one avoids >>having >>a long critical section, i.e., query IPAM while still unlocked, then >>read and update the state locked, if the newly received IP is already >>in there then simply give up lock again and repeat. >>We also have a clusters wide lock for starting HA guests, to set the >>wanted ha-resource state, that is no issue at all, you can start/stop >>many orders of magnitudes more VMs than any HW/Storage could cope >>with. You also need to think about external ipam, where maybe it'll take some seconds to find an available ip and allocate it. (it's depend of size of the subnet, could have also dns update, ...) so, it'll really limit the parallelism of vm start. (Personnaly, If we have choice between reserved at vm/nic create && ephemeral at vm start, it's ok me). > Once an IP is reserved via IPAM, the dnsmasq config can be generated > stateless and idempotent from the pve IPAM and is identical on all nodes > regardless if a VM/CT actually resides on that node or is running or > stopped. This is especially useful for VM migration because the IP > stays consistent without spacial considering. >> >>That should be orthogonal to the feature set, if we have all the info >>saved somewhere else >>But this also speaks against having it in the VM config, as that >>would >>mean that every node needs to parse every guests' config >>periodically, >>which is way worse than some cluster lock and breaks with our base >>axiom that guests are owned by their current node, and only by that, >>and a node should not really alter behavior dependent on some >>"foreign" >>guest. I think that is really more simple to add ip in local dnsmasq at vm start dnsmasq --dhcp-hostsfile=/var/lib/reservation.txt echo "mac ip" >> /var/lib/reservation.txt SIGUP dnsmasq for persistant ip, we just need search previously allocated ip-mac in ipam, then write reservation to dnsmasq and start vm for epheral ip, we need to find && allocated a free ip in ipam, then write the ip/mac in dnsmasq and start vm and for external ipam, I had proposed to use local ipam as read cache. When allocating a new ip (persistent or ephemeral): search mac/ip exist in external ipam true: write it to local pve ipam cache false: allocate a new free ip in external ipam && write it to local pve ipam cache Like this, for persistant ips, we don't care if external ipam is down at vm start. and we can also reuse local ipam ips list for firewall or other stuff, without need to call the external ipam api. From s.lendl at proxmox.com Fri Oct 27 14:53:25 2023 From: s.lendl at proxmox.com (Stefan Lendl) Date: Fri, 27 Oct 2023 14:53:25 +0200 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: <87v8axbjh1.fsf@gmail.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> <87v8axbjh1.fsf@gmail.com> Message-ID: <877cn8mdkq.fsf@gmail.com> Hi Alexandre, I am proposing a slightly different view. I think it's better to keep all IPs, managed by the IPAM in the IPAM and the VM only configures as DHCP. I would implement the 4 mentioned events (vNIC create, destroy, start, stop) in the SDN module and limit interactions between VM configs and the SDN module to these events. On NIC create: the it calls the SDN::nic_join_vnet($bridge, $mac) function that handles IPAM registration if necessary triggers generating DHCP config and so on. Same approach for the other SDN related events. All the logic is implemented in the SDN module. This reduces coupling between VM logic and SDN logic. "DERUMIER, Alexandre" writes: > Hi Stefan (Lendl), > > I'm totally agreed with you, we should have persistent reservation, > at vm create/nic plug, nic delete, vm delete. > > At least , for my usage with multiple cluster on different datacenters, > I really can wait to call ipam to api at each start (for scalability or > for security if ipam is down) > > > This also allow to simply do reservations in dnsmasq file without any > need to restart it. (AFAIK, openstack is using dnsmasq like this too) > > > I'm not sure if true dynamic ephemral ip , changing at each vm > stop/start is interesting for a server vm usage. (maybe for desktop > vmwhere you share a small pool of ip, but I personnaly don't known any > proxmox users using proxmox ve for this) > > > see my proposal here (with handle ephemeral && reserved, but it's even > easier with only reserved): > > https://lists.proxmox.com/pipermail/pve-devel/2023-September/059169.html > > > > > " > I think we could implement ipam call like: > > > create vm or add a new nic --> > ----------------------------- > qm create ... -net0 > bridge=vnet,....,ip=(auto|192.168.0.1|dynamic),ip6=(..) > > > auto : search a free ip in ipam. write the ip address in net0: ...,ip= > ip field > > 192.168.0.1: check if ip is free in ipam && register ip in ipam. write > the ip in ip field. > > > dynamic: write "ephemeral" in net0: ....,ip=ephemeral (This is a > dynamic ip registered at vm start, and release at vm stop) > > > > vm start > --------- > - if ip=ephemeral, find && register a free ip in ipam, write it in vm > net0: ...,ip=192.168.0.10[E] . (maybe with a special flag [E] to > indicate it's ephemeral) > - read ip from vm config && inject in dhcp > > > vm_stop > ------- > if ip is ephemeral (netX: ip=192.168.0.10[E]), delete ip from ipam, > set ip=ephemeral in vm config > > > vm_destroy or nic remove/unplug > ------------------------- > if netX: ...,ip=192.168.0.10 , remove ip from ipam > > > > nic update when vm is running: > ------------------------------ > if ip is defined : netX: ip=192.168.0.10, we don't allow bridge change > or ip change, as vm is not notified about theses changes, and still use > old ip. > > We can allow nic hot-unplug && hotplug. (guest os will remove the ip on > nic removal, and will call dhcp again on nic hotplug) > > > > > nic hotplug with ip=auto: > ------------------------- > > --> add nic in pending state ----> find ip in ipam && write it in > pending ---> do the hotplug in qemu. > > We need to handle the config revert to remove ip from ipam if the nic > hotplug is blocked in pending state(I never see this case until os > don't have pci_hotplug module loaded, but it's better to be carefull ) > > " > > >>>I am currently working on the SDN feature.? This is an initial review >>>of >>>the patch series and I am trying to make a strong case against >>>ephemeral >>>DHCP IP reservation. >>> >>>The current state of the patch series invokes the IPAM on every VM/CT >>>start/stop to add or remove the IP from the IPAM. >>>This triggers the dnsmasq config generation on the specific host with >>>only the MAC/IP mapping of that particular host. > > > > > > From reading the discussion of the v1 patch series I understand this > approach tries to implement the ephemeral IP reservation strategy. From > off-list conversations with Stefan Hanreich, I agree that having > ephemeral IP reservation coordinated by the IPAM requires us to > re-implement DHCP functionality in the IPAM and heavily rely on syncing > between the different services. > > To maintain reliable sync we need to hook into many different places > where the IPAM need to be queried.? Any issues with the implementation > may lead to IPAM and DHCP local config state running out of sync > causing > network issues duplicate multiple IPs. > > Furthermore, every interaction with the IPAM requires a cluster-wide > lock on the IPAM. Having a central cluster-wide lock on every VM > start/stop/migrate will significantly limit parallel operations.? Event > starting two VMs in parallel will be limited by this central lock. At > boot trying to start many VMs (ideally as much in parallel as possible) > is limited by the central IPAM lock even further. > > I argue that we shall not support ephemeral IPs altogether. > The alternative is to make all IPAM reservations persistent. > > Using persistent IPs only reduces the interactions of VM/CTs with the > IPAM to a minimum of NIC joining a subnet and NIC leaving a subnet. I > am > deliberately not referring to VMs because a VM may be part of multiple > VNets or even multiple times in the same VNet (regardless if that is > sensible). > > Cases the IPAM needs to be involved: > > - NIC with DHCP enabled VNet is added to VM config > - NIC with DHCP enabled VNet is removed from VM config > - NIC is assigned to another Bridge > ? can be treated as individual leave + join events > > Cases that are explicitly not covered but may be added if desired: > > - Manually assign an IP address on a NIC > ? will not be automatically visible in the IPAM > - Manually change the MAC on a NIC > ? don't do that > you are on your own. > ? Not handled > change in IPAM manually > > Once an IP is reserved via IPAM, the dnsmasq config can be generated > stateless and idempotent from the pve IPAM and is identical on all > nodes > regardless if a VM/CT actually resides on that node or is running or > stopped.? This is especially useful for VM migration because the IP > stays consistent without spacial considering. > > Snapshot/revert, backup/restore, suspend/hibernate/resume cases are > automatically covered because the IP will already be reserved for that > MAC. > > If the admin wants to change, the IP of a VM this can be done via the > IPAM API/UI which will have to be implemented separately. > > A limitation of this approach vs dynamic IP reservation is that the IP > range on the subnet needs to be large enough to hold all IPs of all, > even stopped, VMs in that subnet. This is in contrast to default DHCP > functionality where only the number of actively running VMs is limited. > It should be enough to mention this in the docs. > > I will further review the code an try to implement the aforementioned > approach. > > Best regards, > Stefan Lendl > > Stefan Hanreich writes: > >> This is a WIP patch series, since I will be gone for 3 weeks and >> wanted to >> share my current progress with the DHCP support for SDN. >> >> This patch series adds support for automatically deploying dnsmasq as >> a DHCP >> server to a simple SDN Zone. >> >> While certainly not 100% polished on some ends (looking at restarting >> systemd >> services in particular), the general idea behind the mechanism shows. >> I wanted >> to gather some feedback on how I approached designing the plugins and >> the >> config regeneration process before comitting to this design by >> creating an API >> and UI around it. >> >> You need to install dnsmasq (and disable it afterwards): >> >> ? apt install dnsmasq && systemctl disable --now dnsmasq >> >> >> You can use the following example configuration for deploying a DHCP >> server in >> a SDN subnet: >> >> /etc/pve/sdn/dhcp.cfg: >> >> ? dnsmasq: nat >> >> >> /etc/pve/sdn/zones.cfg: >> >> ? simple: DHCPNAT >> ????????? ipam pve >> >> >> /etc/pve/sdn/vnets.cfg: >> >> ? vnet: dhcpnat >> ????????? zone DHCPNAT >> >> >> /etc/pve/sdn/subnets.cfg: >> >> ? subnet: DHCPNAT-10.1.0.0-16 >> ????????? vnet dhcpnat >> ????????? dhcp-dns-server 10.1.0.1 >> ????????? dhcp-range server=nat,start-address=10.1.0.100,end- >> address=10.1.0.200 >> ????????? gateway 10.1.0.1 >> ????????? snat 1 >> >> >> Then apply the SDN configuration: >> >> ? pvesh set /cluster/sdn >> >> You need to apply the SDN configuration once after adding the dhcp- >> range lines >> to the configuration, since the running configuration is used for >> managing >> DHCP. It will not work otherwise! >> >> For testing it can be helpful to monitor the following files (e.g. >> with watch) >> to find out what is happening >> ? * /etc/dnsmasq.d//ethers (on each node) >> ? * /etc/pve/priv/ipam.db >> >> Changes from v1 -> v2: >> ? * added hooks for handling DHCP when starting / stopping / .. VMs >> and CTs >> ? * Get an IP from IPAM and register that IP in the DHCP server >> ??? (pve only for now) >> ? * remove lease-time, since it is now infinite and managed by the VM >> lifecycle >> ? * add hooks for setting & deleting DHCP mappings to DHCP plugins >> ? * modified interface of the abstract class to reflect new >> requirements >> ? * added helpers in existing SDN classes >> ? * simplified DHCP configuration settings >> >> >> >> pve-cluster: >> >> Stefan Hanreich (1): >> ? cluster files: add dhcp.cfg >> >> ?src/PVE/Cluster.pm? | 1 + >> ?src/pmxcfs/status.c | 1 + >> ?2 files changed, 2 insertions(+) >> >> >> pve-network: >> >> Stefan Hanreich (6): >> ? subnets: vnets: preparations for DHCP plugins >> ? dhcp: add abstract class for DHCP plugins >> ? dhcp: subnet: add DHCP options to subnet configuration >> ? dhcp: add DHCP plugin for dnsmasq >> ? ipam: Add helper methods for DHCP to PVE IPAM >> ? dhcp: regenerate config for DHCP servers on reload >> >> ?debian/control???????????????????????? |?? 1 + >> ?src/PVE/Network/SDN.pm???????????????? |? 11 +- >> ?src/PVE/Network/SDN/Dhcp.pm??????????? | 192 >> +++++++++++++++++++++++++ >> ?src/PVE/Network/SDN/Dhcp/Dnsmasq.pm??? | 186 >> ++++++++++++++++++++++++ >> ?src/PVE/Network/SDN/Dhcp/Makefile????? |?? 8 ++ >> ?src/PVE/Network/SDN/Dhcp/Plugin.pm???? |? 83 +++++++++++ >> ?src/PVE/Network/SDN/Ipams/PVEPlugin.pm |? 64 +++++++++ >> ?src/PVE/Network/SDN/Makefile?????????? |?? 3 +- >> ?src/PVE/Network/SDN/SubnetPlugin.pm??? |? 32 +++++ >> ?src/PVE/Network/SDN/Subnets.pm???????? |? 43 ++++-- >> ?src/PVE/Network/SDN/Vnets.pm?????????? |? 27 ++-- >> ?11 files changed, 622 insertions(+), 28 deletions(-) >> ?create mode 100644 src/PVE/Network/SDN/Dhcp.pm >> ?create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm >> ?create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile >> ?create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm >> >> >> pve-manager: >> >> Stefan Hanreich (1): >> ? sdn: regenerate DHCP config on reload >> >> ?PVE/API2/Network.pm | 1 + >> ?1 file changed, 1 insertion(+) >> >> >> qemu-server: >> >> Stefan Hanreich (1): >> ? sdn: dhcp: add DHCP setup to vm-network-scripts >> >> ?PVE/QemuServer.pm???????????????? | 14 ++++++++++++++ >> ?vm-network-scripts/pve-bridge???? |? 3 +++ >> ?vm-network-scripts/pve-bridgedown | 19 +++++++++++++++++++ >> ?3 files changed, 36 insertions(+) >> >> >> pve-container: >> >> Stefan Hanreich (1): >> ? sdn: dhcp: setup DHCP mappings in LXC hooks >> >> ?src/PVE/LXC.pm??????????? | 10 ++++++++++ >> ?src/lxc-pve-poststop-hook |? 1 + >> ?src/lxc-pve-prestart-hook |? 9 +++++++++ >> ?3 files changed, 20 insertions(+) >> >> >> Summary over all repositories: >> ? 20 files changed, 681 insertions(+), 28 deletions(-) >> >> -- >> murpp v0.4.0 >> >> >> _______________________________________________ >> pve-devel mailing list >> pve-devel at lists.proxmox.com >> https://antiphishing.cetsi.fr/proxy/v3?i=d1l4NXNNaWE4SWZqU0dLWcuTfdxE >> d98NfWIp9dma5kY&r=MXJUa0FrUVJqc1UwYWxNZ- >> tuXduEO8AMVnCvYVMprCZ3oPilgy3nXcuJTOGH5iK84rVRg8cukFAROdxYRgFTTg&f=c2 >> xMdVN4Smh2R2tOZDdIRKCk7WEocHpTPMerT1Q- >> Aq5qwr8l2xvAWuOGvFsV3frp2oSAgxNUQCpJDHp2iUmTWg&u=https%3A//lists.prox >> mox.com/cgi-bin/mailman/listinfo/pve-devel&k=fjzS > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://antiphishing.cetsi.fr/proxy/v3?i=d1l4NXNNaWE4SWZqU0dLWcuTfdxEd9 > 8NfWIp9dma5kY&r=MXJUa0FrUVJqc1UwYWxNZ- > tuXduEO8AMVnCvYVMprCZ3oPilgy3nXcuJTOGH5iK84rVRg8cukFAROdxYRgFTTg&f=c2xM > dVN4Smh2R2tOZDdIRKCk7WEocHpTPMerT1Q- > Aq5qwr8l2xvAWuOGvFsV3frp2oSAgxNUQCpJDHp2iUmTWg&u=https%3A//lists.proxmo > x.com/cgi-bin/mailman/listinfo/pve-devel&k=fjzS From alexandre.derumier at groupe-cyllene.com Fri Oct 27 15:37:59 2023 From: alexandre.derumier at groupe-cyllene.com (DERUMIER, Alexandre) Date: Fri, 27 Oct 2023 13:37:59 +0000 Subject: [pve-devel] [WIP v2 cluster/network/manager/qemu-server/container 00/10] Add support for DHCP servers to SDN In-Reply-To: <877cn8mdkq.fsf@gmail.com> References: <20231017135507.2220948-1-s.hanreich@proxmox.com> <87v8axbjh1.fsf@gmail.com> <877cn8mdkq.fsf@gmail.com> Message-ID: -------- Message initial -------- De: Stefan Lendl ?: "DERUMIER, Alexandre" Cc: pve-devel at lists.proxmox.com Objet: Re: [pve-devel] [WIP v2 cluster/network/manager/qemu- server/container 00/10] Add support for DHCP servers to SDN Date: 27/10/2023 14:53:25 Hi Alexandre, I am proposing a slightly different view. >>I think it's better to keep all IPs, managed by the IPAM in the IPAM >>and the VM only configures as DHCP. Yes, I'm thinking exactly the same ! I had tried 2year ago to implement ipam with static ip in vm configuration (+ipam), and they are a lot of corner case. >>I would implement the 4 mentioned events (vNIC create, destroy, >>start, >>stop) in the SDN module and limit interactions between VM configs and >>the SDN module to these events. >> >>On NIC create: the it calls the SDN::nic_join_vnet($bridge, $mac) >>function that handles IPAM registration if necessary triggers >>generating >>DHCP config and so on. Same approach for the other SDN related >>events. >> >>All the logic is implemented in the SDN module. This reduces coupling >>between VM logic and SDN logic. sound great :) "DERUMIER, Alexandre" writes: > Hi Stefan (Lendl), > > I'm totally agreed with you, we should have persistent reservation, > at vm create/nic plug, nic delete, vm delete. > > At least , for my usage with multiple cluster on different > datacenters, > I really can wait to call ipam to api at each start (for scalability > or > for security if ipam is down) > > > This also allow to simply do reservations in dnsmasq file without any > need to restart it. (AFAIK, openstack is using dnsmasq like this too) > > > I'm not sure if true dynamic ephemral ip , changing at each vm > stop/start is interesting for a server vm usage. (maybe for desktop > vmwhere you share a small pool of ip, but I personnaly don't known > any > proxmox users using proxmox ve for this) > > > see my proposal here (with handle ephemeral && reserved, but it's > even > easier with only reserved): > > https://antiphishing.cetsi.fr/proxy/v3?i=YXJwbnI5ZGY3YXM2MThBYc__j3mP > QdDC0mZ08oRIJLw&r=d2RpVFJVaTVtcFJRWFNMYgYCddP93Y9SOEaGwAD- > 9JdLrx2JwwKfs9Sn_uiRQCCUgqnCg4WLD- > gLY0eKXrXX4A&f=SVN0TjFBb1k5Qk8zQ2E1YT- > NJ2Y2fJYrRVcVAuRs9UYfyMFrtkoDLcaTV9MhYQZD&u=https%3A//lists.proxmox.c > om/pipermail/pve-devel/2023-September/059169.html&k=ogd1 > > > > > " > I think we could implement ipam call like: > > > create vm or add a new nic? --> > ----------------------------- > qm create ... -net0 > bridge=vnet,....,ip=(auto|192.168.0.1|dynamic),ip6=(..) > > > auto : search a free ip in ipam.? write the ip address in net0: > ...,ip= > ip field > > 192.168.0.1:? check if ip is free in ipam && register ip in ipam. > write > the ip in ip field. > > > dynamic: write "ephemeral" in net0: ....,ip=ephemeral (This is a > dynamic ip registered at vm start, and release at vm stop) > > > > vm start > --------- > - if ip=ephemeral, find && register a free ip in ipam, write it in vm > net0: ...,ip=192.168.0.10[E] .?? (maybe with a special flag [E] to > indicate it's ephemeral) > - read ip from vm config && inject in dhcp > > > vm_stop > ------- > if ip is ephemeral (netX: ip=192.168.0.10[E]),? delete ip from ipam, > set ip=ephemeral in vm config > > > vm_destroy or nic remove/unplug > ------------------------- > if netX: ...,ip=192.168.0.10?? ,? remove ip from ipam > > > > nic update when vm is running: > ------------------------------ > if ip is defined : netX: ip=192.168.0.10,? we don't allow bridge > change > or ip change, as vm is not notified about theses changes, and still > use > old ip. > > We can allow nic hot-unplug && hotplug. (guest os will remove the ip > on > nic removal, and will call dhcp again on nic hotplug) > > > > > nic hotplug with ip=auto: > ------------------------- > > --> add nic in pending state ----> find ip in ipam && write it in > pending ---> do the hotplug in qemu. > > We need to handle the config revert to remove ip from ipam if the nic > hotplug is blocked in pending state(I never see this case until os > don't have pci_hotplug module loaded, but it's better to be carefull > ) > > " > > > > > I am currently working on the SDN feature.? This is an initial > > > review > > > of > > > the patch series and I am trying to make a strong case against > > > ephemeral > > > DHCP IP reservation. > > > > > > The current state of the patch series invokes the IPAM on every > > > VM/CT > > > start/stop to add or remove the IP from the IPAM. > > > This triggers the dnsmasq config generation on the specific host > > > with > > > only the MAC/IP mapping of that particular host. > > > > > > From reading the discussion of the v1 patch series I understand this > approach tries to implement the ephemeral IP reservation strategy. > From > off-list conversations with Stefan Hanreich, I agree that having > ephemeral IP reservation coordinated by the IPAM requires us to > re-implement DHCP functionality in the IPAM and heavily rely on > syncing > between the different services. > > To maintain reliable sync we need to hook into many different places > where the IPAM need to be queried.? Any issues with the > implementation > may lead to IPAM and DHCP local config state running out of sync > causing > network issues duplicate multiple IPs. > > Furthermore, every interaction with the IPAM requires a cluster-wide > lock on the IPAM. Having a central cluster-wide lock on every VM > start/stop/migrate will significantly limit parallel operations.? > Event > starting two VMs in parallel will be limited by this central lock. At > boot trying to start many VMs (ideally as much in parallel as > possible) > is limited by the central IPAM lock even further. > > I argue that we shall not support ephemeral IPs altogether. > The alternative is to make all IPAM reservations persistent. > > Using persistent IPs only reduces the interactions of VM/CTs with the > IPAM to a minimum of NIC joining a subnet and NIC leaving a subnet. I > am > deliberately not referring to VMs because a VM may be part of > multiple > VNets or even multiple times in the same VNet (regardless if that is > sensible). > > Cases the IPAM needs to be involved: > > - NIC with DHCP enabled VNet is added to VM config > - NIC with DHCP enabled VNet is removed from VM config > - NIC is assigned to another Bridge > ? can be treated as individual leave + join events > > Cases that are explicitly not covered but may be added if desired: > > - Manually assign an IP address on a NIC > ? will not be automatically visible in the IPAM > - Manually change the MAC on a NIC > ? don't do that > you are on your own. > ? Not handled > change in IPAM manually > > Once an IP is reserved via IPAM, the dnsmasq config can be generated > stateless and idempotent from the pve IPAM and is identical on all > nodes > regardless if a VM/CT actually resides on that node or is running or > stopped.? This is especially useful for VM migration because the IP > stays consistent without spacial considering. > > Snapshot/revert, backup/restore, suspend/hibernate/resume cases are > automatically covered because the IP will already be reserved for > that > MAC. > > If the admin wants to change, the IP of a VM this can be done via the > IPAM API/UI which will have to be implemented separately. > > A limitation of this approach vs dynamic IP reservation is that the > IP > range on the subnet needs to be large enough to hold all IPs of all, > even stopped, VMs in that subnet. This is in contrast to default DHCP > functionality where only the number of actively running VMs is > limited. > It should be enough to mention this in the docs. > > I will further review the code an try to implement the aforementioned > approach. > > Best regards, > Stefan Lendl > > Stefan Hanreich writes: > > > This is a WIP patch series, since I will be gone for 3 weeks and > > wanted to > > share my current progress with the DHCP support for SDN. > > > > This patch series adds support for automatically deploying dnsmasq > > as > > a DHCP > > server to a simple SDN Zone. > > > > While certainly not 100% polished on some ends (looking at > > restarting > > systemd > > services in particular), the general idea behind the mechanism > > shows. > > I wanted > > to gather some feedback on how I approached designing the plugins > > and > > the > > config regeneration process before comitting to this design by > > creating an API > > and UI around it. > > > > You need to install dnsmasq (and disable it afterwards): > > > > ? apt install dnsmasq && systemctl disable --now dnsmasq > > > > > > You can use the following example configuration for deploying a > > DHCP > > server in > > a SDN subnet: > > > > /etc/pve/sdn/dhcp.cfg: > > > > ? dnsmasq: nat > > > > > > /etc/pve/sdn/zones.cfg: > > > > ? simple: DHCPNAT > > ????????? ipam pve > > > > > > /etc/pve/sdn/vnets.cfg: > > > > ? vnet: dhcpnat > > ????????? zone DHCPNAT > > > > > > /etc/pve/sdn/subnets.cfg: > > > > ? subnet: DHCPNAT-10.1.0.0-16 > > ????????? vnet dhcpnat > > ????????? dhcp-dns-server 10.1.0.1 > > ????????? dhcp-range server=nat,start-address=10.1.0.100,end- > > address=10.1.0.200 > > ????????? gateway 10.1.0.1 > > ????????? snat 1 > > > > > > Then apply the SDN configuration: > > > > ? pvesh set /cluster/sdn > > > > You need to apply the SDN configuration once after adding the dhcp- > > range lines > > to the configuration, since the running configuration is used for > > managing > > DHCP. It will not work otherwise! > > > > For testing it can be helpful to monitor the following files (e.g. > > with watch) > > to find out what is happening > > ? * /etc/dnsmasq.d//ethers (on each node) > > ? * /etc/pve/priv/ipam.db > > > > Changes from v1 -> v2: > > ? * added hooks for handling DHCP when starting / stopping / .. VMs > > and CTs > > ? * Get an IP from IPAM and register that IP in the DHCP server > > ??? (pve only for now) > > ? * remove lease-time, since it is now infinite and managed by the > > VM > > lifecycle > > ? * add hooks for setting & deleting DHCP mappings to DHCP plugins > > ? * modified interface of the abstract class to reflect new > > requirements > > ? * added helpers in existing SDN classes > > ? * simplified DHCP configuration settings > > > > > > > > pve-cluster: > > > > Stefan Hanreich (1): > > ? cluster files: add dhcp.cfg > > > > ?src/PVE/Cluster.pm? | 1 + > > ?src/pmxcfs/status.c | 1 + > > ?2 files changed, 2 insertions(+) > > > > > > pve-network: > > > > Stefan Hanreich (6): > > ? subnets: vnets: preparations for DHCP plugins > > ? dhcp: add abstract class for DHCP plugins > > ? dhcp: subnet: add DHCP options to subnet configuration > > ? dhcp: add DHCP plugin for dnsmasq > > ? ipam: Add helper methods for DHCP to PVE IPAM > > ? dhcp: regenerate config for DHCP servers on reload > > > > ?debian/control???????????????????????? |?? 1 + > > ?src/PVE/Network/SDN.pm???????????????? |? 11 +- > > ?src/PVE/Network/SDN/Dhcp.pm??????????? | 192 > > +++++++++++++++++++++++++ > > ?src/PVE/Network/SDN/Dhcp/Dnsmasq.pm??? | 186 > > ++++++++++++++++++++++++ > > ?src/PVE/Network/SDN/Dhcp/Makefile????? |?? 8 ++ > > ?src/PVE/Network/SDN/Dhcp/Plugin.pm???? |? 83 +++++++++++ > > ?src/PVE/Network/SDN/Ipams/PVEPlugin.pm |? 64 +++++++++ > > ?src/PVE/Network/SDN/Makefile?????????? |?? 3 +- > > ?src/PVE/Network/SDN/SubnetPlugin.pm??? |? 32 +++++ > > ?src/PVE/Network/SDN/Subnets.pm???????? |? 43 ++++-- > > ?src/PVE/Network/SDN/Vnets.pm?????????? |? 27 ++-- > > ?11 files changed, 622 insertions(+), 28 deletions(-) > > ?create mode 100644 src/PVE/Network/SDN/Dhcp.pm > > ?create mode 100644 src/PVE/Network/SDN/Dhcp/Dnsmasq.pm > > ?create mode 100644 src/PVE/Network/SDN/Dhcp/Makefile > > ?create mode 100644 src/PVE/Network/SDN/Dhcp/Plugin.pm > > > > > > pve-manager: > > > > Stefan Hanreich (1): > > ? sdn: regenerate DHCP config on reload > > > > ?PVE/API2/Network.pm | 1 + > > ?1 file changed, 1 insertion(+) > > > > > > qemu-server: > > > > Stefan Hanreich (1): > > ? sdn: dhcp: add DHCP setup to vm-network-scripts > > > > ?PVE/QemuServer.pm???????????????? | 14 ++++++++++++++ > > ?vm-network-scripts/pve-bridge???? |? 3 +++ > > ?vm-network-scripts/pve-bridgedown | 19 +++++++++++++++++++ > > ?3 files changed, 36 insertions(+) > > > > > > pve-container: > > > > Stefan Hanreich (1): > > ? sdn: dhcp: setup DHCP mappings in LXC hooks > > > > ?src/PVE/LXC.pm??????????? | 10 ++++++++++ > > ?src/lxc-pve-poststop-hook |? 1 + > > ?src/lxc-pve-prestart-hook |? 9 +++++++++ > > ?3 files changed, 20 insertions(+) > > > > > > Summary over all repositories: > > ? 20 files changed, 681 insertions(+), 28 deletions(-) > > > > -- > > murpp v0.4.0 > > > > > > _______________________________________________ > > pve-devel mailing list > > pve-devel at lists.proxmox.com > > https://antiphishing.cetsi.fr/proxy/v3?i=YXJwbnI5ZGY3YXM2MThBYc__j3 > > mPQdDC0mZ08oRIJLw&r=d2RpVFJVaTVtcFJRWFNMYgYCddP93Y9SOEaGwAD- > > 9JdLrx2JwwKfs9Sn_uiRQCCUgqnCg4WLD- > > gLY0eKXrXX4A&f=SVN0TjFBb1k5Qk8zQ2E1YT- > > NJ2Y2fJYrRVcVAuRs9UYfyMFrtkoDLcaTV9MhYQZD&u=https%3A//antiphishing. > > cetsi.fr/proxy/v3%3Fi%3Dd1l4NXNNaWE4SWZqU0dLWcuTfdxE&k=ogd1 > > d98NfWIp9dma5kY&r=MXJUa0FrUVJqc1UwYWxNZ- > > tuXduEO8AMVnCvYVMprCZ3oPilgy3nXcuJTOGH5iK84rVRg8cukFAROdxYRgFTTg&f= > > c2 > > xMdVN4Smh2R2tOZDdIRKCk7WEocHpTPMerT1Q- > > Aq5qwr8l2xvAWuOGvFsV3frp2oSAgxNUQCpJDHp2iUmTWg&u=https%3A//lists.pr > > ox > > mox.com/cgi-bin/mailman/listinfo/pve-devel&k=fjzS > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://antiphishing.cetsi.fr/proxy/v3?i=YXJwbnI5ZGY3YXM2MThBYc__j3mP > QdDC0mZ08oRIJLw&r=d2RpVFJVaTVtcFJRWFNMYgYCddP93Y9SOEaGwAD- > 9JdLrx2JwwKfs9Sn_uiRQCCUgqnCg4WLD- > gLY0eKXrXX4A&f=SVN0TjFBb1k5Qk8zQ2E1YT- > NJ2Y2fJYrRVcVAuRs9UYfyMFrtkoDLcaTV9MhYQZD&u=https%3A//antiphishing.ce > tsi.fr/proxy/v3%3Fi%3Dd1l4NXNNaWE4SWZqU0dLWcuTfdxEd9&k=ogd1 > 8NfWIp9dma5kY&r=MXJUa0FrUVJqc1UwYWxNZ- > tuXduEO8AMVnCvYVMprCZ3oPilgy3nXcuJTOGH5iK84rVRg8cukFAROdxYRgFTTg&f=c2 > xM > dVN4Smh2R2tOZDdIRKCk7WEocHpTPMerT1Q- > Aq5qwr8l2xvAWuOGvFsV3frp2oSAgxNUQCpJDHp2iUmTWg&u=https%3A//lists.prox > mo > x.com/cgi-bin/mailman/listinfo/pve-devel&k=fjzS From t.lamprecht at proxmox.com Fri Oct 27 16:42:36 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Fri, 27 Oct 2023 16:42:36 +0200 Subject: [pve-devel] applied: [PATCH docs] ha: Explicitly mention that migrations only affect HA guests In-Reply-To: <20231025095532.54953-1-m.sandoval@proxmox.com> References: <20231025095532.54953-1-m.sandoval@proxmox.com> Message-ID: Am 25/10/2023 um 11:55 schrieb Maximiliano Sandoval R: > Signed-off-by: Maximiliano Sandoval R > --- > ha-manager.adoc | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > applied, thanks! But as talked off-list, I made a follow-up that reworked that whole paragraph, it had a few small errors like a duplicate "to to", and was a bit harder to read than necessary. From a.lauterer at proxmox.com Mon Oct 30 10:45:06 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Mon, 30 Oct 2023 10:45:06 +0100 Subject: [pve-devel] [PATCH 00/12] installer: add crate for common code In-Reply-To: References: <20231025160011.3617524-1-a.lauterer@proxmox.com> Message-ID: Sorry, I missed to patch the Makefile and thanks for sending a patch for that [0]. The Makefile has another bug that I encountered during the RFC phase of the auto installer [0]. I could include that already in a V2 or add it to the auto installer series where the problem actually starts to show. [0] https://lists.proxmox.com/pipermail/pve-devel/2023-October/059676.html [1] https://lists.proxmox.com/pipermail/pve-devel/2023-September/059015.html On 10/27/23 13:41, Christoph Heiss wrote: > > Had a lot of in-person discussions with Aaron over the last few weeks > over this. > There are no real functional changes here that would impact users, > merely a code move. > > Each inidivdual patch (except #1, see my answer on that) builds cleanly > on top of current master. The one dependency (see below) is also already > applied. I also did some quick smoke testing to verify everything really > still works. > > I will go over the the rest of the patches afterwards, but from a glance > there is nothing that I would consider a blocker. > Things like e.g. the `InstallConfig` stuff can be done separately as > followups. > > Also considering this is a great code churn, I would rather have this > get applied sooner than later, such that all future work is done on top > of this. > > The only "complaint" here is that the .deb package does not build with > this series (see my reply on #1). I will shortly send a quick follow-up > patch for that, so you don't have to necessarily re-spin the whole > series just for a single, trivial line. > > LGTM; thus please consider the whole series: > > Reviewed-by: Christoph Heiss > Tested-by: Christoph Heiss > > On Wed, Oct 25, 2023 at 05:59:59PM +0200, Aaron Lauterer wrote: >> >> since work on the auto installer is happenning in parallel, now would be >> a good point to move commonly used code into its own crate. Otherwise >> the auto-installer will always have to play catch up with the ongoing >> development of the tui installer. >> >> I tried to split up the commits as much as possible, but there are two >> larger ones, copying most the code over to the new repo and making the >> switch. The former because it is difficult to pull apart the parts that >> belong together. The latter needed to be this big as most local >> occurences needed to be removed at the same time to avoid dependency >> conflicts. >> >> One last things that is missing, is the "InstallConfig" struct. >> This should also be part of the common crate, but I need to look further >> into how to make it possible that it can be created from structs of each >> crate (tui, auto) as implementing a ::From can only be done within the >> crate where the struct lives IIUC. >> >> This series depends on the patches by Christoph to remove the global >> unsafe setup info, version 2 [0]. Without those patches applied first, >> this series will not apply. >> >> [0] https://lists.proxmox.com/pipermail/pve-devel/2023-October/059628.html >> >> Aaron Lauterer (12): >> add proxmox-installer-common crate >> common: copy common code from tui-installer >> common: utils: add dependency for doc test >> common: make InstallZfsOption public >> common: disk_checks: make functions public >> tui-installer: add dependency for new common crate >> tui: switch to common crate >> tui: remove now unused utils.rs >> common: add installer_setup method >> common: document installer_setup method >> tui: use installer_setup from common cate >> tui: remove unused read_json function >> >> Cargo.toml | 1 + >> proxmox-installer-common/Cargo.toml | 12 + >> proxmox-installer-common/src/disk_checks.rs | 237 ++++++++++ >> proxmox-installer-common/src/lib.rs | 4 + >> proxmox-installer-common/src/options.rs | 387 +++++++++++++++++ >> proxmox-installer-common/src/setup.rs | 368 ++++++++++++++++ >> .../src/utils.rs | 1 + >> proxmox-tui-installer/Cargo.toml | 1 + >> proxmox-tui-installer/src/main.rs | 51 +-- >> proxmox-tui-installer/src/options.rs | 403 +----------------- >> proxmox-tui-installer/src/setup.rs | 324 +------------- >> proxmox-tui-installer/src/system.rs | 2 +- >> proxmox-tui-installer/src/views/bootdisk.rs | 248 +---------- >> proxmox-tui-installer/src/views/mod.rs | 2 +- >> proxmox-tui-installer/src/views/timezone.rs | 4 +- >> 15 files changed, 1054 insertions(+), 991 deletions(-) >> create mode 100644 proxmox-installer-common/Cargo.toml >> create mode 100644 proxmox-installer-common/src/disk_checks.rs >> create mode 100644 proxmox-installer-common/src/lib.rs >> create mode 100644 proxmox-installer-common/src/options.rs >> create mode 100644 proxmox-installer-common/src/setup.rs >> rename {proxmox-tui-installer => proxmox-installer-common}/src/utils.rs (99%) >> >> -- >> 2.39.2 >> >> >> >> _______________________________________________ >> pve-devel mailing list >> pve-devel at lists.proxmox.com >> https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel >> >> From a.zeidler at proxmox.com Mon Oct 30 12:20:13 2023 From: a.zeidler at proxmox.com (Alexander Zeidler) Date: Mon, 30 Oct 2023 12:20:13 +0100 Subject: [pve-devel] [PATCH docs] ha-manager, software updates: add useful BlockIds Message-ID: <20231030112013.140969-1-a.zeidler@proxmox.com> Signed-off-by: Alexander Zeidler --- ha-manager.adoc | 5 +++++ system-software-updates.adoc | 1 + 2 files changed, 6 insertions(+) diff --git a/ha-manager.adoc b/ha-manager.adoc index 8390782..53330a1 100644 --- a/ha-manager.adoc +++ b/ha-manager.adoc @@ -260,6 +260,7 @@ This all gets supervised by the CRM which currently holds the manager master lock. +[[ha_manager_service_states]] Service States ~~~~~~~~~~~~~~ @@ -352,6 +353,7 @@ disabled:: Service is stopped and marked as `disabled` +[[ha_manager_lrm]] Local Resource Manager ~~~~~~~~~~~~~~~~~~~~~~ @@ -416,6 +418,8 @@ what both daemons, the LRM and the CRM, did. You may use `journalctl -u pve-ha-lrm` on the node(s) where the service is and the same command for the pve-ha-crm on the node which is the current master. + +[[ha_manager_crm]] Cluster Resource Manager ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -834,6 +838,7 @@ this is not the case the update process can take too long which, in the worst case, may result in a reset triggered by the watchdog. +[[ha_manager_node_maintenance]] Node Maintenance ---------------- diff --git a/system-software-updates.adoc b/system-software-updates.adoc index 013e171..471ff1e 100644 --- a/system-software-updates.adoc +++ b/system-software-updates.adoc @@ -1,3 +1,4 @@ +[[system_software_updates]] System Software Updates ----------------------- ifdef::wiki[] -- 2.39.2 From f.schauer at proxmox.com Mon Oct 30 12:27:42 2023 From: f.schauer at proxmox.com (Filip Schauer) Date: Mon, 30 Oct 2023 12:27:42 +0100 Subject: [pve-devel] [PATCH manager] ui: lxc: add edit window for device passthrough Message-ID: <20231030112742.44609-1-f.schauer@proxmox.com> Signed-off-by: Filip Schauer --- Depends on: https://lists.proxmox.com/pipermail/pve-devel/2023-October/059616.html www/manager6/Makefile | 1 + www/manager6/Utils.js | 11 +++ www/manager6/lxc/DeviceEdit.js | 158 +++++++++++++++++++++++++++++++++ www/manager6/lxc/Resources.js | 28 +++++- 4 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 www/manager6/lxc/DeviceEdit.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index 57e1b48f..373c8f6d 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -185,6 +185,7 @@ JSSRC= \ lxc/CmdMenu.js \ lxc/Config.js \ lxc/CreateWizard.js \ + lxc/DeviceEdit.js \ lxc/DNS.js \ lxc/FeaturesEdit.js \ lxc/MPEdit.js \ diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index 8f46c07e..1ea7b42a 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -1605,6 +1605,17 @@ Ext.define('PVE.Utils', { } }, + dev_count: 256, + + forEachDev: function(func) { + for (let i = 0; i < PVE.Utils.dev_count; i++) { + let cont = func(i); + if (!cont && cont !== undefined) { + return; + } + } + }, + hardware_counts: { net: 32, usb: 14, diff --git a/www/manager6/lxc/DeviceEdit.js b/www/manager6/lxc/DeviceEdit.js new file mode 100644 index 00000000..e4790c4e --- /dev/null +++ b/www/manager6/lxc/DeviceEdit.js @@ -0,0 +1,158 @@ +Ext.define('PVE.lxc.DeviceInputPanel', { + extend: 'Proxmox.panel.InputPanel', + mixins: ['Proxmox.Mixin.CBind'], + + autoComplete: false, + + cbindData: function(initialConfig) { + let me = this; + if (!me.pveSelNode) { + throw "no pveSelNode given"; + } + + return { nodename: me.pveSelNode.data.node }; + }, + + viewModel: { + data: {}, + }, + + setVMConfig: function(vmconfig) { + var me = this; + me.vmconfig = vmconfig; + }, + + onGetValues: function(values) { + var me = this; + if (!me.confid) { + let max_devices = 256; + for (let i = 0; i < max_devices; i++) { + let id = 'dev' + i.toString(); + if (!me.vmconfig[id]) { + me.confid = id; + break; + } + } + } + var val = ""; + var type = me.down('radiofield').getGroupValue(); + switch (type) { + case 'path': + val = values[type]; + delete values[type]; + break; + case 'usbmapped': + val = 'usbmapping=' + values[type]; + delete values[type]; + break; + default: + throw "invalid type selected"; + } + + values[me.confid] = val; + return values; + }, + + items: [ + { + xtype: 'fieldcontainer', + defaultType: 'radiofield', + layout: 'fit', + items: [ + { + name: 'dev', + inputValue: 'usbmapped', + boxLabel: gettext('Use mapped USB device'), + reference: 'usbmapped', + submitValue: false, + checked: true, + }, + { + xtype: 'pveUSBMapSelector', + disabled: true, + name: 'usbmapped', + cbind: { nodename: '{nodename}' }, + bind: { disabled: '{!usbmapped.checked}' }, + allowBlank: false, + fieldLabel: gettext('Choose Device'), + labelAlign: 'right', + }, + { + name: 'dev', + inputValue: 'path', + boxLabel: gettext('Use Device Path'), + reference: 'path', + submitValue: false, + }, + { + xtype: 'textfield', + disabled: true, + type: 'device', + name: 'path', + cbind: { pveSelNode: '{pveSelNode}' }, + bind: { disabled: '{!path.checked}' }, + editable: true, + allowBlank: false, + fieldLabel: gettext('Device Path'), + labelAlign: 'right', + }, + ], + }, + ], +}); + +Ext.define('PVE.lxc.DeviceEdit', { + extend: 'Proxmox.window.Edit', + + vmconfig: undefined, + + isAdd: true, + width: 400, + subject: gettext('Device'), + + initComponent: function() { + var me = this; + + me.isCreate = !me.confid; + + var ipanel = Ext.create('PVE.lxc.DeviceInputPanel', { + confid: me.confid, + pveSelNode: me.pveSelNode, + }); + + Ext.apply(me, { + items: [ipanel], + }); + + me.callParent(); + + me.load({ + success: function(response, options) { + ipanel.setVMConfig(response.result.data); + if (me.isCreate) { + return; + } + + let data = PVE.Parser.parsePropertyString(response.result.data[me.confid], 'path'); + let path, usbmapped; + let dev; + + if (data.path) { + path = data.path; + dev = 'path'; + } else if (data.usbmapping) { + usbmapped = data.usbmapping; + dev = 'usbmapped'; + } + + var values = { + dev, + path, + usbmapped, + }; + + ipanel.setValues(values); + }, + }); + }, +}); diff --git a/www/manager6/lxc/Resources.js b/www/manager6/lxc/Resources.js index 85112345..9dcb74eb 100644 --- a/www/manager6/lxc/Resources.js +++ b/www/manager6/lxc/Resources.js @@ -135,6 +135,17 @@ Ext.define('PVE.lxc.RessourceView', { }; }, true); + PVE.Utils.forEachDev(function(i) { + confid = 'dev' + i; + rows[confid] = { + group: 7, + order: i, + tdCls: 'pve-itype-icon-pci', + editor: 'PVE.lxc.DeviceEdit', + header: gettext('Device') + ' (' + confid + ')', + }; + }); + var baseurl = 'nodes/' + nodename + '/lxc/' + vmid + '/config'; me.selModel = Ext.create('Ext.selection.RowModel', {}); @@ -311,6 +322,7 @@ Ext.define('PVE.lxc.RessourceView', { let isDisk = isRootFS || key.match(/^(mp|unused)\d+/); let isUnusedDisk = key.match(/^unused\d+/); let isUsedDisk = isDisk && !isUnusedDisk; + let isDevice = key.match(/^dev\d+/); let noedit = isDelete || !rowdef.editor; if (!noedit && Proxmox.UserName !== 'root at pam' && key.match(/^mp\d+$/)) { @@ -326,7 +338,7 @@ Ext.define('PVE.lxc.RessourceView', { reassign_menuitem.setDisabled(isRootFS); resize_menuitem.setDisabled(isUnusedDisk); - remove_btn.setDisabled(!isDisk || isRootFS || !diskCap || pending); + remove_btn.setDisabled(!(isDisk || isDevice) || isRootFS || !diskCap || pending); revert_btn.setDisabled(!pending); remove_btn.setText(isUsedDisk ? remove_btn.altText : remove_btn.defaultText); @@ -380,6 +392,20 @@ Ext.define('PVE.lxc.RessourceView', { }); }, }, + { + text: gettext('Device Passthrough'), + iconCls: 'pve-itype-icon-pci', + handler: function() { + Ext.create('PVE.lxc.DeviceEdit', { + autoShow: true, + url: `/api2/extjs/${baseurl}`, + pveSelNode: me.pveSelNode, + listeners: { + destroy: () => me.reload(), + }, + }); + }, + }, ], }), }, -- 2.39.2 From a.zeidler at proxmox.com Mon Oct 30 13:10:10 2023 From: a.zeidler at proxmox.com (Alexander Zeidler) Date: Mon, 30 Oct 2023 13:10:10 +0100 Subject: [pve-devel] [PATCH docs] sysadmin: revise firmware chapter, add firmware repo section Message-ID: <20231030121010.155191-1-a.zeidler@proxmox.com> Chapter "Firmware Updates": * improve the structure and clarity of information provided * mention which update methods are when available/recommended * add information about the already pre-installed pve-firmware package * emphasise the importance of CPU microcode updates, how to interpret versions and how to recover a possibly unbootable system * move info about non-free-firmware repo to "Package Repositories" Chapter "Package Repositories": * add new section "Debian Firmware Repository" Signed-off-by: Alexander Zeidler --- Depends on "[PATCH docs] ha-manager, software updates: add useful BlockIds" (https://lists.proxmox.com/pipermail/pve-devel/2023-October/059710.html) firmware-updates.adoc | 233 ++++++++++++++++++++++++++++------------- pve-package-repos.adoc | 18 ++++ 2 files changed, 179 insertions(+), 72 deletions(-) diff --git a/firmware-updates.adoc b/firmware-updates.adoc index 3b3064e..7e25a50 100644 --- a/firmware-updates.adoc +++ b/firmware-updates.adoc @@ -4,103 +4,192 @@ Firmware Updates ifdef::wiki[] :pve-toplevel: endif::wiki[] - Firmware updates from this chapter should be applied when running {pve} on a bare-metal server. Whether configuring firmware updates is appropriate within guests, e.g. when using device pass-through, depends strongly on your setup and is therefore out of scope. -Regular firmware updates for devices are just as important for proper operation -as regular software updates. There are several ways to obtain and apply those -updates. The methods listed in this chapter can also be combined to minimize the -chance of missing an important update. +In addition to regular software updates, firmware updates are also important +for reliable and secure operation. + +When obtaining and applying firmware updates, a combination of available options +is recommended to get them as early as possible or at all. -TIP: When a firmware was updated, a system reboot is the safest way to apply the -new version. +The term firmware is usually divided linguistically into microcode (for CPUs) +and firmware (for other devices). [[sysadmin_firmware_persistent]] Persistent Firmware ~~~~~~~~~~~~~~~~~~~ -The following methods write the new firmware permanently to the respective -device. The firmware therefore remains up to date regardless of the booted -operating system. - -TIP: When using a user space application or 'fwupd', the hardware must usually -have been manufactured after 2014, the system must have been booted with UEFI -and the EFI partition manually mounted. +This section is suitable for all devices. Updated microcode, which is usually +included in a BIOS/UEFI update, is stored on the motherboard, whereas other +firmware is stored on the respective device. This persistent method is +especially important for the CPU, as it enables the earliest possible regular +loading of the updated microcode at boot time. -CAUTION: When updating the BIOS/UEFI itself, its settings are usually reset. Be -prepared to reconfigure them afterwards. +CAUTION: With some updates, such as for BIOS/UEFI or storage controller, the +device configuration could be reset. Please follow the vendor's instructions +carefully and back up the current configuration. +Please check with your vendor which update methods are available. -[[sysadmin_firmware_persistent_vendor_specific]] -Vendor-specific -^^^^^^^^^^^^^^^ -Firmware updates are usually available from the vendor directly. Please check -with your vendor what options are available. +* Convenient update methods for servers can include Dell's Lifecycle Manager or +Service Packs from HPE. -Depending on the platform and vendor, there are convenient methods available. -For servers, for example, Dell's Lifecycle Manager or Service Packs from HPE. -Sometimes there are Linux utilities available as well. Examples are +* Sometimes there are Linux utilities available as well. Examples are https://network.nvidia.com/support/firmware/mlxup-mft/['mlxup'] for NVIDIA ConnectX or https://techdocs.broadcom.com/us/en/storage-and-ethernet-connectivity/ethernet-nic-controllers/bcm957xxx/adapters/software-installation/updating-the-firmware/manually-updating-the-adapter-firmware-on-linuxesx.html['bnxtnvm'/'niccli'] for Broadcom network cards. +* https://fwupd.org[LVFS] could also be an option if there is a cooperation with +a https://fwupd.org/lvfs/vendors/[vendor] and +https://fwupd.org/lvfs/devices/[supported hardware] in use. The technical +requirement for this is that the system was manufactured after 2014, is booted +via UEFI and the easiest way is to mount the EFI partition from which you boot +(`mount /dev/disk/by-partuuid/ /boot/efi`) before installing +'fwupd'. -[[sysadmin_firmware_persistent_lvfs_fwupd]] -Linux Vendor Firmware Service (LVFS) via fwupd -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -On https://fwupd.org['LVFS'], vendors can make their firmware updates available -in a standardized way to a wide range of Linux hosts. Here is the growing list -of participating https://fwupd.org/lvfs/vendors/[vendors] and their currently -supported https://fwupd.org/lvfs/devices/[devices]. - -To use 'fwupd', manually mount your -https://pve.proxmox.com/pve-docs/pve-admin-guide.html#sysboot_installer_part_scheme[EFI System Partition] -(ESP) you booted from on `/boot/`. After installing the package 'fwupd', update -firmware with the following commands: ----- -# fwupdmgr refresh -# fwupdmgr get-updates -# fwupdmgr update -# reboot ----- +TIP: If the update instructions require a host reboot, make sure that it can be +done safely. See also xref:ha_manager_node_maintenance[Node Maintenance]. [[sysadmin_firmware_runtime_files]] Runtime Firmware Files ~~~~~~~~~~~~~~~~~~~~~~ -The following methods keep the firmware files available at the {pve} host and do -not persist it on the device itself. Whenever a device is initialized, usually -during the boot process, the corresponding firmware is loaded into the RAM of -the respective device. These methods do not provide and can not update firmware -that is used in the very early boot process (e.g. BIOS/UEFI, hard disks). +This method stores firmware on the {pve} operating system and will pass it to a +device if its xref:sysadmin_firmware_persistent[persisted firmware] is less +recent. It is supported by devices such as network and graphics cards, but not +by those that rely on persisted firmware such as the motherboard and hard disks. In {pve} the package `pve-firmware` is already installed by default. Therefore, -with the normal system updates (APT), the included firmware of common hardware -is automatically kept up to date. Be aware that CPU microcode updates are -located in a separate Debian repository component, which is not configured by -default. - - -[[sysadmin_firmware_runtime_files_debian_repo]] -Debian Firmware Repository -^^^^^^^^^^^^^^^^^^^^^^^^^^ -Starting with Debian Bookworm ({pve} 8) non-free firmware (as defined by -https://www.debian.org/social_contract#guidelines[DFSG]) has been moved to the -newly created Debian repository component `non-free-firmware`. It contains -firmware for CPUs (called microcode) as well as other firmware. In the past, -CPUs repeatedly had security vulnerabilities beside other issues. Using this -update method (additional) to apply microcode updates is convenient, safe and -fast. - -To be able to install microcode updates or other firmware from the -`non-free-firmware` component, edit the file `/etc/apt/sources.list`, append -`non-free-firmware` to the end of each of the three Debian repository lines and -run `apt-get update`. - -To keep the CPU microcode up to date, depending on the vendor, install the -package `intel-microcode` or `amd64-microcode` and reboot your {pve} host -afterwards. +with the normal xref:system_software_updates[system updates (APT)], included +firmware of common hardware is automatically kept up to date. + +An additional xref:sysadmin_debian_firmware_repo[Debian Firmware Repository] +exists, but is not configured by default. + +If you try to install an additional firmware package but it conflicts, APT will +abort the installation. Perhaps the particular firmware can be obtained in +another way. + + +[[sysadmin_firmware_cpu]] +CPU Microcode Updates +~~~~~~~~~~~~~~~~~~~~~ +Microcode updates are intended to fix found security vulnerabilities and other +serious CPU bugs. While the CPU performance can be affected, a patched microcode +is usually still more performant than an unpatched microcode where the kernel +itself has to do mitigations. Depending on the CPU type, it is possible that +performance results of the flawed factory state can no longer be achieved +without knowingly running the CPU in an unsafe state. + +To get an overview of present CPU vulnerabilities and their mitigations, run +`lscpu`. Current real-world known vulnerabilities can only show up if the +{pve} host is xref:system_software_updates[up to date], its version not +xref:faq-support-table[end of life], and has at least been rebooted since the +last kernel update. + +Besides the recommended microcode update via +xref:sysadmin_firmware_persistent[persistent] BIOS/UEFI updates, there is also +an independent method via *Early OS Microcode Updates*. It is convenient to use +and also quite helpful when the motherboard vendor no longer provides BIOS/UEFI +updates. Regardless of the method in use, a reboot is always needed to apply a +microcode update. + + +Set up Early OS Microcode Updates +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +After enabling the +xref:sysadmin_debian_firmware_repo[Debian Firmware Repository], run +`apt install ` and reboot the {pve} host +xref:ha_manager_node_maintenance[safely] afterwards. + +Infrequent future microcode updates also require a reboot to be loaded. + + +Microcode Version +^^^^^^^^^^^^^^^^^ +To get the current running microcode revision for comparison or debugging +purposes: + +---- +# grep microcode /proc/cpuinfo | uniq +microcode : 0xf0 +---- + +Since a microcode package contains microcode for different CPU types, an updated +microcode for your CPU will only be included occasionally. Therefore, date +information from the package can also not be matched to a specific release date +from the vendor. + +If the microcode package is installed, the system has been rebooted, and the +microcode included in the package is more recent than that on the motherboard +and CPU, the message "microcode updated early" appears in the log: + +---- +# dmesg | grep microcode +[ 0.000000] microcode: microcode updated early to revision 0xf0, date = 2021-11-12 +[ 0.896580] microcode: Microcode Update Driver: v2.2. +---- + + +[[sysadmin_firmware_troubleshooting]] +Troubleshooting +^^^^^^^^^^^^^^^ +For debugging purposes, the set up Early OS Microcode Update applied regularly +at system boot can be temporarily disabled as follows: + +1. make sure that the host can be rebooted xref:ha_manager_node_maintenance[safely] +2. reboot the host to get to the GRUB menu (hold `SHIFT` if it is hidden) +3. at the desired {pve} boot entry press `E` +4. go to the line which starts with `linux` and append separated by a space +*`dis_ucode_ldr`* +5. press `CTRL-X` to boot this time without an Early OS Microcode Update + +If a problem related to a recent microcode update is suspected, a package +downgrade should be considered instead of package removal +(`apt purge `). Otherwise, a too old +xref:sysadmin_firmware_persistent[persisted] microcode might be loaded, even +though a more recent one would run without problems. + +A downgrade is possible if an earlier microcode package version is +available in the Debian repository, as shown in this example: + +---- +# apt list -a intel-microcode +Listing... Done +intel-microcode/stable-security,now 3.20230808.1~deb12u1 amd64 [installed] +intel-microcode/stable 3.20230512.1 amd64 +---- +---- +# apt install intel-microcode=3.202305* +... +Selected version '3.20230512.1' (Debian:12.1/stable [amd64]) for 'intel-microcode' +... +dpkg: warning: downgrading intel-microcode from 3.20230808.1~deb12u1 to 3.20230512.1 +... +intel-microcode: microcode will be updated at next boot +... +---- + +Make sure (again) that the host can be rebooted +xref:ha_manager_node_maintenance[safely]. To apply an older microcode +potentially included in the microcode package for your CPU type, reboot now. + +[TIP] +==== +It makes sense to hold the downgraded package for a while and try more recent +versions again at a later time. Even if the package version is the same in the +future, system updates may have fixed the experienced problem in the meantime. +---- +# apt-mark hold intel-microcode +intel-microcode set on hold. +---- +---- +# apt-mark unhold intel-microcode +# apt update +# apt upgrade +---- +==== diff --git a/pve-package-repos.adoc b/pve-package-repos.adoc index deda092..89d041e 100644 --- a/pve-package-repos.adoc +++ b/pve-package-repos.adoc @@ -208,6 +208,24 @@ See the respective https://pve.proxmox.com/wiki/Category:Ceph_Upgrade[upgrade guide] for details. +[[sysadmin_debian_firmware_repo]] +Debian Firmware Repository +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Starting with Debian Bookworm ({pve} 8) non-free firmware (as defined by +https://www.debian.org/social_contract#guidelines[DFSG]) has been moved to the +newly created Debian repository component `non-free-firmware`. + +Enable this repository if you want to set up +xref:sysadmin_firmware_cpu[Early OS Microcode Updates] or need additional +xref:sysadmin_firmware_runtime_files[Runtime Firmware Files] not already +included in the pre-installed package `pve-firmware`. + +To be able to install packages from this component, run +`editor /etc/apt/sources.list`, append `non-free-firmware` to the end of each +`.debian.org` repository line and run `apt update`. + + [[repos_secure_apt]] SecureApt -- 2.39.2 From d.csapak at proxmox.com Mon Oct 30 13:58:11 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Mon, 30 Oct 2023 13:58:11 +0100 Subject: [pve-devel] [PATCH manager 2/2] ui: BulkAction: add clear filters button In-Reply-To: <20231030125811.11759-1-d.csapak@proxmox.com> References: <20231030125811.11759-1-d.csapak@proxmox.com> Message-ID: <20231030125811.11759-2-d.csapak@proxmox.com> to be able to clear all of them at once Signed-off-by: Dominik Csapak --- sending this as a separate patch since i'm not sure if the button is worth it. if deemed appropriate could also be squashed into the previous patch www/manager6/window/BulkAction.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/www/manager6/window/BulkAction.js b/www/manager6/window/BulkAction.js index 333d5d30..44171390 100644 --- a/www/manager6/window/BulkAction.js +++ b/www/manager6/window/BulkAction.js @@ -162,6 +162,13 @@ Ext.define('PVE.window.BulkAction', { let tagList = Object.keys(tagMap).map(key => ({ value: key })); let haList = Object.keys(haMap).map(key => [key, key]); + let clearFilters = function() { + me.down('#namefilter').setValue(''); + ['name', 'status', 'pool', 'type', 'hastate', 'includetag', 'excludetag', 'vmid'].forEach((filter) => { + me.down(`#${filter}filter`).setValue(''); + }); + }; + let filterChange = function() { let nameValue = me.down('#namefilter').getValue(); let filterCount = 0; @@ -192,10 +199,13 @@ Ext.define('PVE.window.BulkAction', { } let fieldSet = me.down('#filters'); + let clearBtn = me.down('#clearBtn'); if (filterCount) { fieldSet.setTitle(Ext.String.format(gettext('Filters ({0})'), filterCount)); + clearBtn.setDisabled(false); } else { fieldSet.setTitle(gettext('Filters')); + clearBtn.setDisabled(true); } let filterFn = function(value) { @@ -383,6 +393,22 @@ Ext.define('PVE.window.BulkAction', { change: filterChange, }, }, + { + xtype: 'container', + layout: { + type: 'vbox', + align: 'end', + }, + items: [ + { + xtype: 'button', + itemId: 'clearBtn', + text: gettext('Clear Filters'), + disabled: true, + handler: clearFilters, + }, + ], + }, ], }, ], -- 2.39.2 From d.csapak at proxmox.com Mon Oct 30 13:58:10 2023 From: d.csapak at proxmox.com (Dominik Csapak) Date: Mon, 30 Oct 2023 13:58:10 +0100 Subject: [pve-devel] [PATCH manager 1/2] ui: BulkActions: rework filters and include tags Message-ID: <20231030125811.11759-1-d.csapak@proxmox.com> This moves the filters out of the grid header for the BulkActions and puts them into their own fieldset above the grid. With that, we can easily include a tags filter (one include and one exclude list). The filter fieldset is collapsible and shows the active filters in parenthesis. aside from that the filter should be the same as before. To achieve the result, we regenerate the filterFn on every change of every filter field, and set it with an 'id' so that only that filter is overridden each time. To make this work, we have to change three tiny details: * manually set the labelWidths for the fields, otherwise it breaks the ones in the fieldset. * change the counting in the 'getErrors' of the VMSelector, so that we actually get the count of selected VMs, not the one from the selectionModel * override the plugins to '' in the BulkAction windows, so that e.g. in the backup window we still have the filters in the grid header (we could add a filter box there too, but that is already very crowded and would take up too much space for now) Signed-off-by: Dominik Csapak --- www/manager6/form/VMSelector.js | 10 +- www/manager6/window/BulkAction.js | 261 +++++++++++++++++++++++++++++- 2 files changed, 269 insertions(+), 2 deletions(-) diff --git a/www/manager6/form/VMSelector.js b/www/manager6/form/VMSelector.js index d59847f2..8186ea99 100644 --- a/www/manager6/form/VMSelector.js +++ b/www/manager6/form/VMSelector.js @@ -18,6 +18,8 @@ Ext.define('PVE.form.VMSelector', { sorters: 'vmid', }, + userCls: 'proxmox-tags-full', + columnsDeclaration: [ { header: 'ID', @@ -80,6 +82,12 @@ Ext.define('PVE.form.VMSelector', { }, }, }, + { + header: gettext('Tags'), + dataIndex: 'tags', + renderer: tags => PVE.Utils.renderTags(tags, PVE.UIOptions.tagOverrides), + flex: 1, + }, { header: 'HA ' + gettext('Status'), dataIndex: 'hastate', @@ -186,7 +194,7 @@ Ext.define('PVE.form.VMSelector', { getErrors: function(value) { let me = this; if (!me.isDisabled() && me.allowBlank === false && - me.getSelectionModel().getCount() === 0) { + me.getValue().length === 0) { me.addBodyCls(['x-form-trigger-wrap-default', 'x-form-trigger-wrap-invalid']); return [gettext('No VM selected')]; } diff --git a/www/manager6/window/BulkAction.js b/www/manager6/window/BulkAction.js index 949e167e..333d5d30 100644 --- a/www/manager6/window/BulkAction.js +++ b/www/manager6/window/BulkAction.js @@ -59,12 +59,14 @@ Ext.define('PVE.window.BulkAction', { name: 'target', disallowedNodes: [me.nodename], fieldLabel: gettext('Target node'), + labelWidth: 300, allowBlank: false, onlineValidator: true, }, { xtype: 'proxmoxintegerfield', name: 'maxworkers', + labelWidth: 300, minValue: 1, maxValue: 100, value: 1, @@ -75,6 +77,7 @@ Ext.define('PVE.window.BulkAction', { xtype: 'fieldcontainer', fieldLabel: gettext('Allow local disk migration'), layout: 'hbox', + labelWidth: 300, items: [{ xtype: 'proxmoxcheckbox', name: 'with-local-disks', @@ -112,6 +115,7 @@ Ext.define('PVE.window.BulkAction', { { xtype: 'proxmoxcheckbox', name: 'force-stop', + labelWidth: 120, fieldLabel: gettext('Force Stop'), boxLabel: gettext('Force stop guest if shutdown times out.'), checked: true, @@ -121,6 +125,7 @@ Ext.define('PVE.window.BulkAction', { xtype: 'proxmoxintegerfield', name: 'timeout', fieldLabel: gettext('Timeout (s)'), + labelWidth: 120, emptyText: '180', minValue: 0, maxValue: 7200, @@ -129,6 +134,260 @@ Ext.define('PVE.window.BulkAction', { ); } + let statusMap = []; + let poolMap = []; + let haMap = []; + let tagMap = []; + PVE.data.ResourceStore.each((rec) => { + if (['qemu', 'lxc'].indexOf(rec.data.type) !== -1) { + statusMap[rec.data.status] = true; + } + if (rec.data.type === 'pool') { + poolMap[rec.data.pool] = true; + } + if (rec.data.hastate !== "") { + haMap[rec.data.hastate] = true; + } + if (rec.data.tags !== "") { + rec.data.tags.split(/[,; ]/).forEach((tag) => { + if (tag !== '') { + tagMap[tag] = true; + } + }); + } + }); + + let statusList = Object.keys(statusMap).map(key => [key, key]); + let poolList = Object.keys(poolMap).map(key => [key, key]); + let tagList = Object.keys(tagMap).map(key => ({ value: key })); + let haList = Object.keys(haMap).map(key => [key, key]); + + let filterChange = function() { + let nameValue = me.down('#namefilter').getValue(); + let filterCount = 0; + + if (nameValue !== '') { + filterCount++; + } + + let arrayFiltersData = []; + ['status', 'pool', 'type', 'hastate'].forEach((filter) => { + let selected = me.down(`#${filter}filter`).getValue() ?? []; + if (selected.length) { + filterCount++; + arrayFiltersData.push([filter, [...selected]]); + } + }); + let includeTags = me.down('#includetagfilter').getValue() ?? []; + if (includeTags.length) { + filterCount++; + } + let excludeTags = me.down('#excludetagfilter').getValue() ?? []; + if (excludeTags.length) { + filterCount++; + } + let vmid = me.down('#vmidfilter').getValue(); + if (vmid) { + filterCount++; + } + + let fieldSet = me.down('#filters'); + if (filterCount) { + fieldSet.setTitle(Ext.String.format(gettext('Filters ({0})'), filterCount)); + } else { + fieldSet.setTitle(gettext('Filters')); + } + + let filterFn = function(value) { + let name = value.data.name.toLowerCase().indexOf(nameValue.toLowerCase()) !== -1; + let arrayFilters = arrayFiltersData.every(([filter, selected]) => + !selected.length || selected.indexOf(value.data[filter]) !== -1); + let tags = value.data.tags.split(/[;, ]/).filter(t => !!t); + let includeFilter = !includeTags.length || tags.some(tag => includeTags.indexOf(tag) !== -1); + let excludeFilter = !excludeTags.length || tags.every(tag => excludeTags.indexOf(tag) === -1); + let vmidFilter = !vmid || value.data.vmid === vmid; + + return name && arrayFilters && includeFilter && excludeFilter && vmidFilter; + }; + me.down('#vms').getStore().setFilters({ + id: 'customFilter', + filterFn, + }); + me.down('#vms').checkChange(); + }; + + items.push({ + xtype: 'fieldset', + itemId: 'filters', + collapsible: true, + title: gettext('Filters'), + layout: 'hbox', + items: [ + { + xtype: 'container', + flex: 1, + padding: 5, + layout: { + type: 'vbox', + align: 'stretch', + }, + defaults: { + listeners: { + change: filterChange, + }, + isFormField: false, + }, + items: [ + { + fieldLabel: gettext("Name"), + itemId: 'namefilter', + xtype: 'textfield', + }, + { + xtype: 'combobox', + itemId: 'statusfilter', + fieldLabel: gettext("Status"), + emptyText: gettext('All'), + multiSelect: true, + editable: false, + store: statusList, + }, + { + xtype: 'combobox', + itemId: 'poolfilter', + fieldLabel: gettext("Pool"), + emptyText: gettext('All'), + editable: false, + multiSelect: true, + store: poolList, + }, + ], + }, + { + xtype: 'container', + layout: { + type: 'vbox', + align: 'stretch', + }, + flex: 1, + padding: 5, + defaults: { + listeners: { + change: filterChange, + }, + isFormField: false, + }, + items: [ + { + xtype: 'combobox', + itemId: 'typefilter', + fieldLabel: gettext("Type"), + emptyText: gettext('All'), + editable: false, + multiSelect: true, + store: [ + ['lxc', gettext('CT')], + ['qemu', gettext('VM')], + ], + }, + { + xtype: 'proxmoxComboGrid', + itemId: 'includetagfilter', + fieldLabel: gettext("Include Tags"), + emptyText: gettext('All'), + editable: false, + multiSelect: true, + valueField: 'value', + displayField: 'value', + listConfig: { + userCls: 'proxmox-tags-full', + columns: [ + { + dataIndex: 'value', + flex: 1, + renderer: value => + PVE.Utils.renderTags(value, PVE.UIOptions.tagOverrides), + }, + ], + }, + store: { + data: tagList, + }, + listeners: { + change: filterChange, + }, + }, + { + xtype: 'proxmoxComboGrid', + itemId: 'excludetagfilter', + fieldLabel: gettext("Exclude Tags"), + emptyText: gettext('None'), + multiSelect: true, + editable: false, + valueField: 'value', + displayField: 'value', + listConfig: { + userCls: 'proxmox-tags-full', + columns: [ + { + dataIndex: 'value', + flex: 1, + renderer: value => + PVE.Utils.renderTags(value, PVE.UIOptions.tagOverrides), + }, + ], + }, + store: { + data: tagList, + }, + listeners: { + change: filterChange, + }, + }, + ], + }, + { + xtype: 'container', + layout: { + type: 'vbox', + align: 'stretch', + }, + flex: 1, + padding: 5, + defaults: { + listeners: { + change: filterChange, + }, + isFormField: false, + }, + items: [ + { + xtype: 'combobox', + itemId: 'hastatefilter', + fieldLabel: gettext("HA status"), + emptyText: gettext('All'), + multiSelect: true, + editable: false, + store: haList, + listeners: { + change: filterChange, + }, + }, + { + xtype: 'pveGuestIDSelector', + itemId: 'vmidfilter', + fieldLabel: gettext('VMID'), + allowBlank: true, + name: 'vmid', + listeners: { + change: filterChange, + }, + }, + ], + }, + ], + }); + items.push({ xtype: 'vmselector', itemId: 'vms', @@ -137,6 +396,7 @@ Ext.define('PVE.window.BulkAction', { height: 300, selectAll: true, allowBlank: false, + plugins: '', nodename: me.nodename, action: me.action, listeners: { @@ -159,7 +419,6 @@ Ext.define('PVE.window.BulkAction', { align: 'stretch', }, fieldDefaults: { - labelWidth: me.action === 'migrateall' ? 300 : 120, anchor: '100%', }, items: items, -- 2.39.2 From t.lamprecht at proxmox.com Mon Oct 30 14:09:31 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Mon, 30 Oct 2023 14:09:31 +0100 Subject: [pve-devel] applied: [PATCH docs] ha-manager, software updates: add useful BlockIds In-Reply-To: <20231030112013.140969-1-a.zeidler@proxmox.com> References: <20231030112013.140969-1-a.zeidler@proxmox.com> Message-ID: <2fe4b0bd-9069-458e-8b5f-507988070871@proxmox.com> Am 30/10/2023 um 12:20 schrieb Alexander Zeidler: > Signed-off-by: Alexander Zeidler > --- > ha-manager.adoc | 5 +++++ > system-software-updates.adoc | 1 + > 2 files changed, 6 insertions(+) > > applied, with s/BlockIds/anchors/ in the subject, easier to understand when copied into the changelog (and asciidoc calls them anchors), thanks! From w.bumiller at proxmox.com Mon Oct 30 14:12:09 2023 From: w.bumiller at proxmox.com (Wolfgang Bumiller) Date: Mon, 30 Oct 2023 14:12:09 +0100 Subject: [pve-devel] [PATCH v2 guest-common 1/1] Add foreach_passthrough_device In-Reply-To: <20231024125554.131800-3-f.schauer@proxmox.com> References: <20231024125554.131800-1-f.schauer@proxmox.com> <20231024125554.131800-3-f.schauer@proxmox.com> Message-ID: On Tue, Oct 24, 2023 at 02:55:54PM +0200, Filip Schauer wrote: > Add a function to iterate over passthrough devices of a provided > container config. As container specific code this should be in pve-container. > > Signed-off-by: Filip Schauer > --- > src/PVE/AbstractConfig.pm | 44 +++++++++++++++++++++++++++++++++++++++ > 1 file changed, 44 insertions(+) > > diff --git a/src/PVE/AbstractConfig.pm b/src/PVE/AbstractConfig.pm > index a286b13..ed26e91 100644 > --- a/src/PVE/AbstractConfig.pm > +++ b/src/PVE/AbstractConfig.pm > @@ -484,6 +484,50 @@ sub foreach_volume { > return $class->foreach_volume_full($conf, undef, $func, @param); > } > > +sub foreach_passthrough_device { > + my ($class, $conf, $func, @param) = @_; > + > + foreach my $key (keys %$conf) { > + next if $key !~ m/^dev(\d+)$/; > + > + my $device = $class->parse_device($conf->{$key}); (`parse_device` does not exist in AbstractConfig) > + > + if (defined($device->{path})) { > + die "Device path $device->{path} does not start with /dev/\n" > + if $device->{path} !~ m!^/dev/!; IMO `parse_device()` should be responsible for the above check, and `verify_lxc_dev_string()` already does this, so the above check can just be dropped. > + > + my $sanitized_path = substr($conf->{$key}, 1); > + die "Device /$sanitized_path does not exist\n" unless (-e "/$sanitized_path"); Since we now have a property string and `parse_device()`, it would make more sense to pass `$device` rather than this path. In fact, thinking about this more I'm not sure passing the substring through was the best idea, since it's rather specific to how it'll end up in the lxc config. That's my bad. > + > + $func->($key, $sanitized_path, @param); > + } elsif (defined($device->{usbmapping})) { > + my $mapping = $device->{usbmapping}; > + my $map_devices = PVE::Mapping::USB::find_on_current_node($mapping); > + > + die "USB device mapping not found for '$mapping'\n" > + if !$map_devices || !scalar($map_devices->@*); > + > + die "More than one USB mapping per host not supported\n" > + if scalar($map_devices->@*) > 1; > + > + eval { > + PVE::Mapping::USB::assert_valid($mapping, $map_devices->[0]); > + }; > + if (my $err = $@) { > + die "USB Mapping invalid (hardware probably changed): $err\n"; > + } > + > + my $map_device = $map_devices->[0]; > + my $lsusb_output = `/usr/bin/lsusb -d $map_device->{id}`; > + my ($bus_id, $device_id) = $lsusb_output =~ /Bus (\d+) Device (\d+)/; ^ qx/backticks should be avoided, it'll be interpreted by a shell Also, we don't need this :-) See `PVE::SysFSTools::scan_usb()` which does essentially the same thing without invoking an external binary. > + > + $func->($key, "dev/bus/usb/$bus_id/$device_id", @param); So this will do... something :-) But unfortunately it won't deal with the *interesting* bits. So while I do like the idea of having such mappings, for it to make sense we'd also need to figure out the device specific nodes. Eg. for input devices we'd want to include `/dev/input/eventXY`, for eg. FIDO keys we'd want `/dev/hidraw*` devices. I'm not sure how much work we should put into this in the initial implementation, the question is mainly whether we want to do it like *this* in the first place and how much we plan to support. Or maybe it would make more sense to have specific entries for hidraw, event devices, block devices, ... instead? I'm not sure. > + } else { > + die "Either path or usbmapping has to be defined for $key"; > + } > + } > +} > + > # $volume_map is a hash of 'old_volid' => 'new_volid' pairs. > # This method replaces 'old_volid' by 'new_volid' throughout the config including snapshots, pending > # changes, unused volumes and vmstate volumes. > -- > 2.39.2 From t.lamprecht at proxmox.com Mon Oct 30 14:13:48 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Mon, 30 Oct 2023 14:13:48 +0100 Subject: [pve-devel] applied: [PATCH docs] sysadmin: revise firmware chapter, add firmware repo section In-Reply-To: <20231030121010.155191-1-a.zeidler@proxmox.com> References: <20231030121010.155191-1-a.zeidler@proxmox.com> Message-ID: <540ec811-b283-4b58-be95-5ad992c67ef3@proxmox.com> Am 30/10/2023 um 13:10 schrieb Alexander Zeidler: > Chapter "Firmware Updates": > * improve the structure and clarity of information provided > * mention which update methods are when available/recommended > * add information about the already pre-installed pve-firmware package > * emphasise the importance of CPU microcode updates, how to interpret > versions and how to recover a possibly unbootable system > * move info about non-free-firmware repo to "Package Repositories" > > Chapter "Package Repositories": > * add new section "Debian Firmware Repository" > > Signed-off-by: Alexander Zeidler > --- > Depends on "[PATCH docs] ha-manager, software updates: add useful BlockIds" > (https://lists.proxmox.com/pipermail/pve-devel/2023-October/059710.html) > > firmware-updates.adoc | 233 ++++++++++++++++++++++++++++------------- > pve-package-repos.adoc | 18 ++++ > 2 files changed, 179 insertions(+), 72 deletions(-) > > applied, thanks! but I restructured the "set up early os ?code updates" section to a actionable list, and dropped the subtle link to the HA maintenance mode there, such links are rather odd and for users it's not really clear what they want to tell them here, especially as it's only specific to HA for now (and how is the maintenance mode safe, but a normal reboot isn't?) If, you'd need to add a chapter/section about (safe) host reboot, that describes how things work, what one might want to look out for to ensure guest uptime, and in the future also things like full cluster poweroff (can be very tricky, e.g., with ceph and/or HA). I also reworded the part about detecting the version, and that not every update will necessarily include a update for one's CPU - feel free to follow-up if something got lost. From w.bumiller at proxmox.com Mon Oct 30 14:34:54 2023 From: w.bumiller at proxmox.com (Wolfgang Bumiller) Date: Mon, 30 Oct 2023 14:34:54 +0100 Subject: [pve-devel] [PATCH v2 container 1/1] Add device passthrough In-Reply-To: <20231024125554.131800-2-f.schauer@proxmox.com> References: <20231024125554.131800-1-f.schauer@proxmox.com> <20231024125554.131800-2-f.schauer@proxmox.com> Message-ID: <2rzmdty5ax4v5fssxkvjey4rfhzrcdmjzx5dti4m73lpbekqcf@3wna2j3j2jks> On Tue, Oct 24, 2023 at 02:55:53PM +0200, Filip Schauer wrote: > Add a dev[n] argument to the container config to pass devices through to > a container. A device can be passed by its path. Alternatively a mapped > USB device can be passed through with usbmapping=. > > Signed-off-by: Filip Schauer > --- > src/PVE/LXC.pm | 34 +++++++++++++++++++++++- > src/PVE/LXC/Config.pm | 60 +++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 93 insertions(+), 1 deletion(-) > > diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm > index c9b5ba7..a3ddb62 100644 > --- a/src/PVE/LXC.pm > +++ b/src/PVE/LXC.pm > @@ -5,7 +5,8 @@ use warnings; > > use Cwd qw(); > use Errno qw(ELOOP ENOTDIR EROFS ECONNREFUSED EEXIST); > -use Fcntl qw(O_RDONLY O_WRONLY O_NOFOLLOW O_DIRECTORY); > +use Fcntl qw(O_RDONLY O_WRONLY O_NOFOLLOW O_DIRECTORY :mode); > +use File::Basename; > use File::Path; > use File::Spec; > use IO::Poll qw(POLLIN POLLHUP); > @@ -639,6 +640,37 @@ sub update_lxc_config { > $raw .= "lxc.mount.auto = sys:mixed\n"; > } > > + # Clear passthrough directory from previous run > + my $passthrough_dir = "/var/lib/lxc/$vmid/passthrough"; > + File::Path::rmtree($passthrough_dir); I think we need to make a few changes here. First: we don't necessarily need this directory. Having a device list would certainly be nice, but it makes more sense to just have a file we can easily parse (possibly even just a json hash), like the `devices` file we already create in the pre-start hook, except prepared *for* the pre-start hook, which *should* be able to just `mknod` the devices right into the container's `/dev` on startup. We'd also avoid "lingering" device nodes with potentially harmful uid/permissions in /var, which is certainly better from a security POV. But note that we do need the `lxc.cgroup2.*` entries before starting the container in order to ensure the devices cgroup has the right permissions. > + > + PVE::LXC::Config->foreach_passthrough_device($conf, sub { > + my ($key, $sanitized_path) = @_; > + > + my $absolute_path = "/$sanitized_path"; > + my ($mode, $rdev) = (stat($absolute_path))[2, 6]; > + die "Could not find major and minor ids of device $absolute_path.\n" > + unless ($mode && $rdev); > + > + my $major = PVE::Tools::dev_t_major($rdev); > + my $minor = PVE::Tools::dev_t_minor($rdev); > + my $device_type_char = S_ISBLK($mode) ? 'b' : 'c'; > + my $passthrough_device_path = "$passthrough_dir/$sanitized_path"; > + File::Path::make_path(dirname($passthrough_device_path)); > + PVE::Tools::run_command([ > + '/usr/bin/mknod', > + '-m', '0660', > + $passthrough_device_path, > + $device_type_char, > + $major, > + $minor > + ]); It's probably worth adding a helper for the mknod syscall to `PVE::Tools`, there are a bunch of syscalls in there already. > + chown 100000, 100000, $passthrough_device_path if ($unprivileged); ^ This isn't necessarily the correct id. Users may have custom id mappings. `PVE::LXC::parse_id_maps($conf)` returns the mapping alongside the root uid and gid. (See for example `sub mount_all` for how it's used. > + > + $raw .= "lxc.cgroup2.devices.allow = $device_type_char $major:$minor rw\n"; > + $raw .= "lxc.mount.entry = $passthrough_device_path $sanitized_path none bind,create=file\n"; > + }); > + > # WARNING: DO NOT REMOVE this without making sure that loop device nodes > # cannot be exposed to the container with r/w access (cgroup perms). > # When this is enabled mounts will still remain in the monitor's namespace > diff --git a/src/PVE/LXC/Config.pm b/src/PVE/LXC/Config.pm > index 56e1f10..edd813e 100644 > --- a/src/PVE/LXC/Config.pm > +++ b/src/PVE/LXC/Config.pm > @@ -29,6 +29,7 @@ mkdir $lockdir; > mkdir "/etc/pve/nodes/$nodename/lxc"; > my $MAX_MOUNT_POINTS = 256; > my $MAX_UNUSED_DISKS = $MAX_MOUNT_POINTS; > +my $MAX_DEVICES = 256; > > # BEGIN implemented abstract methods from PVE::AbstractConfig > > @@ -908,6 +909,49 @@ for (my $i = 0; $i < $MAX_UNUSED_DISKS; $i++) { > } > } > > +PVE::JSONSchema::register_format('pve-lxc-dev-string', \&verify_lxc_dev_string); > +sub verify_lxc_dev_string { > + my ($dev, $noerr) = @_; > + > + if ( > + $dev =~ m@/\.\.?/@ || > + $dev =~ m@/\.\.?$@ || > + $dev !~ m!^/dev/! > + ) { > + return undef if $noerr; > + die "$dev is not a valid device path\n"; > + } > + > + return $dev; > +} > + > +my $dev_desc = { > + path => { > + optional => 1, > + type => 'string', > + default_key => 1, > + format => 'pve-lxc-dev-string', > + format_description => 'Path', > + description => 'Device to pass through to the container', > + verbose_description => 'Path to the device to pass through to the container' > + }, > + usbmapping => { > + optional => 1, > + type => 'string', > + format => 'pve-configid', > + format_description => 'mapping-id', > + description => 'The ID of a cluster wide USB mapping.' > + } > +}; > + > +for (my $i = 0; $i < $MAX_DEVICES; $i++) { > + $confdesc->{"dev$i"} = { > + optional => 1, > + type => 'string', format => $dev_desc, > + description => "Device to pass through to the container", > + } > +} > + > sub parse_pct_config { > my ($filename, $raw, $strict) = @_; > > @@ -1255,6 +1299,22 @@ sub parse_volume { > return; > } > > +sub parse_device { > + my ($class, $device_string, $noerr) = @_; > + > + my $res; > + eval { $res = PVE::JSONSchema::parse_property_string($dev_desc, $device_string) }; > + if ($@) { > + return undef if $noerr; > + die $@; > + } > + > + die "Either path or usbmapping has to be defined" > + unless (defined($res->{path}) || defined($res->{usbmapping})); > + > + return $res; > +} > + > sub print_volume { > my ($class, $key, $volume) = @_; > > -- > 2.39.2 > > > > _______________________________________________ > pve-devel mailing list > pve-devel at lists.proxmox.com > https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel > > From t.lamprecht at proxmox.com Mon Oct 30 17:30:15 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Mon, 30 Oct 2023 17:30:15 +0100 Subject: [pve-devel] [PATCH qemu-server] fix #4957: add vendor and product information passthrough for SCSI-Disks In-Reply-To: <20231025123719.38036-1-h.duerr@proxmox.com> References: <20231025123719.38036-1-h.duerr@proxmox.com> Message-ID: I mean, the properties are relatively straight forward, but some commit message would be still nice to have ? e.g., how did you check if the values propagate into the guest, can this On 25/10/2023 14:37, Hannes Duerr wrote: > Signed-off-by: Hannes Duerr > --- > PVE/QemuServer.pm | 12 ++++++++++++ > PVE/QemuServer/Drive.pm | 26 ++++++++++++++++++++++++++ > 2 files changed, 38 insertions(+) > > diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm > index 2cd8948..69be3af 100644 > --- a/PVE/QemuServer.pm > +++ b/PVE/QemuServer.pm > @@ -1482,6 +1482,18 @@ sub print_drivedevice_full { > } > $device .= ",wwn=$drive->{wwn}" if $drive->{wwn}; > > + # only scsi-hd supports passing vendor and product information should we error out then if it's set to other types? Not here, as it's already in the config, but erroring out on on config update/create could be better UX than accepting it, but then not using it. > + if ($devicetype eq 'hd') { > + if (my $vendor = $drive->{vendor}) { > + $vendor = URI::Escape::uri_unescape($vendor); > + $device .= ",vendor=$vendor"; > + } > + if (my $product = $drive->{product}) { > + $product = URI::Escape::uri_unescape($product); > + $device .= ",product=$product"; > + } > + } > + > } elsif ($drive->{interface} eq 'ide' || $drive->{interface} eq 'sata') { > my $maxdev = ($drive->{interface} eq 'sata') ? $PVE::QemuServer::Drive::MAX_SATA_DISKS : 2; > my $controller = int($drive->{index} / $maxdev); > diff --git a/PVE/QemuServer/Drive.pm b/PVE/QemuServer/Drive.pm > index e24ba12..20efc2f 100644 > --- a/PVE/QemuServer/Drive.pm > +++ b/PVE/QemuServer/Drive.pm > @@ -159,6 +159,28 @@ my %iothread_fmt = ( iothread => { > optional => 1, > }); > > +my %product_fmt = ( > + product => { > + type => 'string', > + format => 'urlencoded', > + format_description => 'product', > + maxLength => 40*3, # *3 since it's %xx url enoded wouldn't that be for the worst case, e.g., if one would only enter spaces or colons? And what about utf-8, that would be even bigger (not sure though of we support that here). > + description => "The drive's product name, url-encoded, up to 40 bytes long.", the 40 bytesa aren't checked though? We would need to do so manually after decoding it. > + optional => 1, > + }, > +); > + > +my %vendor_fmt = ( > + vendor => { > + type => 'string', > + format => 'urlencoded', > + format_description => 'vendor', > + maxLength => 40*3, # *3 since it's %xx url enoded > + description => "The drive's vendor name, url-encoded, up to 40 bytes long.", same here w.r.t. maxLength and 40 bytes max as above. > + optional => 1, > + }, > +); > + > my %model_fmt = ( > model => { > type => 'string', From t.lamprecht at proxmox.com Tue Oct 31 10:00:44 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 31 Oct 2023 10:00:44 +0100 Subject: [pve-devel] [PATCH v2 guest-common 1/1] Add foreach_passthrough_device In-Reply-To: References: <20231024125554.131800-1-f.schauer@proxmox.com> <20231024125554.131800-3-f.schauer@proxmox.com> Message-ID: <95f912b4-25b8-403e-8112-50ce689cf21a@proxmox.com> On 30/10/2023 14:12, Wolfgang Bumiller wrote: > On Tue, Oct 24, 2023 at 02:55:54PM +0200, Filip Schauer wrote: >> + >> + $func->($key, "dev/bus/usb/$bus_id/$device_id", @param); > > So this will do... something :-) > But unfortunately it won't deal with the *interesting* bits. > > So while I do like the idea of having such mappings, for it to make > sense we'd also need to figure out the device specific nodes. > Eg. for input devices we'd want to include `/dev/input/eventXY`, for eg. > FIDO keys we'd want `/dev/hidraw*` devices. > > I'm not sure how much work we should put into this in the initial > implementation, the question is mainly whether we want to do it like > *this* in the first place and how much we plan to support. Hmm, yeah, let's leave the USB mappings out for now, not really winning much here. > > Or maybe it would make more sense to have specific entries for hidraw, > event devices, block devices, ... instead? > I'm not sure. A device (node) mapping with a sub-type like block ("disk") devices might be indeed better then, as that could be re-used for VMs too, e.g., for passing through a /dev/sda (well, more likely via /dev/disk/by-id/...) device through. IIUC, that would be also more in line with Dominik's feedback. In any way, let's get raw pass-through right first, adding in mappings should be doable then, we added them only much later for VMs too, so I'd think one would have to actively try to really make that hard to do in the future here. From f.gleumes at proxmox.com Tue Oct 31 10:05:11 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Tue, 31 Oct 2023 10:05:11 +0100 Subject: [pve-devel] [PATCH manager v3 2/5] fix #4497: acme: add support for external account bindings In-Reply-To: <20231031090514.23629-1-f.gleumes@proxmox.com> References: <20231031090514.23629-1-f.gleumes@proxmox.com> Message-ID: <20231031090514.23629-3-f.gleumes@proxmox.com> Signed-off-by: Folke Gleumes --- No changes in v3 PVE/API2/ACMEAccount.pm | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/PVE/API2/ACMEAccount.pm b/PVE/API2/ACMEAccount.pm index b790843a..ec4eba24 100644 --- a/PVE/API2/ACMEAccount.pm +++ b/PVE/API2/ACMEAccount.pm @@ -115,6 +115,18 @@ __PACKAGE__->register_method ({ default => $acme_default_directory_url, optional => 1, }), + 'eab-kid' => { + type => 'string', + description => 'Key Identifier for External Account Binding.', + requires => 'eab-hmac-key', + optional => 1, + }, + 'eab-hmac-key' => { + type => 'string', + description => 'HMAC key for External Account Binding.', + requires => 'eab-kid', + optional => 1, + }, }, }, returns => { @@ -130,6 +142,9 @@ __PACKAGE__->register_method ({ my $account_file = "${acme_account_dir}/${account_name}"; mkdir $acme_account_dir if ! -e $acme_account_dir; + my $eab_kid = extract_param($param, 'eab-kid'); + my $eab_hmac_key = extract_param($param, 'eab-hmac-key'); + raise_param_exc({'name' => "ACME account config file '${account_name}' already exists."}) if -e $account_file; @@ -145,7 +160,17 @@ __PACKAGE__->register_method ({ print "Generating ACME account key..\n"; $acme->init(4096); print "Registering ACME account..\n"; - eval { $acme->new_account($param->{tos_url}, contact => $contact); }; + + my %info = (contact => $contact); + if (defined($eab_kid)) { + $info{eab} = { + kid => $eab_kid, + hmac_key => $eab_hmac_key + }; + } + + eval { $acme->new_account($param->{tos_url}, %info); }; + if (my $err = $@) { unlink $account_file; die "Registration failed: $err\n"; -- 2.39.2 From f.gleumes at proxmox.com Tue Oct 31 10:05:10 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Tue, 31 Oct 2023 10:05:10 +0100 Subject: [pve-devel] [PATCH acme v3 1/5] fix #4497: add support for external account bindings In-Reply-To: <20231031090514.23629-1-f.gleumes@proxmox.com> References: <20231031090514.23629-1-f.gleumes@proxmox.com> Message-ID: <20231031090514.23629-2-f.gleumes@proxmox.com> implementation acording to rfc8555 section 7.3.4 Signed-off-by: Folke Gleumes --- Changes since v2: Transport eab credentials in the info hash, but don't reuse it as payload. Instead, needed values are extracted and, if needed, transformed into a new hash. While this limits how the info hash can be used, AFAIK there are no fields that could be set when creating a new account [0], that can't be produced with this abi. [0] https://datatracker.ietf.org/doc/html/rfc8555#section-7.1.2 src/PVE/ACME.pm | 48 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/src/PVE/ACME.pm b/src/PVE/ACME.pm index 3f66182..bf5410d 100644 --- a/src/PVE/ACME.pm +++ b/src/PVE/ACME.pm @@ -7,10 +7,10 @@ use POSIX; use Data::Dumper; use Date::Parse; -use MIME::Base64 qw(encode_base64url); +use MIME::Base64 qw(encode_base64url decode_base64); use File::Path qw(make_path); use JSON; -use Digest::SHA qw(sha256 sha256_hex); +use Digest::SHA qw(sha256 sha256_hex hmac_sha256); use HTTP::Request; use LWP::UserAgent; @@ -251,6 +251,28 @@ sub jws { }; } +# EAB signing using the HS256 alg (HMAC/SHA256). +my sub external_account_binding_jws { + my ($eab_kid, $eab_hmac_key, $jwk, $url) = @_; + + my $protected = { + alg => 'HS256', + kid => $eab_kid, + url => $url, + }; + $protected = encode(tojs($protected)); + + my $payload = encode(tojs($jwk)); + my $signdata = "$protected.$payload"; + my $signature = encode(hmac_sha256($signdata, $eab_hmac_key)); + + return { + protected => $protected, + payload => $payload, + signature => $signature, + }; +} + sub __get_result { my ($resp, $code, $plain) = @_; @@ -300,8 +322,8 @@ sub list_methods { } # return (optional) meta directory entry. -# this is public because it might contain the ToS, which should be displayed -# and agreed to before creating an account +# this is public because it might contain the ToS and EAB requirements, +# which have to be considered before creating an account sub get_meta { my ($self) = @_; my $methods = $self->__get_methods(); @@ -331,6 +353,8 @@ sub __new_account { # Create a new account using data in %info. # Optionally pass $tos_url to agree to the given Terms of Service +# %info must have at least a 'contact' field and may have a 'eab' field +# containing a hash with 'kid' and 'hmac_key' set. # POST to newAccount endpoint # Expects a '201 Created' reply # Saves and returns the account data @@ -338,12 +362,24 @@ sub new_account { my ($self, $tos_url, %info) = @_; my $url = $self->_method('newAccount'); + my %payload = ( contact => $info{contact} ); + + if (defined($info{eab})) { + my $eab_hmac_key = decode_base64($info{eab}->{hmac_key}); + $payload{externalAccountBinding} = external_account_binding_jws( + $info{eab}->{kid}, + $eab_hmac_key, + $self->jwk(), + $url + ); + } + if ($tos_url) { $self->{tos} = $tos_url; - $info{termsOfServiceAgreed} = JSON::true; + $payload{termsOfServiceAgreed} = JSON::true; } - return $self->__new_account(201, $url, 1, %info); + return $self->__new_account(201, $url, 1, %payload); } # Update existing account with new %info -- 2.39.2 From f.gleumes at proxmox.com Tue Oct 31 10:05:14 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Tue, 31 Oct 2023 10:05:14 +0100 Subject: [pve-devel] [PATCH manager v3 5/5] ui/acme: switch to new meta endpoint In-Reply-To: <20231031090514.23629-1-f.gleumes@proxmox.com> References: <20231031090514.23629-1-f.gleumes@proxmox.com> Message-ID: <20231031090514.23629-6-f.gleumes@proxmox.com> Besides the switch from tos to meta endpoint, this fixes a visual bug, where the 'Accept TOS' button would show up, even if no ToS was needed. Signed-off-by: Folke Gleumes --- Changes since v2: fixed tabs/spaces www/manager6/node/ACME.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/www/manager6/node/ACME.js b/www/manager6/node/ACME.js index 9f1dabce..21137b1a 100644 --- a/www/manager6/node/ACME.js +++ b/www/manager6/node/ACME.js @@ -79,15 +79,19 @@ Ext.define('PVE.node.ACMEAccountCreate', { checkbox.setHidden(true); Proxmox.Utils.API2Request({ - url: '/cluster/acme/tos', + url: '/cluster/acme/meta', method: 'GET', params: { directory: value, }, success: function(response, opt) { - field.setValue(response.result.data); - disp.setValue(response.result.data); - checkbox.setHidden(false); + if (response.result.data.termsOfService) { + field.setValue(response.result.data.termsOfService); + disp.setValue(response.result.data.termsOfService); + checkbox.setHidden(false); + } else { + disp.setValue(undefined); + } }, failure: function(response, opt) { Ext.Msg.alert(gettext('Error'), response.htmlStatus); -- 2.39.2 From f.gleumes at proxmox.com Tue Oct 31 10:05:13 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Tue, 31 Oct 2023 10:05:13 +0100 Subject: [pve-devel] [PATCH manager v3 4/5] fix #4497: cli/acme: detect eab and ask for credentials In-Reply-To: <20231031090514.23629-1-f.gleumes@proxmox.com> References: <20231031090514.23629-1-f.gleumes@proxmox.com> Message-ID: <20231031090514.23629-5-f.gleumes@proxmox.com> Since external account binding is advertised the same way as the ToS, it can be detected when creating an account and asked for if needed. Signed-off-by: Folke Gleumes --- No changes in v3 PVE/CLI/pvenode.pm | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/PVE/CLI/pvenode.pm b/PVE/CLI/pvenode.pm index acef6c3b..a6fbb34e 100644 --- a/PVE/CLI/pvenode.pm +++ b/PVE/CLI/pvenode.pm @@ -83,6 +83,7 @@ __PACKAGE__->register_method({ code => sub { my ($param) = @_; + my $custom_directory = 0; if (!$param->{directory}) { my $directories = PVE::API2::ACMEAccount->get_directories({}); print "Directory endpoints:\n"; @@ -100,6 +101,7 @@ __PACKAGE__->register_method({ $selection = $1; if ($selection == $i) { $param->{directory} = $term->readline("Enter custom URL: "); + $custom_directory = 1; return; } elsif ($selection < $i && $selection >= 0) { $param->{directory} = $directories->[$selection]->{url}; @@ -117,8 +119,9 @@ __PACKAGE__->register_method({ } } print "\nAttempting to fetch Terms of Service from '$param->{directory}'..\n"; - my $tos = PVE::API2::ACMEAccount->get_tos({ directory => $param->{directory} }); - if ($tos) { + my $meta = PVE::API2::ACMEAccount->get_meta({ directory => $param->{directory} }); + if ($meta->{termsOfService}) { + my $tos = $meta->{termsOfService}; print "Terms of Service: $tos\n"; my $term = Term::ReadLine->new('pvenode'); my $agreed = $term->readline('Do you agree to the above terms? [y|N]: '); @@ -129,6 +132,25 @@ __PACKAGE__->register_method({ } else { print "No Terms of Service found, proceeding.\n"; } + + my $eab_enabled = $meta->{externalAccountRequired}; + if (!$eab_enabled && $custom_directory) { + my $term = Term::ReadLine->new('pvenode'); + my $agreed = $term->readline('Do you want to use external account binding? [y|N]: '); + $eab_enabled = ($agreed =~ /^y$/i); + } elsif ($eab_enabled) { + print "The CA requires external account binding.\n"; + } + if ($eab_enabled) { + print "You should have received a key id and a key from your CA.\n"; + my $term = Term::ReadLine->new('pvenode'); + my $eab_kid = $term->readline('Enter EAB key id: '); + my $eab_hmac_key = $term->readline('Enter EAB key: '); + + $param->{'eab-kid'} = $eab_kid; + $param->{'eab-hmac-key'} = $eab_hmac_key; + } + print "\nAttempting to register account with '$param->{directory}'..\n"; $upid_exit->(PVE::API2::ACMEAccount->register_account($param)); -- 2.39.2 From f.gleumes at proxmox.com Tue Oct 31 10:05:12 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Tue, 31 Oct 2023 10:05:12 +0100 Subject: [pve-devel] [PATCH manager v3 3/5] api/acme: deprecate tos endpoint in favor of meta In-Reply-To: <20231031090514.23629-1-f.gleumes@proxmox.com> References: <20231031090514.23629-1-f.gleumes@proxmox.com> Message-ID: <20231031090514.23629-4-f.gleumes@proxmox.com> The ToS endpoint ignored data that is needed to detect if EAB needs to be used. Instead of adding a new endpoint that does the same request, the tos endpoint is deprecated and replaced by the meta endpoint, that returns all information returned by the directory. Signed-off-by: Folke Gleumes --- No changes in v3 PVE/API2/ACMEAccount.pm | 56 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/PVE/API2/ACMEAccount.pm b/PVE/API2/ACMEAccount.pm index ec4eba24..bc45d5ab 100644 --- a/PVE/API2/ACMEAccount.pm +++ b/PVE/API2/ACMEAccount.pm @@ -62,6 +62,7 @@ __PACKAGE__->register_method ({ return [ { name => 'account' }, { name => 'tos' }, + { name => 'meta' }, { name => 'directories' }, { name => 'plugins' }, { name => 'challenge-schema' }, @@ -333,11 +334,12 @@ __PACKAGE__->register_method ({ return $update_account->($param, 'deactivate', status => 'deactivated'); }}); +# TODO: deprecated, remove with pve 9 __PACKAGE__->register_method ({ name => 'get_tos', path => 'tos', method => 'GET', - description => "Retrieve ACME TermsOfService URL from CA.", + description => "Retrieve ACME TermsOfService URL from CA. Deprecated, please use /cluster/acme/meta.", permissions => { user => 'all' }, parameters => { additionalProperties => 0, @@ -364,6 +366,58 @@ __PACKAGE__->register_method ({ return $meta ? $meta->{termsOfService} : undef; }}); +__PACKAGE__->register_method ({ + name => 'get_meta', + path => 'meta', + method => 'GET', + description => "Retrieve ACME Directory Meta Information", + permissions => { user => 'all' }, + parameters => { + additionalProperties => 0, + properties => { + directory => get_standard_option('pve-acme-directory-url', { + default => $acme_default_directory_url, + optional => 1, + }), + }, + }, + returns => { + type => 'object', + additionalProperties => 1, + properties => { + termsOfService => { + type => 'string', + optional => 1, + description => 'ACME TermsOfService URL.', + }, + externalAccountRequired => { + type => 'boolean', + optional => 1, + description => 'EAB Required' + }, + website => { + type => 'string', + optional => 1, + description => 'URL to more information about the ACME server.' + }, + caaIdentities => { + type => 'string', + optional => 1, + description => 'Hostnames referring to the ACME servers.' + }, + }, + }, + code => sub { + my ($param) = @_; + + my $directory = extract_param($param, 'directory') // $acme_default_directory_url; + + my $acme = PVE::ACME->new(undef, $directory); + my $meta = $acme->get_meta(); + + return $meta; + }}); + __PACKAGE__->register_method ({ name => 'get_directories', path => 'directories', -- 2.39.2 From f.gleumes at proxmox.com Tue Oct 31 10:05:09 2023 From: f.gleumes at proxmox.com (Folke Gleumes) Date: Tue, 31 Oct 2023 10:05:09 +0100 Subject: [pve-devel] [PATCH acme v3 0/5] fix #4497: add support for external account bindings Message-ID: <20231031090514.23629-1-f.gleumes@proxmox.com> Changes since v2: * reverted the new_account abi to be non breaking Changes since v1: * fixed nit's * expanded meta endpoint by all return values defined in the rfc * expanded new_account signature by field for eab credentials * allow for eab even if not required This patch series adds functionality to use acme directiories that require the use of external account binding, as specified in rfc 8555 section 7.3.4. To avoid code duplication and redundant calls to the CA, the `/cluster/acme/tos` endpoint has been deprecated and it's function will be covered by the new `/cluster/acme/meta` endpoint, which exposes all meta information provided by the CA, including the flag indicating that EAB needs to be used. The underlying call to the CA remains the same. The CLI interface will only ask for the EAB credentials if needed, similar to how it works for the ToS. The patches have been tested to work with and without EAB by using pebble [0] as the CA. [0] https://github.com/letsencrypt/pebble acme: Folke Gleumes (1): fix #4497: add support for external account bindings src/PVE/ACME.pm | 48 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 6 deletions(-) manager: Folke Gleumes (4): fix #4497: acme: add support for external account bindings api/acme: deprecate tos endpoint in favor of meta fix #4497: cli/acme: detect eab and ask for credentials ui/acme: switch to new meta endpoint PVE/API2/ACMEAccount.pm | 83 ++++++++++++++++++++++++++++++++++++++- PVE/CLI/pvenode.pm | 26 +++++++++++- www/manager6/node/ACME.js | 12 ++++-- 3 files changed, 113 insertions(+), 8 deletions(-) -- 2.39.2 From t.lamprecht at proxmox.com Tue Oct 31 10:52:12 2023 From: t.lamprecht at proxmox.com (Thomas Lamprecht) Date: Tue, 31 Oct 2023 10:52:12 +0100 Subject: [pve-devel] applied: [PATCH pve-ceph] fix compatibility with CPUs not supporting SSE 4.1 instructions In-Reply-To: <20230918154656.2717366-1-s.hanreich@proxmox.com> References: <20230918154656.2717366-1-s.hanreich@proxmox.com> Message-ID: On 18/09/2023 17:46, Stefan Hanreich wrote: > One of our users ran into issues with running Ceph on older CPU > architectures [1]. This is apparently due to a bug in gcc-12 that > leads to SSE 4.1 instructions always being executed rather than > dynamically dispatching functions using those instructions. Those > binaries then break on older CPUs that do not support this instruction > set. > > I've ran some benchmarks with `rados bench` against our last release > (18.2.0-pve2) and this new version. The commands were taken from our > latest Ceph benchmarking paper [2]. The results showed that this patch > does not lead to performance regressions on newer hardware. > > 18.2.0-pve2 this patch > Read EC 4574.28 4651.95 > Write EC 3739.59 3773.87 > Read Replicated 5345.34 5568.41 > Write Replicated 4123.28 4066.19 > (numbers correspond to bandwidth in MB/s) > > [1] https://forum.proxmox.com/threads/proxmox-8-ceph-quincy-monitor-no-longer-working-on-amd-opteron-2427.129613 > [2] https://www.proxmox.com/en/downloads/proxmox-virtual-environment/documentation/proxmox-ve-ceph-benchmark-2020-09 > > Signed-off-by: Stefan Hanreich > --- > ...y-with-CPUs-not-supporting-SSE-4.1-i.patch | 32 +++++++++++++++++++ > patches/series | 1 + > 2 files changed, 33 insertions(+) > create mode 100644 patches/0015-fix-compatibility-with-CPUs-not-supporting-SSE-4.1-i.patch > > applied, with a reworded commit message, shifting the blame to the combination of gf-complete and gcc-12, as the former does some rather funky stuff too, thanks! Having this reported to the GCC and/or gf-complete people, ideally with a reduced example (compiling ceph is a bit overkill ;-) Using elfx86exts [0] as mentioned in the debian bug [1] should be enough to ensure your reduced example is still affected and contains SSE 4.1 instructions. [0]: https://github.com/pkgw/elfx86exts [1]: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1012935#10 From w.bumiller at proxmox.com Tue Oct 31 12:04:47 2023 From: w.bumiller at proxmox.com (Wolfgang Bumiller) Date: Tue, 31 Oct 2023 12:04:47 +0100 Subject: [pve-devel] [PATCH qemu-server] fix #4957: add vendor and product information passthrough for SCSI-Disks In-Reply-To: References: <20231025123719.38036-1-h.duerr@proxmox.com> Message-ID: <7656jxzfcvf7khoiyhcrnqjuzzoquq2kgulsvv3dkx2ex6nkbe@qsreiptl4ork> On Mon, Oct 30, 2023 at 05:30:15PM +0100, Thomas Lamprecht wrote: > I mean, the properties are relatively straight forward, but some commit > message would be still nice to have ? e.g., how did you check if the values > propagate into the guest, can this > > On 25/10/2023 14:37, Hannes Duerr wrote: > > Signed-off-by: Hannes Duerr > > --- > > PVE/QemuServer.pm | 12 ++++++++++++ > > PVE/QemuServer/Drive.pm | 26 ++++++++++++++++++++++++++ > > 2 files changed, 38 insertions(+) > > > > diff --git a/PVE/QemuServer.pm b/PVE/QemuServer.pm > > index 2cd8948..69be3af 100644 > > --- a/PVE/QemuServer.pm > > +++ b/PVE/QemuServer.pm > > @@ -1482,6 +1482,18 @@ sub print_drivedevice_full { > > } > > $device .= ",wwn=$drive->{wwn}" if $drive->{wwn}; > > > > + # only scsi-hd supports passing vendor and product information > > should we error out then if it's set to other types? Not here, as it's > already in the config, but erroring out on on config update/create could > be better UX than accepting it, but then not using it. > > > + if ($devicetype eq 'hd') { > > + if (my $vendor = $drive->{vendor}) { > > + $vendor = URI::Escape::uri_unescape($vendor); > > + $device .= ",vendor=$vendor"; > > + } > > + if (my $product = $drive->{product}) { > > + $product = URI::Escape::uri_unescape($product); > > + $device .= ",product=$product"; > > + } > > + } > > + > > } elsif ($drive->{interface} eq 'ide' || $drive->{interface} eq 'sata') { > > my $maxdev = ($drive->{interface} eq 'sata') ? $PVE::QemuServer::Drive::MAX_SATA_DISKS : 2; > > my $controller = int($drive->{index} / $maxdev); > > diff --git a/PVE/QemuServer/Drive.pm b/PVE/QemuServer/Drive.pm > > index e24ba12..20efc2f 100644 > > --- a/PVE/QemuServer/Drive.pm > > +++ b/PVE/QemuServer/Drive.pm > > @@ -159,6 +159,28 @@ my %iothread_fmt = ( iothread => { > > optional => 1, > > }); > > > > +my %product_fmt = ( > > + product => { > > + type => 'string', > > + format => 'urlencoded', > > + format_description => 'product', > > + maxLength => 40*3, # *3 since it's %xx url enoded > > wouldn't that be for the worst case, e.g., if one would only enter spaces > or colons? And what about utf-8, that would be even bigger (not sure though > of we support that here). > > > + description => "The drive's product name, url-encoded, up to 40 bytes long.", > > the 40 bytesa aren't checked though? We would need to do so manually > after decoding it. AFAICT that's how it is for the other existing `format => 'urlencoded'` options. Our schema validation isn't smart enough for this currently. We *could* add this sort of thing, though, if we wanted to? The registered format verifiers could get the value's schema passed as an additional parameter, then the `pve_verify_urlencoded()` sub in JSONSchema.pm could check the decoded length against `$schema->{maxLength} / 3` or (or a custom "extension" `decodedLength => `, but that's actually just superfluous in a way... Unfortunately we can't put the lower number in 'maxLength' since that's checked independently of the validation sub). From w.bumiller at proxmox.com Tue Oct 31 12:19:59 2023 From: w.bumiller at proxmox.com (Wolfgang Bumiller) Date: Tue, 31 Oct 2023 12:19:59 +0100 Subject: [pve-devel] [PATCH pve-network] Fix #4917: evpn: forbid vlan-aware bridge In-Reply-To: <20231027115328.654782-1-aderumier@odiso.com> References: <20231027115328.654782-1-aderumier@odiso.com> Message-ID: On Fri, Oct 27, 2023 at 01:53:28PM +0200, Alexandre Derumier wrote: > Do it on vnet update instead throwing a warning at config generation. > > Signed-off-by: Alexandre Derumier > --- > src/PVE/Network/SDN/Zones/EvpnPlugin.pm | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm > index 5e9f8ec..655a9f0 100644 > --- a/src/PVE/Network/SDN/Zones/EvpnPlugin.pm > +++ b/src/PVE/Network/SDN/Zones/EvpnPlugin.pm > @@ -117,7 +117,6 @@ sub generate_sdn_config { > > die "missing vxlan tag" if !$tag; > die "missing controller" if !$controller; > - warn "vlan-aware vnet can't be enabled with evpn plugin" if $vnet->{vlanaware}; What are the symptoms of this? If it's just ignored there could be existing setups where this might still be set, in which case we should keep the warning. From the code it seems the places where this property is used from the this package's base packages gets overridden so it would be ignored. But if it didn't work and those setups wouldn't stick around then it makes sense to remove it. > > my @peers = PVE::Tools::split_list($controller->{'peers'}); > > @@ -309,6 +308,7 @@ sub vnet_update_hook { > > raise_param_exc({ tag => "missing vxlan tag"}) if !defined($tag); > raise_param_exc({ tag => "vxlan tag max value is 16777216"}) if $tag > 16777216; > + raise_param_exc({ 'vlan-aware' => "vlan-aware option can't be enabled with evpn"}) if $vnet->{vlanaware}; > > # verify that tag is not already defined globally (vxlan-id are unique) > foreach my $id (keys %{$vnet_cfg->{ids}}) { > -- > 2.39.2 From a.lauterer at proxmox.com Tue Oct 31 12:27:41 2023 From: a.lauterer at proxmox.com (Aaron Lauterer) Date: Tue, 31 Oct 2023 12:27:41 +0100 Subject: [pve-devel] [PATCH manager 3/3] pvereport: add sdn config directory In-Reply-To: <20231003113638.2221189-1-a.lauterer@proxmox.com> References: <20231003113638.2221189-1-a.lauterer@proxmox.com> Message-ID: <20231031112741.1782134-1-a.lauterer@proxmox.com> the /etc/pve/sdn directory contains the config files, not just what they translate to in interface configs (/etc/network/interfaces.d/snd). The current way will also include dotifiles that may contain the current/running state. Which can be useful to troubleshoot. Signed-off-by: Aaron Lauterer --- the other 2 patches still apply, therefore I sent this as a nr 3 in this series PVE/Report.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/PVE/Report.pm b/PVE/Report.pm index 34ddd204..12d7d9a5 100644 --- a/PVE/Report.pm +++ b/PVE/Report.pm @@ -77,6 +77,7 @@ my $init_report_cmds = sub { 'ip -details -6 route show', 'cat /etc/network/interfaces', sub { dir2text('/etc/network/interfaces.d/', '.*') }, + sub { dir2text('/etc/pve/sdn/', '.*') }, ], }, firewall => { -- 2.39.2 From c.heiss at proxmox.com Tue Oct 31 13:10:52 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 31 Oct 2023 13:10:52 +0100 Subject: [pve-devel] [PATCH installer v3 0/8] fix #4829: set up lower default limit for ZFS ARC in installer Message-ID: <20231031121108.1130299-1-c.heiss@proxmox.com> Fixes #4829. Introduces a new ZFS install option `arc_max` (aptly named after the corresponding module option `zfs_arc_max`). For PVE installations, this can be adjusted when creating a ZFS RAID under "Advanced Options". The default value is choosen as 10% of system memory, clamped to between 64 MiB as lower limit and 16 GiB as upper limit. For PBS and PMG, the option is (currently) hidden. If the option is set to a non-zero value, a new file /etc/modprobe.d/zfs.conf gets written during install, setting the `zfs_arc_max` module option as appropriate. Tested by installing PVE, PBS and PMG, using both the GUI and TUI installer. For PVE, checked that the `zfs` module option gets correctly written & applied, the latter by looking at the output of `arc_summary`. For PBS and PMG, verified that no modprobe options file is created and the ARC size is set to default. v1: https://lists.proxmox.com/pipermail/pve-devel/2023-August/058830.html v2: https://lists.proxmox.com/pipermail/pve-devel/2023-October/059606.html Notable changes v1 -> v2: * rebased on latest master * fix arc_max value set in TUI not being applied correctly Notable changes v2 -> v3: * rebased on latest master * new patch explaining query_total_memory, which is used extensively in this patchset * new patch unifying product handling a bit * documented all calculations better w.r.t. to their units * moved modprobe setup into separate sub Christoph Heiss (8): run env: add comment for query_total_memory() tui: views: add optional suffix label for `NumericEditView`s tui: bootdisk: simplify product handling by passing the config directly fix #4829: install: add new ZFS `arc_max` setup option fix #4829: proxinstall: expose new `arc_max` ZFS option for PVE installations fix #4829: test: add tests for new zfs_arc_max calculations fix #4829: tui: setup: add new ZFS `arc_max` option fix #4829: tui: bootdisk: expose new `arc_max` ZFS option for PVE installations Makefile | 3 + Proxmox/Install.pm | 14 +++ Proxmox/Install/Config.pm | 1 + Proxmox/Install/RunEnv.pm | 47 ++++++++ debian/control | 1 + proxinstall | 15 +++ proxmox-tui-installer/src/main.rs | 2 +- proxmox-tui-installer/src/options.rs | 57 +++++++++- proxmox-tui-installer/src/setup.rs | 2 + proxmox-tui-installer/src/views/bootdisk.rs | 116 +++++++++++++------- proxmox-tui-installer/src/views/mod.rs | 104 ++++++++++++++---- test/Makefile | 10 ++ test/zfs-arc-max.pl | 81 ++++++++++++++ 13 files changed, 386 insertions(+), 67 deletions(-) create mode 100644 test/Makefile create mode 100755 test/zfs-arc-max.pl -- 2.41.0 From c.heiss at proxmox.com Tue Oct 31 13:10:55 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 31 Oct 2023 13:10:55 +0100 Subject: [pve-devel] [PATCH installer v3 3/8] tui: bootdisk: simplify product handling by passing the config directly In-Reply-To: <20231031121108.1130299-1-c.heiss@proxmox.com> References: <20231031121108.1130299-1-c.heiss@proxmox.com> Message-ID: <20231031121108.1130299-4-c.heiss@proxmox.com> No functional changes. Signed-off-by: Christoph Heiss --- Changes v2 -> v3: * new patch proxmox-tui-installer/src/views/bootdisk.rs | 50 ++++++++++++--------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index ba08c8b..3addd6c 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -134,10 +134,9 @@ impl AdvancedBootdiskOptionsView { .child(DummyView.full_width()); match &options.advanced { - AdvancedBootdiskOptions::Lvm(lvm) => view.add_child(LvmBootdiskOptionsView::new( - lvm, - product_conf.product == ProxmoxProduct::PVE, - )), + AdvancedBootdiskOptions::Lvm(lvm) => { + view.add_child(LvmBootdiskOptionsView::new(lvm, &product_conf)) + } AdvancedBootdiskOptions::Zfs(zfs) => { view.add_child(ZfsBootdiskOptionsView::new(disks, zfs)) } @@ -150,10 +149,8 @@ impl AdvancedBootdiskOptionsView { } fn fstype_on_submit(siv: &mut Cursive, disks: &[Disk], fstype: &FsType) { - let is_pve = siv - .user_data::() - .map(|state| state.setup_info.config.product == ProxmoxProduct::PVE) - .unwrap_or_default(); + let state = siv.user_data::().unwrap(); + let product_conf = state.setup_info.config.clone(); siv.call_on_name("advanced-bootdisk-options-dialog", |view: &mut Dialog| { if let Some(AdvancedBootdiskOptionsView { view }) = @@ -161,18 +158,15 @@ impl AdvancedBootdiskOptionsView { { view.remove_child(3); match fstype { - FsType::Ext4 | FsType::Xfs => view.add_child(LvmBootdiskOptionsView::new( - &LvmBootdiskOptions::defaults_from(&disks[0]), - is_pve, - )), - FsType::Zfs(_) => view.add_child(ZfsBootdiskOptionsView::new( - disks, - &ZfsBootdiskOptions::defaults_from(disks), - )), - FsType::Btrfs(_) => view.add_child(BtrfsBootdiskOptionsView::new( - disks, - &BtrfsBootdiskOptions::defaults_from(disks), - )), + FsType::Ext4 | FsType::Xfs => view.add_child( + LvmBootdiskOptionsView::new_with_defaults(&disks[0], &product_conf), + ), + FsType::Zfs(_) => { + view.add_child(ZfsBootdiskOptionsView::new_with_defaults(disks)) + } + FsType::Btrfs(_) => { + view.add_child(BtrfsBootdiskOptionsView::new_with_defaults(disks)) + } } } }); @@ -261,7 +255,9 @@ struct LvmBootdiskOptionsView { } impl LvmBootdiskOptionsView { - fn new(options: &LvmBootdiskOptions, show_extra_fields: bool) -> Self { + fn new(options: &LvmBootdiskOptions, product_conf: &ProductConfig) -> Self { + let show_extra_fields = product_conf.product == ProxmoxProduct::PVE; + // TODO: Set maximum accordingly to disk size let view = FormView::new() .child( @@ -295,6 +291,10 @@ impl LvmBootdiskOptionsView { } } + fn new_with_defaults(disk: &Disk, product_conf: &ProductConfig) -> Self { + Self::new(&LvmBootdiskOptions::defaults_from(disk), product_conf) + } + fn get_values(&mut self) -> Option { let min_lvm_free_id = if self.has_extra_fields { 4 } else { 2 }; @@ -481,6 +481,10 @@ impl BtrfsBootdiskOptionsView { Self { view } } + fn new_with_defaults(disks: &[Disk]) -> Self { + Self::new(disks, &BtrfsBootdiskOptions::defaults_from(disks)) + } + fn get_values(&mut self) -> Option<(Vec, BtrfsBootdiskOptions)> { let (disks, selected_disks) = self.view.get_disks_and_selection()?; let disk_size = self.view.inner_mut()?.get_value::(0)?; @@ -543,6 +547,10 @@ impl ZfsBootdiskOptionsView { Self { view } } + fn new_with_defaults(disks: &[Disk]) -> Self { + Self::new(disks, &ZfsBootdiskOptions::defaults_from(disks)) + } + fn get_values(&mut self) -> Option<(Vec, ZfsBootdiskOptions)> { let (disks, selected_disks) = self.view.get_disks_and_selection()?; let view = self.view.inner_mut()?; -- 2.42.0 From c.heiss at proxmox.com Tue Oct 31 13:10:56 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 31 Oct 2023 13:10:56 +0100 Subject: [pve-devel] [PATCH installer v3 4/8] fix #4829: install: add new ZFS `arc_max` setup option In-Reply-To: <20231031121108.1130299-1-c.heiss@proxmox.com> References: <20231031121108.1130299-1-c.heiss@proxmox.com> Message-ID: <20231031121108.1130299-5-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- Changes v1 -> v2: * no changes Changes v2 -> v3: * better documented calculation and renamed some variables to reflect its unit (thanks Thomas!) * moved modprobe config setup into separate sub Proxmox/Install.pm | 14 ++++++++++++ Proxmox/Install/Config.pm | 1 + Proxmox/Install/RunEnv.pm | 45 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/Proxmox/Install.pm b/Proxmox/Install.pm index 1117fc4..f371716 100644 --- a/Proxmox/Install.pm +++ b/Proxmox/Install.pm @@ -291,6 +291,19 @@ sub get_zfs_raid_setup { return ($devlist, $cmd); } +# If the maximum ARC size for ZFS was explicitly changed by the user, applies +# it to the new system by setting the `zfs_arc_max` module parameter in /etc/modprobe.d/zfs.conf +my sub zfs_setup_module_conf { + my ($targetdir) = @_; + + my $arc_max = Proxmox::Install::Config::get_zfs_opt('arc_max'); + my $arc_max_mib = Proxmox::Install::RunEnv::clamp_zfs_arc_max($arc_max) * 1024 * 1024; + + if ($arc_max > 0) { + file_write_all("$targetdir/etc/modprobe.d/zfs.conf", "options zfs zfs_arc_max=$arc_max\n") + } +} + sub get_btrfs_raid_setup { my $filesys = Proxmox::Install::Config::get_filesys(); @@ -1141,6 +1154,7 @@ _EOD file_write_all("$targetdir/etc/kernel/cmdline", "root=ZFS=$zfs_pool_name/ROOT/$zfs_root_volume_name boot=zfs\n"); + zfs_setup_module_conf($targetdir); } diversion_remove($targetdir, "/usr/sbin/update-grub"); diff --git a/Proxmox/Install/Config.pm b/Proxmox/Install/Config.pm index 024f62a..f496d61 100644 --- a/Proxmox/Install/Config.pm +++ b/Proxmox/Install/Config.pm @@ -72,6 +72,7 @@ my sub init_cfg { compress => 'on', checksum => 'on', copies => 1, + arc_max => Proxmox::Install::RunEnv::default_zfs_arc_max(), # in MiB }, # TODO: single disk selection config target_hd => undef, diff --git a/Proxmox/Install/RunEnv.pm b/Proxmox/Install/RunEnv.pm index 3e810b2..9116397 100644 --- a/Proxmox/Install/RunEnv.pm +++ b/Proxmox/Install/RunEnv.pm @@ -303,6 +303,51 @@ sub query_installation_environment : prototype() { return $output; } +# OpenZFS specifies 64 MiB as the absolute minimum: +# https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max +our $ZFS_ARC_MIN_SIZE_MIB = 64; # MiB + +# See https://bugzilla.proxmox.com/show_bug.cgi?id=4829 +our $ZFS_ARC_MAX_SIZE_MIB = 16 * 1024; # 16384 MiB = 16 GiB +our $ZFS_ARC_SYSMEM_PERCENTAGE = 0.1; # use 10% of available system memory by default + +# Calculates the default upper limit for the ZFS ARC size. +# Returns the default ZFS maximum ARC size in MiB. +sub default_zfs_arc_max { + # Use ZFS default on non-PVE + return 0 if Proxmox::Install::ISOEnv::get('product') ne 'pve'; + + my $default_mib = get('total_memory') * $ZFS_ARC_SYSMEM_PERCENTAGE; + my $rounded_mib = int(sprintf('%.0f', $default_mib)); + print "total_memory:" . get('total_memory') . " mib_rounded:$rounded_mib\n"; + + if ($rounded_mib > $ZFS_ARC_MAX_SIZE_MIB) { + return $ZFS_ARC_MAX_SIZE_MIB; + } elsif ($rounded_mib < $ZFS_ARC_MIN_SIZE_MIB) { + return $ZFS_ARC_MIN_SIZE_MIB; + } + + return $rounded_mib; +} + +# Clamps the provided ZFS arc_max value (in MiB) to the accepted bounds. The +# lower is specified by `$ZFS_ARC_MIN_SIZE_MIB`, the upper by the available system memory. +# Returns the clamped value in MiB. +sub clamp_zfs_arc_max { + my ($mib) = @_; + + return $mib if $mib == 0; + + my $total_mem_mib = get('total_memory'); + if ($mib > $total_mem_mib) { + return $total_mem_mib; + } elsif ($mib < $ZFS_ARC_MIN_SIZE_MIB) { + return $ZFS_ARC_MIN_SIZE_MIB; + } + + return $mib; +} + my $_env = undef; sub get { my ($k) = @_; -- 2.42.0 From c.heiss at proxmox.com Tue Oct 31 13:10:58 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 31 Oct 2023 13:10:58 +0100 Subject: [pve-devel] [PATCH installer v3 6/8] fix #4829: test: add tests for new zfs_arc_max calculations In-Reply-To: <20231031121108.1130299-1-c.heiss@proxmox.com> References: <20231031121108.1130299-1-c.heiss@proxmox.com> Message-ID: <20231031121108.1130299-7-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- Changes v1 -> v2: * no changes Changes v2 -> v3: * no changes Makefile | 3 ++ debian/control | 1 + test/Makefile | 10 ++++++ test/zfs-arc-max.pl | 81 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+) create mode 100644 test/Makefile create mode 100755 test/zfs-arc-max.pl diff --git a/Makefile b/Makefile index 111fe4b..13a2b81 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ $(BUILDDIR): proxmox-low-level-installer \ proxmox-tui-installer/ \ spice-vdagent.sh \ + test/ \ unconfigured.sh \ xinitrc \ $@.tmp @@ -75,7 +76,9 @@ $(DSC): $(BUILDDIR) sbuild: $(DSC) sbuild $(DSC) +.PHONY: test test: + $(MAKE) -C test check $(CARGO) test --workspace $(CARGO_BUILD_ARGS) DESTDIR= diff --git a/debian/control b/debian/control index 3d13019..d77b12a 100644 --- a/debian/control +++ b/debian/control @@ -11,6 +11,7 @@ Build-Depends: cargo:native, librust-regex-1+default-dev (>= 1.7~~), librust-serde-1+default-dev, librust-serde-json-1+default-dev, + libtest-mockmodule-perl, perl, rustc:native, Standards-Version: 4.5.1 diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..fb80fc4 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,10 @@ +all: + +export PERLLIB=.. + +.PHONY: check +check: test-zfs-arc-max + +.PHONY: test-zfs-arc-max +test-zfs-arc-max: + ./zfs-arc-max.pl diff --git a/test/zfs-arc-max.pl b/test/zfs-arc-max.pl new file mode 100755 index 0000000..74cb9b5 --- /dev/null +++ b/test/zfs-arc-max.pl @@ -0,0 +1,81 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More; +use Test::MockModule qw(strict); + +my $proxmox_install_runenv = Test::MockModule->new('Proxmox::Install::RunEnv'); +my $proxmox_install_isoenv = Test::MockModule->new('Proxmox::Install::ISOEnv'); + +sub mock_product { + my ($product) = @_; + + $proxmox_install_isoenv->redefine( + get => sub { + my ($k) = @_; + return $product if $k eq 'product'; + die "iso environment key $k not mocked!\n"; + }, + ); +} + +my %default_tests = ( + 16 => 64, # at least 64 MiB + 1024 => 102, + 4 * 1024 => 410, + 8 * 1024 => 819, + 150 * 1024 => 15360, + 160 * 1024 => 16384, + 1024 * 1024 => 16384, # maximum of 16 GiB +); + +while (my ($total_mem, $expected) = each %default_tests) { + $proxmox_install_runenv->redefine( + get => sub { + my ($k) = @_; + return $total_mem if $k eq 'total_memory'; + die "runtime environment key $k not mocked!\n"; + }, + ); + + mock_product('pve'); + is(Proxmox::Install::RunEnv::default_zfs_arc_max(), $expected, + "$expected MiB should be zfs_arc_max for PVE with $total_mem MiB system memory"); + + mock_product('pbs'); + is(Proxmox::Install::RunEnv::default_zfs_arc_max(), 0, + "zfs_arc_max should default to `0` for PBS with $total_mem MiB system memory"); + + mock_product('pmg'); + is(Proxmox::Install::RunEnv::default_zfs_arc_max(), 0, + "zfs_arc_max should default to `0` for PMG with $total_mem MiB system memory"); +} + +my @clamp_tests = ( + # input, total system memory, expected + [ 0, 4 * 1024, 0 ], + [ 16, 4 * 1024, 64 ], + [ 4 * 1024, 4 * 1024, 4 * 1024 ], + [ 4 * 1024, 6 * 1024, 4 * 1024 ], + [ 8 * 1024, 4 * 1024, 4 * 1024 ], +); + +mock_product('pve'); +foreach (@clamp_tests) { + my ($input, $total_mem, $expected) = @$_; + + $proxmox_install_runenv->redefine( + get => sub { + my ($k) = @_; + return $total_mem if $k eq 'total_memory'; + die "runtime environment key $k not mocked!\n"; + }, + ); + + is(Proxmox::Install::RunEnv::clamp_zfs_arc_max($input), $expected, + "$input MiB zfs_arc_max should be clamped to $expected MiB with $total_mem MiB system memory"); +} + +done_testing(); -- 2.42.0 From c.heiss at proxmox.com Tue Oct 31 13:10:59 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 31 Oct 2023 13:10:59 +0100 Subject: [pve-devel] [PATCH installer v3 7/8] fix #4829: tui: setup: add new ZFS `arc_max` option In-Reply-To: <20231031121108.1130299-1-c.heiss@proxmox.com> References: <20231031121108.1130299-1-c.heiss@proxmox.com> Message-ID: <20231031121108.1130299-8-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- Changes v1 -> v2: * updated comment for ZfsBootdiskOptions::defaults_from() accordingly Changes v2 -> v2: * documented the unit of the return value of default_zfs_arc_max() proxmox-tui-installer/src/options.rs | 57 +++++++++++++++++++-- proxmox-tui-installer/src/setup.rs | 2 + proxmox-tui-installer/src/views/bootdisk.rs | 31 ++++++----- 3 files changed, 71 insertions(+), 19 deletions(-) diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs index 85b39b8..c9b036d 100644 --- a/proxmox-tui-installer/src/options.rs +++ b/proxmox-tui-installer/src/options.rs @@ -1,7 +1,9 @@ use std::net::{IpAddr, Ipv4Addr}; use std::{cmp, fmt}; -use crate::setup::{LocaleInfo, NetworkInfo, RuntimeInfo, SetupInfo}; +use crate::setup::{ + LocaleInfo, NetworkInfo, ProductConfig, ProxmoxProduct, RuntimeInfo, SetupInfo, +}; use crate::utils::{CidrAddress, Fqdn}; use crate::SummaryOption; @@ -190,21 +192,23 @@ pub struct ZfsBootdiskOptions { pub compress: ZfsCompressOption, pub checksum: ZfsChecksumOption, pub copies: usize, + pub arc_max: usize, pub disk_size: f64, pub selected_disks: Vec, } impl ZfsBootdiskOptions { - /// This panics if the provided slice is empty. - pub fn defaults_from(disks: &[Disk]) -> Self { - let disk = &disks[0]; + /// Panics if the disk list is empty. + pub fn defaults_from(runinfo: &RuntimeInfo, product_conf: &ProductConfig) -> Self { + let disk = &runinfo.disks[0]; Self { ashift: 12, compress: ZfsCompressOption::default(), checksum: ZfsChecksumOption::default(), copies: 1, + arc_max: default_zfs_arc_max(product_conf.product, runinfo.total_memory), disk_size: disk.size, - selected_disks: (0..disks.len()).collect(), + selected_disks: (0..runinfo.disks.len()).collect(), } } } @@ -444,6 +448,27 @@ impl InstallerOptions { } } +/// Calculates the default upper limit for the ZFS ARC size. +/// See also and +/// https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max +/// +/// # Arguments +/// * `product` - The product to be installed +/// * `total_memory` - Total memory installed in the system, in MiB +/// +/// # Returns +/// The default ZFS maximum ARC size in MiB for this system. +fn default_zfs_arc_max(product: ProxmoxProduct, total_memory: usize) -> usize { + if product != ProxmoxProduct::PVE { + // Use ZFS default for non-PVE + 0 + } else { + ((total_memory as f64) / 10.) + .round() + .clamp(64., 16. * 1024.) as usize + } +} + #[cfg(test)] mod tests { use super::*; @@ -470,6 +495,28 @@ mod tests { } } + #[test] + fn zfs_arc_limit() { + const TESTS: &[(usize, usize)] = &[ + (16, 64), // at least 64 MiB + (1024, 102), + (4 * 1024, 410), + (8 * 1024, 819), + (150 * 1024, 15360), + (160 * 1024, 16384), + (1024 * 1024, 16384), // maximum of 16 GiB + ]; + + for (total_memory, expected) in TESTS { + assert_eq!( + default_zfs_arc_max(ProxmoxProduct::PVE, *total_memory), + *expected + ); + assert_eq!(default_zfs_arc_max(ProxmoxProduct::PBS, *total_memory), 0); + assert_eq!(default_zfs_arc_max(ProxmoxProduct::PMG, *total_memory), 0); + } + } + #[test] fn network_options_from_setup_network_info() { let setup = dummy_setup_info(); diff --git a/proxmox-tui-installer/src/setup.rs b/proxmox-tui-installer/src/setup.rs index 5575759..e9fe70d 100644 --- a/proxmox-tui-installer/src/setup.rs +++ b/proxmox-tui-installer/src/setup.rs @@ -114,6 +114,7 @@ struct InstallZfsOption { #[serde(serialize_with = "serialize_as_display")] checksum: ZfsChecksumOption, copies: usize, + arc_max: usize, } impl From for InstallZfsOption { @@ -123,6 +124,7 @@ impl From for InstallZfsOption { compress: opts.compress, checksum: opts.checksum, copies: opts.copies, + arc_max: opts.arc_max, } } } diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index 3addd6c..07ca5b7 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -16,7 +16,7 @@ use crate::{ FsType, LvmBootdiskOptions, ZfsBootdiskOptions, ZfsRaidLevel, FS_TYPES, ZFS_CHECKSUM_OPTIONS, ZFS_COMPRESS_OPTIONS, }, - setup::{BootType, ProductConfig}, + setup::{BootType, ProductConfig, RuntimeInfo}, }; use crate::{setup::ProxmoxProduct, InstallerState}; @@ -123,10 +123,7 @@ impl AdvancedBootdiskOptionsView { .position(|t| *t == options.fstype) .unwrap_or_default(), ) - .on_submit({ - let disks = disks.to_owned(); - move |siv, fstype| Self::fstype_on_submit(siv, &disks, fstype) - }); + .on_submit(move |siv, fstype| Self::fstype_on_submit(siv, fstype)); let mut view = LinearLayout::vertical() .child(DummyView.full_width()) @@ -148,8 +145,9 @@ impl AdvancedBootdiskOptionsView { Self { view } } - fn fstype_on_submit(siv: &mut Cursive, disks: &[Disk], fstype: &FsType) { + fn fstype_on_submit(siv: &mut Cursive, fstype: &FsType) { let state = siv.user_data::().unwrap(); + let runinfo = state.runtime_info.clone(); let product_conf = state.setup_info.config.clone(); siv.call_on_name("advanced-bootdisk-options-dialog", |view: &mut Dialog| { @@ -159,13 +157,14 @@ impl AdvancedBootdiskOptionsView { view.remove_child(3); match fstype { FsType::Ext4 | FsType::Xfs => view.add_child( - LvmBootdiskOptionsView::new_with_defaults(&disks[0], &product_conf), + LvmBootdiskOptionsView::new_with_defaults(&runinfo.disks[0], &product_conf), ), - FsType::Zfs(_) => { - view.add_child(ZfsBootdiskOptionsView::new_with_defaults(disks)) - } + FsType::Zfs(_) => view.add_child(ZfsBootdiskOptionsView::new_with_defaults( + &runinfo, + &product_conf, + )), FsType::Btrfs(_) => { - view.add_child(BtrfsBootdiskOptionsView::new_with_defaults(disks)) + view.add_child(BtrfsBootdiskOptionsView::new_with_defaults(&runinfo.disks)) } } } @@ -179,7 +178,7 @@ impl AdvancedBootdiskOptionsView { 0, SelectView::new() .popup() - .with_all(disks.iter().map(|d| (d.to_string(), d.clone()))), + .with_all(runinfo.disks.iter().map(|d| (d.to_string(), d.clone()))), ); } other => view.replace_child(0, TextView::new(other.to_string())), @@ -547,8 +546,11 @@ impl ZfsBootdiskOptionsView { Self { view } } - fn new_with_defaults(disks: &[Disk]) -> Self { - Self::new(disks, &ZfsBootdiskOptions::defaults_from(disks)) + fn new_with_defaults(runinfo: &RuntimeInfo, product_conf: &ProductConfig) -> Self { + Self::new( + &runinfo.disks, + &ZfsBootdiskOptions::defaults_from(runinfo, product_conf), + ) } fn get_values(&mut self) -> Option<(Vec, ZfsBootdiskOptions)> { @@ -568,6 +570,7 @@ impl ZfsBootdiskOptionsView { compress, checksum, copies, + arc_max: 0, // use built-in ZFS default value disk_size, selected_disks, }, -- 2.42.0 From c.heiss at proxmox.com Tue Oct 31 13:10:53 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 31 Oct 2023 13:10:53 +0100 Subject: [pve-devel] [PATCH installer v3 1/8] run env: add comment for query_total_memory() In-Reply-To: <20231031121108.1130299-1-c.heiss@proxmox.com> References: <20231031121108.1130299-1-c.heiss@proxmox.com> Message-ID: <20231031121108.1130299-2-c.heiss@proxmox.com> This is mainly to explicitly document the unit of its return value. No functional changes. Signed-off-by: Christoph Heiss --- Changes v2 -> v3: * new patch Proxmox/Install/RunEnv.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Proxmox/Install/RunEnv.pm b/Proxmox/Install/RunEnv.pm index c9d788b..3e810b2 100644 --- a/Proxmox/Install/RunEnv.pm +++ b/Proxmox/Install/RunEnv.pm @@ -18,6 +18,8 @@ my sub fromjs : prototype($) { } my $mem_total = undef; +# Returns the system memory size in MiB, and falls back to 512 MiB if it +# could not be determined. sub query_total_memory : prototype() { return $mem_total if defined($mem_total); -- 2.42.0 From c.heiss at proxmox.com Tue Oct 31 13:10:57 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 31 Oct 2023 13:10:57 +0100 Subject: [pve-devel] [PATCH installer v3 5/8] fix #4829: proxinstall: expose new `arc_max` ZFS option for PVE installations In-Reply-To: <20231031121108.1130299-1-c.heiss@proxmox.com> References: <20231031121108.1130299-1-c.heiss@proxmox.com> Message-ID: <20231031121108.1130299-6-c.heiss@proxmox.com> Signed-off-by: Christoph Heiss --- Changes v1 -> v2: * no changes Changes v2 -> v3: * no changes proxinstall | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/proxinstall b/proxinstall index 64c8bab..f1a3c02 100755 --- a/proxinstall +++ b/proxinstall @@ -1162,6 +1162,21 @@ my $create_raid_advanced_grid = sub { $spinbutton_copies->set_value($copies); push @$labeled_widgets, "copies", $spinbutton_copies; + if ($iso_env->{product} eq 'pve') { + my $total_memory = Proxmox::Install::RunEnv::get('total_memory'); + + my $spinbutton_arc_max = Gtk3::SpinButton->new_with_range( + $Proxmox::Install::RunEnv::ZFS_ARC_MIN_SIZE, $total_memory, 1); + $spinbutton_arc_max->set_tooltip_text('Maximum ARC size in megabytes'); + $spinbutton_arc_max->signal_connect('value-changed' => sub { + my $w = shift; + Proxmox::Install::Config::set_zfs_opt('arc_max', $w->get_value_as_int()); + }); + my $arc_max = Proxmox::Install::Config::get_zfs_opt('arc_max'); + $spinbutton_arc_max->set_value($arc_max); + push @$labeled_widgets, "ARC max size", $spinbutton_arc_max; + } + push @$labeled_widgets, "hdsize", $hdsize_btn; return $create_label_widget_grid->($labeled_widgets);; }; -- 2.42.0 From c.heiss at proxmox.com Tue Oct 31 13:10:54 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 31 Oct 2023 13:10:54 +0100 Subject: [pve-devel] [PATCH installer v3 2/8] tui: views: add optional suffix label for `NumericEditView`s In-Reply-To: <20231031121108.1130299-1-c.heiss@proxmox.com> References: <20231031121108.1130299-1-c.heiss@proxmox.com> Message-ID: <20231031121108.1130299-3-c.heiss@proxmox.com> Most of the churn here is due to changing the inner view from an `EditView` to a `LinearLayout`. Also prompted the introduction of two small helpers .inner() and .inner_mut() to simplify things everywhere else in the view. Signed-off-by: Christoph Heiss --- Changes v1 -> v2: * no changes Changes v2 -> v3: * added #[allow(unused)] attribute to ::new_with_suffix(), to avoid warnings until it is actually used proxmox-tui-installer/src/views/mod.rs | 105 +++++++++++++++++++------ 1 file changed, 83 insertions(+), 22 deletions(-) diff --git a/proxmox-tui-installer/src/views/mod.rs b/proxmox-tui-installer/src/views/mod.rs index aa24fa4..8882ce9 100644 --- a/proxmox-tui-installer/src/views/mod.rs +++ b/proxmox-tui-installer/src/views/mod.rs @@ -19,16 +19,38 @@ mod timezone; pub use timezone::*; pub struct NumericEditView { - view: EditView, + view: LinearLayout, max_value: Option, max_content_width: Option, allow_empty: bool, } impl NumericEditView { + /// Creates a new [`NumericEditView`], with the value set to `0`. pub fn new() -> Self { + let view = LinearLayout::horizontal().child(EditView::new().content("0").full_width()); + + Self { + view, + max_value: None, + max_content_width: None, + allow_empty: false, + } + } + + /// Creates a new [`NumericEditView`], with the value set to `0` and a label to the right of it + /// with the given content, separated by a space. + /// + /// # Arguments + /// * `suffix` - Content for the label to the right of it. + #[allow(unused)] + pub fn new_with_suffix(suffix: &str) -> Self { + let view = LinearLayout::horizontal() + .child(EditView::new().content("0").full_width()) + .child(TextView::new(format!(" {suffix}"))); + Self { - view: EditView::new().content("0"), + view, max_value: None, max_content_width: None, allow_empty: false, @@ -42,7 +64,7 @@ impl NumericEditView { pub fn max_content_width(mut self, width: usize) -> Self { self.max_content_width = Some(width); - self.view.set_max_content_width(self.max_content_width); + self.inner_mut().set_max_content_width(Some(width)); self } @@ -50,24 +72,25 @@ impl NumericEditView { self.allow_empty = value; if value { - self.view = EditView::new(); + *self.inner_mut() = EditView::new(); } else { - self.view = EditView::new().content("0"); + *self.inner_mut() = EditView::new().content("0"); } - self.view.set_max_content_width(self.max_content_width); + let max_content_width = self.max_content_width; + self.inner_mut().set_max_content_width(max_content_width); self } pub fn get_content(&self) -> Result::Err> { assert!(!self.allow_empty); - self.view.get_content().parse() + self.inner().get_content().parse() } pub fn get_content_maybe(&self) -> Option::Err>> { - let content = self.view.get_content(); + let content = self.inner().get_content(); if !content.is_empty() { - Some(self.view.get_content().parse()) + Some(self.inner().get_content().parse()) } else { None } @@ -83,7 +106,7 @@ impl NumericEditView { if let Ok(val) = self.get_content() { if result.is_consumed() && val > max { // Restore the original value, before the insert - let cb = self.view.set_content((*original).clone()); + let cb = self.inner_mut().set_content((*original).clone()); return EventResult::with_cb_once(move |siv| { result.process(siv); cb(siv); @@ -94,16 +117,54 @@ impl NumericEditView { result } + + /// Provides an immutable reference to the inner [`EditView`]. + fn inner(&self) -> &EditView { + // Safety: Invariant; first child must always exist and be a `EditView` + self.view + .get_child(0) + .unwrap() + .downcast_ref::>() + .unwrap() + .get_inner() + } + + /// Provides a mutable reference to the inner [`EditView`]. + fn inner_mut(&mut self) -> &mut EditView { + // Safety: Invariant; first child must always exist and be a `EditView` + self.view + .get_child_mut(0) + .unwrap() + .downcast_mut::>() + .unwrap() + .get_inner_mut() + } + + /// Sets the content of the inner [`EditView`]. This correctly swaps out the content without + /// modifying the [`EditView`] in any way. + /// + /// Chainable variant. + /// + /// # Arguments + /// * `content` - New, stringified content for the inner [`EditView`]. Must be a valid value + /// according to the containet type `T`. + fn content_inner(mut self, content: &str) -> Self { + let mut inner = EditView::new(); + std::mem::swap(self.inner_mut(), &mut inner); + inner = inner.content(content); + std::mem::swap(self.inner_mut(), &mut inner); + self + } } pub type FloatEditView = NumericEditView; pub type IntegerEditView = NumericEditView; impl ViewWrapper for FloatEditView { - cursive::wrap_impl!(self.view: EditView); + cursive::wrap_impl!(self.view: LinearLayout); fn wrap_on_event(&mut self, event: Event) -> EventResult { - let original = self.view.get_content(); + let original = self.inner_mut().get_content(); let has_decimal_place = original.find('.').is_some(); @@ -114,13 +175,13 @@ impl ViewWrapper for FloatEditView { }; let decimal_places = self - .view + .inner_mut() .get_content() .split_once('.') .map(|(_, s)| s.len()) .unwrap_or_default(); if decimal_places > 2 { - let cb = self.view.set_content((*original).clone()); + let cb = self.inner_mut().set_content((*original).clone()); return EventResult::with_cb_once(move |siv| { result.process(siv); cb(siv); @@ -132,17 +193,17 @@ impl ViewWrapper for FloatEditView { } impl FloatEditView { - pub fn content(mut self, content: f64) -> Self { - self.view = self.view.content(format!("{:.2}", content)); - self + /// Sets the value of the [`FloatEditView`]. + pub fn content(self, content: f64) -> Self { + self.content_inner(&format!("{:.2}", content)) } } impl ViewWrapper for IntegerEditView { - cursive::wrap_impl!(self.view: EditView); + cursive::wrap_impl!(self.view: LinearLayout); fn wrap_on_event(&mut self, event: Event) -> EventResult { - let original = self.view.get_content(); + let original = self.inner_mut().get_content(); let result = match event { // Drop all other characters than numbers; allow dots if not set to integer-only @@ -155,9 +216,9 @@ impl ViewWrapper for IntegerEditView { } impl IntegerEditView { - pub fn content(mut self, content: usize) -> Self { - self.view = self.view.content(content.to_string()); - self + /// Sets the value of the [`IntegerEditView`]. + pub fn content(self, content: usize) -> Self { + self.content_inner(&content.to_string()) } } -- 2.42.0 From c.heiss at proxmox.com Tue Oct 31 13:11:00 2023 From: c.heiss at proxmox.com (Christoph Heiss) Date: Tue, 31 Oct 2023 13:11:00 +0100 Subject: [pve-devel] [PATCH installer v3 8/8] fix #4829: tui: bootdisk: expose new `arc_max` ZFS option for PVE installations In-Reply-To: <20231031121108.1130299-1-c.heiss@proxmox.com> References: <20231031121108.1130299-1-c.heiss@proxmox.com> Message-ID: <20231031121108.1130299-9-c.heiss@proxmox.com> To set the maximum value for arc_max accordingly, simply pass down `RuntimeInfo` directly instead of the disks array to the views. Signed-off-by: Christoph Heiss --- Changes v1 -> v2: * fix ZFS_ARC_MIN_SIZE to be MiB rather than bytes Changes v2 -> v3: * renamed `ZFS_ARC_MIN_SIZE` -> `ZFS_ARC_MIN_SIZE_MIB` and documented the origin of its value proxmox-tui-installer/src/main.rs | 2 +- proxmox-tui-installer/src/views/bootdisk.rs | 55 +++++++++++++++------ proxmox-tui-installer/src/views/mod.rs | 1 - 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs index 81fe3ca..5365d51 100644 --- a/proxmox-tui-installer/src/main.rs +++ b/proxmox-tui-installer/src/main.rs @@ -413,7 +413,7 @@ fn bootdisk_dialog(siv: &mut Cursive) -> InstallerView { InstallerView::new( &state, - BootdiskOptionsView::new(siv, &state.runtime_info.disks, &state.options.bootdisk) + BootdiskOptionsView::new(siv, &state.runtime_info, &state.options.bootdisk) .with_name("bootdisk-options"), Box::new(|siv| { let options = siv.call_on_name("bootdisk-options", BootdiskOptionsView::get_values); diff --git a/proxmox-tui-installer/src/views/bootdisk.rs b/proxmox-tui-installer/src/views/bootdisk.rs index 07ca5b7..da4706b 100644 --- a/proxmox-tui-installer/src/views/bootdisk.rs +++ b/proxmox-tui-installer/src/views/bootdisk.rs @@ -20,6 +20,10 @@ use crate::{ }; use crate::{setup::ProxmoxProduct, InstallerState}; +/// OpenZFS specifies 64 MiB as the absolute minimum: +/// https://openzfs.github.io/openzfs-docs/Performance%20and%20Tuning/Module%20Parameters.html#zfs-arc-max +const ZFS_ARC_MIN_SIZE: usize = 64; // MiB + pub struct BootdiskOptionsView { view: LinearLayout, advanced_options: Rc>, @@ -27,13 +31,13 @@ pub struct BootdiskOptionsView { } impl BootdiskOptionsView { - pub fn new(siv: &mut Cursive, disks: &[Disk], options: &BootdiskOptions) -> Self { + pub fn new(siv: &mut Cursive, runinfo: &RuntimeInfo, options: &BootdiskOptions) -> Self { let bootdisk_form = FormView::new() .child( "Target harddisk", SelectView::new() .popup() - .with_all(disks.iter().map(|d| (d.to_string(), d.clone()))), + .with_all(runinfo.disks.iter().map(|d| (d.to_string(), d.clone()))), ) .with_name("bootdisk-options-target-disk"); @@ -47,11 +51,11 @@ impl BootdiskOptionsView { let advanced_button = LinearLayout::horizontal() .child(DummyView.full_width()) .child(Button::new("Advanced options", { - let disks = disks.to_owned(); + let runinfo = runinfo.clone(); let options = advanced_options.clone(); move |siv| { siv.add_layer(advanced_options_view( - &disks, + &runinfo, options.clone(), product_conf.clone(), )); @@ -104,7 +108,7 @@ struct AdvancedBootdiskOptionsView { } impl AdvancedBootdiskOptionsView { - fn new(disks: &[Disk], options: &BootdiskOptions, product_conf: ProductConfig) -> Self { + fn new(runinfo: &RuntimeInfo, options: &BootdiskOptions, product_conf: ProductConfig) -> Self { let filter_btrfs = |fstype: &&FsType| -> bool { product_conf.enable_btrfs || !fstype.is_btrfs() }; @@ -135,10 +139,10 @@ impl AdvancedBootdiskOptionsView { view.add_child(LvmBootdiskOptionsView::new(lvm, &product_conf)) } AdvancedBootdiskOptions::Zfs(zfs) => { - view.add_child(ZfsBootdiskOptionsView::new(disks, zfs)) + view.add_child(ZfsBootdiskOptionsView::new(runinfo, zfs, &product_conf)) } AdvancedBootdiskOptions::Btrfs(btrfs) => { - view.add_child(BtrfsBootdiskOptionsView::new(disks, btrfs)) + view.add_child(BtrfsBootdiskOptionsView::new(&runinfo.disks, btrfs)) } }; @@ -508,7 +512,13 @@ struct ZfsBootdiskOptionsView { impl ZfsBootdiskOptionsView { // TODO: Re-apply previous disk selection from `options` correctly - fn new(disks: &[Disk], options: &ZfsBootdiskOptions) -> Self { + fn new( + runinfo: &RuntimeInfo, + options: &ZfsBootdiskOptions, + product_conf: &ProductConfig, + ) -> Self { + let is_pve = product_conf.product == ProxmoxProduct::PVE; + let inner = FormView::new() .child("ashift", IntegerEditView::new().content(options.ashift)) .child( @@ -536,9 +546,16 @@ impl ZfsBootdiskOptionsView { ), ) .child("copies", IntegerEditView::new().content(options.copies)) + .child_conditional( + is_pve, + "ARC max size", + IntegerEditView::new_with_suffix("MiB") + .max_value(runinfo.total_memory) + .content(options.arc_max), + ) .child("hdsize", DiskSizeEditView::new().content(options.disk_size)); - let view = MultiDiskOptionsView::new(disks, &options.selected_disks, inner) + let view = MultiDiskOptionsView::new(&runinfo.disks, &options.selected_disks, inner) .top_panel(TextView::new( "ZFS is not compatible with hardware RAID controllers, for details see the documentation." ).center()); @@ -548,20 +565,30 @@ impl ZfsBootdiskOptionsView { fn new_with_defaults(runinfo: &RuntimeInfo, product_conf: &ProductConfig) -> Self { Self::new( - &runinfo.disks, + runinfo, &ZfsBootdiskOptions::defaults_from(runinfo, product_conf), + product_conf, ) } fn get_values(&mut self) -> Option<(Vec, ZfsBootdiskOptions)> { let (disks, selected_disks) = self.view.get_disks_and_selection()?; let view = self.view.inner_mut()?; + let has_arc_max = view.len() >= 6; + let disk_size_index = if has_arc_max { 5 } else { 4 }; let ashift = view.get_value::(0)?; let compress = view.get_value::, _>(1)?; let checksum = view.get_value::, _>(2)?; let copies = view.get_value::(3)?; - let disk_size = view.get_value::(4)?; + let disk_size = view.get_value::(disk_size_index)?; + + let arc_max = if has_arc_max { + view.get_value::(4)? + .max(ZFS_ARC_MIN_SIZE) + } else { + 0 // use built-in ZFS default value + }; Some(( disks, @@ -570,7 +597,7 @@ impl ZfsBootdiskOptionsView { compress, checksum, copies, - arc_max: 0, // use built-in ZFS default value + arc_max, disk_size, selected_disks, }, @@ -583,12 +610,12 @@ impl ViewWrapper for ZfsBootdiskOptionsView { } fn advanced_options_view( - disks: &[Disk], + runinfo: &RuntimeInfo, options: Rc>, product_conf: ProductConfig, ) -> impl View { Dialog::around(AdvancedBootdiskOptionsView::new( - disks, + runinfo, &(*options).borrow(), product_conf, )) diff --git a/proxmox-tui-installer/src/views/mod.rs b/proxmox-tui-installer/src/views/mod.rs index 8882ce9..e997968 100644 --- a/proxmox-tui-installer/src/views/mod.rs +++ b/proxmox-tui-installer/src/views/mod.rs @@ -43,7 +43,6 @@ impl NumericEditView { /// /// # Arguments /// * `suffix` - Content for the label to the right of it. - #[allow(unused)] pub fn new_with_suffix(suffix: &str) -> Self { let view = LinearLayout::horizontal() .child(EditView::new().content("0").full_width()) -- 2.42.0