Package: release.debian.org Severity: normal Tags: bullseye User: release.debian....@packages.debian.org Usertags: pu
Dear SRMs, I would like to update Ganeti to the current upstream bugfix version (3.0.2) - including all Debian packaging fixes currently in unstable - and I seek your approval. 3.0.2 was released a while back[1] as a bugfix-only release. Due to my involvement upstream, I had full oversight of the release process and I can confirm it solves important issues, the vast majority of which affect Bullseye, while it does not introduce any breaking changes in behavior. Note that every commit since v3.0 has been tested against Debian Stable and Testing upstream using an automated CI/CD pipeline. I believe bumping to 3.0.2 is much safer and cleaner than cherry-picking at least a dozen of commits as patches on top of 3.0.1. Apart from upstream fixes, this p-u also includes fixes for Debian bugs #993559 and #1000040 affecting Debian packaging, as well as the removal of an unnecessary dependency on bridge-utils. Note that I might revert the latter for the stable update to avoid breaking any custom scripts (e.g. hooks) that still rely on brctl mid-release. I'm attaching a full source debdiff against 3.0.1-2. The following information is for ease of review, please let me know if there is any additional information I can provide. [1] https://github.com/ganeti/ganeti/releases/tag/v3.0.2 Cheers, Apollon Annotated upstream commit list since 3.0.1 ------------------------------------------ $ git log --oneline --no-merges v3.0.1..v3.0.2 6f97ee783 Prepare for the 3.0.2 release Version bump & documentation changes 4e35c0093 Fix lvcreate for newer lvm versions (#1586) Already fixed in Bullseye (#982960), patch dropped in this p-u. 7b89f42fd KVM: handle asynchronous events during QMP connect Reliability fix, issue present in Bullseye. Documented as affecting Bullseye in https://github.com/ganeti/ganeti/issues/1649 2d04d0190 Fix NIC hotplug with `vhost_net=True` Reliability fix, issue present in Bullseye, see https://github.com/ganeti/ganeti/issues/1651 f1e6d7ee1 relax VLAN check with VETHs Slight behavior change, backwards compatible 9e7210a0b doc: Remove duplicate index entry. FTBFS, fixed post-bullseye in our 3.0.1-3 (#997053) c5bb09744 work around dynamic auto-ro for live migration Compatibility fix for QEMU >=4.0, affects bullseye 06a043ad8 KVM: use same code to generate aio/cache parameters for KVM start and hotplugging 59994617c KVM hypervisor: only accept valid combinations of disk_aio and disk_cache cb1810cf9 Refactor: Move KVM parameter check/validation code to separate methods Slight code refactoring to properly sanitize disk parameters using the same rules when the instance starts up and when disks are hotplugged. Fixes disk hotplugging under certain scenarios, see https://github.com/ganeti/ganeti/issues/1645. Note that these commits are responsible for the majority of the diffstat in this p-u. c851eb73b make check-local happy: fix end-of-line whitespace Linting, irrelevant for Debian 543c9e243 allow Ganeti project copyright and year 202x Documentation change 432bab2fa add `check-local` to the github CI CI, irrelevant for Debian d512200d7 Qemu-Guest-Agent (QGA): use implicit PCI slot Fixes disk/NIC hotplugging when using the QEMU guest agent, see https://github.com/ganeti/ganeti/issues/1620. Affects Bullseye. 3951ba628 Do not load ROM file for NICs during hotplug when QEMU is running in chroot mode Fixes hotplugging issue, affects Bulsseye 185481da9 Warn users that changing DRBD parameters does not affect running instances CLI output change c4a603b6e Fix live migration of xen instances (#1582) Included as patch in Bullseye, dropped in this p-u d4e89d2f4 RAPI: Correctly return HTTP 400 on request parse error Leftover bug from the Python 3 conversion, affects Bullseye 2df1f2cd3 fix building docs on Debian Bullseye (#1602) FTBS, affects Bullseye, fixed in Debian in 3.0.1-3, patch dropped in this p-u. d845e7d68 Adjust for Pyparsing 3.0. Bugfix, affects Bookworm but does not break Bullseye. a5ad39397 Fix Byte vs String comparison Leftover bug from the Python 3 conversion, affects some CLI commands in Bullseye. 95125c644 Loosen Cabal version constraints. Releng 8f8e82933 Adjust for TupE type change in Template Haskell 2.16. GHC 8.10 compatibility using conditional code, noop under Bullseye. f0189ae15 fix dictionary usage bug leftover from 2to3 migration Leftover bug from the Python 3 conversion c562509fe Fix unsupported keymap include in >=qemu-4.0 (#1612) QEMU 4.0 compatibility fix, affects Bullseye
diff -Nru ganeti-3.0.1/autotools/check-header ganeti-3.0.2/autotools/check-header --- ganeti-3.0.1/autotools/check-header 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/autotools/check-header 2022-02-28 22:51:21.000000000 +0200 @@ -76,10 +76,11 @@ _SHEBANG = re.compile(r"^#(?:|!(?:/usr/bin/python3?(?:| -u)|/bin/(?:|ba)sh))$") -_COPYRIGHT_YEAR = r"20[01][0-9]" -_COPYRIGHT = re.compile(r"# Copyright \(C\) (%s(?:, %s)*) Google Inc\.$" % +_COPYRIGHT_YEAR = r"20[012][0-9]" +_COPYRIGHT = re.compile(r"# Copyright \(C\) (%s(?:, %s)*) " + "(Google Inc\.|the Ganeti project)$" % (_COPYRIGHT_YEAR, _COPYRIGHT_YEAR)) -_COPYRIGHT_DESC = "Copyright (C) <year>[, <year> ...] Google Inc." +_COPYRIGHT_DESC = "Copyright (C) <year>[, <year> ...] the Ganeti project" _AUTOGEN = "# This file is automatically generated, do not edit!" diff -Nru ganeti-3.0.1/cabal/ganeti.template.cabal ganeti-3.0.2/cabal/ganeti.template.cabal --- ganeti-3.0.1/cabal/ganeti.template.cabal 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/cabal/ganeti.template.cabal 2022-02-28 22:51:21.000000000 +0200 @@ -64,7 +64,7 @@ , utf8-string >= 0.3.7 , attoparsec >= 0.10.1.1 && < 0.14 - , base64-bytestring >= 1.0.0.1 && < 1.1 + , base64-bytestring >= 1.0.0.1 && < 1.2 , case-insensitive >= 0.4.0.1 && < 1.3 , curl >= 1.3.7 && < 1.4 , hinotify >= 0.3.2 && < 0.5 @@ -95,7 +95,7 @@ if flag(htest) build-depends: HUnit >= 1.2.4.2 && < 1.7 - , QuickCheck >= 2.8 && < 2.14 + , QuickCheck >= 2.8 && < 2.15 , test-framework >= 0.6 && < 0.9 , test-framework-hunit >= 0.2.7 && < 0.4 , test-framework-quickcheck2 >= 0.2.12.1 && < 0.4 diff -Nru ganeti-3.0.1/configure.ac ganeti-3.0.2/configure.ac --- ganeti-3.0.1/configure.ac 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/configure.ac 2022-02-28 22:51:21.000000000 +0200 @@ -1,7 +1,7 @@ # Configure script for Ganeti m4_define([gnt_version_major], [3]) m4_define([gnt_version_minor], [0]) -m4_define([gnt_version_revision], [1]) +m4_define([gnt_version_revision], [2]) m4_define([gnt_version_suffix], []) m4_define([gnt_version_full], m4_format([%d.%d.%d%s], diff -Nru ganeti-3.0.1/debian/changelog ganeti-3.0.2/debian/changelog --- ganeti-3.0.1/debian/changelog 2021-03-02 15:13:17.000000000 +0200 +++ ganeti-3.0.2/debian/changelog 2022-06-25 13:48:16.000000000 +0300 @@ -1,3 +1,36 @@ +ganeti (3.0.2-1~deb11u1) bullseye; urgency=medium + + * Rebuild for bullseye. + + -- Apollon Oikonomopoulos <apoi...@debian.org> Sat, 25 Jun 2022 13:48:16 +0300 + +ganeti (3.0.2-1) unstable; urgency=medium + + * New upstream release (closes: #1006003, #993920) + * Drop patches merged upstream + * d/copyright: adjust years and upstream copyright info + + -- Apollon Oikonomopoulos <apoi...@debian.org> Sat, 12 Mar 2022 23:23:27 +0200 + +ganeti (3.0.1-4) unstable; urgency=medium + + * Drop ganeti-3.0's dependency on bridge-utils (Ganeti 3.0 uses iproute2). + + -- Apollon Oikonomopoulos <apoi...@debian.org> Mon, 31 Jan 2022 08:14:57 +0200 + +ganeti (3.0.1-3) unstable; urgency=medium + + * postrm: remove diversion only on package removal (Closes: #993559) + * Restore the diversion on postinst in case it was accidentally removed + due to #993559. + * Fix FTBFS by removing duplicate index entry. + Thanks to Marius Bakke (Closes: #997053) + * d/control: remove unnecessary B-D on libpcre3-dev (Closes: #1000040) + * Fix FTBFS with sphinx >= 2.1. + Thanks to Sascha Lucas + + -- Apollon Oikonomopoulos <apoi...@debian.org> Thu, 16 Dec 2021 14:22:41 +0200 + ganeti (3.0.1-2) unstable; urgency=medium * Ignore signatures while creating LVs (Closes: #982960) diff -Nru ganeti-3.0.1/debian/control ganeti-3.0.2/debian/control --- ganeti-3.0.1/debian/control 2021-03-02 14:42:23.000000000 +0200 +++ ganeti-3.0.2/debian/control 2022-06-25 13:48:16.000000000 +0300 @@ -36,7 +36,6 @@ libghc-test-framework-hunit-dev, libghc-temporary-dev, libghc-old-time-dev, - libpcre3-dev, libcurl4-openssl-dev, python3-simplejson, python3-pyparsing, @@ -97,7 +96,6 @@ lvm2, openssh-client, openssh-server, - bridge-utils, iproute2, iputils-arping, libcap2-bin, diff -Nru ganeti-3.0.1/debian/control.in ganeti-3.0.2/debian/control.in --- ganeti-3.0.1/debian/control.in 2021-03-02 14:40:51.000000000 +0200 +++ ganeti-3.0.2/debian/control.in 2022-06-25 13:46:01.000000000 +0300 @@ -36,7 +36,6 @@ libghc-test-framework-hunit-dev, libghc-temporary-dev, libghc-old-time-dev, - libpcre3-dev, libcurl4-openssl-dev, python3-simplejson, python3-pyparsing, @@ -97,7 +96,6 @@ lvm2, openssh-client, openssh-server, - bridge-utils, iproute2, iputils-arping, libcap2-bin, diff -Nru ganeti-3.0.1/debian/copyright ganeti-3.0.2/debian/copyright --- ganeti-3.0.1/debian/copyright 2019-12-16 22:12:01.000000000 +0200 +++ ganeti-3.0.2/debian/copyright 2022-06-25 13:46:01.000000000 +0300 @@ -4,11 +4,12 @@ Files: * Copyright: Copyright (c) 2006-2015 Google Inc. + Copyright (c) 2018, 2021 Ganeti Project Contributors License: BSD-2-Clause Files: debian/* Copyright: Copyright (c) 2007 Leonardo Rodrigues de Mello <l...@lmello.eu.org> - Copyright (c) 2007-2017 Debian Ganeti Team <pkg-gan...@lists.alioth.debian.org> + Copyright (c) 2007-2022 Debian Ganeti Team <pkg-gan...@lists.alioth.debian.org> License: GPL-2+ License: BSD-2-Clause diff -Nru ganeti-3.0.1/debian/ganeti-3.0.postinst ganeti-3.0.2/debian/ganeti-3.0.postinst --- ganeti-3.0.1/debian/ganeti-3.0.postinst 2021-03-02 14:42:23.000000000 +0200 +++ ganeti-3.0.2/debian/ganeti-3.0.postinst 2022-06-25 13:48:16.000000000 +0300 @@ -4,4 +4,17 @@ . /usr/share/debconf/confmodule +# Restore the 2.16 version.py diversion in case it was accidentally removed +# during upgrade. See #993559 + +if [ -z "$(dpkg-divert --list /usr/share/ganeti/2.16/ganeti/utils/version.py)" ]; then + echo "Restoring diversion of /usr/share/ganeti/2.16/ganeti/utils/version.py)" + dpkg-divert --add --rename --package ganeti-3.0 \ + --divert /usr/share/ganeti/2.16/ganeti/utils/version.py.orig \ + /usr/share/ganeti/2.16/ganeti/utils/version.py +fi + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + #DEBHELPER# diff -Nru ganeti-3.0.1/debian/ganeti-3.0.postrm ganeti-3.0.2/debian/ganeti-3.0.postrm --- ganeti-3.0.1/debian/ganeti-3.0.postrm 2021-03-02 14:42:23.000000000 +0200 +++ ganeti-3.0.2/debian/ganeti-3.0.postrm 2022-06-25 13:48:16.000000000 +0300 @@ -2,7 +2,7 @@ set -e -if [ "$1" != "purge" ]; then +if [ "$1" = "remove" ]; then dpkg-divert --remove --rename --package ganeti-3.0 \ --divert /usr/share/ganeti/2.16/ganeti/utils/version.py.orig \ /usr/share/ganeti/2.16/ganeti/utils/version.py diff -Nru ganeti-3.0.1/debian/patches/0002-remove-hardcoded-libc-linux-constants.patch ganeti-3.0.2/debian/patches/0002-remove-hardcoded-libc-linux-constants.patch --- ganeti-3.0.1/debian/patches/0002-remove-hardcoded-libc-linux-constants.patch 2021-03-02 15:11:18.000000000 +0200 +++ ganeti-3.0.2/debian/patches/0002-remove-hardcoded-libc-linux-constants.patch 2022-06-25 13:46:01.000000000 +0300 @@ -50,10 +50,10 @@ create mode 100644 autotools/HeaderConstants.hsc diff --git a/Makefile.am b/Makefile.am -index d489264..414bbfc 100644 +index aa92d2e..3a0825d 100644 --- a/Makefile.am +++ b/Makefile.am -@@ -2324,6 +2324,9 @@ src/Ganeti/Hs2Py/ListConstants.hs: src/Ganeti/Hs2Py/ListConstants.hs.in \ +@@ -2326,6 +2326,9 @@ src/Ganeti/Hs2Py/ListConstants.hs: src/Ganeti/Hs2Py/ListConstants.hs.in \ src/Ganeti/Curl/Internal.hs: src/Ganeti/Curl/Internal.hsc | stamp-directories hsc2hs -o $@ $< @@ -63,7 +63,7 @@ test/hs/Test/Ganeti/TestImports.hs: test/hs/Test/Ganeti/TestImports.hs.in \ $(built_base_sources) set -e; \ -@@ -2340,7 +2343,7 @@ lib/_constants.py: Makefile src/hs2py lib/_constants.py.in | stamp-directories +@@ -2342,7 +2345,7 @@ lib/_constants.py: Makefile src/hs2py lib/_constants.py.in | stamp-directories lib/constants.py: lib/_constants.py @@ -72,7 +72,7 @@ | $(built_base_sources) @echo "m4 ... >" $@ @m4 -DPACKAGE_VERSION="$(PACKAGE_VERSION)" \ -@@ -2418,7 +2421,7 @@ src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \ +@@ -2420,7 +2423,7 @@ src/AutoConf.hs: Makefile src/AutoConf.hs.in $(PRINT_PY_CONSTANTS) \ done)" \ -DAF_INET4="$$(PYTHONPATH=. $(PYTHON) $(PRINT_PY_CONSTANTS) AF_INET4)" \ -DAF_INET6="$$(PYTHONPATH=. $(PYTHON) $(PRINT_PY_CONSTANTS) AF_INET6)" \ @@ -205,10 +205,10 @@ +tungetfeatures :: Integer +tungetfeatures = AutoConf.tungetfeatures diff --git a/test/py/ganeti.hypervisor.hv_kvm_unittest.py b/test/py/ganeti.hypervisor.hv_kvm_unittest.py -index 9c3c90a..274a0bd 100755 +index 4e05121..1549018 100755 --- a/test/py/ganeti.hypervisor.hv_kvm_unittest.py +++ b/test/py/ganeti.hypervisor.hv_kvm_unittest.py -@@ -426,7 +426,7 @@ class TestGetTunFeatures(unittest.TestCase): +@@ -889,7 +889,7 @@ class TestGetTunFeatures(unittest.TestCase): self.assertTrue(result is None) def _FakeIoctl(self, features, fd, request, buf): diff -Nru ganeti-3.0.1/debian/patches/0003-Fix-lvcreate-for-newer-lvm-versions-1586.patch ganeti-3.0.2/debian/patches/0003-Fix-lvcreate-for-newer-lvm-versions-1586.patch --- ganeti-3.0.1/debian/patches/0003-Fix-lvcreate-for-newer-lvm-versions-1586.patch 2021-03-02 15:11:18.000000000 +0200 +++ ganeti-3.0.2/debian/patches/0003-Fix-lvcreate-for-newer-lvm-versions-1586.patch 1970-01-01 02:00:00.000000000 +0200 @@ -1,30 +0,0 @@ -From: Valentin <47387140+vali...@users.noreply.github.com> -Date: Sun, 21 Feb 2021 11:24:31 +0100 -Subject: Fix lvcreate for newer lvm versions (#1586) - -When run non-interactively, newer LVM versions (>= 2.03.10) -will fail (unless `--yes` is specified) when an existing filesystem -signature is encountered while creating a new LV. Using `-Wn` -disables this check. - -(cherry picked from commit f8d6260b3762960a342aff716ae5fc6759c5d086) ---- - lib/storage/bdev.py | 5 ++++- - 1 file changed, 4 insertions(+), 1 deletion(-) - -diff --git a/lib/storage/bdev.py b/lib/storage/bdev.py -index 56ac19a..901a804 100644 ---- a/lib/storage/bdev.py -+++ b/lib/storage/bdev.py -@@ -215,7 +215,10 @@ class LogicalVolume(base.BlockDev): - # create an optimally-striped volume; in that case, we want to try - # with N, N-1, ..., 2, and finally 1 (non-stripped) number of - # stripes -- cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name] -+ # When run non-interactively, newer LVM versions will fail (unless -+ # `--yes` is specified) when an existing filesystem signature is -+ # encountered while creating a new LV. Using `-Wn` disables this check. -+ cmd = ["lvcreate", "-Wn", "-L%dm" % size, "-n%s" % lv_name] - for stripes_arg in range(stripes, 0, -1): - result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist) - if not result.failed: diff -Nru ganeti-3.0.1/debian/patches/0004-Fix-live-migration-of-xen-instances-1582.patch ganeti-3.0.2/debian/patches/0004-Fix-live-migration-of-xen-instances-1582.patch --- ganeti-3.0.1/debian/patches/0004-Fix-live-migration-of-xen-instances-1582.patch 2021-03-02 15:13:14.000000000 +0200 +++ ganeti-3.0.2/debian/patches/0004-Fix-live-migration-of-xen-instances-1582.patch 1970-01-01 02:00:00.000000000 +0200 @@ -1,71 +0,0 @@ -From: Wurzelmann <wurzelm...@users.noreply.github.com> -Date: Sat, 6 Feb 2021 20:19:35 +0100 -Subject: Fix live migration of xen instances (#1582) - -This PR fixes issue #1574. Config deletion has been unified (setting the config path first and deleting the file, if it exists and creating it) and an improved runtime check has been introduced. - -The test used before just took the cpu usage in seconds into account, which lead to problems on our new cluster due to its quite beefy AMD cpus; just migrating an idle instance did not increase the number of cpu seconds above 0. - -(cherry picked from commit e7dcff35607a6241409f3cfcacd124584c642d94) ---- - lib/hypervisor/hv_xen.py | 23 +++++++++++++++++------ - 1 file changed, 17 insertions(+), 6 deletions(-) - -diff --git a/lib/hypervisor/hv_xen.py b/lib/hypervisor/hv_xen.py -index 2590c08..1344d73 100644 ---- a/lib/hypervisor/hv_xen.py -+++ b/lib/hypervisor/hv_xen.py -@@ -180,6 +180,16 @@ def _InstanceDomID(info): - return info[1] - - -+def _InstanceRunning(info): -+ """Get instance runtime from instance info tuple. -+ @type info: tuple -+ @param info: instance info as parsed by _ParseInstanceList() -+ -+ @return: bool -+ """ -+ return info[4] == hv_base.HvInstanceState.RUNNING -+ -+ - def _InstanceRuntime(info): - """Get instance runtime from instance info tuple. - @type info: tuple -@@ -780,10 +790,11 @@ class XenHypervisor(hv_base.BaseHypervisor): - This version of the function just writes the config file from static data. - - """ -+ cfg_file = self._ConfigFileName(instance_name) -+ - # just in case it exists - utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name)) - -- cfg_file = self._ConfigFileName(instance_name) - try: - utils.WriteFile(cfg_file, data=data) - except EnvironmentError as err: -@@ -1065,10 +1076,10 @@ class XenHypervisor(hv_base.BaseHypervisor): - if not force: - self._ShutdownInstance(name, hvparams, timeout) - -- # TODO: Xen does always destroy the instnace after trying a gracefull -- # shutdown. That means doing another attempt with force=True will not make -- # any difference. This differs in behaviour from other hypervisors and -- # should be cleaned up. -+ # TODO: Xen always destroys the instance after trying a graceful shutdown. -+ # That means doing another attempt with force=True will not make any -+ # difference. This differs in behaviour from other hypervisors and should -+ # be cleaned up. - result = self._DestroyInstanceIfAlive(name, hvparams) - if result is not None and result.failed and \ - self.GetInstanceInfo(name, hvparams=hvparams) is not None: -@@ -1295,7 +1306,7 @@ class XenHypervisor(hv_base.BaseHypervisor): - # We should recreate the config file if the domain is present and running, - # regardless if we think the migration succeeded or not. - info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams) -- if info and _InstanceRuntime(info) != 0: -+ if info and _InstanceRunning(info): - self._WriteConfigFile(instance.name, config) - - if not success: diff -Nru ganeti-3.0.1/debian/patches/series ganeti-3.0.2/debian/patches/series --- ganeti-3.0.1/debian/patches/series 2021-03-02 15:13:14.000000000 +0200 +++ ganeti-3.0.2/debian/patches/series 2022-06-25 13:46:01.000000000 +0300 @@ -1,4 +1,2 @@ 0001-verify-warn-about-weak-certs.patch 0002-remove-hardcoded-libc-linux-constants.patch -0003-Fix-lvcreate-for-newer-lvm-versions-1586.patch -0004-Fix-live-migration-of-xen-instances-1582.patch diff -Nru ganeti-3.0.1/debian/rules ganeti-3.0.2/debian/rules --- ganeti-3.0.1/debian/rules 2021-03-02 14:40:51.000000000 +0200 +++ ganeti-3.0.2/debian/rules 2022-06-25 13:46:01.000000000 +0300 @@ -29,11 +29,6 @@ $(error Empty VCS version, the target must be run within the packaging git repository) else debian/control: debian/control.in - for file in debian/templates/*-VER*; do \ - fname="$$(echo $$file | sed -e 's#VER#$(VCS_VER)#' -e 's#templates/##')"; \ - sed -e 's/@version@/$(VCS_VER)/g' $$file >"$$fname"; \ - done - for package in ganeti-haskell-$(VCS_VER) ganeti-htools-$(VCS_VER) ganeti-$(VCS_VER); do \ for file in debian/templates/versioned.*; do \ kind="$$(echo $$file | sed -r 's#.*\.(.*)#\1#')"; \ @@ -43,6 +38,11 @@ done; \ done + for file in debian/templates/*-VER*; do \ + fname="$$(echo $$file | sed -e 's#VER#$(VCS_VER)#' -e 's#templates/##')"; \ + sed -e 's/@version@/$(VCS_VER)/g' $$file >"$$fname"; \ + done + sed -s 's/#VER#/$(VCS_VER)/g' debian/control.in > debian/control endif diff -Nru ganeti-3.0.1/debian/templates/ganeti-VER.postinst ganeti-3.0.2/debian/templates/ganeti-VER.postinst --- ganeti-3.0.1/debian/templates/ganeti-VER.postinst 1970-01-01 02:00:00.000000000 +0200 +++ ganeti-3.0.2/debian/templates/ganeti-VER.postinst 2022-06-25 13:46:01.000000000 +0300 @@ -0,0 +1,20 @@ +#!/bin/sh + +set -e + +. /usr/share/debconf/confmodule + +# Restore the 2.16 version.py diversion in case it was accidentally removed +# during upgrade. See #993559 + +if [ -z "$(dpkg-divert --list /usr/share/ganeti/2.16/ganeti/utils/version.py)" ]; then + echo "Restoring diversion of /usr/share/ganeti/2.16/ganeti/utils/version.py)" + dpkg-divert --add --rename --package ganeti-@version@ \ + --divert /usr/share/ganeti/2.16/ganeti/utils/version.py.orig \ + /usr/share/ganeti/2.16/ganeti/utils/version.py +fi + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# diff -Nru ganeti-3.0.1/debian/templates/ganeti-VER.postrm ganeti-3.0.2/debian/templates/ganeti-VER.postrm --- ganeti-3.0.1/debian/templates/ganeti-VER.postrm 2021-01-14 10:21:47.000000000 +0200 +++ ganeti-3.0.2/debian/templates/ganeti-VER.postrm 2022-06-25 13:46:01.000000000 +0300 @@ -2,7 +2,7 @@ set -e -if [ "$1" != "purge" ]; then +if [ "$1" = "remove" ]; then dpkg-divert --remove --rename --package ganeti-@version@ \ --divert /usr/share/ganeti/2.16/ganeti/utils/version.py.orig \ /usr/share/ganeti/2.16/ganeti/utils/version.py diff -Nru ganeti-3.0.1/doc/index.rst ganeti-3.0.2/doc/index.rst --- ganeti-3.0.1/doc/index.rst 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/doc/index.rst 2022-02-28 22:51:21.000000000 +0200 @@ -119,7 +119,6 @@ design-hotplug.rst design-internal-shutdown.rst design-kvmd.rst - design-location.rst design-linuxha.rst design-location.rst design-lu-generated-jobs.rst diff -Nru ganeti-3.0.1/.github/workflows/ci.yml ganeti-3.0.2/.github/workflows/ci.yml --- ganeti-3.0.1/.github/workflows/ci.yml 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/.github/workflows/ci.yml 2022-02-28 22:51:21.000000000 +0200 @@ -35,6 +35,11 @@ - name: Build run: make -j 2 + - name: Check Local + run: mv .github/ /tmp/ + && LC_ALL=C make check-local + && mv /tmp/.github/ . + - name: Python tests run: make py-tests diff -Nru ganeti-3.0.1/INSTALL ganeti-3.0.2/INSTALL --- ganeti-3.0.1/INSTALL 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/INSTALL 2022-02-28 22:51:21.000000000 +0200 @@ -33,7 +33,7 @@ - `Python OpenSSL bindings <https://www.pyopenssl.org/>`_ - `simplejson Python module <https://simplejson.readthedocs.io/>`_ - `pyparsing Python module <https://pyparsing-docs.readthedocs.io/>`_, version - 1.4.6 or above + 1.5.7 or above - `pyinotify Python module <https://github.com/seb-m/pyinotify>`_ - `PycURL Python module <http://pycurl.io/>`_ - `socat <http://www.dest-unreach.org/socat/>`_, see :ref:`note diff -Nru ganeti-3.0.1/lib/build/shell_example_lexer.py ganeti-3.0.2/lib/build/shell_example_lexer.py --- ganeti-3.0.1/lib/build/shell_example_lexer.py 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/lib/build/shell_example_lexer.py 2022-02-28 22:51:21.000000000 +0200 @@ -41,6 +41,7 @@ from pygments.lexer import RegexLexer, bygroups, include from pygments.token import Name, Text, Generic, Comment +import sphinx class ShellExampleLexer(RegexLexer): @@ -79,4 +80,8 @@ def setup(app): - app.add_lexer("shell-example", ShellExampleLexer()) + version = tuple(map(int, sphinx.__version__.split('.'))) + if version >= (2, 1, 0): + app.add_lexer("shell-example", ShellExampleLexer) + else: + app.add_lexer("shell-example", ShellExampleLexer()) diff -Nru ganeti-3.0.1/lib/build/sphinx_ext.py ganeti-3.0.2/lib/build/sphinx_ext.py --- ganeti-3.0.1/lib/build/sphinx_ext.py 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/lib/build/sphinx_ext.py 2022-02-28 22:51:21.000000000 +0200 @@ -360,7 +360,7 @@ # Force custom title kwargs["refexplicit"] = True - kwargs["refdomain"] = None + kwargs["refdomain"] = "std" return sphinx.addnodes.pending_xref(*args, **kwargs) diff -Nru ganeti-3.0.1/lib/cmdlib/cluster/__init__.py ganeti-3.0.2/lib/cmdlib/cluster/__init__.py --- ganeti-3.0.1/lib/cmdlib/cluster/__init__.py 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/lib/cmdlib/cluster/__init__.py 2022-02-28 22:51:21.000000000 +0200 @@ -1272,6 +1272,16 @@ self._CheckDrbdHelper(vm_capable_node_uuids, drbd_enabled, drbd_gets_enabled) + if (self.op.diskparams is not None and + constants.DT_DRBD8 in self.op.diskparams): + self.LogWarning("Changing DRBD parameters only affects devices created " + "in the future, not existing ones") + self.LogWarning("You need to shutdown and start (not reboot!) existing " + "instances to adopt the changes") + self.LogWarning("Alternatively you can swap the secondary node by " + "running `gnt-instance replace-disks --new-secondary " + "$instance`") + # validate params changes if self.op.beparams: objects.UpgradeBeParams(self.op.beparams) diff -Nru ganeti-3.0.1/lib/http/server.py ganeti-3.0.2/lib/http/server.py --- ganeti-3.0.1/lib/http/server.py 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/lib/http/server.py 2022-02-28 22:51:21.000000000 +0200 @@ -158,8 +158,7 @@ # message-body, [...]" return (http.HttpMessageWriter.HasMessageBody(self) and - (request_method is not None and - request_method != http.HTTP_HEAD) and + request_method != http.HTTP_HEAD and response_code >= http.HTTP_OK and response_code not in (http.HTTP_NO_CONTENT, http.HTTP_NOT_MODIFIED)) @@ -314,6 +313,8 @@ _HandleServerRequestInner(self._handler, request_msg, req_msg_reader) except http.HttpException as err: self._SetError(self.responses, self._handler, response_msg, err) + request_msg = http.HttpMessage() + req_msg_reader = None else: # Only wait for client to close if we didn't have any exception. force_close = False diff -Nru ganeti-3.0.1/lib/hypervisor/hv_kvm/__init__.py ganeti-3.0.2/lib/hypervisor/hv_kvm/__init__.py --- ganeti-3.0.1/lib/hypervisor/hv_kvm/__init__.py 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/lib/hypervisor/hv_kvm/__init__.py 2022-02-28 22:51:21.000000000 +0200 @@ -72,21 +72,20 @@ MonitorSocket from ganeti.hypervisor.hv_kvm.netdev import OpenTap +from ganeti.hypervisor.hv_kvm.validation import check_boot_parameters, \ + check_console_parameters, \ + check_disk_cache_parameters, \ + check_security_model,\ + check_spice_parameters, \ + check_vnc_parameters, \ + validate_machine_version, \ + validate_security_model, \ + validate_spice_parameters, \ + validate_vnc_parameters _KVM_NETWORK_SCRIPT = pathutils.CONF_DIR + "/kvm-vif-bridge" _KVM_START_PAUSED_FLAG = "-S" -#: SPICE parameters which depend on L{constants.HV_KVM_SPICE_BIND} -_SPICE_ADDITIONAL_PARAMS = frozenset([ - constants.HV_KVM_SPICE_IP_VERSION, - constants.HV_KVM_SPICE_PASSWORD_FILE, - constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR, - constants.HV_KVM_SPICE_JPEG_IMG_COMPR, - constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR, - constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION, - constants.HV_KVM_SPICE_USE_TLS, - ]) - # below constants show the format of runtime file # the nics are in second possition, while the disks in 4th (last) # moreover disk entries are stored as a list of in tuples @@ -488,7 +487,6 @@ _CTRL_DIR = _ROOT_DIR + "/ctrl" # contains instances control sockets _CONF_DIR = _ROOT_DIR + "/conf" # contains instances startup data _NICS_DIR = _ROOT_DIR + "/nic" # contains instances nic <-> tap associations - _KEYMAP_DIR = _ROOT_DIR + "/keymap" # contains instances keymaps # KVM instances with chroot enabled are started in empty chroot directories. _CHROOT_DIR = _ROOT_DIR + "/chroot" # for empty chroot directories # After an instance is stopped, its chroot directory is removed. @@ -498,7 +496,7 @@ # a separate directory, called 'chroot-quarantine'. _CHROOT_QUARANTINE_DIR = _ROOT_DIR + "/chroot-quarantine" _DIRS = [_ROOT_DIR, _PIDS_DIR, _UIDS_DIR, _CTRL_DIR, _CONF_DIR, _NICS_DIR, - _CHROOT_DIR, _CHROOT_QUARANTINE_DIR, _KEYMAP_DIR] + _CHROOT_DIR, _CHROOT_QUARANTINE_DIR] PARAMETERS = { constants.HV_KVM_PATH: hv_base.REQ_FILE_CHECK, @@ -609,11 +607,9 @@ _CPU_INFO_CMD = "info cpus" _DEFAULT_MACHINE_VERSION_RE = re.compile(r"^(\S+).*\(default\)", re.M) - _CHECK_MACHINE_VERSION_RE = \ - staticmethod(lambda x: re.compile(r"^(%s)[ ]+.*PC" % x, re.M)) _QMP_RE = re.compile(r"^-qmp\s", re.M) - _SPICE_RE = re.compile(r"^-spice\s", re.M) + _VHOST_RE = re.compile(r"^-netdev\stap.*,vhost=on\|off", re.M | re.S) _VIRTIO_NET_QUEUES_RE = re.compile(r"^-netdev\stap.*,fds=x:y:...:z", re.M) _ENABLE_KVM_RE = re.compile(r"^-enable-kvm\s", re.M) @@ -629,6 +625,10 @@ # different than -drive is starting) _BOOT_RE = re.compile(r"^-drive\s([^-]|(?<!^)-)*,boot=on\|off", re.M | re.S) _UUID_RE = re.compile(r"^-uuid\s", re.M) + # The auto-read-only option is on the -blockdev, Ganeti uses this at -drive + _AUTO_RO_RE = \ + re.compile(r"^-blockdev\s([^-]|(?<!^)-)*,auto-read-only=on\|off", + re.M | re.S) _INFO_VERSION_RE = \ re.compile(r'^QEMU (\d+)\.(\d+)(\.(\d+))?.*monitor.*', re.M) @@ -868,13 +868,6 @@ return utils.PathJoin(cls._InstanceNICDir(instance_name), str(seq)) @classmethod - def _InstanceKeymapFile(cls, instance_name): - """Returns the name of the file containing the keymap for a given instance - - """ - return utils.PathJoin(cls._KEYMAP_DIR, instance_name) - - @classmethod def _TryReadUidFile(cls, uid_file): """Try to read a uid file @@ -900,7 +893,6 @@ utils.RemoveFile(cls._InstanceQmpMonitor(instance_name)) utils.RemoveFile(cls._InstanceQemuGuestAgentMonitor(instance_name)) utils.RemoveFile(cls._InstanceKVMRuntime(instance_name)) - utils.RemoveFile(cls._InstanceKeymapFile(instance_name)) uid_file = cls._InstanceUidFile(instance_name) uid = cls._TryReadUidFile(uid_file) utils.RemoveFile(uid_file) @@ -1141,6 +1133,40 @@ data.append(info) return data + @staticmethod + def _GenerateDiskAioCacheParameters(disk_aio, disk_cache, dev_type): + """Generate appropriate aio/cache parameters for QEMU + + @type disk_aio: string + @param disk_aio: the instance's AIO parameter + @type disk_cache: string + @param disk_cache: the instance's disk cache parameter + @type dev_type: string + @param dev_type: the disk type in use + @rtype: string + @return: parameter string suitable for QEMU drive parameters + + """ + + if (dev_type in constants.DTS_EXT_MIRROR + and dev_type != constants.DT_RBD): + logging.warning("KVM: overriding disk_cache setting '%s' with 'none'" + " to prevent shared storage corruption on migration", + disk_cache) + disk_cache = constants.HT_CACHE_NONE + + if not disk_aio: + # QEMU defaults to 'threads', so do we + disk_aio = constants.HT_KVM_AIO_THREADS + + if disk_aio == constants.HT_KVM_AIO_NATIVE: + return ",aio=native,cache=none" + else: + if disk_cache == constants.HT_CACHE_DEFAULT: + return ",aio=threads" + else: + return ",aio=threads,cache=%s" % disk_cache + def _GenerateKVMBlockDevicesOptions(self, up_hvp, kvm_disks, kvmhelp, devlist): """Generate KVM options regarding instance's block devices. @@ -1185,12 +1211,6 @@ if_val = ",if=%s" % iface # for the -drive option device_driver = None # without -device option - # AIO mode - aio_mode = up_hvp[constants.HV_KVM_DISK_AIO] - if aio_mode == constants.HT_KVM_AIO_NATIVE: - aio_val = ",aio=%s" % aio_mode - else: - aio_val = "" # discard mode discard_mode = up_hvp[constants.HV_DISK_DISCARD] if discard_mode == constants.HT_DISCARD_DEFAULT: @@ -1200,24 +1220,9 @@ # Cache mode disk_cache = up_hvp[constants.HV_DISK_CACHE] for cfdev, link_name, uri in kvm_disks: - if (cfdev.dev_type in constants.DTS_EXT_MIRROR - and cfdev.dev_type != constants.DT_RBD): - if disk_cache != "none": - # TODO: make this a hard error, instead of a silent overwrite - logging.warning("KVM: overriding disk_cache setting '%s' with 'none'" - " to prevent shared storage corruption on migration", - disk_cache) - cache_val = ",cache=none" - elif aio_mode == constants.HT_KVM_AIO_NATIVE and disk_cache != "none": - # TODO: make this a hard error, instead of a silent overwrite - logging.warning("KVM: overriding disk_cache setting '%s' with 'none'" - " to prevent QEMU failures in version 2.6+", - disk_cache) - cache_val = ",cache=none" - elif disk_cache != constants.HT_CACHE_DEFAULT: - cache_val = ",cache=%s" % disk_cache - else: - cache_val = "" + aio_cache_val = self._GenerateDiskAioCacheParameters( + up_hvp[constants.HV_KVM_DISK_AIO], up_hvp[constants.HV_DISK_CACHE], + cfdev.dev_type) if cfdev.mode != constants.DISK_RDWR: raise errors.HypervisorError("Instance has read-only disks which" " are not supported by KVM") @@ -1231,8 +1236,8 @@ drive_uri = _GetDriveURI(cfdev, link_name, uri) - drive_val = "file=%s,format=raw%s%s%s%s%s" % \ - (drive_uri, if_val, boot_val, cache_val, aio_val, discard_val) + drive_val = "file=%s,format=raw%s%s%s%s" % \ + (drive_uri, if_val, boot_val, aio_cache_val, discard_val) # virtio-blk-pci case if device_driver is not None: @@ -1246,6 +1251,14 @@ dev_val += ",drive=%s" % kvm_devid dev_opts.extend(["-device", dev_val]) + # QEMU 4.0 introduced dynamic auto-read-only for file-backed drives. This + # is unhandled in Ganeti and breaks live migration with + # security_model=user|pool, disable it here. See also + # HotAddDevice/drive_add_fn which solves a similar problem for hotpluged + # disks + if self._AUTO_RO_RE.search(kvmhelp): + drive_val += ",auto-read-only=off" + dev_opts.extend(["-drive", drive_val]) return dev_opts @@ -1662,15 +1675,13 @@ # Add guest agent socket if hvp[constants.HV_USE_GUEST_AGENT]: - qga_addr = utils.GetFreeSlot(bus_slots[_PCI_BUS], reserve=True) - qga_pci_info = "bus=%s,addr=%s" % (_PCI_BUS, hex(qga_addr)) qga_path = self._InstanceQemuGuestAgentMonitor(instance.name) logging.info("KVM: Guest Agent available at %s", qga_path) # The 'qga0' identified can change, but the 'org.qemu.guest_agent.0' # string is the default expected by the Guest Agent. kvm_cmd.extend([ "-chardev", "socket,path=%s,server,nowait,id=qga0" % qga_path, - "-device", "virtio-serial,id=qga0,%s" % qga_pci_info, + "-device", "virtio-serial,id=qga0", "-device", "virtserialport,chardev=qga0,name=org.qemu.guest_agent.0", ]) @@ -1896,15 +1907,10 @@ if security_model == constants.HT_SM_USER: kvm_cmd.extend(["-runas", conf_hvp[constants.HV_SECURITY_DOMAIN]]) + # the VNC keymap keymap = conf_hvp[constants.HV_KEYMAP] if keymap: - keymap_path = self._InstanceKeymapFile(name) - # If a keymap file is specified, KVM won't use its internal defaults. By - # first including the "en-us" layout, an error on loading the actual - # layout (e.g. because it can't be found) won't lead to a non-functional - # keyboard. A keyboard with incorrect keys is still better than none. - utils.WriteFile(keymap_path, data="include en-us\ninclude %s\n" % keymap) - kvm_cmd.extend(["-k", keymap_path]) + kvm_cmd.extend(["-k", keymap]) # We have reasons to believe changing something like the nic driver/type # upon migration won't exactly fly with the instance kernel, so for nic @@ -2296,10 +2302,9 @@ cmd += ",auto-read-only=off" # When hot plugging a disk, parameters should match the current runtime. # I.e. for live migration, the cache mode is critical. - if up_hvp[constants.HV_DISK_CACHE] != constants.HT_CACHE_DEFAULT: - cmd += ",cache=%s" % up_hvp[constants.HV_DISK_CACHE] - if up_hvp[constants.HV_KVM_DISK_AIO] == constants.HT_KVM_AIO_NATIVE: - cmd += ",aio=%s" % up_hvp[constants.HV_KVM_DISK_AIO] + cmd += self._GenerateDiskAioCacheParameters( + up_hvp[constants.HV_KVM_DISK_AIO], up_hvp[constants.HV_DISK_CACHE], + device_type) if up_hvp[constants.HV_DISK_DISCARD] != constants.HT_DISCARD_DEFAULT: cmd += ",discard=%s" % up_hvp[constants.HV_DISK_DISCARD] self._CallMonitorCommand(instance.name, cmd) @@ -2313,12 +2318,14 @@ self.qmp.HotAddDisk(device, kvm_devid, uri, drive_add_fn) elif dev_type == constants.HOTPLUG_TARGET_NIC: kvmpath = instance.hvparams[constants.HV_KVM_PATH] + is_chrooted = instance.hvparams[constants.HV_KVM_USE_CHROOT] kvmhelp = self._GetKVMOutput(kvmpath, self._KVMOPT_HELP) devlist = self._GetKVMOutput(kvmpath, self._KVMOPT_DEVICELIST) features, _, _ = self._GetNetworkDeviceFeatures(up_hvp, devlist, kvmhelp) (tap, tapfds, vhostfds) = OpenTap(features=features) self._ConfigureNIC(instance, seq, device, tap) - self.qmp.HotAddNic(device, kvm_devid, tapfds, vhostfds, features) + self.qmp.HotAddNic(device, kvm_devid, tapfds, vhostfds, features, + is_chrooted) utils.WriteFile(self._InstanceNICFile(instance.name, seq), data=tap) self._VerifyHotplugCommand(instance, kvm_devid, True) @@ -2789,67 +2796,12 @@ """ super(KVMHypervisor, cls).CheckParameterSyntax(hvparams) - kernel_path = hvparams[constants.HV_KERNEL_PATH] - if kernel_path: - if not hvparams[constants.HV_ROOT_PATH]: - raise errors.HypervisorError("Need a root partition for the instance," - " if a kernel is defined") - - if (hvparams[constants.HV_VNC_X509_VERIFY] and - not hvparams[constants.HV_VNC_X509]): - raise errors.HypervisorError("%s must be defined, if %s is" % - (constants.HV_VNC_X509, - constants.HV_VNC_X509_VERIFY)) - - if hvparams[constants.HV_SERIAL_CONSOLE]: - serial_speed = hvparams[constants.HV_SERIAL_SPEED] - valid_speeds = constants.VALID_SERIAL_SPEEDS - if not serial_speed or serial_speed not in valid_speeds: - raise errors.HypervisorError("Invalid serial console speed, must be" - " one of: %s" % - utils.CommaJoin(valid_speeds)) - - boot_order = hvparams[constants.HV_BOOT_ORDER] - if (boot_order == constants.HT_BO_CDROM and - not hvparams[constants.HV_CDROM_IMAGE_PATH]): - raise errors.HypervisorError("Cannot boot from cdrom without an" - " ISO path") - - security_model = hvparams[constants.HV_SECURITY_MODEL] - if security_model == constants.HT_SM_USER: - if not hvparams[constants.HV_SECURITY_DOMAIN]: - raise errors.HypervisorError("A security domain (user to run kvm as)" - " must be specified") - elif (security_model == constants.HT_SM_NONE or - security_model == constants.HT_SM_POOL): - if hvparams[constants.HV_SECURITY_DOMAIN]: - raise errors.HypervisorError("Cannot have a security domain when the" - " security model is 'none' or 'pool'") - - spice_bind = hvparams[constants.HV_KVM_SPICE_BIND] - spice_ip_version = hvparams[constants.HV_KVM_SPICE_IP_VERSION] - if spice_bind: - if spice_ip_version != constants.IFACE_NO_IP_VERSION_SPECIFIED: - # if an IP version is specified, the spice_bind parameter must be an - # IP of that family - if (netutils.IP4Address.IsValid(spice_bind) and - spice_ip_version != constants.IP4_VERSION): - raise errors.HypervisorError("SPICE: Got an IPv4 address (%s), but" - " the specified IP version is %s" % - (spice_bind, spice_ip_version)) - - if (netutils.IP6Address.IsValid(spice_bind) and - spice_ip_version != constants.IP6_VERSION): - raise errors.HypervisorError("SPICE: Got an IPv6 address (%s), but" - " the specified IP version is %s" % - (spice_bind, spice_ip_version)) - else: - # All the other SPICE parameters depend on spice_bind being set. Raise an - # error if any of them is set without it. - for param in _SPICE_ADDITIONAL_PARAMS: - if hvparams[param]: - raise errors.HypervisorError("SPICE: %s requires %s to be set" % - (param, constants.HV_KVM_SPICE_BIND)) + check_boot_parameters(hvparams) + check_security_model(hvparams) + check_console_parameters(hvparams) + check_vnc_parameters(hvparams) + check_spice_parameters(hvparams) + check_disk_cache_parameters(hvparams) @classmethod def ValidateParameters(cls, hvparams): @@ -2862,56 +2814,16 @@ """ super(KVMHypervisor, cls).ValidateParameters(hvparams) + validate_security_model(hvparams) + validate_vnc_parameters(hvparams) + kvm_path = hvparams[constants.HV_KVM_PATH] - security_model = hvparams[constants.HV_SECURITY_MODEL] - if security_model == constants.HT_SM_USER: - username = hvparams[constants.HV_SECURITY_DOMAIN] - try: - pwd.getpwnam(username) - except KeyError: - raise errors.HypervisorError("Unknown security domain user %s" - % username) - vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS] - if vnc_bind_address: - bound_to_addr = (netutils.IP4Address.IsValid(vnc_bind_address) or - netutils.IP6Address.IsValid(vnc_bind_address)) - is_interface = netutils.IsValidInterface(vnc_bind_address) - is_path = utils.IsNormAbsPath(vnc_bind_address) - if not bound_to_addr and not is_interface and not is_path: - raise errors.HypervisorError("VNC: The %s parameter must be either" - " a valid IP address, an interface name," - " or an absolute path" % - constants.HV_VNC_BIND_ADDRESS) + kvm_output = cls._GetKVMOutput(kvm_path, cls._KVMOPT_HELP) + validate_spice_parameters(hvparams, kvm_output) - spice_bind = hvparams[constants.HV_KVM_SPICE_BIND] - if spice_bind: - # only one of VNC and SPICE can be used currently. - if hvparams[constants.HV_VNC_BIND_ADDRESS]: - raise errors.HypervisorError("Both SPICE and VNC are configured, but" - " only one of them can be used at a" - " given time") - - # check that KVM supports SPICE - kvmhelp = cls._GetKVMOutput(kvm_path, cls._KVMOPT_HELP) - if not cls._SPICE_RE.search(kvmhelp): - raise errors.HypervisorError("SPICE is configured, but it is not" - " supported according to 'kvm --help'") - - # if spice_bind is not an IP address, it must be a valid interface - bound_to_addr = (netutils.IP4Address.IsValid(spice_bind) or - netutils.IP6Address.IsValid(spice_bind)) - if not bound_to_addr and not netutils.IsValidInterface(spice_bind): - raise errors.HypervisorError("SPICE: The %s parameter must be either" - " a valid IP address or interface name" % - constants.HV_KVM_SPICE_BIND) - - machine_version = hvparams[constants.HV_KVM_MACHINE_VERSION] - if machine_version: - output = cls._GetKVMOutput(kvm_path, cls._KVMOPT_MLIST) - if not cls._CHECK_MACHINE_VERSION_RE(machine_version).search(output): - raise errors.HypervisorError("Unsupported machine version: %s" % - machine_version) + kvm_output = cls._GetKVMOutput(kvm_path, cls._KVMOPT_MLIST) + validate_machine_version(hvparams, kvm_output) @classmethod def PowercycleNode(cls, hvparams=None): diff -Nru ganeti-3.0.1/lib/hypervisor/hv_kvm/monitor.py ganeti-3.0.2/lib/hypervisor/hv_kvm/monitor.py --- ganeti-3.0.1/lib/hypervisor/hv_kvm/monitor.py 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/lib/hypervisor/hv_kvm/monitor.py 2022-02-28 22:51:21.000000000 +0200 @@ -260,6 +260,8 @@ def __init__(self, monitor_filename): super(QmpConnection, self).__init__(monitor_filename) + self.version = None + self.package = None self._buf = b"" self.supported_commands = None @@ -281,13 +283,21 @@ """ super(QmpConnection, self).connect() - # Check if we receive a correct greeting message from the server - # (As per the QEMU Protocol Specification 0.1 - section 2.2) - greeting = self._Recv() - if not greeting[self._FIRST_MESSAGE_KEY]: - self._connected = False - raise errors.HypervisorError("kvm: QMP communication error (wrong" - " server greeting") + # sometimes we receive asynchronous events instead of the intended greeting + # message - we ignore these for now. However, only 5 times to not get stuck + # in an endless connect() loop. + for x in range(0, 4): + # Check if we receive a correct greeting message from the server + # (As per the QEMU Protocol Specification 0.1 - section 2.2) + greeting = self._Recv() + if greeting[self._EVENT_KEY]: + continue + if not greeting[self._FIRST_MESSAGE_KEY]: + self._connected = False + raise errors.HypervisorError("kvm: QMP communication error (wrong" + " server greeting)") + else: + break # Extract the version info from the greeting and make it available to users # of the monitor. @@ -465,7 +475,8 @@ return ret @_ensure_connection - def HotAddNic(self, nic, devid, tapfds=None, vhostfds=None, features=None): + def HotAddNic(self, nic, devid, tapfds=None, vhostfds=None, features=None, + is_chrooted=False): """Hot-add a NIC First pass the tapfds, then netdev_add and then device_add @@ -492,6 +503,7 @@ "id": devid, "fds": ":".join(fdnames), } + if enable_vhost: fdnames = [] for i, fd in enumerate(vhostfds): @@ -500,7 +512,7 @@ fdnames.append(fdname) arguments.update({ - "vhost": "on", + "vhost": True, "vhostfds": ":".join(fdnames), }) self.Execute("netdev_add", arguments) @@ -509,6 +521,11 @@ "netdev": devid, "mac": nic.mac, } + if is_chrooted: + # do not try to load a rom file when we are running qemu chrooted + arguments.update({ + "romfile": "", + }) # Note that hvinfo that _GenerateDeviceHVInfo() creates # should include *only* the driver, id, bus, and addr keys arguments.update(self._filter_hvinfo(nic.hvinfo)) diff -Nru ganeti-3.0.1/lib/hypervisor/hv_kvm/validation.py ganeti-3.0.2/lib/hypervisor/hv_kvm/validation.py --- ganeti-3.0.1/lib/hypervisor/hv_kvm/validation.py 1970-01-01 02:00:00.000000000 +0200 +++ ganeti-3.0.2/lib/hypervisor/hv_kvm/validation.py 2022-02-28 22:51:21.000000000 +0200 @@ -0,0 +1,206 @@ +# +# + +# Copyright (C) 2022 the Ganeti project +# 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. +# +# 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 HOLDER 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. + + +"""KVM hypervisor parameter/syntax validation helpers + +""" + +import re +import pwd + +from ganeti import constants +from ganeti import netutils +from ganeti import errors +from ganeti import utils + +#: SPICE parameters which depend on L{constants.HV_KVM_SPICE_BIND} +_SPICE_ADDITIONAL_PARAMS = frozenset([ + constants.HV_KVM_SPICE_IP_VERSION, + constants.HV_KVM_SPICE_PASSWORD_FILE, + constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR, + constants.HV_KVM_SPICE_JPEG_IMG_COMPR, + constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR, + constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION, + constants.HV_KVM_SPICE_USE_TLS, + ]) + +_SPICE_RE = re.compile(r"^-spice\s", re.M) +_CHECK_MACHINE_VERSION_RE = [lambda x: re.compile(r"^(%s)[ ]+.*PC" % x, re.M)] + + +def check_spice_parameters(hvparams): + spice_bind = hvparams[constants.HV_KVM_SPICE_BIND] + spice_ip_version = hvparams[constants.HV_KVM_SPICE_IP_VERSION] + if spice_bind: + if spice_ip_version != constants.IFACE_NO_IP_VERSION_SPECIFIED: + # if an IP version is specified, the spice_bind parameter must be an + # IP of that family + if (netutils.IP4Address.IsValid(spice_bind) and + spice_ip_version != constants.IP4_VERSION): + raise errors.HypervisorError("SPICE: Got an IPv4 address (%s), but" + " the specified IP version is %s" % + (spice_bind, spice_ip_version)) + + if (netutils.IP6Address.IsValid(spice_bind) and + spice_ip_version != constants.IP6_VERSION): + raise errors.HypervisorError("SPICE: Got an IPv6 address (%s), but" + " the specified IP version is %s" % + (spice_bind, spice_ip_version)) + else: + # All the other SPICE parameters depend on spice_bind being set. Raise an + # error if any of them is set without it. + for param in _SPICE_ADDITIONAL_PARAMS: + if hvparams[param]: + raise errors.HypervisorError("SPICE: %s requires %s to be set" % + (param, constants.HV_KVM_SPICE_BIND)) + return True + + +def validate_spice_parameters(hvparams, kvm_help_output): + spice_bind = hvparams[constants.HV_KVM_SPICE_BIND] + if spice_bind: + # only one of VNC and SPICE can be used currently. + if hvparams[constants.HV_VNC_BIND_ADDRESS]: + raise errors.HypervisorError("Both SPICE and VNC are configured, but" + " only one of them can be used at a" + " given time") + + # check that KVM supports SPICE + + if not _SPICE_RE.search(kvm_help_output): + raise errors.HypervisorError("SPICE is configured, but it is not" + " supported according to 'kvm --help'") + + # if spice_bind is not an IP address, it must be a valid interface + bound_to_addr = (netutils.IP4Address.IsValid(spice_bind) or + netutils.IP6Address.IsValid(spice_bind)) + if not bound_to_addr and not netutils.IsValidInterface(spice_bind): + raise errors.HypervisorError("SPICE: The %s parameter must be either" + " a valid IP address or interface name" % + constants.HV_KVM_SPICE_BIND) + return True + + +def check_vnc_parameters(hvparams): + if (hvparams[constants.HV_VNC_X509_VERIFY] and + not hvparams[constants.HV_VNC_X509]): + raise errors.HypervisorError("%s must be defined, if %s is" % + (constants.HV_VNC_X509, + constants.HV_VNC_X509_VERIFY)) + return True + + +def validate_vnc_parameters(hvparams): + vnc_bind_address = hvparams[constants.HV_VNC_BIND_ADDRESS] + if vnc_bind_address: + bound_to_addr = (netutils.IP4Address.IsValid(vnc_bind_address) or + netutils.IP6Address.IsValid(vnc_bind_address)) + is_interface = netutils.IsValidInterface(vnc_bind_address) + is_path = utils.IsNormAbsPath(vnc_bind_address) + if not bound_to_addr and not is_interface and not is_path: + raise errors.HypervisorError("VNC: The %s parameter must be either" + " a valid IP address, an interface name," + " or an absolute path" % + constants.HV_VNC_BIND_ADDRESS) + return True + + +def check_security_model(hvparams): + security_model = hvparams[constants.HV_SECURITY_MODEL] + if security_model == constants.HT_SM_USER: + if not hvparams[constants.HV_SECURITY_DOMAIN]: + raise errors.HypervisorError( + "A security domain (user to run kvm as)" + " must be specified") + elif (security_model == constants.HT_SM_NONE or + security_model == constants.HT_SM_POOL): + if hvparams[constants.HV_SECURITY_DOMAIN]: + raise errors.HypervisorError( + "Cannot have a security domain when the" + " security model is 'none' or 'pool'") + return True + + +def validate_security_model(hvparams): + security_model = hvparams[constants.HV_SECURITY_MODEL] + if security_model == constants.HT_SM_USER: + username = hvparams[constants.HV_SECURITY_DOMAIN] + try: + pwd.getpwnam(username) + except KeyError: + raise errors.HypervisorError("Unknown security domain user %s" + % username) + return True + + +def check_boot_parameters(hvparams): + boot_order = hvparams[constants.HV_BOOT_ORDER] + if (boot_order == constants.HT_BO_CDROM and + not hvparams[constants.HV_CDROM_IMAGE_PATH]): + raise errors.HypervisorError("Cannot boot from cdrom without an" + " ISO path") + kernel_path = hvparams[constants.HV_KERNEL_PATH] + if kernel_path: + if not hvparams[constants.HV_ROOT_PATH]: + raise errors.HypervisorError("Need a root partition for the instance," + " if a kernel is defined") + return True + + +def check_console_parameters(hvparams): + if hvparams[constants.HV_SERIAL_CONSOLE]: + serial_speed = hvparams[constants.HV_SERIAL_SPEED] + valid_speeds = constants.VALID_SERIAL_SPEEDS + if not serial_speed or serial_speed not in valid_speeds: + raise errors.HypervisorError("Invalid serial console speed, must be" + " one of: %s" % + utils.CommaJoin(valid_speeds)) + return True + + +def validate_machine_version(hvparams, kvm_machine_output): + machine_version = hvparams[constants.HV_KVM_MACHINE_VERSION] + if machine_version: + for test in _CHECK_MACHINE_VERSION_RE: + if not test(machine_version).search(kvm_machine_output): + raise errors.HypervisorError("Unsupported machine version: %s" % + machine_version) + return True + + +def check_disk_cache_parameters(hvparams): + disk_aio = hvparams[constants.HV_KVM_DISK_AIO] + disk_cache = hvparams[constants.HV_DISK_CACHE] + if disk_aio == constants.HT_KVM_AIO_NATIVE and \ + disk_cache != constants.HT_CACHE_NONE: + raise errors.HypervisorError("When 'disk_aio' is set to 'native', the " + "only supported value for 'disk_cache' is " + "'none'.") + return True diff -Nru ganeti-3.0.1/lib/hypervisor/hv_xen.py ganeti-3.0.2/lib/hypervisor/hv_xen.py --- ganeti-3.0.1/lib/hypervisor/hv_xen.py 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/lib/hypervisor/hv_xen.py 2022-02-28 22:51:21.000000000 +0200 @@ -180,6 +180,16 @@ return info[1] +def _InstanceRunning(info): + """Get instance runtime from instance info tuple. + @type info: tuple + @param info: instance info as parsed by _ParseInstanceList() + + @return: bool + """ + return info[4] == hv_base.HvInstanceState.RUNNING + + def _InstanceRuntime(info): """Get instance runtime from instance info tuple. @type info: tuple @@ -780,10 +790,11 @@ This version of the function just writes the config file from static data. """ + cfg_file = self._ConfigFileName(instance_name) + # just in case it exists utils.RemoveFile(utils.PathJoin(self._cfgdir, "auto", instance_name)) - cfg_file = self._ConfigFileName(instance_name) try: utils.WriteFile(cfg_file, data=data) except EnvironmentError as err: @@ -1065,10 +1076,10 @@ if not force: self._ShutdownInstance(name, hvparams, timeout) - # TODO: Xen does always destroy the instnace after trying a gracefull - # shutdown. That means doing another attempt with force=True will not make - # any difference. This differs in behaviour from other hypervisors and - # should be cleaned up. + # TODO: Xen always destroys the instance after trying a graceful shutdown. + # That means doing another attempt with force=True will not make any + # difference. This differs in behaviour from other hypervisors and should + # be cleaned up. result = self._DestroyInstanceIfAlive(name, hvparams) if result is not None and result.failed and \ self.GetInstanceInfo(name, hvparams=hvparams) is not None: @@ -1295,7 +1306,7 @@ # We should recreate the config file if the domain is present and running, # regardless if we think the migration succeeded or not. info = self.GetInstanceInfo(instance.name, hvparams=instance.hvparams) - if info and _InstanceRuntime(info) != 0: + if info and _InstanceRunning(info): self._WriteConfigFile(instance.name, config) if not success: diff -Nru ganeti-3.0.1/lib/qlang.py ganeti-3.0.2/lib/qlang.py --- ganeti-3.0.1/lib/qlang.py 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/lib/qlang.py 2022-02-28 22:51:21.000000000 +0200 @@ -221,7 +221,7 @@ glob_cond ^ not_glob_cond) # Associativity operators - filter_expr = pyp.operatorPrecedence(condition, [ + filter_expr = pyp.infixNotation(condition, [ (pyp.Keyword("not").suppress(), 1, pyp.opAssoc.RIGHT, lambda toks: [[OP_NOT, toks[0][0]]]), (pyp.Keyword("and").suppress(), 2, pyp.opAssoc.LEFT, diff -Nru ganeti-3.0.1/lib/storage/bdev.py ganeti-3.0.2/lib/storage/bdev.py --- ganeti-3.0.1/lib/storage/bdev.py 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/lib/storage/bdev.py 2022-02-28 22:51:21.000000000 +0200 @@ -215,7 +215,10 @@ # create an optimally-striped volume; in that case, we want to try # with N, N-1, ..., 2, and finally 1 (non-stripped) number of # stripes - cmd = ["lvcreate", "-L%dm" % size, "-n%s" % lv_name] + # When run non-interactively, newer LVM versions will fail (unless + # `--yes` is specified) when an existing filesystem signature is + # encountered while creating a new LV. Using `-Wn` disables this check. + cmd = ["lvcreate", "-Wn", "-L%dm" % size, "-n%s" % lv_name] for stripes_arg in range(stripes, 0, -1): result = utils.RunCmd(cmd + ["-i%d" % stripes_arg] + [vg_name] + pvlist) if not result.failed: diff -Nru ganeti-3.0.1/lib/tools/common.py ganeti-3.0.2/lib/tools/common.py --- ganeti-3.0.1/lib/tools/common.py 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/lib/tools/common.py 2022-02-28 22:51:21.000000000 +0200 @@ -99,7 +99,7 @@ cert_encoded = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert) complete_cert_encoded = key_encoded + cert_encoded - if not cert_pem == complete_cert_encoded: + if not cert_pem == complete_cert_encoded.decode('ascii'): logging.error("The certificate differs after being reencoded. Please" " renew the certificates cluster-wide to prevent future" " inconsistencies.") diff -Nru ganeti-3.0.1/Makefile.am ganeti-3.0.2/Makefile.am --- ganeti-3.0.1/Makefile.am 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/Makefile.am 2022-02-28 22:51:21.000000000 +0200 @@ -545,7 +545,8 @@ hypervisor_hv_kvm_PYTHON = \ lib/hypervisor/hv_kvm/__init__.py \ lib/hypervisor/hv_kvm/monitor.py \ - lib/hypervisor/hv_kvm/netdev.py + lib/hypervisor/hv_kvm/netdev.py \ + lib/hypervisor/hv_kvm/validation.py jqueue_PYTHON = \ lib/jqueue/__init__.py \ @@ -1846,6 +1847,7 @@ test/data/kvm_0.9.1_help_boot_test.txt \ test/data/kvm_1.0_help.txt \ test/data/kvm_1.1.2_help.txt \ + test/data/kvm_6.0.0_machine.txt \ test/data/kvm_runtime.json \ test/data/lvs_lv.txt \ test/data/NEWS_OK.txt \ diff -Nru ganeti-3.0.1/man/gnt-instance.rst ganeti-3.0.2/man/gnt-instance.rst --- ganeti-3.0.1/man/gnt-instance.rst 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/man/gnt-instance.rst 2022-02-28 22:51:21.000000000 +0200 @@ -904,6 +904,18 @@ Path to the userspace KVM (or qemu) program. +vhost\_net + Valid for the KVM hypervisor. + + This boolean option determines whether the tap devices used by the KVM + paravirtual nics (virtio-net) will use accelerated data plane, passing + network packets directly between host and guest kernel, without going + through userspace emulation layer (qemu). + + Historically it is set to ``false`` by default. New Clusters created + with Ganeti-3.1 and newer defaults to ``true``. Everyone is encouraged to + enable it. + vnet\_hdr Valid for the KVM hypervisor. diff -Nru ganeti-3.0.1/NEWS ganeti-3.0.2/NEWS --- ganeti-3.0.1/NEWS 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/NEWS 2022-02-28 22:51:21.000000000 +0200 @@ -2,6 +2,39 @@ ==== +Version 3.0.2 +------------- + +*(Released Mon, 28 Feb 2022)* + +Changes since 3.0.1 +~~~~~~~~~~~~~~~~~~~ + +This release contains the following bug- and compatibility fixes: + + - KVM: fix NIC hotplugging with ``vhost_net=True`` (#1651), + ``use_chroot=True`` (#1644) and ``use_guest_agent=True`` (#1620). + - KVM: fix asynchronous events breaking QMP handshakes (#1649) + - KVM: handle ``disk_cache`` consistently between boot and hotplugging + (#1645) + - KVM: fix live migration with non-root / chrooted QEMU (dynamic auto-ro) + (#1603) + - KVM: fix unsupported keymap include in >=qemu-4.0 (#1612) + - XEN: fix live migration of xen instances (#1582) + - NET: relax VLAN check with veth devices (#1533) + - LVM: fix lvcreate for newer lvm versions (#1586) + - DRBD: warn users that altered DRBD parameters do not affect existing + devices (#781) + - Node-Add: byte/string comparison causes false-positive warning (#1635) + - RAPI: return HTTP 400 on request parse error (#1610) + - build: fix building docs on Debian Bullseye (#1602) + - build: adjust for Pyparsing 3.0 (#1638) + - build: adjust for TupE type change in Template Haskell 2.16 (#1613) + - build: permit base64-bytestring 1.1 and QuickCheck 2.14 (#1613) + - tools: fix 2to3 leftover for move-instance (#1616) + - Docs: fix building on recent sphinx versions (#1602) + + Version 3.0.1 ------------- diff -Nru ganeti-3.0.1/src/Ganeti/THH/Compat.hs ganeti-3.0.2/src/Ganeti/THH/Compat.hs --- ganeti-3.0.1/src/Ganeti/THH/Compat.hs 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/src/Ganeti/THH/Compat.hs 2022-02-28 22:51:21.000000000 +0200 @@ -6,7 +6,7 @@ {- -Copyright (C) 2018 Ganeti Project Contributors. +Copyright (C) 2018, 2021 Ganeti Project Contributors. All rights reserved. Redistribution and use in source and binary forms, with or without @@ -39,6 +39,7 @@ , gntDataD , extractDataDConstructors , myNotStrict + , nonUnaryTupE ) where import Language.Haskell.TH @@ -104,3 +105,12 @@ #else myNotStrict = NotStrict #endif + +-- | TupE changed from '[Exp] -> Exp' to '[Maybe Exp] -> Exp'. +-- Provide the old signature for compatibility. +nonUnaryTupE :: [Exp] -> Exp +#if MIN_VERSION_template_haskell(2,16,0) +nonUnaryTupE es = TupE $ map Just es +#else +nonUnaryTupE es = TupE $ es +#endif diff -Nru ganeti-3.0.1/src/Ganeti/THH/Types.hs ganeti-3.0.2/src/Ganeti/THH/Types.hs --- ganeti-3.0.1/src/Ganeti/THH/Types.hs 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/src/Ganeti/THH/Types.hs 2022-02-28 22:51:21.000000000 +0200 @@ -49,6 +49,7 @@ import Control.Monad (liftM, replicateM) import Language.Haskell.TH import qualified Text.JSON as J +import Ganeti.THH.Compat (nonUnaryTupE) -- | This fills the gap between @()@ and @(,)@, providing a wrapper for -- 1-element tuples. It's needed for RPC, where arguments for a function are @@ -123,4 +124,4 @@ f <- newName "f" ps <- replicateM n (newName "x") return $ LamE (VarP f : map VarP ps) - (AppE (VarE f) (TupE $ map VarE ps)) + (AppE (VarE f) (nonUnaryTupE $ map VarE ps)) diff -Nru ganeti-3.0.1/test/data/kvm_6.0.0_machine.txt ganeti-3.0.2/test/data/kvm_6.0.0_machine.txt --- ganeti-3.0.1/test/data/kvm_6.0.0_machine.txt 1970-01-01 02:00:00.000000000 +0200 +++ ganeti-3.0.2/test/data/kvm_6.0.0_machine.txt 2022-02-28 22:51:21.000000000 +0200 @@ -0,0 +1,96 @@ +Supported machines are: +microvm microvm (i386) +pc-i440fx-zesty Ubuntu 17.04 PC (i440FX + PIIX, 1996) +pc-i440fx-yakkety Ubuntu 16.10 PC (i440FX + PIIX, 1996) +pc-i440fx-xenial Ubuntu 16.04 PC (i440FX + PIIX, 1996) +pc-i440fx-wily Ubuntu 15.04 PC (i440FX + PIIX, 1996) +pc-i440fx-trusty Ubuntu 14.04 PC (i440FX + PIIX, 1996) +ubuntu Ubuntu 21.10 PC (i440FX + PIIX, 1996) (alias of pc-i440fx-impish) +pc-i440fx-impish Ubuntu 21.10 PC (i440FX + PIIX, 1996) (default) +pc-i440fx-impish-hpb Ubuntu 21.10 PC (i440FX + PIIX +host-phys-bits=true, 1996) +pc-i440fx-hirsute Ubuntu 21.04 PC (i440FX + PIIX, 1996) +pc-i440fx-hirsute-hpb Ubuntu 21.04 PC (i440FX + PIIX +host-phys-bits=true, 1996) +pc-i440fx-groovy Ubuntu 20.10 PC (i440FX + PIIX, 1996) +pc-i440fx-groovy-hpb Ubuntu 20.10 PC (i440FX + PIIX +host-phys-bits=true, 1996) +pc-i440fx-focal Ubuntu 20.04 PC (i440FX + PIIX, 1996) +pc-i440fx-focal-hpb Ubuntu 20.04 PC (i440FX + PIIX +host-phys-bits=true, 1996) +pc-i440fx-eoan Ubuntu 19.10 PC (i440FX + PIIX, 1996) +pc-i440fx-eoan-hpb Ubuntu 19.10 PC (i440FX + PIIX +host-phys-bits=true, 1996) +pc-i440fx-disco Ubuntu 19.04 PC (i440FX + PIIX, 1996) +pc-i440fx-disco-hpb Ubuntu 19.04 PC (i440FX + PIIX +host-phys-bits=true, 1996) +pc-i440fx-cosmic Ubuntu 18.10 PC (i440FX + PIIX, 1996) +pc-i440fx-cosmic-hpb Ubuntu 18.10 PC (i440FX + PIIX +host-phys-bits=true, 1996) +pc-i440fx-bionic Ubuntu 18.04 PC (i440FX + PIIX, 1996) +pc-i440fx-bionic-hpb Ubuntu 18.04 PC (i440FX + PIIX, +host-phys-bits=true, 1996) +pc-i440fx-artful Ubuntu 17.10 PC (i440FX + PIIX, 1996) +pc Standard PC (i440FX + PIIX, 1996) (alias of pc-i440fx-6.0) +pc-i440fx-6.0 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-5.2 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-5.1 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-5.0 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-4.2 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-4.1 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-4.0 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-3.1 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-3.0 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.9 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.8 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.7 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.6 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.5 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.4 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.3 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.2 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.12 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.11 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.10 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.1 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-2.0 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-1.7 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-1.6 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-1.5 Standard PC (i440FX + PIIX, 1996) +pc-i440fx-1.4 Standard PC (i440FX + PIIX, 1996) +pc-q35-zesty Ubuntu 17.04 PC (Q35 + ICH9, 2009) +pc-q35-yakkety Ubuntu 16.10 PC (Q35 + ICH9, 2009) +pc-q35-xenial Ubuntu 16.04 PC (Q35 + ICH9, 2009) +ubuntu-q35 Ubuntu 21.10 PC (Q35 + ICH9, 2009) (alias of pc-q35-impish) +pc-q35-impish Ubuntu 21.10 PC (Q35 + ICH9, 2009) +pc-q35-impish-hpb Ubuntu 21.10 PC (Q35 + ICH9, +host-phys-bits=true, 2009) +pc-q35-hirsute Ubuntu 21.04 PC (Q35 + ICH9, 2009) +pc-q35-hirsute-hpb Ubuntu 21.04 PC (Q35 + ICH9, +host-phys-bits=true, 2009) +pc-q35-groovy Ubuntu 20.10 PC (Q35 + ICH9, 2009) +pc-q35-groovy-hpb Ubuntu 20.10 PC (Q35 + ICH9, +host-phys-bits=true, 2009) +pc-q35-focal Ubuntu 20.04 PC (Q35 + ICH9, 2009) +pc-q35-focal-hpb Ubuntu 20.04 PC (Q35 + ICH9, +host-phys-bits=true, 2009) +pc-q35-eoan Ubuntu 19.10 PC (Q35 + ICH9, 2009) +pc-q35-eoan-hpb Ubuntu 19.10 PC (Q35 + ICH9, +host-phys-bits=true, 2009) +pc-q35-disco Ubuntu 19.04 PC (Q35 + ICH9, 2009) +pc-q35-disco-hpb Ubuntu 19.04 PC (Q35 + ICH9, +host-phys-bits=true, 2009) +pc-q35-cosmic Ubuntu 18.10 PC (Q35 + ICH9, 2009) +pc-q35-cosmic-hpb Ubuntu 18.10 PC (Q35 + ICH9, +host-phys-bits=true, 2009) +pc-q35-bionic Ubuntu 18.04 PC (Q35 + ICH9, 2009) +pc-q35-bionic-hpb Ubuntu 18.04 PC (Q35 + ICH9, +host-phys-bits=true, 2009) +pc-q35-artful Ubuntu 17.10 PC (Q35 + ICH9, 2009) +q35 Standard PC (Q35 + ICH9, 2009) (alias of pc-q35-6.0) +pc-q35-6.0 Standard PC (Q35 + ICH9, 2009) +pc-q35-5.2 Standard PC (Q35 + ICH9, 2009) +pc-q35-5.1 Standard PC (Q35 + ICH9, 2009) +pc-q35-5.0 Standard PC (Q35 + ICH9, 2009) +pc-q35-4.2 Standard PC (Q35 + ICH9, 2009) +pc-q35-4.1 Standard PC (Q35 + ICH9, 2009) +pc-q35-4.0.1 Standard PC (Q35 + ICH9, 2009) +pc-q35-4.0 Standard PC (Q35 + ICH9, 2009) +pc-q35-3.1 Standard PC (Q35 + ICH9, 2009) +pc-q35-3.0 Standard PC (Q35 + ICH9, 2009) +pc-q35-2.9 Standard PC (Q35 + ICH9, 2009) +pc-q35-2.8 Standard PC (Q35 + ICH9, 2009) +pc-q35-2.7 Standard PC (Q35 + ICH9, 2009) +pc-q35-2.6 Standard PC (Q35 + ICH9, 2009) +pc-q35-2.5 Standard PC (Q35 + ICH9, 2009) +pc-q35-2.4 Standard PC (Q35 + ICH9, 2009) +pc-q35-2.12 Standard PC (Q35 + ICH9, 2009) +pc-q35-2.11 Standard PC (Q35 + ICH9, 2009) +pc-q35-2.10 Standard PC (Q35 + ICH9, 2009) +isapc ISA-only PC +none empty machine +x-remote Experimental remote machine \ No newline at end of file diff -Nru ganeti-3.0.1/test/py/ganeti.hypervisor.hv_kvm_unittest.py ganeti-3.0.2/test/py/ganeti.hypervisor.hv_kvm_unittest.py --- ganeti-3.0.1/test/py/ganeti.hypervisor.hv_kvm_unittest.py 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/test/py/ganeti.hypervisor.hv_kvm_unittest.py 2022-02-28 22:51:21.000000000 +0200 @@ -49,6 +49,7 @@ from ganeti.hypervisor import hv_kvm import ganeti.hypervisor.hv_kvm.netdev as netdev import ganeti.hypervisor.hv_kvm.monitor as monitor +import ganeti.hypervisor.hv_kvm.validation as validation import mock import testutils @@ -149,6 +150,468 @@ self.socket.close() +class TestParameterCheck(testutils.GanetiTestCase): + def testInvalidVncParameters(self): + invalid_data = { + constants.HV_VNC_X509_VERIFY: True, + constants.HV_VNC_X509: None + } + + self.assertRaises(errors.HypervisorError, + validation.check_vnc_parameters, invalid_data) + + def testValidVncParameters(self): + valid_data = { + constants.HV_VNC_X509_VERIFY: True, + constants.HV_VNC_X509: "mycert.pem" + } + + self.assertTrue(validation.check_vnc_parameters(valid_data)) + + def testInvalidSecurityModel(self): + invalid_data = { + constants.HV_SECURITY_MODEL: constants.HT_SM_USER, + constants.HV_SECURITY_DOMAIN: None + } + + self.assertRaises(errors.HypervisorError, + validation.check_security_model, invalid_data) + + invalid_data = { + constants.HV_SECURITY_MODEL: constants.HT_SM_NONE, + constants.HV_SECURITY_DOMAIN: "secure_user" + } + + self.assertRaises(errors.HypervisorError, + validation.check_security_model, invalid_data) + + invalid_data = { + constants.HV_SECURITY_MODEL: constants.HT_SM_POOL, + constants.HV_SECURITY_DOMAIN: "secure_user" + } + + self.assertRaises(errors.HypervisorError, + validation.check_security_model, invalid_data) + + def testValidSecurityModel(self): + valid_data = { + constants.HV_SECURITY_MODEL: constants.HT_SM_USER, + constants.HV_SECURITY_DOMAIN: "secure_user" + } + + self.assertTrue(validation.check_security_model(valid_data)) + + valid_data = { + constants.HV_SECURITY_MODEL: constants.HT_SM_POOL, + constants.HV_SECURITY_DOMAIN: None + } + + self.assertTrue(validation.check_security_model(valid_data)) + + valid_data = { + constants.HV_SECURITY_MODEL: constants.HT_SM_NONE, + constants.HV_SECURITY_DOMAIN: None + } + + self.assertTrue(validation.check_security_model(valid_data)) + + def testInvalidBootParameters(self): + invalid_data = { + constants.HV_BOOT_ORDER: constants.HT_BO_CDROM, + constants.HV_CDROM_IMAGE_PATH: None, + constants.HV_KERNEL_PATH: "/some/path", + constants.HV_ROOT_PATH: "/" + } + + self.assertRaises(errors.HypervisorError, + validation.check_boot_parameters, invalid_data) + + invalid_data = { + constants.HV_BOOT_ORDER: constants.HT_BO_CDROM, + constants.HV_CDROM_IMAGE_PATH: "/cd.iso", + constants.HV_KERNEL_PATH: "/some/path", + constants.HV_ROOT_PATH: None + } + + self.assertRaises(errors.HypervisorError, + validation.check_boot_parameters, invalid_data) + + def testValidBootParameters(self): + valid_data = { + constants.HV_BOOT_ORDER: constants.HT_BO_CDROM, + constants.HV_CDROM_IMAGE_PATH: "/cd.iso", + constants.HV_KERNEL_PATH: "/some/path", + constants.HV_ROOT_PATH: "/" + } + + self.assertTrue(validation.check_boot_parameters(valid_data)) + + valid_data = { + constants.HV_BOOT_ORDER: constants.HT_BO_DISK, + constants.HV_CDROM_IMAGE_PATH: None, + constants.HV_KERNEL_PATH: "/some/path", + constants.HV_ROOT_PATH: "/" + } + + self.assertTrue(validation.check_boot_parameters(valid_data)) + + valid_data = { + constants.HV_BOOT_ORDER: constants.HT_BO_DISK, + constants.HV_CDROM_IMAGE_PATH: None, + constants.HV_KERNEL_PATH: None, + constants.HV_ROOT_PATH: None + } + + self.assertTrue(validation.check_boot_parameters(valid_data)) + + def testInvalidConsoleParameters(self): + invalid_data = { + constants.HV_SERIAL_CONSOLE: True, + constants.HV_SERIAL_SPEED: None, + } + + self.assertRaises(errors.HypervisorError, + validation.check_console_parameters, invalid_data) + + invalid_data = { + constants.HV_SERIAL_CONSOLE: True, + constants.HV_SERIAL_SPEED: 1, + } + + self.assertRaises(errors.HypervisorError, + validation.check_console_parameters, invalid_data) + + def testValidConsoleParameters(self): + valid_data = { + constants.HV_SERIAL_CONSOLE: False + } + + self.assertTrue(validation.check_console_parameters(valid_data)) + + for speed in constants.VALID_SERIAL_SPEEDS: + valid_data = { + constants.HV_SERIAL_CONSOLE: True, + constants.HV_SERIAL_SPEED: speed + } + + self.assertTrue(validation.check_console_parameters(valid_data), + "Testing serial console speed %d" % speed) + + def testInvalidSpiceParameters(self): + invalid_data = { + constants.HV_KVM_SPICE_BIND: "0.0.0.0", + constants.HV_KVM_SPICE_IP_VERSION: constants.IP6_VERSION + } + + self.assertRaises(errors.HypervisorError, + validation.check_spice_parameters, invalid_data) + + invalid_data = { + constants.HV_KVM_SPICE_BIND: "::", + constants.HV_KVM_SPICE_IP_VERSION: constants.IP4_VERSION + } + + self.assertRaises(errors.HypervisorError, + validation.check_spice_parameters, invalid_data) + + invalid_data = { + constants.HV_KVM_SPICE_BIND: None, + constants.HV_KVM_SPICE_IP_VERSION: None, + constants.HV_KVM_SPICE_PASSWORD_FILE: "password.txt", + constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR: None, + constants.HV_KVM_SPICE_JPEG_IMG_COMPR: None, + constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: None, + constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: None, + constants.HV_KVM_SPICE_USE_TLS: True + } + + self.assertRaises(errors.HypervisorError, + validation.check_spice_parameters, invalid_data) + + def testValidSpiceParameters(self): + valid_data = { + constants.HV_KVM_SPICE_BIND: None, + constants.HV_KVM_SPICE_IP_VERSION: None, + constants.HV_KVM_SPICE_PASSWORD_FILE: None, + constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR: None, + constants.HV_KVM_SPICE_JPEG_IMG_COMPR: None, + constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: None, + constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: None, + constants.HV_KVM_SPICE_USE_TLS: None + } + + self.assertTrue(validation.check_spice_parameters(valid_data)) + + valid_data = { + constants.HV_KVM_SPICE_BIND: "0.0.0.0", + constants.HV_KVM_SPICE_IP_VERSION: constants.IP4_VERSION, + constants.HV_KVM_SPICE_PASSWORD_FILE: "password.txt", + constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR: "glz", + constants.HV_KVM_SPICE_JPEG_IMG_COMPR: "never", + constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: "never", + constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: "off", + constants.HV_KVM_SPICE_USE_TLS: True + } + + self.assertTrue(validation.check_spice_parameters(valid_data)) + + valid_data = { + constants.HV_KVM_SPICE_BIND: "::", + constants.HV_KVM_SPICE_IP_VERSION: constants.IP6_VERSION, + constants.HV_KVM_SPICE_PASSWORD_FILE: "password.txt", + constants.HV_KVM_SPICE_LOSSLESS_IMG_COMPR: "glz", + constants.HV_KVM_SPICE_JPEG_IMG_COMPR: "never", + constants.HV_KVM_SPICE_ZLIB_GLZ_IMG_COMPR: "never", + constants.HV_KVM_SPICE_STREAMING_VIDEO_DETECTION: "off", + constants.HV_KVM_SPICE_USE_TLS: True + } + + self.assertTrue(validation.check_spice_parameters(valid_data)) + + def testInvalidDiskCacheParameters(self): + invalid_data = { + constants.HV_KVM_DISK_AIO: constants.HT_KVM_AIO_NATIVE, + constants.HV_DISK_CACHE: constants.HT_CACHE_WBACK + } + + self.assertRaises(errors.HypervisorError, + validation.check_disk_cache_parameters, invalid_data) + + invalid_data = { + constants.HV_KVM_DISK_AIO: constants.HT_KVM_AIO_NATIVE, + constants.HV_DISK_CACHE: constants.HT_CACHE_WTHROUGH + } + + self.assertRaises(errors.HypervisorError, + validation.check_disk_cache_parameters, invalid_data) + + invalid_data = { + constants.HV_KVM_DISK_AIO: constants.HT_KVM_AIO_NATIVE, + constants.HV_DISK_CACHE: constants.HT_CACHE_DEFAULT + } + + self.assertRaises(errors.HypervisorError, + validation.check_disk_cache_parameters, invalid_data) + + def testValidDiskCacheParameters(self): + valid_data = { + constants.HV_KVM_DISK_AIO: constants.HT_KVM_AIO_THREADS, + constants.HV_DISK_CACHE: constants.HT_CACHE_WBACK + } + + self.assertTrue(validation.check_disk_cache_parameters(valid_data)) + + valid_data = { + constants.HV_KVM_DISK_AIO: constants.HT_KVM_AIO_THREADS, + constants.HV_DISK_CACHE: constants.HT_CACHE_WTHROUGH + } + + self.assertTrue(validation.check_disk_cache_parameters(valid_data)) + + valid_data = { + constants.HV_KVM_DISK_AIO: constants.HT_KVM_AIO_THREADS, + constants.HV_DISK_CACHE: constants.HT_CACHE_DEFAULT + } + + self.assertTrue(validation.check_disk_cache_parameters(valid_data)) + + valid_data = { + constants.HV_KVM_DISK_AIO: constants.HT_KVM_AIO_NATIVE, + constants.HV_DISK_CACHE: constants.HT_CACHE_NONE + } + + self.assertTrue(validation.check_disk_cache_parameters(valid_data)) + + +class TestParameterValidation(testutils.GanetiTestCase): + def testInvalidVncParameters(self): + # invalid IPv4 address + invalid_data = { + constants.HV_VNC_BIND_ADDRESS: "192.0.2.5.5", + } + + self.assertRaises(errors.HypervisorError, + validation.validate_vnc_parameters, invalid_data) + + # invalid network interface + invalid_data = { + constants.HV_VNC_BIND_ADDRESS: "doesnotexist0", + } + + self.assertRaises(errors.HypervisorError, + validation.validate_vnc_parameters, invalid_data) + + def testValidVncParameters(self): + valid_data = { + constants.HV_VNC_BIND_ADDRESS: "127.0.0.1" + } + + self.assertTrue(validation.validate_vnc_parameters(valid_data)) + + valid_data = { + constants.HV_VNC_BIND_ADDRESS: "lo" + } + + self.assertTrue(validation.validate_vnc_parameters(valid_data)) + + def testInvalidSecurityModelParameters(self): + invalid_data = { + constants.HV_SECURITY_MODEL: constants.HT_SM_USER, + constants.HV_SECURITY_DOMAIN: "really-non-existing-user" + } + + self.assertRaises(errors.HypervisorError, + validation.validate_security_model, invalid_data) + + def testValidSecurityModelParameters(self): + valid_data = { + constants.HV_SECURITY_MODEL: constants.HT_SM_NONE + } + + self.assertTrue(validation.validate_security_model(valid_data)) + + valid_data = { + constants.HV_SECURITY_MODEL: constants.HT_SM_POOL + } + + self.assertTrue(validation.validate_security_model(valid_data)) + + valid_data = { + constants.HV_SECURITY_MODEL: constants.HT_SM_USER, + constants.HV_SECURITY_DOMAIN: "root" + } + + self.assertTrue(validation.validate_security_model(valid_data)) + + def testInvalidMachineVersion(self): + kvm_machine_output = testutils.ReadTestData("kvm_6.0.0_machine.txt") + invalid_data = { + constants.HV_KVM_MACHINE_VERSION: "some-invalid-machine-type" + } + self.assertRaises(errors.HypervisorError, + validation.validate_machine_version, invalid_data, + kvm_machine_output) + + def testValidMachineVersion(self): + kvm_machine_output = testutils.ReadTestData("kvm_6.0.0_machine.txt") + valid_data = { + constants.HV_KVM_MACHINE_VERSION: "pc-i440fx-6.0" + } + self.assertTrue(validation.validate_machine_version(valid_data, + kvm_machine_output)) + + def testInvalidSpiceParameters(self): + kvm_help_too_old = testutils.ReadTestData("kvm_0.9.1_help.txt") + kvm_help_working = testutils.ReadTestData("kvm_1.1.2_help.txt") + + invalid_data = { + constants.HV_KVM_SPICE_BIND: "0.0.0.0", + constants.HV_VNC_BIND_ADDRESS: "0.0.0.0" + } + + self.assertRaises(errors.HypervisorError, + validation.validate_spice_parameters, invalid_data, + kvm_help_working) + + invalid_data = { + constants.HV_KVM_SPICE_BIND: "0.0.0.0", + constants.HV_VNC_BIND_ADDRESS: None + } + + self.assertRaises(errors.HypervisorError, + validation.validate_spice_parameters, invalid_data, + kvm_help_too_old) + + invalid_data = { + constants.HV_KVM_SPICE_BIND: "invalid-interface0", + constants.HV_VNC_BIND_ADDRESS: None + } + + self.assertRaises(errors.HypervisorError, + validation.validate_spice_parameters, invalid_data, + kvm_help_working) + + def testValidSpiceParameters(self): + kvm_help_working = testutils.ReadTestData("kvm_1.1.2_help.txt") + + valid_data = { + constants.HV_KVM_SPICE_BIND: "0.0.0.0", + constants.HV_VNC_BIND_ADDRESS: None + } + + self.assertTrue(validation.validate_spice_parameters(valid_data, + kvm_help_working)) + + valid_data = { + constants.HV_KVM_SPICE_BIND: "::", + constants.HV_VNC_BIND_ADDRESS: None + } + + self.assertTrue(validation.validate_spice_parameters(valid_data, + kvm_help_working)) + + valid_data = { + constants.HV_KVM_SPICE_BIND: "lo", + constants.HV_VNC_BIND_ADDRESS: None + } + + self.assertTrue(validation.validate_spice_parameters(valid_data, + kvm_help_working)) + + +class TestDiskParameters(testutils.GanetiTestCase): + def testGenerateDiskAioCacheParameters(self): + test_cases = { + "aio_threaded_safe_storage_default_cache": { + "disk_aio": constants.HT_KVM_AIO_THREADS, + "disk_cache": constants.HT_CACHE_DEFAULT, + "dev_type": constants.DT_DRBD8, + "expected_string": ",aio=threads" + }, + "aio_threaded_unsafe_storage_default_cache": { + "disk_aio": constants.HT_KVM_AIO_THREADS, + "disk_cache": constants.HT_CACHE_DEFAULT, + "dev_type": constants.DT_SHARED_FILE, + "expected_string": ",aio=threads,cache=none" + }, + "aio_threaded_safe_storage_writeback_cache": { + "disk_aio": constants.HT_KVM_AIO_THREADS, + "disk_cache": constants.HT_CACHE_WBACK, + "dev_type": constants.DT_RBD, + "expected_string": ",aio=threads,cache=writeback" + }, + "aio_native_safe_storage_none_cache": { + "disk_aio": constants.HT_KVM_AIO_NATIVE, + "disk_cache": constants.HT_CACHE_NONE, + "dev_type": constants.DT_DRBD8, + "expected_string": ",aio=native,cache=none" + }, + "aio_native_safe_storage_writethrough_cache": { + "disk_aio": constants.HT_KVM_AIO_NATIVE, + "disk_cache": constants.HT_CACHE_WTHROUGH, + "dev_type": constants.DT_DRBD8, + "expected_string": ",aio=native,cache=none" + }, + "aio_native_unsafe_storage_writethrough_cache": { + "disk_aio": constants.HT_KVM_AIO_NATIVE, + "disk_cache": constants.HT_CACHE_WTHROUGH, + "dev_type": constants.DT_GLUSTER, + "expected_string": ",aio=native,cache=none" + }, + "aio_unset_safe_storage_none_cache": { + "disk_aio": None, + "disk_cache": constants.HT_CACHE_NONE, + "dev_type": constants.DT_DRBD8, + "expected_string": ",aio=threads,cache=none" + } + } + + for name, data in test_cases.items(): + self.assertEqual(hv_kvm.KVMHypervisor._GenerateDiskAioCacheParameters( + data["disk_aio"], data["disk_cache"], data["dev_type"]), + data["expected_string"], name) + + class TestQmpMessage(testutils.GanetiTestCase): def testSerialization(self): test_data = { @@ -382,7 +845,7 @@ fixed = set([ constants.HV_KVM_SPICE_BIND, constants.HV_KVM_SPICE_TLS_CIPHERS, constants.HV_KVM_SPICE_USE_VDAGENT, constants.HV_KVM_SPICE_AUDIO_COMPR]) - self.assertEqual(hv_kvm._SPICE_ADDITIONAL_PARAMS, params - fixed) + self.assertEqual(hv_kvm.validation._SPICE_ADDITIONAL_PARAMS, params - fixed) class TestHelpRegexps(testutils.GanetiTestCase): @@ -569,6 +1032,7 @@ def __repr__(self): return "<Postfix %s>" % self.string + class TestKvmRuntime(testutils.GanetiTestCase): """The _ExecuteKvmRuntime is at the core of all KVM operations.""" @@ -606,8 +1070,8 @@ (PostfixMatcher('/run/ganeti/kvm-hypervisor/conf'), 0o775), (PostfixMatcher('/run/ganeti/kvm-hypervisor/nic'), 0o775), (PostfixMatcher('/run/ganeti/kvm-hypervisor/chroot'), 0o775), - (PostfixMatcher('/run/ganeti/kvm-hypervisor/chroot-quarantine'), 0o775), - (PostfixMatcher('/run/ganeti/kvm-hypervisor/keymap'), 0o775)]) + (PostfixMatcher('/run/ganeti/kvm-hypervisor/chroot-quarantine'), 0o775) + ]) def testStartInstance(self): hypervisor = hv_kvm.KVMHypervisor() @@ -627,5 +1091,6 @@ self.mocks['run_cmd'].side_effect = RunCmd hypervisor.StartInstance(self.instance, [], False) + if __name__ == "__main__": testutils.GanetiTestProgram() diff -Nru ganeti-3.0.1/tools/move-instance ganeti-3.0.2/tools/move-instance --- ganeti-3.0.1/tools/move-instance 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/tools/move-instance 2022-02-28 22:51:21.000000000 +0200 @@ -761,7 +761,7 @@ job_id = cl.GetInstanceInfo(name, static=True) result = poll_job_fn(cl, job_id) assert len(result[0].keys()) == 1 - return result[0][result[0].keys()[0]] + return result[0][list(result[0].keys())[0]] @staticmethod def _PrepareExport(cl, poll_job_fn, name): diff -Nru ganeti-3.0.1/tools/net-common.in ganeti-3.0.2/tools/net-common.in --- ganeti-3.0.1/tools/net-common.in 2021-02-03 10:24:16.000000000 +0200 +++ ganeti-3.0.2/tools/net-common.in 2022-02-28 22:51:21.000000000 +0200 @@ -76,9 +76,15 @@ # tap on the VLAN aware bridge will not receive traffic if [ -r /proc/net/vlan/config ]; then local vlan_interface="$(awk -F '[| ]*' -v vlan="^${VID}$" 'match($2, vlan) { print $1 }' /proc/net/vlan/config)" + local lower_devs="$(awk -F '[| ]*' -v vlan="^${VID}$" 'match($2, vlan) { print $3 }' /proc/net/vlan/config)" if [ -n "${vlan_interface}" ]; then - echo "VLAN ${VID} is in use by interface ${vlan_interface}" - exit 1 + for i in ${lower_devs}; do + # allow bridge stacking and vlan overlap for veth devices + if [ "$(ip -o link show dev ${i} type veth | wc -l)" -ne 1 ]; then + echo "VLAN ${VID} is in use by lower interface ${i}" + exit 1 + fi + done fi fi