dcf656e899
OBS-URL: https://build.opensuse.org/package/show/systemsmanagement:saltstack/salt?expand=0&rev=181
8843 lines
318 KiB
Diff
8843 lines
318 KiB
Diff
From ff3273ffb5be499d14a0023b8b9f8baed133807b Mon Sep 17 00:00:00 2001
|
|
From: Cedric Bosdonnat <cbosdonnat@suse.com>
|
|
Date: Tue, 12 Jan 2021 11:28:24 +0100
|
|
Subject: [PATCH] Open suse 3002.2 virt network (#311)
|
|
|
|
* Bump to `pytest-salt-factories >= 0.120.0`
|
|
|
|
* Switch to using the pytest-salt-factories loader mock support
|
|
|
|
* Fix the new iothreads virtual disk parameter
|
|
|
|
io_uring has recently been added as another IO policy on virtual disks.
|
|
Keep the parameter opened for changes. Also add the optional iothread ID
|
|
in case the user wants to pin a disk to some IO thread (and thus to a
|
|
CPU).
|
|
|
|
* Remove deprecated update parameter in virt.defined and virt.running
|
|
|
|
* Fix indentation of Jinja instructions in libvirt_domain.jinja
|
|
|
|
* Use more opt_attribute macro in libvirt_domain.jinja
|
|
|
|
* Unify XML indentation in libvirt_domain.jinja
|
|
|
|
* Move virt network generation tests to pytest
|
|
|
|
* Extract XML space stripping function from virt module
|
|
|
|
Stripping spaces and indentation from XML could also be useful in other
|
|
places, moving to xmlutil to help reuse.
|
|
|
|
* Fix link in virt state documentation
|
|
|
|
* Extract the XML cleanup code from virt.pool_update
|
|
|
|
The network_update code will be rather similar to the pool_update one.
|
|
In order to share the XML tree cleanup for easier comparisons, create a
|
|
helper function in the virt module.
|
|
|
|
* virt: expose more properties in virt.network_define
|
|
|
|
In order to let users define more types of virtual networks, expose more
|
|
of the libvirt virtual network properties.
|
|
|
|
* Remove useless code in virt pytest fixture
|
|
|
|
* Add virt.network_update function
|
|
|
|
In order to enhance the virt.network_defined state, a function to test
|
|
if an update is needed and update the network is needed. This is done by
|
|
the newly added virt.network_update function.
|
|
|
|
* Convert the virt network state unit tests to pytest
|
|
|
|
Converting these tests helped reducing the number of lines of code
|
|
thanks to the pytest parametrize feature. This is also the occasion to
|
|
split the big tests into smaller ones to report more meaningfull errors
|
|
and make it more readable.
|
|
|
|
* Let virt.network_update state change existing networks
|
|
|
|
Instead of simply reporting existing networks, update them if needed
|
|
like other states. Also bubble up the new properties from the
|
|
virt.define() function.
|
|
|
|
* Add virt.node_devices function
|
|
|
|
For the user to be able to pass host devices through he needs to get a
|
|
list of the devices that can be passed.
|
|
|
|
* virt: add PCI and USB host devices support to virt init and update
|
|
|
|
In quite a few cases it may be useful to pass a PCI or USB device from
|
|
the host to the VM. Add support for this in the virt.init() and
|
|
virt.update() functions.
|
|
|
|
* Convert virt domain state unit tests to pytest
|
|
|
|
While converting the virt domain-related states to pytest I realized
|
|
the __opts__["test"] == False case was not handled in some of them.
|
|
This commit also fixes the return code for virt.shutdown,
|
|
virt.powered_off, virt.snapshot and virt.rebooted states. It also
|
|
prevents the actual call to be issued in test mode.
|
|
|
|
* Add host_devices to virt running and defined states
|
|
|
|
Expose the new host_devices parameter to the virt.running and
|
|
virt.defined states.
|
|
|
|
* Convert virt _diff_nics() unit test to pytest
|
|
|
|
* virt: better compare NICs of running VMs
|
|
|
|
On a running guest, libvirt changes the XML definition of the network
|
|
interfaces of type "network" to the type of the network (for instance
|
|
bridge). In such a case the virt.update() function will find the two
|
|
NICs different even if they may not be... so we need to try harder to
|
|
compare.
|
|
|
|
* virt: hostdev network fixes
|
|
|
|
A network with hostdev forward mode has no bridge and no mac. So we need
|
|
to handle this in a few places in the virt module.
|
|
|
|
* virt: extract the live update code from the update function
|
|
|
|
In order to help reusing the device changes computing code and avoid
|
|
getting a giant virt.update(), move the live update code of it into a
|
|
specific internal function.
|
|
|
|
* virt: better handle comparison of hostdev NIC interfaces
|
|
|
|
When a domain has a NIC of type network pointing to a network with hostdev
|
|
forward, libvirt changes its running XML definition with a hostdev
|
|
interface with a PCI address from those in the network.
|
|
|
|
Handle this case to avoid useless interface detaching / attaching.
|
|
|
|
* virt: better compare hostdev networks
|
|
|
|
Libvirt adds the PCI addresses of the SR-IOV device virtual functions
|
|
when only providing the physical function. Those need to be removed in
|
|
order to avoid network changes for no reason in virt.network_update()
|
|
|
|
* Add xmlutil function dumping a node into a string
|
|
|
|
Co-authored-by: Pedro Algarvio <pedro@algarvio.me>
|
|
---
|
|
changelog/59143.added | 1 +
|
|
requirements/pytest.txt | 2 +-
|
|
requirements/static/ci/py3.5/darwin.txt | 2 +-
|
|
requirements/static/ci/py3.5/freebsd.txt | 2 +-
|
|
requirements/static/ci/py3.5/linux.txt | 2 +-
|
|
requirements/static/ci/py3.5/windows.txt | 2 +-
|
|
requirements/static/ci/py3.6/darwin.txt | 2 +-
|
|
requirements/static/ci/py3.6/freebsd.txt | 2 +-
|
|
requirements/static/ci/py3.6/linux.txt | 2 +-
|
|
requirements/static/ci/py3.6/windows.txt | 2 +-
|
|
requirements/static/ci/py3.7/darwin.txt | 2 +-
|
|
requirements/static/ci/py3.7/freebsd.txt | 2 +-
|
|
requirements/static/ci/py3.7/linux.txt | 2 +-
|
|
requirements/static/ci/py3.7/windows.txt | 2 +-
|
|
requirements/static/ci/py3.8/darwin.txt | 2 +-
|
|
requirements/static/ci/py3.8/freebsd.txt | 2 +-
|
|
requirements/static/ci/py3.8/linux.txt | 2 +-
|
|
requirements/static/ci/py3.9/darwin.txt | 2 +-
|
|
requirements/static/ci/py3.9/freebsd.txt | 2 +-
|
|
requirements/static/ci/py3.9/linux.txt | 2 +-
|
|
salt/modules/virt.py | 1260 +++++++++---
|
|
salt/states/virt.py | 477 ++++-
|
|
salt/templates/virt/libvirt_domain.jinja | 646 ++++---
|
|
salt/templates/virt/libvirt_macros.jinja | 3 +
|
|
salt/templates/virt/libvirt_network.jinja | 98 +-
|
|
salt/utils/xmlutil.py | 29 +
|
|
tests/conftest.py | 2 +-
|
|
tests/pytests/functional/modules/test_opkg.py | 8 +-
|
|
tests/pytests/unit/beacons/test_sensehat.py | 8 +-
|
|
tests/pytests/unit/beacons/test_status.py | 8 +-
|
|
.../pytests/unit/modules/test_alternatives.py | 8 +-
|
|
.../pytests/unit/modules/test_ansiblegate.py | 13 +-
|
|
tests/pytests/unit/modules/test_archive.py | 8 +-
|
|
.../pytests/unit/modules/test_azurearm_dns.py | 8 +-
|
|
tests/pytests/unit/modules/test_nilrt_ip.py | 8 +-
|
|
tests/pytests/unit/modules/test_opkg.py | 8 +-
|
|
.../pytests/unit/modules/test_restartcheck.py | 8 +-
|
|
.../unit/modules/test_slackware_service.py | 12 +-
|
|
tests/pytests/unit/modules/test_swarm.py | 10 +-
|
|
tests/pytests/unit/modules/test_tls.py | 12 +-
|
|
tests/pytests/unit/modules/virt/conftest.py | 88 +-
|
|
.../pytests/unit/modules/virt/test_domain.py | 473 ++++-
|
|
.../pytests/unit/modules/virt/test_helpers.py | 25 +
|
|
tests/pytests/unit/modules/virt/test_host.py | 219 +++
|
|
.../pytests/unit/modules/virt/test_network.py | 450 +++++
|
|
tests/pytests/unit/output/test_highstate.py | 8 +-
|
|
.../pytests/unit/states/test_alternatives.py | 8 +-
|
|
tests/pytests/unit/states/test_ini_manage.py | 24 +-
|
|
tests/pytests/unit/states/virt/__init__.py | 0
|
|
tests/pytests/unit/states/virt/conftest.py | 36 +
|
|
tests/pytests/unit/states/virt/test_domain.py | 840 ++++++++
|
|
.../pytests/unit/states/virt/test_helpers.py | 99 +
|
|
.../pytests/unit/states/virt/test_network.py | 476 +++++
|
|
tests/pytests/unit/utils/test_xmlutil.py | 14 +
|
|
tests/unit/modules/test_linux_sysctl.py | 173 --
|
|
tests/unit/modules/test_virt.py | 137 +-
|
|
tests/unit/states/test_virt.py | 1703 +----------------
|
|
57 files changed, 4689 insertions(+), 2757 deletions(-)
|
|
create mode 100644 changelog/59143.added
|
|
create mode 100644 salt/templates/virt/libvirt_macros.jinja
|
|
create mode 100644 tests/pytests/unit/modules/virt/test_host.py
|
|
create mode 100644 tests/pytests/unit/modules/virt/test_network.py
|
|
create mode 100644 tests/pytests/unit/states/virt/__init__.py
|
|
create mode 100644 tests/pytests/unit/states/virt/conftest.py
|
|
create mode 100644 tests/pytests/unit/states/virt/test_domain.py
|
|
create mode 100644 tests/pytests/unit/states/virt/test_helpers.py
|
|
create mode 100644 tests/pytests/unit/states/virt/test_network.py
|
|
delete mode 100644 tests/unit/modules/test_linux_sysctl.py
|
|
|
|
diff --git a/changelog/59143.added b/changelog/59143.added
|
|
new file mode 100644
|
|
index 0000000000..802e925a53
|
|
--- /dev/null
|
|
+++ b/changelog/59143.added
|
|
@@ -0,0 +1 @@
|
|
+Add more network and PCI/USB host devices passthrough support to virt module and states
|
|
diff --git a/requirements/pytest.txt b/requirements/pytest.txt
|
|
index 96faa73c27..77d60767d1 100644
|
|
--- a/requirements/pytest.txt
|
|
+++ b/requirements/pytest.txt
|
|
@@ -2,6 +2,6 @@ mock >= 3.0.0
|
|
# PyTest
|
|
pytest >= 6.1.0
|
|
pytest-salt
|
|
-pytest-salt-factories >= 0.93.0
|
|
+pytest-salt-factories >= 0.120.0
|
|
pytest-tempdir >= 2019.10.12
|
|
pytest-helpers-namespace >= 2019.1.8
|
|
diff --git a/requirements/static/ci/py3.5/darwin.txt b/requirements/static/ci/py3.5/darwin.txt
|
|
index acfb43b542..efaac38353 100644
|
|
--- a/requirements/static/ci/py3.5/darwin.txt
|
|
+++ b/requirements/static/ci/py3.5/darwin.txt
|
|
@@ -89,7 +89,7 @@ pyopenssl==19.0.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.5/freebsd.txt b/requirements/static/ci/py3.5/freebsd.txt
|
|
index 868cea5220..d4faa715c9 100644
|
|
--- a/requirements/static/ci/py3.5/freebsd.txt
|
|
+++ b/requirements/static/ci/py3.5/freebsd.txt
|
|
@@ -91,7 +91,7 @@ pyopenssl==19.1.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.5/linux.txt b/requirements/static/ci/py3.5/linux.txt
|
|
index c6b57bf491..6b64d844db 100644
|
|
--- a/requirements/static/ci/py3.5/linux.txt
|
|
+++ b/requirements/static/ci/py3.5/linux.txt
|
|
@@ -184,7 +184,7 @@ pyopenssl==19.1.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.5/windows.txt b/requirements/static/ci/py3.5/windows.txt
|
|
index 8646edac12..3de8e54de0 100644
|
|
--- a/requirements/static/ci/py3.5/windows.txt
|
|
+++ b/requirements/static/ci/py3.5/windows.txt
|
|
@@ -82,7 +82,7 @@ pymysql==0.9.3
|
|
pyopenssl==19.0.0
|
|
pyparsing==2.4.5 # via packaging
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.6/darwin.txt b/requirements/static/ci/py3.6/darwin.txt
|
|
index 223ae11a0a..cf560de09d 100644
|
|
--- a/requirements/static/ci/py3.6/darwin.txt
|
|
+++ b/requirements/static/ci/py3.6/darwin.txt
|
|
@@ -94,7 +94,7 @@ pyopenssl==19.0.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc, netmiko
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.6/freebsd.txt b/requirements/static/ci/py3.6/freebsd.txt
|
|
index 6493dd4c8f..13a7678376 100644
|
|
--- a/requirements/static/ci/py3.6/freebsd.txt
|
|
+++ b/requirements/static/ci/py3.6/freebsd.txt
|
|
@@ -96,7 +96,7 @@ pyopenssl==19.1.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc, netmiko
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.6/linux.txt b/requirements/static/ci/py3.6/linux.txt
|
|
index 3317837a35..55800bfa25 100644
|
|
--- a/requirements/static/ci/py3.6/linux.txt
|
|
+++ b/requirements/static/ci/py3.6/linux.txt
|
|
@@ -188,7 +188,7 @@ pyopenssl==19.1.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc, netmiko
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.6/windows.txt b/requirements/static/ci/py3.6/windows.txt
|
|
index eae87eadb1..325e6ec969 100644
|
|
--- a/requirements/static/ci/py3.6/windows.txt
|
|
+++ b/requirements/static/ci/py3.6/windows.txt
|
|
@@ -81,7 +81,7 @@ pymysql==0.9.3
|
|
pyopenssl==19.0.0
|
|
pyparsing==2.4.5 # via packaging
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.7/darwin.txt b/requirements/static/ci/py3.7/darwin.txt
|
|
index d7c43ab796..8411522975 100644
|
|
--- a/requirements/static/ci/py3.7/darwin.txt
|
|
+++ b/requirements/static/ci/py3.7/darwin.txt
|
|
@@ -92,7 +92,7 @@ pyopenssl==19.0.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc, netmiko
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.7/freebsd.txt b/requirements/static/ci/py3.7/freebsd.txt
|
|
index 8c7a7df48b..98c4c85dfe 100644
|
|
--- a/requirements/static/ci/py3.7/freebsd.txt
|
|
+++ b/requirements/static/ci/py3.7/freebsd.txt
|
|
@@ -94,7 +94,7 @@ pyopenssl==19.1.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc, netmiko
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.7/linux.txt b/requirements/static/ci/py3.7/linux.txt
|
|
index 9c6a5139b2..c3490e6ba6 100644
|
|
--- a/requirements/static/ci/py3.7/linux.txt
|
|
+++ b/requirements/static/ci/py3.7/linux.txt
|
|
@@ -186,7 +186,7 @@ pyopenssl==19.1.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc, netmiko
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.7/windows.txt b/requirements/static/ci/py3.7/windows.txt
|
|
index 7ca5bc9b49..53b5db2734 100644
|
|
--- a/requirements/static/ci/py3.7/windows.txt
|
|
+++ b/requirements/static/ci/py3.7/windows.txt
|
|
@@ -79,7 +79,7 @@ pymysql==0.9.3
|
|
pyopenssl==19.0.0
|
|
pyparsing==2.4.5 # via packaging
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.8/darwin.txt b/requirements/static/ci/py3.8/darwin.txt
|
|
index f410432e54..541fd4c2d6 100644
|
|
--- a/requirements/static/ci/py3.8/darwin.txt
|
|
+++ b/requirements/static/ci/py3.8/darwin.txt
|
|
@@ -91,7 +91,7 @@ pyopenssl==19.0.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc, netmiko
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.8/freebsd.txt b/requirements/static/ci/py3.8/freebsd.txt
|
|
index d0c20f466c..6030e259d1 100644
|
|
--- a/requirements/static/ci/py3.8/freebsd.txt
|
|
+++ b/requirements/static/ci/py3.8/freebsd.txt
|
|
@@ -93,7 +93,7 @@ pyopenssl==19.1.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc, netmiko
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.8/linux.txt b/requirements/static/ci/py3.8/linux.txt
|
|
index 9ae7e8957e..da66159c3e 100644
|
|
--- a/requirements/static/ci/py3.8/linux.txt
|
|
+++ b/requirements/static/ci/py3.8/linux.txt
|
|
@@ -186,7 +186,7 @@ pyopenssl==19.1.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc, netmiko
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.9/darwin.txt b/requirements/static/ci/py3.9/darwin.txt
|
|
index 3e6b92586d..50a3c95995 100644
|
|
--- a/requirements/static/ci/py3.9/darwin.txt
|
|
+++ b/requirements/static/ci/py3.9/darwin.txt
|
|
@@ -91,7 +91,7 @@ pyopenssl==19.0.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc, netmiko
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.9/freebsd.txt b/requirements/static/ci/py3.9/freebsd.txt
|
|
index 48da272966..08e5e3c51e 100644
|
|
--- a/requirements/static/ci/py3.9/freebsd.txt
|
|
+++ b/requirements/static/ci/py3.9/freebsd.txt
|
|
@@ -93,7 +93,7 @@ pyopenssl==19.1.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc, netmiko
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/requirements/static/ci/py3.9/linux.txt b/requirements/static/ci/py3.9/linux.txt
|
|
index ae6683ea03..d11c63ce7a 100644
|
|
--- a/requirements/static/ci/py3.9/linux.txt
|
|
+++ b/requirements/static/ci/py3.9/linux.txt
|
|
@@ -186,7 +186,7 @@ pyopenssl==19.1.0
|
|
pyparsing==2.4.5 # via junos-eznc, packaging
|
|
pyserial==3.4 # via junos-eznc, netmiko
|
|
pytest-helpers-namespace==2019.1.8
|
|
-pytest-salt-factories==0.93.0
|
|
+pytest-salt-factories==0.120.0
|
|
pytest-salt==2020.1.27
|
|
pytest-tempdir==2019.10.12
|
|
pytest==6.1.1
|
|
diff --git a/salt/modules/virt.py b/salt/modules/virt.py
|
|
index b852f8175d..9f61983e8d 100644
|
|
--- a/salt/modules/virt.py
|
|
+++ b/salt/modules/virt.py
|
|
@@ -428,7 +428,8 @@ def _get_nics(dom):
|
|
Get domain network interfaces from a libvirt domain object.
|
|
"""
|
|
nics = {}
|
|
- doc = ElementTree.fromstring(dom.XMLDesc(0))
|
|
+ # Don't expose the active configuration since it may be changed by libvirt
|
|
+ doc = ElementTree.fromstring(dom.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE))
|
|
for iface_node in doc.findall("devices/interface"):
|
|
nic = {}
|
|
nic["type"] = iface_node.get("type")
|
|
@@ -814,6 +815,7 @@ def _gen_xml(
|
|
serials=None,
|
|
consoles=None,
|
|
stop_on_reboot=False,
|
|
+ host_devices=None,
|
|
**kwargs
|
|
):
|
|
"""
|
|
@@ -953,7 +955,8 @@ def _gen_xml(
|
|
"disk_bus": disk["model"],
|
|
"format": disk.get("format", "raw"),
|
|
"index": str(i),
|
|
- "io": "threads" if disk.get("iothreads", False) else "native",
|
|
+ "io": disk.get("io", "native"),
|
|
+ "iothread": disk.get("iothread_id", None),
|
|
}
|
|
targets.append(disk_context["target_dev"])
|
|
if disk.get("source_file"):
|
|
@@ -1001,6 +1004,44 @@ def _gen_xml(
|
|
context["disks"].append(disk_context)
|
|
context["nics"] = nicp
|
|
|
|
+ # Process host devices passthrough
|
|
+ hostdev_context = []
|
|
+ try:
|
|
+ for hostdev_name in host_devices or []:
|
|
+ hostdevice = conn.nodeDeviceLookupByName(hostdev_name)
|
|
+ doc = ElementTree.fromstring(hostdevice.XMLDesc())
|
|
+ if "pci" in hostdevice.listCaps():
|
|
+ hostdev_context.append(
|
|
+ {
|
|
+ "type": "pci",
|
|
+ "domain": "0x{:04x}".format(
|
|
+ int(doc.find("./capability[@type='pci']/domain").text)
|
|
+ ),
|
|
+ "bus": "0x{:02x}".format(
|
|
+ int(doc.find("./capability[@type='pci']/bus").text)
|
|
+ ),
|
|
+ "slot": "0x{:02x}".format(
|
|
+ int(doc.find("./capability[@type='pci']/slot").text)
|
|
+ ),
|
|
+ "function": "0x{}".format(
|
|
+ doc.find("./capability[@type='pci']/function").text
|
|
+ ),
|
|
+ }
|
|
+ )
|
|
+ elif "usb_device" in hostdevice.listCaps():
|
|
+ vendor_id = doc.find(".//vendor").get("id")
|
|
+ product_id = doc.find(".//product").get("id")
|
|
+ hostdev_context.append(
|
|
+ {"type": "usb", "vendor": vendor_id, "product": product_id}
|
|
+ )
|
|
+ # For the while we only handle pci and usb passthrough
|
|
+ except libvirt.libvirtError as err:
|
|
+ conn.close()
|
|
+ raise CommandExecutionError(
|
|
+ "Failed to get host devices: " + err.get_error_message()
|
|
+ )
|
|
+ context["hostdevs"] = hostdev_context
|
|
+
|
|
context["os_type"] = os_type
|
|
context["arch"] = arch
|
|
fn_ = "libvirt_domain.jinja"
|
|
@@ -1044,23 +1085,75 @@ def _gen_vol_xml(
|
|
return template.render(**context)
|
|
|
|
|
|
-def _gen_net_xml(name, bridge, forward, vport, tag=None, ip_configs=None):
|
|
+def _gen_net_xml(
|
|
+ name,
|
|
+ bridge,
|
|
+ forward,
|
|
+ vport,
|
|
+ tag=None,
|
|
+ ip_configs=None,
|
|
+ mtu=None,
|
|
+ domain=None,
|
|
+ nat=None,
|
|
+ interfaces=None,
|
|
+ addresses=None,
|
|
+ physical_function=None,
|
|
+ dns=None,
|
|
+):
|
|
"""
|
|
Generate the XML string to define a libvirt network
|
|
"""
|
|
+ if isinstance(vport, str):
|
|
+ vport_context = {"type": vport}
|
|
+ else:
|
|
+ vport_context = vport
|
|
+
|
|
+ if isinstance(tag, (str, int)):
|
|
+ tag_context = {"tags": [{"id": tag}]}
|
|
+ else:
|
|
+ tag_context = tag
|
|
+
|
|
+ addresses_context = []
|
|
+ if addresses:
|
|
+ matches = [
|
|
+ re.fullmatch(r"([0-9]+):([0-9A-Fa-f]+):([0-9A-Fa-f]+)\.([0-9])", addr)
|
|
+ for addr in addresses.lower().split(" ")
|
|
+ ]
|
|
+ addresses_context = [
|
|
+ {
|
|
+ "domain": m.group(1),
|
|
+ "bus": m.group(2),
|
|
+ "slot": m.group(3),
|
|
+ "function": m.group(4),
|
|
+ }
|
|
+ for m in matches
|
|
+ if m
|
|
+ ]
|
|
+
|
|
context = {
|
|
"name": name,
|
|
"bridge": bridge,
|
|
+ "mtu": mtu,
|
|
+ "domain": domain,
|
|
"forward": forward,
|
|
- "vport": vport,
|
|
- "tag": tag,
|
|
+ "nat": nat,
|
|
+ "interfaces": interfaces.split(" ") if interfaces else [],
|
|
+ "addresses": addresses_context,
|
|
+ "pf": physical_function,
|
|
+ "vport": vport_context,
|
|
+ "vlan": tag_context,
|
|
+ "dns": dns,
|
|
"ip_configs": [
|
|
{
|
|
"address": ipaddress.ip_network(config["cidr"]),
|
|
"dhcp_ranges": config.get("dhcp_ranges", []),
|
|
+ "hosts": config.get("hosts", {}),
|
|
+ "bootp": config.get("bootp", {}),
|
|
+ "tftp": config.get("tftp"),
|
|
}
|
|
for config in ip_configs or []
|
|
],
|
|
+ "yesno": lambda v: "yes" if v else "no",
|
|
}
|
|
fn_ = "libvirt_network.jinja"
|
|
try:
|
|
@@ -1813,6 +1906,7 @@ def init(
|
|
serials=None,
|
|
consoles=None,
|
|
stop_on_reboot=False,
|
|
+ host_devices=None,
|
|
**kwargs
|
|
):
|
|
"""
|
|
@@ -2143,6 +2237,13 @@ def init(
|
|
|
|
.. versionadded:: Aluminium
|
|
|
|
+ :param host_devices:
|
|
+ List of host devices to passthrough to the guest.
|
|
+ The value is a list of device names as provided by the :py:func:`~salt.modules.virt.node_devices` function.
|
|
+ (Default: ``None``)
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
.. _init-cpu-def:
|
|
|
|
.. rubric:: cpu parameters definition
|
|
@@ -2485,9 +2586,17 @@ def init(
|
|
hostname_property: virt:hostname
|
|
sparse_volume: True
|
|
|
|
- iothreads
|
|
- When ``True`` dedicated threads will be used for the I/O of the disk.
|
|
- (Default: ``False``)
|
|
+ io
|
|
+ I/O control policy. String value amongst ``native``, ``threads`` and ``io_uring``.
|
|
+ (Default: ``native``)
|
|
+
|
|
+ ..versionadded:: Aluminium
|
|
+
|
|
+ iothread_id
|
|
+ I/O thread id to assign the disk to.
|
|
+ (Default: none assigned)
|
|
+
|
|
+ ..versionadded:: Aluminium
|
|
|
|
.. _init-graphics-def:
|
|
|
|
@@ -2706,6 +2815,7 @@ def init(
|
|
serials,
|
|
consoles,
|
|
stop_on_reboot,
|
|
+ host_devices,
|
|
**kwargs
|
|
)
|
|
log.debug("New virtual machine definition: %s", vm_xml)
|
|
@@ -2764,10 +2874,20 @@ def _nics_equal(nic1, nic2):
|
|
"""
|
|
Filter out elements to ignore when comparing nics
|
|
"""
|
|
+ source_node = nic.find("source")
|
|
+ source_attrib = source_node.attrib if source_node is not None else {}
|
|
+ source_type = "network" if "network" in source_attrib else nic.attrib["type"]
|
|
+
|
|
+ source_getters = {
|
|
+ "network": lambda n: n.get("network"),
|
|
+ "bridge": lambda n: n.get("bridge"),
|
|
+ "direct": lambda n: n.get("dev"),
|
|
+ "hostdev": lambda n: _format_pci_address(n.find("address")),
|
|
+ }
|
|
return {
|
|
- "type": nic.attrib["type"],
|
|
- "source": nic.find("source").attrib[nic.attrib["type"]]
|
|
- if nic.find("source") is not None
|
|
+ "type": source_type,
|
|
+ "source": source_getters[source_type](source_node)
|
|
+ if source_node is not None
|
|
else None,
|
|
"model": nic.find("model").attrib["type"]
|
|
if nic.find("model") is not None
|
|
@@ -2819,6 +2939,32 @@ def _graphics_equal(gfx1, gfx2):
|
|
)
|
|
|
|
|
|
+def _hostdevs_equal(dev1, dev2):
|
|
+ """
|
|
+ Test if two hostdevs devices should be considered the same device
|
|
+ """
|
|
+
|
|
+ def _filter_hostdevs(dev):
|
|
+ """
|
|
+ When the domain is running, the hostdevs element may contain additional properties.
|
|
+ This function will only keep the ones we care about
|
|
+ """
|
|
+ type_ = dev.get("type")
|
|
+ definition = {
|
|
+ "type": type_,
|
|
+ }
|
|
+ if type_ == "pci":
|
|
+ address_node = dev.find("./source/address")
|
|
+ for attr in ["domain", "bus", "slot", "function"]:
|
|
+ definition[attr] = address_node.get(attr)
|
|
+ elif type_ == "usb":
|
|
+ for attr in ["vendor", "product"]:
|
|
+ definition[attr] = dev.find("./source/" + attr).get("id")
|
|
+ return definition
|
|
+
|
|
+ return _filter_hostdevs(dev1) == _filter_hostdevs(dev2)
|
|
+
|
|
+
|
|
def _diff_lists(old, new, comparator):
|
|
"""
|
|
Compare lists to extract the changes
|
|
@@ -2919,6 +3065,16 @@ def _diff_graphics_lists(old, new):
|
|
return _diff_lists(old, new, _graphics_equal)
|
|
|
|
|
|
+def _diff_hostdev_lists(old, new):
|
|
+ """
|
|
+ Compare hostdev devices definitions to extract the changes
|
|
+
|
|
+ :param old: list of ElementTree nodes representing the old hostdev devices
|
|
+ :param new: list of ElementTree nodes representing the new hostdev devices
|
|
+ """
|
|
+ return _diff_lists(old, new, _hostdevs_equal)
|
|
+
|
|
+
|
|
def _expand_cpuset(cpuset):
|
|
"""
|
|
Expand the libvirt cpuset and nodeset values into a list of cpu/node IDs
|
|
@@ -3014,6 +3170,218 @@ def _diff_console_list(old, new):
|
|
return _diff_lists(old, new, _serial_or_concole_equal)
|
|
|
|
|
|
+def _format_pci_address(node):
|
|
+ return "{}:{}:{}.{}".format(
|
|
+ node.get("domain").replace("0x", ""),
|
|
+ node.get("bus").replace("0x", ""),
|
|
+ node.get("slot").replace("0x", ""),
|
|
+ node.get("function").replace("0x", ""),
|
|
+ )
|
|
+
|
|
+
|
|
+def _almost_equal(current, new):
|
|
+ """
|
|
+ return True if the parameters are numbers that are almost
|
|
+ """
|
|
+ if current is None or new is None:
|
|
+ return False
|
|
+ return abs(current - new) / current < 1e-03
|
|
+
|
|
+
|
|
+def _compute_device_changes(old_xml, new_xml, to_skip):
|
|
+ """
|
|
+ Compute the device changes between two domain XML definitions.
|
|
+ """
|
|
+ devices_node = old_xml.find("devices")
|
|
+ changes = {}
|
|
+ for dev_type in to_skip:
|
|
+ changes[dev_type] = {}
|
|
+ if not to_skip[dev_type]:
|
|
+ old = devices_node.findall(dev_type)
|
|
+ new = new_xml.findall("devices/{}".format(dev_type))
|
|
+ changes[dev_type] = globals()["_diff_{}_lists".format(dev_type)](old, new)
|
|
+ return changes
|
|
+
|
|
+
|
|
+def _get_pci_addresses(node):
|
|
+ """
|
|
+ Get all the pci addresses in the node in 0000:00:00.0 form
|
|
+ """
|
|
+ return {_format_pci_address(address) for address in node.findall(".//address")}
|
|
+
|
|
+
|
|
+def _correct_networks(conn, desc):
|
|
+ """
|
|
+ Adjust the interface devices matching existing networks.
|
|
+ Returns the network interfaces XML definition as string mapped to the new device node.
|
|
+ """
|
|
+ networks = [ElementTree.fromstring(net.XMLDesc()) for net in conn.listAllNetworks()]
|
|
+ nics = desc.findall("devices/interface")
|
|
+ device_map = {}
|
|
+ for nic in nics:
|
|
+ if nic.get("type") == "hostdev":
|
|
+ # Do we have a network matching this NIC PCI address?
|
|
+ addr = _get_pci_addresses(nic.find("source"))
|
|
+ matching_nets = [
|
|
+ net
|
|
+ for net in networks
|
|
+ if net.find("forward").get("mode") == "hostdev"
|
|
+ and addr & _get_pci_addresses(net)
|
|
+ ]
|
|
+ if matching_nets:
|
|
+ # We need to store the XML before modifying it
|
|
+ # since libvirt needs it to detach the device
|
|
+ old_xml = ElementTree.tostring(nic)
|
|
+ nic.set("type", "network")
|
|
+ nic.find("source").set("network", matching_nets[0].find("name").text)
|
|
+ device_map[nic] = old_xml
|
|
+ return device_map
|
|
+
|
|
+
|
|
+def _update_live(domain, new_desc, mem, cpu, old_mem, old_cpu, to_skip, test):
|
|
+ """
|
|
+ Perform the live update of a domain.
|
|
+ """
|
|
+ status = {}
|
|
+ errors = []
|
|
+
|
|
+ if not domain.isActive():
|
|
+ return status, errors
|
|
+
|
|
+ # Do the live changes now that we know the definition has been properly set
|
|
+ # From that point on, failures are not blocking to try to live update as much
|
|
+ # as possible.
|
|
+ commands = []
|
|
+ if cpu and (isinstance(cpu, int) or isinstance(cpu, dict) and cpu.get("maximum")):
|
|
+ new_cpu = cpu.get("maximum") if isinstance(cpu, dict) else cpu
|
|
+ if old_cpu != new_cpu and new_cpu is not None:
|
|
+ commands.append(
|
|
+ {
|
|
+ "device": "cpu",
|
|
+ "cmd": "setVcpusFlags",
|
|
+ "args": [new_cpu, libvirt.VIR_DOMAIN_AFFECT_LIVE],
|
|
+ }
|
|
+ )
|
|
+ if mem:
|
|
+ if isinstance(mem, dict):
|
|
+ # setMemoryFlags takes memory amount in KiB
|
|
+ new_mem = (
|
|
+ int(_handle_unit(mem.get("current")) / 1024)
|
|
+ if "current" in mem
|
|
+ else None
|
|
+ )
|
|
+ elif isinstance(mem, int):
|
|
+ new_mem = int(mem * 1024)
|
|
+
|
|
+ if not _almost_equal(old_mem, new_mem) and new_mem is not None:
|
|
+ commands.append(
|
|
+ {
|
|
+ "device": "mem",
|
|
+ "cmd": "setMemoryFlags",
|
|
+ "args": [new_mem, libvirt.VIR_DOMAIN_AFFECT_LIVE],
|
|
+ }
|
|
+ )
|
|
+
|
|
+ # Compute the changes with the live definition
|
|
+ old_desc = ElementTree.fromstring(domain.XMLDesc(0))
|
|
+ changed_devices = {"interface": _correct_networks(domain.connect(), old_desc)}
|
|
+ changes = _compute_device_changes(old_desc, new_desc, to_skip)
|
|
+
|
|
+ # Look for removable device source changes
|
|
+ removable_changes = []
|
|
+ new_disks = []
|
|
+ for new_disk in changes["disk"].get("new", []):
|
|
+ device = new_disk.get("device", "disk")
|
|
+ if device not in ["cdrom", "floppy"]:
|
|
+ new_disks.append(new_disk)
|
|
+ continue
|
|
+
|
|
+ target_dev = new_disk.find("target").get("dev")
|
|
+ matching = [
|
|
+ old_disk
|
|
+ for old_disk in changes["disk"].get("deleted", [])
|
|
+ if old_disk.get("device", "disk") == device
|
|
+ and old_disk.find("target").get("dev") == target_dev
|
|
+ ]
|
|
+ if not matching:
|
|
+ new_disks.append(new_disk)
|
|
+ else:
|
|
+ # libvirt needs to keep the XML exactly as it was before
|
|
+ updated_disk = matching[0]
|
|
+ changes["disk"]["deleted"].remove(updated_disk)
|
|
+ removable_changes.append(updated_disk)
|
|
+ source_node = updated_disk.find("source")
|
|
+ new_source_node = new_disk.find("source")
|
|
+ source_file = (
|
|
+ new_source_node.get("file") if new_source_node is not None else None
|
|
+ )
|
|
+
|
|
+ updated_disk.set("type", "file")
|
|
+ # Detaching device
|
|
+ if source_node is not None:
|
|
+ updated_disk.remove(source_node)
|
|
+
|
|
+ # Attaching device
|
|
+ if source_file:
|
|
+ ElementTree.SubElement(
|
|
+ updated_disk, "source", attrib={"file": source_file}
|
|
+ )
|
|
+
|
|
+ changes["disk"]["new"] = new_disks
|
|
+
|
|
+ for dev_type in ["disk", "interface", "hostdev"]:
|
|
+ for added in changes[dev_type].get("new", []):
|
|
+ commands.append(
|
|
+ {
|
|
+ "device": dev_type,
|
|
+ "cmd": "attachDevice",
|
|
+ "args": [xmlutil.element_to_str(added)],
|
|
+ }
|
|
+ )
|
|
+
|
|
+ for removed in changes[dev_type].get("deleted", []):
|
|
+ removed_def = changed_devices.get(dev_type, {}).get(
|
|
+ removed, ElementTree.tostring(removed)
|
|
+ )
|
|
+ commands.append(
|
|
+ {
|
|
+ "device": dev_type,
|
|
+ "cmd": "detachDevice",
|
|
+ "args": [salt.utils.stringutils.to_str(removed_def)],
|
|
+ }
|
|
+ )
|
|
+
|
|
+ for updated_disk in removable_changes:
|
|
+ commands.append(
|
|
+ {
|
|
+ "device": "disk",
|
|
+ "cmd": "updateDeviceFlags",
|
|
+ "args": [xmlutil.element_to_str(updated_disk)],
|
|
+ }
|
|
+ )
|
|
+
|
|
+ for cmd in commands:
|
|
+ try:
|
|
+ ret = 0 if test else getattr(domain, cmd["cmd"])(*cmd["args"])
|
|
+ device_type = cmd["device"]
|
|
+ if device_type in ["cpu", "mem"]:
|
|
+ status[device_type] = not ret
|
|
+ else:
|
|
+ actions = {
|
|
+ "attachDevice": "attached",
|
|
+ "detachDevice": "detached",
|
|
+ "updateDeviceFlags": "updated",
|
|
+ }
|
|
+ device_status = status.setdefault(device_type, {})
|
|
+ cmd_status = device_status.setdefault(actions[cmd["cmd"]], [])
|
|
+ cmd_status.append(cmd["args"][0])
|
|
+
|
|
+ except libvirt.libvirtError as err:
|
|
+ errors.append(str(err))
|
|
+
|
|
+ return status, errors
|
|
+
|
|
+
|
|
def update(
|
|
name,
|
|
cpu=0,
|
|
@@ -3033,6 +3401,7 @@ def update(
|
|
serials=None,
|
|
consoles=None,
|
|
stop_on_reboot=False,
|
|
+ host_devices=None,
|
|
**kwargs
|
|
):
|
|
"""
|
|
@@ -3220,6 +3589,13 @@ def update(
|
|
hpet:
|
|
present: False
|
|
|
|
+ :param host_devices:
|
|
+ List of host devices to passthrough to the guest.
|
|
+ The value is a list of device names as provided by the :py:func:`~salt.modules.virt.node_devices` function.
|
|
+ (Default: ``None``)
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
:return:
|
|
|
|
Returns a dictionary indicating the status of what has been done. It is structured in
|
|
@@ -3254,7 +3630,7 @@ def update(
|
|
}
|
|
conn = __get_conn(**kwargs)
|
|
domain = _get_domain(conn, name)
|
|
- desc = ElementTree.fromstring(domain.XMLDesc(0))
|
|
+ desc = ElementTree.fromstring(domain.XMLDesc(libvirt.VIR_DOMAIN_XML_INACTIVE))
|
|
need_update = False
|
|
|
|
# Compute the XML to get the disks, interfaces and graphics
|
|
@@ -3283,6 +3659,7 @@ def update(
|
|
serial=serials,
|
|
consoles=consoles,
|
|
stop_on_reboot=stop_on_reboot,
|
|
+ host_devices=host_devices,
|
|
**kwargs
|
|
)
|
|
)
|
|
@@ -3326,11 +3703,6 @@ def update(
|
|
old_mem = int(_get_with_unit(desc.find("memory")) / 1024)
|
|
old_cpu = int(desc.find("./vcpu").text)
|
|
|
|
- def _almost_equal(current, new):
|
|
- if current is None or new is None:
|
|
- return False
|
|
- return abs(current - new) / current < 1e-03
|
|
-
|
|
def _yesno_attribute(path, xpath, attr_name, ignored=None):
|
|
return xmlutil.attribute(
|
|
path, xpath, attr_name, ignored, lambda v: "yes" if v else "no"
|
|
@@ -3669,26 +4041,24 @@ def update(
|
|
|
|
# Update the XML definition with the new disks and diff changes
|
|
devices_node = desc.find("devices")
|
|
- parameters = {
|
|
- "disk": ["disks", "disk_profile"],
|
|
- "interface": ["interfaces", "nic_profile"],
|
|
- "graphics": ["graphics"],
|
|
- "serial": ["serial"],
|
|
- "console": ["console"],
|
|
+ func_locals = locals()
|
|
+
|
|
+ def _skip_update(names):
|
|
+ return all(func_locals.get(n) is None for n in names)
|
|
+
|
|
+ to_skip = {
|
|
+ "disk": _skip_update(["disks", "disk_profile"]),
|
|
+ "interface": _skip_update(["interfaces", "nic_profile"]),
|
|
+ "graphics": _skip_update(["graphics"]),
|
|
+ "serial": _skip_update(["serial"]),
|
|
+ "console": _skip_update(["console"]),
|
|
+ "hostdev": _skip_update(["host_devices"]),
|
|
}
|
|
- changes = {}
|
|
- for dev_type in parameters:
|
|
- changes[dev_type] = {}
|
|
- func_locals = locals()
|
|
- if [
|
|
- param
|
|
- for param in parameters[dev_type]
|
|
- if func_locals.get(param, None) is not None
|
|
- ]:
|
|
+ changes = _compute_device_changes(desc, new_desc, to_skip)
|
|
+ for dev_type in changes:
|
|
+ if not to_skip[dev_type]:
|
|
old = devices_node.findall(dev_type)
|
|
- new = new_desc.findall("devices/{}".format(dev_type))
|
|
- changes[dev_type] = globals()["_diff_{}_lists".format(dev_type)](old, new)
|
|
- if changes[dev_type]["deleted"] or changes[dev_type]["new"]:
|
|
+ if changes[dev_type].get("deleted") or changes[dev_type].get("new"):
|
|
for item in old:
|
|
devices_node.remove(item)
|
|
devices_node.extend(changes[dev_type]["sorted"])
|
|
@@ -3713,151 +4083,22 @@ def update(
|
|
elif item in changes["disk"]["new"] and not source_file:
|
|
_disk_volume_create(conn, all_disks[idx])
|
|
if not test:
|
|
- xml_desc = ElementTree.tostring(desc)
|
|
+ xml_desc = xmlutil.element_to_str(desc)
|
|
log.debug("Update virtual machine definition: %s", xml_desc)
|
|
- conn.defineXML(salt.utils.stringutils.to_str(xml_desc))
|
|
+ conn.defineXML(xml_desc)
|
|
status["definition"] = True
|
|
except libvirt.libvirtError as err:
|
|
conn.close()
|
|
raise err
|
|
|
|
- # Do the live changes now that we know the definition has been properly set
|
|
- # From that point on, failures are not blocking to try to live update as much
|
|
- # as possible.
|
|
- commands = []
|
|
- removable_changes = []
|
|
- if domain.isActive() and live:
|
|
- if cpu and (
|
|
- isinstance(cpu, int) or isinstance(cpu, dict) and cpu.get("maximum")
|
|
- ):
|
|
- new_cpu = cpu.get("maximum") if isinstance(cpu, dict) else cpu
|
|
- if old_cpu != new_cpu and new_cpu is not None:
|
|
- commands.append(
|
|
- {
|
|
- "device": "cpu",
|
|
- "cmd": "setVcpusFlags",
|
|
- "args": [new_cpu, libvirt.VIR_DOMAIN_AFFECT_LIVE],
|
|
- }
|
|
- )
|
|
- if mem:
|
|
- if isinstance(mem, dict):
|
|
- # setMemoryFlags takes memory amount in KiB
|
|
- new_mem = (
|
|
- int(_handle_unit(mem.get("current")) / 1024)
|
|
- if "current" in mem
|
|
- else None
|
|
- )
|
|
- elif isinstance(mem, int):
|
|
- new_mem = int(mem * 1024)
|
|
-
|
|
- if not _almost_equal(old_mem, new_mem) and new_mem is not None:
|
|
- commands.append(
|
|
- {
|
|
- "device": "mem",
|
|
- "cmd": "setMemoryFlags",
|
|
- "args": [new_mem, libvirt.VIR_DOMAIN_AFFECT_LIVE],
|
|
- }
|
|
- )
|
|
-
|
|
- # Look for removable device source changes
|
|
- new_disks = []
|
|
- for new_disk in changes["disk"].get("new", []):
|
|
- device = new_disk.get("device", "disk")
|
|
- if device not in ["cdrom", "floppy"]:
|
|
- new_disks.append(new_disk)
|
|
- continue
|
|
-
|
|
- target_dev = new_disk.find("target").get("dev")
|
|
- matching = [
|
|
- old_disk
|
|
- for old_disk in changes["disk"].get("deleted", [])
|
|
- if old_disk.get("device", "disk") == device
|
|
- and old_disk.find("target").get("dev") == target_dev
|
|
- ]
|
|
- if not matching:
|
|
- new_disks.append(new_disk)
|
|
- else:
|
|
- # libvirt needs to keep the XML exactly as it was before
|
|
- updated_disk = matching[0]
|
|
- changes["disk"]["deleted"].remove(updated_disk)
|
|
- removable_changes.append(updated_disk)
|
|
- source_node = updated_disk.find("source")
|
|
- new_source_node = new_disk.find("source")
|
|
- source_file = (
|
|
- new_source_node.get("file")
|
|
- if new_source_node is not None
|
|
- else None
|
|
- )
|
|
-
|
|
- updated_disk.set("type", "file")
|
|
- # Detaching device
|
|
- if source_node is not None:
|
|
- updated_disk.remove(source_node)
|
|
-
|
|
- # Attaching device
|
|
- if source_file:
|
|
- ElementTree.SubElement(updated_disk, "source", file=source_file)
|
|
-
|
|
- changes["disk"]["new"] = new_disks
|
|
-
|
|
- for dev_type in ["disk", "interface"]:
|
|
- for added in changes[dev_type].get("new", []):
|
|
- commands.append(
|
|
- {
|
|
- "device": dev_type,
|
|
- "cmd": "attachDevice",
|
|
- "args": [
|
|
- salt.utils.stringutils.to_str(
|
|
- ElementTree.tostring(added)
|
|
- )
|
|
- ],
|
|
- }
|
|
- )
|
|
-
|
|
- for removed in changes[dev_type].get("deleted", []):
|
|
- commands.append(
|
|
- {
|
|
- "device": dev_type,
|
|
- "cmd": "detachDevice",
|
|
- "args": [
|
|
- salt.utils.stringutils.to_str(
|
|
- ElementTree.tostring(removed)
|
|
- )
|
|
- ],
|
|
- }
|
|
- )
|
|
-
|
|
- for updated_disk in removable_changes:
|
|
- commands.append(
|
|
- {
|
|
- "device": "disk",
|
|
- "cmd": "updateDeviceFlags",
|
|
- "args": [
|
|
- salt.utils.stringutils.to_str(
|
|
- ElementTree.tostring(updated_disk)
|
|
- )
|
|
- ],
|
|
- }
|
|
- )
|
|
-
|
|
- for cmd in commands:
|
|
- try:
|
|
- ret = getattr(domain, cmd["cmd"])(*cmd["args"]) if not test else 0
|
|
- device_type = cmd["device"]
|
|
- if device_type in ["cpu", "mem"]:
|
|
- status[device_type] = not bool(ret)
|
|
- else:
|
|
- actions = {
|
|
- "attachDevice": "attached",
|
|
- "detachDevice": "detached",
|
|
- "updateDeviceFlags": "updated",
|
|
- }
|
|
- status[device_type][actions[cmd["cmd"]]].append(cmd["args"][0])
|
|
-
|
|
- except libvirt.libvirtError as err:
|
|
- if "errors" not in status:
|
|
- status["errors"] = []
|
|
- status["errors"].append(str(err))
|
|
+ if live:
|
|
+ live_status, errors = _update_live(
|
|
+ domain, new_desc, mem, cpu, old_mem, old_cpu, to_skip, test
|
|
+ )
|
|
+ status.update(live_status)
|
|
+ if errors:
|
|
+ status_errors = status.setdefault("errors", [])
|
|
+ status_errors += errors
|
|
|
|
conn.close()
|
|
return status
|
|
@@ -4107,6 +4348,121 @@ def node_info(**kwargs):
|
|
return info
|
|
|
|
|
|
+def _node_devices(conn):
|
|
+ """
|
|
+ List the host available devices, using an established connection.
|
|
+
|
|
+ :param conn: the libvirt connection handle to use.
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+ """
|
|
+ devices = conn.listAllDevices()
|
|
+
|
|
+ devices_infos = []
|
|
+ for dev in devices:
|
|
+ root = ElementTree.fromstring(dev.XMLDesc())
|
|
+
|
|
+ # Only list PCI and USB devices that can be passed through as well as NICs
|
|
+ if not set(dev.listCaps()) & {"pci", "usb_device", "net"}:
|
|
+ continue
|
|
+
|
|
+ infos = {
|
|
+ "caps": " ".join(dev.listCaps()),
|
|
+ }
|
|
+
|
|
+ if "net" in dev.listCaps():
|
|
+ parent = root.find(".//parent").text
|
|
+ # Don't show, lo, dummies and libvirt-created NICs
|
|
+ if parent == "computer":
|
|
+ continue
|
|
+ infos.update(
|
|
+ {
|
|
+ "name": root.find(".//interface").text,
|
|
+ "address": root.find(".//address").text,
|
|
+ "device name": parent,
|
|
+ "state": root.find(".//link").get("state"),
|
|
+ }
|
|
+ )
|
|
+ devices_infos.append(infos)
|
|
+ continue
|
|
+
|
|
+ vendor_node = root.find(".//vendor")
|
|
+ vendor_id = vendor_node.get("id").lower()
|
|
+ product_node = root.find(".//product")
|
|
+ product_id = product_node.get("id").lower()
|
|
+ infos.update(
|
|
+ {"name": dev.name(), "vendor_id": vendor_id, "product_id": product_id}
|
|
+ )
|
|
+
|
|
+ # Vendor or product display name may not be set
|
|
+ if vendor_node.text:
|
|
+ infos["vendor"] = vendor_node.text
|
|
+ if product_node.text:
|
|
+ infos["product"] = product_node.text
|
|
+
|
|
+ if "pci" in dev.listCaps():
|
|
+ infos["address"] = "{:04x}:{:02x}:{:02x}.{}".format(
|
|
+ int(root.find(".//domain").text),
|
|
+ int(root.find(".//bus").text),
|
|
+ int(root.find(".//slot").text),
|
|
+ root.find(".//function").text,
|
|
+ )
|
|
+ class_node = root.find(".//class")
|
|
+ if class_node is not None:
|
|
+ infos["PCI class"] = class_node.text
|
|
+
|
|
+ # Get the list of Virtual Functions if any
|
|
+ vf_addresses = [
|
|
+ _format_pci_address(vf)
|
|
+ for vf in root.findall(
|
|
+ "./capability[@type='pci']/capability[@type='virt_functions']/address"
|
|
+ )
|
|
+ ]
|
|
+ if vf_addresses:
|
|
+ infos["virtual functions"] = vf_addresses
|
|
+
|
|
+ # Get the Physical Function if any
|
|
+ pf = root.find(
|
|
+ "./capability[@type='pci']/capability[@type='phys_function']/address"
|
|
+ )
|
|
+ if pf is not None:
|
|
+ infos["physical function"] = _format_pci_address(pf)
|
|
+ elif "usb_device" in dev.listCaps():
|
|
+ infos["address"] = "{:03}:{:03}".format(
|
|
+ int(root.find(".//bus").text), int(root.find(".//device").text)
|
|
+ )
|
|
+
|
|
+ # Don't list the pci bridges and USB hosts from the linux foundation
|
|
+ linux_usb_host = vendor_id == "0x1d6b" and product_id in [
|
|
+ "0x0001",
|
|
+ "0x0002",
|
|
+ "0x0003",
|
|
+ ]
|
|
+ if (
|
|
+ root.find(".//capability[@type='pci-bridge']") is None
|
|
+ and not linux_usb_host
|
|
+ ):
|
|
+ devices_infos.append(infos)
|
|
+
|
|
+ return devices_infos
|
|
+
|
|
+
|
|
+def node_devices(**kwargs):
|
|
+ """
|
|
+ List the host available devices.
|
|
+
|
|
+ :param connection: libvirt connection URI, overriding defaults
|
|
+ :param username: username to connect with, overriding defaults
|
|
+ :param password: password to connect with, overriding defaults
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+ """
|
|
+ conn = __get_conn(**kwargs)
|
|
+ devs = _node_devices(conn)
|
|
+ conn.close()
|
|
+ return devs
|
|
+
|
|
+
|
|
def get_nics(vm_, **kwargs):
|
|
"""
|
|
Return info about the network interfaces of a named vm
|
|
@@ -5791,9 +6147,7 @@ def snapshot(domain, name=None, suffix=None, **kwargs):
|
|
n_name.text = name
|
|
|
|
conn = __get_conn(**kwargs)
|
|
- _get_domain(conn, domain).snapshotCreateXML(
|
|
- salt.utils.stringutils.to_str(ElementTree.tostring(doc))
|
|
- )
|
|
+ _get_domain(conn, domain).snapshotCreateXML(xmlutil.element_to_str(doc))
|
|
conn.close()
|
|
|
|
return {"name": name}
|
|
@@ -6464,10 +6818,8 @@ def cpu_baseline(full=False, migratable=False, out="libvirt", **kwargs):
|
|
conn = __get_conn(**kwargs)
|
|
caps = ElementTree.fromstring(conn.getCapabilities())
|
|
cpu = caps.find("host/cpu")
|
|
- log.debug(
|
|
- "Host CPU model definition: %s",
|
|
- salt.utils.stringutils.to_str(ElementTree.tostring(cpu)),
|
|
- )
|
|
+ host_cpu_def = xmlutil.element_to_str(cpu)
|
|
+ log.debug("Host CPU model definition: %s", host_cpu_def)
|
|
|
|
flags = 0
|
|
if migratable:
|
|
@@ -6482,11 +6834,7 @@ def cpu_baseline(full=False, migratable=False, out="libvirt", **kwargs):
|
|
# This one is only in 1.1.3+
|
|
flags += libvirt.VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES
|
|
|
|
- cpu = ElementTree.fromstring(
|
|
- conn.baselineCPU(
|
|
- [salt.utils.stringutils.to_str(ElementTree.tostring(cpu))], flags
|
|
- )
|
|
- )
|
|
+ cpu = ElementTree.fromstring(conn.baselineCPU([host_cpu_def], flags))
|
|
conn.close()
|
|
|
|
if full and not getattr(libvirt, "VIR_CONNECT_BASELINE_CPU_EXPAND_FEATURES", False):
|
|
@@ -6532,18 +6880,70 @@ def cpu_baseline(full=False, migratable=False, out="libvirt", **kwargs):
|
|
return ElementTree.tostring(cpu)
|
|
|
|
|
|
-def network_define(name, bridge, forward, ipv4_config=None, ipv6_config=None, **kwargs):
|
|
+def network_define(
|
|
+ name,
|
|
+ bridge,
|
|
+ forward,
|
|
+ ipv4_config=None,
|
|
+ ipv6_config=None,
|
|
+ vport=None,
|
|
+ tag=None,
|
|
+ autostart=True,
|
|
+ start=True,
|
|
+ mtu=None,
|
|
+ domain=None,
|
|
+ nat=None,
|
|
+ interfaces=None,
|
|
+ addresses=None,
|
|
+ physical_function=None,
|
|
+ dns=None,
|
|
+ **kwargs
|
|
+):
|
|
"""
|
|
Create libvirt network.
|
|
|
|
- :param name: Network name
|
|
- :param bridge: Bridge name
|
|
- :param forward: Forward mode(bridge, router, nat)
|
|
- :param vport: Virtualport type
|
|
- :param tag: Vlan tag
|
|
- :param autostart: Network autostart (default True)
|
|
- :param start: Network start (default True)
|
|
- :param ipv4_config: IP v4 configuration
|
|
+ :param name: Network name.
|
|
+ :param bridge: Bridge name.
|
|
+ :param forward: Forward mode (bridge, router, nat).
|
|
+
|
|
+ .. versionchanged:: Aluminium
|
|
+ a ``None`` value creates an isolated network with no forwarding at all
|
|
+
|
|
+ :param vport: Virtualport type.
|
|
+ The value can also be a dictionary with ``type`` and ``parameters`` keys.
|
|
+ The ``parameters`` value is a dictionary of virtual port parameters.
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - vport:
|
|
+ type: openvswitch
|
|
+ parameters:
|
|
+ interfaceid: 09b11c53-8b5c-4eeb-8f00-d84eaa0aaa4f
|
|
+
|
|
+ .. versionchanged:: Aluminium
|
|
+ possible dictionary value
|
|
+
|
|
+ :param tag: Vlan tag.
|
|
+ The value can also be a dictionary with the ``tags`` and optional ``trunk`` keys.
|
|
+ ``trunk`` is a boolean value indicating whether to use VLAN trunking.
|
|
+ ``tags`` is a list of dictionaries with keys ``id`` and ``nativeMode``.
|
|
+ The ``nativeMode`` value can be one of ``tagged`` or ``untagged``.
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - tag:
|
|
+ trunk: True
|
|
+ tags:
|
|
+ - id: 42
|
|
+ nativeMode: untagged
|
|
+ - id: 47
|
|
+
|
|
+ .. versionchanged:: Aluminium
|
|
+ possible dictionary value
|
|
+
|
|
+ :param autostart: Network autostart (default True).
|
|
+ :param start: Network start (default True).
|
|
+ :param ipv4_config: IP v4 configuration.
|
|
Dictionary describing the IP v4 setup like IP range and
|
|
a possible DHCP configuration. The structure is documented
|
|
in net-define-ip_.
|
|
@@ -6551,7 +6951,7 @@ def network_define(name, bridge, forward, ipv4_config=None, ipv6_config=None, **
|
|
.. versionadded:: 3000
|
|
:type ipv4_config: dict or None
|
|
|
|
- :param ipv6_config: IP v6 configuration
|
|
+ :param ipv6_config: IP v6 configuration.
|
|
Dictionary describing the IP v6 setup like IP range and
|
|
a possible DHCP configuration. The structure is documented
|
|
in net-define-ip_.
|
|
@@ -6559,13 +6959,108 @@ def network_define(name, bridge, forward, ipv4_config=None, ipv6_config=None, **
|
|
.. versionadded:: 3000
|
|
:type ipv6_config: dict or None
|
|
|
|
- :param connection: libvirt connection URI, overriding defaults
|
|
- :param username: username to connect with, overriding defaults
|
|
- :param password: password to connect with, overriding defaults
|
|
+ :param connection: libvirt connection URI, overriding defaults.
|
|
+ :param username: username to connect with, overriding defaults.
|
|
+ :param password: password to connect with, overriding defaults.
|
|
+
|
|
+ :param mtu: size of the Maximum Transmission Unit (MTU) of the network.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param domain: DNS domain name of the DHCP server.
|
|
+ The value is a dictionary with a mandatory ``name`` property and an optional ``localOnly`` boolean one.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - domain:
|
|
+ name: lab.acme.org
|
|
+ localOnly: True
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param nat: addresses and ports to route in NAT forward mode.
|
|
+ The value is a dictionary with optional keys ``address`` and ``port``.
|
|
+ Both values are a dictionary with ``start`` and ``end`` values.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: nat
|
|
+ - nat:
|
|
+ address:
|
|
+ start: 1.2.3.4
|
|
+ end: 1.2.3.10
|
|
+ port:
|
|
+ start: 500
|
|
+ end: 1000
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param interfaces: whitespace separated list of network interfaces devices that can be used for this network.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: passthrough
|
|
+ - interfaces: "eth10 eth11 eth12"
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param addresses: whitespace separated list of addreses of PCI devices that can be used for this network in `hostdev` forward mode.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: hostdev
|
|
+ - interfaces: "0000:04:00.1 0000:e3:01.2"
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param physical_function: device name of the physical interface to use in ``hostdev`` forward mode.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: hostdev
|
|
+ - physical_function: "eth0"
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param dns: virtual network DNS configuration.
|
|
+ The value is a dictionary described in net-define-dns_.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - dns:
|
|
+ forwarders:
|
|
+ - domain: example.com
|
|
+ addr: 192.168.1.1
|
|
+ - addr: 8.8.8.8
|
|
+ - domain: www.example.com
|
|
+ txt:
|
|
+ example.com: "v=spf1 a -all"
|
|
+ _http.tcp.example.com: "name=value,paper=A4"
|
|
+ hosts:
|
|
+ 192.168.1.2:
|
|
+ - mirror.acme.lab
|
|
+ - test.acme.lab
|
|
+ srvs:
|
|
+ - name: ldap
|
|
+ protocol: tcp
|
|
+ domain: ldapserver.example.com
|
|
+ target: .
|
|
+ port: 389
|
|
+ priority: 1
|
|
+ weight: 10
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
|
|
.. _net-define-ip:
|
|
|
|
- ** IP configuration definition
|
|
+ .. rubric:: IP configuration definition
|
|
|
|
Both the IPv4 and IPv6 configuration dictionaries can contain the following properties:
|
|
|
|
@@ -6573,7 +7068,47 @@ def network_define(name, bridge, forward, ipv4_config=None, ipv6_config=None, **
|
|
CIDR notation for the network. For example '192.168.124.0/24'
|
|
|
|
dhcp_ranges
|
|
- A list of dictionary with ``'start'`` and ``'end'`` properties.
|
|
+ A list of dictionaries with ``'start'`` and ``'end'`` properties.
|
|
+
|
|
+ hosts
|
|
+ A list of dictionaries with ``ip`` property and optional ``name``, ``mac`` and ``id`` properties.
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ bootp
|
|
+ A dictionary with a ``file`` property and an optional ``server`` one.
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ tftp
|
|
+ The path to the TFTP root directory to serve.
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ .. _net-define-dns:
|
|
+
|
|
+ .. rubric:: DNS configuration definition
|
|
+
|
|
+ The DNS configuration dictionary contains the following optional properties:
|
|
+
|
|
+ forwarders
|
|
+ List of alternate DNS forwarders to use.
|
|
+ Each item is a dictionary with the optional ``domain`` and ``addr`` keys.
|
|
+ If both are provided, the requests to the domain are forwarded to the server at the ``addr``.
|
|
+ If only ``domain`` is provided the requests matching this domain will be resolved locally.
|
|
+ If only ``addr`` is provided all requests will be forwarded to this DNS server.
|
|
+
|
|
+ txt:
|
|
+ Dictionary of TXT fields to set.
|
|
+
|
|
+ hosts:
|
|
+ Dictionary of host DNS entries.
|
|
+ The key is the IP of the host, and the value is a list of hostnames for it.
|
|
+
|
|
+ srvs:
|
|
+ List of SRV DNS entries.
|
|
+ Each entry is a dictionary with the mandatory ``name`` and ``protocol`` keys.
|
|
+ Entries can also have ``target``, ``port``, ``priority`` and ``weight`` optional properties.
|
|
|
|
CLI Example:
|
|
|
|
@@ -6586,8 +7121,6 @@ def network_define(name, bridge, forward, ipv4_config=None, ipv6_config=None, **
|
|
conn = __get_conn(**kwargs)
|
|
vport = kwargs.get("vport", None)
|
|
tag = kwargs.get("tag", None)
|
|
- autostart = kwargs.get("autostart", True)
|
|
- starting = kwargs.get("start", True)
|
|
|
|
net_xml = _gen_net_xml(
|
|
name,
|
|
@@ -6596,6 +7129,13 @@ def network_define(name, bridge, forward, ipv4_config=None, ipv6_config=None, **
|
|
vport,
|
|
tag=tag,
|
|
ip_configs=[config for config in [ipv4_config, ipv6_config] if config],
|
|
+ mtu=mtu,
|
|
+ domain=domain,
|
|
+ nat=nat,
|
|
+ interfaces=interfaces,
|
|
+ addresses=addresses,
|
|
+ physical_function=physical_function,
|
|
+ dns=dns,
|
|
)
|
|
try:
|
|
conn.networkDefineXML(net_xml)
|
|
@@ -6615,12 +7155,12 @@ def network_define(name, bridge, forward, ipv4_config=None, ipv6_config=None, **
|
|
conn.close()
|
|
return False
|
|
|
|
- if (starting is True or autostart is True) and network.isActive() != 1:
|
|
+ if (start or autostart) and network.isActive() != 1:
|
|
network.create()
|
|
|
|
- if autostart is True and network.autostart() != 1:
|
|
+ if autostart and network.autostart() != 1:
|
|
network.setAutostart(int(autostart))
|
|
- elif autostart is False and network.autostart() == 1:
|
|
+ elif not autostart and network.autostart() == 1:
|
|
network.setAutostart(int(autostart))
|
|
|
|
conn.close()
|
|
@@ -6628,6 +7168,271 @@ def network_define(name, bridge, forward, ipv4_config=None, ipv6_config=None, **
|
|
return True
|
|
|
|
|
|
+def _remove_empty_xml_node(node):
|
|
+ """
|
|
+ Remove the nodes with no children, no text and no attribute
|
|
+ """
|
|
+ for child in node:
|
|
+ if not child.tail and not child.text and not child.items() and not child:
|
|
+ node.remove(child)
|
|
+ else:
|
|
+ _remove_empty_xml_node(child)
|
|
+ return node
|
|
+
|
|
+
|
|
+def network_update(
|
|
+ name,
|
|
+ bridge,
|
|
+ forward,
|
|
+ ipv4_config=None,
|
|
+ ipv6_config=None,
|
|
+ vport=None,
|
|
+ tag=None,
|
|
+ mtu=None,
|
|
+ domain=None,
|
|
+ nat=None,
|
|
+ interfaces=None,
|
|
+ addresses=None,
|
|
+ physical_function=None,
|
|
+ dns=None,
|
|
+ test=False,
|
|
+ **kwargs
|
|
+):
|
|
+ """
|
|
+ Update a virtual network if needed.
|
|
+
|
|
+ :param name: Network name.
|
|
+ :param bridge: Bridge name.
|
|
+ :param forward: Forward mode (bridge, router, nat).
|
|
+ A ``None`` value creates an isolated network with no forwarding at all.
|
|
+
|
|
+ :param vport: Virtualport type.
|
|
+ The value can also be a dictionary with ``type`` and ``parameters`` keys.
|
|
+ The ``parameters`` value is a dictionary of virtual port parameters.
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - vport:
|
|
+ type: openvswitch
|
|
+ parameters:
|
|
+ interfaceid: 09b11c53-8b5c-4eeb-8f00-d84eaa0aaa4f
|
|
+
|
|
+ :param tag: Vlan tag.
|
|
+ The value can also be a dictionary with the ``tags`` and optional ``trunk`` keys.
|
|
+ ``trunk`` is a boolean value indicating whether to use VLAN trunking.
|
|
+ ``tags`` is a list of dictionaries with keys ``id`` and ``nativeMode``.
|
|
+ The ``nativeMode`` value can be one of ``tagged`` or ``untagged``.
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - tag:
|
|
+ trunk: True
|
|
+ tags:
|
|
+ - id: 42
|
|
+ nativeMode: untagged
|
|
+ - id: 47
|
|
+
|
|
+ :param ipv4_config: IP v4 configuration.
|
|
+ Dictionary describing the IP v4 setup like IP range and
|
|
+ a possible DHCP configuration. The structure is documented
|
|
+ in net-define-ip_.
|
|
+
|
|
+ :type ipv4_config: dict or None
|
|
+
|
|
+ :param ipv6_config: IP v6 configuration.
|
|
+ Dictionary describing the IP v6 setup like IP range and
|
|
+ a possible DHCP configuration. The structure is documented
|
|
+ in net-define-ip_.
|
|
+
|
|
+ :type ipv6_config: dict or None
|
|
+
|
|
+ :param connection: libvirt connection URI, overriding defaults.
|
|
+ :param username: username to connect with, overriding defaults.
|
|
+ :param password: password to connect with, overriding defaults.
|
|
+
|
|
+ :param mtu: size of the Maximum Transmission Unit (MTU) of the network.
|
|
+ (default ``None``)
|
|
+
|
|
+ :param domain: DNS domain name of the DHCP server.
|
|
+ The value is a dictionary with a mandatory ``name`` property and an optional ``localOnly`` boolean one.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - domain:
|
|
+ name: lab.acme.org
|
|
+ localOnly: True
|
|
+
|
|
+ :param nat: addresses and ports to route in NAT forward mode.
|
|
+ The value is a dictionary with optional keys ``address`` and ``port``.
|
|
+ Both values are a dictionary with ``start`` and ``end`` values.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: nat
|
|
+ - nat:
|
|
+ address:
|
|
+ start: 1.2.3.4
|
|
+ end: 1.2.3.10
|
|
+ port:
|
|
+ start: 500
|
|
+ end: 1000
|
|
+
|
|
+ :param interfaces: whitespace separated list of network interfaces devices that can be used for this network.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: passthrough
|
|
+ - interfaces: "eth10 eth11 eth12"
|
|
+
|
|
+ :param addresses: whitespace separated list of addreses of PCI devices that can be used for this network in `hostdev` forward mode.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: hostdev
|
|
+ - interfaces: "0000:04:00.1 0000:e3:01.2"
|
|
+
|
|
+ :param physical_function: device name of the physical interface to use in ``hostdev`` forward mode.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: hostdev
|
|
+ - physical_function: "eth0"
|
|
+
|
|
+ :param dns: virtual network DNS configuration.
|
|
+ The value is a dictionary described in net-define-dns_.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - dns:
|
|
+ forwarders:
|
|
+ - domain: example.com
|
|
+ addr: 192.168.1.1
|
|
+ - addr: 8.8.8.8
|
|
+ - domain: www.example.com
|
|
+ txt:
|
|
+ example.com: "v=spf1 a -all"
|
|
+ _http.tcp.example.com: "name=value,paper=A4"
|
|
+ hosts:
|
|
+ 192.168.1.2:
|
|
+ - mirror.acme.lab
|
|
+ - test.acme.lab
|
|
+ srvs:
|
|
+ - name: ldap
|
|
+ protocol: tcp
|
|
+ domain: ldapserver.example.com
|
|
+ target: .
|
|
+ port: 389
|
|
+ priority: 1
|
|
+ weight: 10
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+ """
|
|
+ # Get the current definition to compare the two
|
|
+ conn = __get_conn(**kwargs)
|
|
+ needs_update = False
|
|
+ try:
|
|
+ net = conn.networkLookupByName(name)
|
|
+ old_xml = ElementTree.fromstring(net.XMLDesc())
|
|
+
|
|
+ # Compute new definition
|
|
+ new_xml = ElementTree.fromstring(
|
|
+ _gen_net_xml(
|
|
+ name,
|
|
+ bridge,
|
|
+ forward,
|
|
+ vport,
|
|
+ tag=tag,
|
|
+ ip_configs=[config for config in [ipv4_config, ipv6_config] if config],
|
|
+ mtu=mtu,
|
|
+ domain=domain,
|
|
+ nat=nat,
|
|
+ interfaces=interfaces,
|
|
+ addresses=addresses,
|
|
+ physical_function=physical_function,
|
|
+ dns=dns,
|
|
+ )
|
|
+ )
|
|
+
|
|
+ elements_to_copy = ["uuid", "mac"]
|
|
+ for to_copy in elements_to_copy:
|
|
+ element = old_xml.find(to_copy)
|
|
+ # mac may not be present (hostdev network for instance)
|
|
+ if element is not None:
|
|
+ new_xml.insert(1, element)
|
|
+
|
|
+ # Libvirt adds a connection attribute on running networks, remove before comparing
|
|
+ old_xml.attrib.pop("connections", None)
|
|
+
|
|
+ # Libvirt adds the addresses of the VF devices on running networks with the ph passed
|
|
+ # Those need to be removed before comparing
|
|
+ if old_xml.find("forward/pf") is not None:
|
|
+ forward_node = old_xml.find("forward")
|
|
+ address_nodes = forward_node.findall("address")
|
|
+ for node in address_nodes:
|
|
+ forward_node.remove(node)
|
|
+
|
|
+ # Remove libvirt auto-added bridge attributes to compare
|
|
+ default_bridge_attribs = {"stp": "on", "delay": "0"}
|
|
+ old_bridge_node = old_xml.find("bridge")
|
|
+ if old_bridge_node is not None:
|
|
+ for key, value in default_bridge_attribs.items():
|
|
+ if old_bridge_node.get(key, None) == value:
|
|
+ old_bridge_node.attrib.pop(key, None)
|
|
+
|
|
+ # Libvirt may also add the whole bridge network since the name can be computed
|
|
+ # If the bridge name starts with virbr in a nat, route, open or isolated network
|
|
+ # there is a good change it has been autogenerated...
|
|
+ old_forward = (
|
|
+ old_xml.find("forward").get("mode")
|
|
+ if old_xml.find("forward") is not None
|
|
+ else None
|
|
+ )
|
|
+ if (
|
|
+ old_forward == forward
|
|
+ and forward in ["nat", "route", "open", None]
|
|
+ and bridge is None
|
|
+ and old_bridge_node.get("name", "").startswith("virbr")
|
|
+ ):
|
|
+ old_bridge_node.attrib.pop("name", None)
|
|
+
|
|
+ # In the ipv4 address, we need to convert netmask to prefix in the old XML
|
|
+ ipv4_nodes = [
|
|
+ node
|
|
+ for node in old_xml.findall("ip")
|
|
+ if node.get("family", "ipv4") == "ipv4"
|
|
+ ]
|
|
+ for ip_node in ipv4_nodes:
|
|
+ netmask = ip_node.attrib.pop("netmask")
|
|
+ if netmask:
|
|
+ address = ipaddress.ip_network(
|
|
+ "{}/{}".format(ip_node.get("address"), netmask), strict=False
|
|
+ )
|
|
+ ip_node.set("prefix", str(address.prefixlen))
|
|
+
|
|
+ # Add default ipv4 family if needed
|
|
+ for doc in [old_xml, new_xml]:
|
|
+ for node in doc.findall("ip"):
|
|
+ if "family" not in node.keys():
|
|
+ node.set("family", "ipv4")
|
|
+
|
|
+ # Filter out spaces and empty elements since those would mislead the comparison
|
|
+ _remove_empty_xml_node(xmlutil.strip_spaces(old_xml))
|
|
+ xmlutil.strip_spaces(new_xml)
|
|
+
|
|
+ needs_update = xmlutil.to_dict(old_xml, True) != xmlutil.to_dict(new_xml, True)
|
|
+ if needs_update and not test:
|
|
+ conn.networkDefineXML(xmlutil.element_to_str(new_xml))
|
|
+ finally:
|
|
+ conn.close()
|
|
+ return needs_update
|
|
+
|
|
+
|
|
def list_networks(**kwargs):
|
|
"""
|
|
List all virtual networks.
|
|
@@ -6687,6 +7492,16 @@ def network_info(name=None, **kwargs):
|
|
lease["type"] = "unknown"
|
|
return leases
|
|
|
|
+ def _net_get_bridge(net):
|
|
+ """
|
|
+ Get the bridge of the network or None
|
|
+ """
|
|
+ try:
|
|
+ return net.bridgeName()
|
|
+ except libvirt.libvirtError as err:
|
|
+ # Some network configurations have no bridge
|
|
+ return None
|
|
+
|
|
try:
|
|
nets = [
|
|
net for net in conn.listAllNetworks() if name is None or net.name() == name
|
|
@@ -6694,7 +7509,7 @@ def network_info(name=None, **kwargs):
|
|
result = {
|
|
net.name(): {
|
|
"uuid": net.UUIDString(),
|
|
- "bridge": net.bridgeName(),
|
|
+ "bridge": _net_get_bridge(net),
|
|
"autostart": net.autostart(),
|
|
"active": net.isActive(),
|
|
"persistent": net.isPersistent(),
|
|
@@ -7453,37 +8268,12 @@ def pool_update(
|
|
new_xml.insert(1, element)
|
|
|
|
# Filter out spaces and empty elements like <source/> since those would mislead the comparison
|
|
- def visit_xml(node, fn):
|
|
- fn(node)
|
|
- for child in node:
|
|
- visit_xml(child, fn)
|
|
-
|
|
- def space_stripper(node):
|
|
- if node.tail is not None:
|
|
- node.tail = node.tail.strip(" \t\n")
|
|
- if node.text is not None:
|
|
- node.text = node.text.strip(" \t\n")
|
|
-
|
|
- visit_xml(old_xml, space_stripper)
|
|
- visit_xml(new_xml, space_stripper)
|
|
-
|
|
- def empty_node_remover(node):
|
|
- for child in node:
|
|
- if (
|
|
- not child.tail
|
|
- and not child.text
|
|
- and not child.items()
|
|
- and not child
|
|
- ):
|
|
- node.remove(child)
|
|
-
|
|
- visit_xml(old_xml, empty_node_remover)
|
|
+ _remove_empty_xml_node(xmlutil.strip_spaces(old_xml))
|
|
+ xmlutil.strip_spaces(new_xml)
|
|
|
|
needs_update = xmlutil.to_dict(old_xml, True) != xmlutil.to_dict(new_xml, True)
|
|
if needs_update and not test:
|
|
- conn.storagePoolDefineXML(
|
|
- salt.utils.stringutils.to_str(ElementTree.tostring(new_xml))
|
|
- )
|
|
+ conn.storagePoolDefineXML(xmlutil.element_to_str(new_xml))
|
|
finally:
|
|
conn.close()
|
|
return needs_update
|
|
diff --git a/salt/states/virt.py b/salt/states/virt.py
|
|
index 784cdca73c..c677c9ad84 100644
|
|
--- a/salt/states/virt.py
|
|
+++ b/salt/states/virt.py
|
|
@@ -161,7 +161,8 @@ def _virt_call(
|
|
:param state: the expected final state of the VM. If None the VM state won't be checked.
|
|
:return: the salt state return
|
|
"""
|
|
- ret = {"name": domain, "changes": {}, "result": True, "comment": ""}
|
|
+ result = True if not __opts__["test"] else None
|
|
+ ret = {"name": domain, "changes": {}, "result": result, "comment": ""}
|
|
targeted_domains = fnmatch.filter(__salt__["virt.list_domains"](), domain)
|
|
changed_domains = list()
|
|
ignored_domains = list()
|
|
@@ -174,15 +175,17 @@ def _virt_call(
|
|
domain_state = __salt__["virt.vm_state"](targeted_domain)
|
|
action_needed = domain_state.get(targeted_domain) != state
|
|
if action_needed:
|
|
- response = __salt__["virt.{}".format(function)](
|
|
- targeted_domain,
|
|
- connection=connection,
|
|
- username=username,
|
|
- password=password,
|
|
- **kwargs
|
|
- )
|
|
- if isinstance(response, dict):
|
|
- response = response["name"]
|
|
+ response = True
|
|
+ if not __opts__["test"]:
|
|
+ response = __salt__["virt.{}".format(function)](
|
|
+ targeted_domain,
|
|
+ connection=connection,
|
|
+ username=username,
|
|
+ password=password,
|
|
+ **kwargs
|
|
+ )
|
|
+ if isinstance(response, dict):
|
|
+ response = response["name"]
|
|
changed_domains.append({"domain": targeted_domain, function: response})
|
|
else:
|
|
noaction_domains.append(targeted_domain)
|
|
@@ -288,7 +291,6 @@ def defined(
|
|
arch=None,
|
|
boot=None,
|
|
numatune=None,
|
|
- update=True,
|
|
boot_dev=None,
|
|
hypervisor_features=None,
|
|
clock=None,
|
|
@@ -296,6 +298,7 @@ def defined(
|
|
consoles=None,
|
|
stop_on_reboot=False,
|
|
live=True,
|
|
+ host_devices=None,
|
|
):
|
|
"""
|
|
Starts an existing guest, or defines and starts a new VM with specified arguments.
|
|
@@ -498,10 +501,6 @@ def defined(
|
|
|
|
.. versionadded:: 3000
|
|
|
|
- :param update: set to ``False`` to prevent updating a defined domain. (Default: ``True``)
|
|
-
|
|
- .. deprecated:: sodium
|
|
-
|
|
:param boot_dev:
|
|
Space separated list of devices to boot from sorted by decreasing priority.
|
|
Values can be ``hd``, ``fd``, ``cdrom`` or ``network``.
|
|
@@ -595,6 +594,13 @@ def defined(
|
|
|
|
.. versionadded:: Aluminium
|
|
|
|
+ :param host_devices:
|
|
+ List of host devices to passthrough to the guest.
|
|
+ The value is a list of device names as provided by the :py:func:`~salt.modules.virt.node_devices` function.
|
|
+ (Default: ``None``)
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
.. rubric:: Example States
|
|
|
|
Make sure a virtual machine called ``domain_name`` is defined:
|
|
@@ -641,31 +647,30 @@ def defined(
|
|
if name in __salt__["virt.list_domains"](
|
|
connection=connection, username=username, password=password
|
|
):
|
|
- status = {}
|
|
- if update:
|
|
- status = __salt__["virt.update"](
|
|
- name,
|
|
- cpu=cpu,
|
|
- mem=mem,
|
|
- disk_profile=disk_profile,
|
|
- disks=disks,
|
|
- nic_profile=nic_profile,
|
|
- interfaces=interfaces,
|
|
- graphics=graphics,
|
|
- live=live,
|
|
- connection=connection,
|
|
- username=username,
|
|
- password=password,
|
|
- boot=boot,
|
|
- numatune=numatune,
|
|
- serials=serials,
|
|
- consoles=consoles,
|
|
- test=__opts__["test"],
|
|
- boot_dev=boot_dev,
|
|
- hypervisor_features=hypervisor_features,
|
|
- clock=clock,
|
|
- stop_on_reboot=stop_on_reboot,
|
|
- )
|
|
+ status = __salt__["virt.update"](
|
|
+ name,
|
|
+ cpu=cpu,
|
|
+ mem=mem,
|
|
+ disk_profile=disk_profile,
|
|
+ disks=disks,
|
|
+ nic_profile=nic_profile,
|
|
+ interfaces=interfaces,
|
|
+ graphics=graphics,
|
|
+ live=live,
|
|
+ connection=connection,
|
|
+ username=username,
|
|
+ password=password,
|
|
+ boot=boot,
|
|
+ numatune=numatune,
|
|
+ serials=serials,
|
|
+ consoles=consoles,
|
|
+ test=__opts__["test"],
|
|
+ boot_dev=boot_dev,
|
|
+ hypervisor_features=hypervisor_features,
|
|
+ clock=clock,
|
|
+ stop_on_reboot=stop_on_reboot,
|
|
+ host_devices=host_devices,
|
|
+ )
|
|
ret["changes"][name] = status
|
|
if not status.get("definition"):
|
|
ret["comment"] = "Domain {} unchanged".format(name)
|
|
@@ -706,6 +711,7 @@ def defined(
|
|
hypervisor_features=hypervisor_features,
|
|
clock=clock,
|
|
stop_on_reboot=stop_on_reboot,
|
|
+ host_devices=host_devices,
|
|
)
|
|
ret["changes"][name] = {"definition": True}
|
|
ret["comment"] = "Domain {} defined".format(name)
|
|
@@ -731,7 +737,6 @@ def running(
|
|
install=True,
|
|
pub_key=None,
|
|
priv_key=None,
|
|
- update=False,
|
|
connection=None,
|
|
username=None,
|
|
password=None,
|
|
@@ -745,6 +750,7 @@ def running(
|
|
serials=None,
|
|
consoles=None,
|
|
stop_on_reboot=False,
|
|
+ host_devices=None,
|
|
):
|
|
"""
|
|
Starts an existing guest, or defines and starts a new VM with specified arguments.
|
|
@@ -826,10 +832,6 @@ def running(
|
|
:param seed_cmd: Salt command to execute to seed the image. (Default: ``'seed.apply'``)
|
|
|
|
.. versionadded:: 2019.2.0
|
|
- :param update: set to ``True`` to update a defined domain. (Default: ``False``)
|
|
-
|
|
- .. versionadded:: 2019.2.0
|
|
- .. deprecated:: sodium
|
|
:param connection: libvirt connection URI, overriding defaults
|
|
|
|
.. versionadded:: 2019.2.0
|
|
@@ -962,6 +964,13 @@ def running(
|
|
clock:
|
|
timezone: CEST
|
|
|
|
+ :param host_devices:
|
|
+ List of host devices to passthrough to the guest.
|
|
+ The value is a list of device names as provided by the :py:func:`~salt.modules.virt.node_devices` function.
|
|
+ (Default: ``None``)
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
.. rubric:: Example States
|
|
|
|
Make sure an already-defined virtual machine called ``domain_name`` is running:
|
|
@@ -1005,12 +1014,6 @@ def running(
|
|
"""
|
|
merged_disks = disks
|
|
|
|
- if not update:
|
|
- salt.utils.versions.warn_until(
|
|
- "Aluminium",
|
|
- "'update' parameter has been deprecated. Future behavior will be the one of update=True"
|
|
- "It will be removed in {version}.",
|
|
- )
|
|
ret = defined(
|
|
name,
|
|
cpu=cpu,
|
|
@@ -1028,7 +1031,6 @@ def running(
|
|
os_type=os_type,
|
|
arch=arch,
|
|
boot=boot,
|
|
- update=update,
|
|
boot_dev=boot_dev,
|
|
numatune=numatune,
|
|
hypervisor_features=hypervisor_features,
|
|
@@ -1039,6 +1041,7 @@ def running(
|
|
password=password,
|
|
serials=serials,
|
|
consoles=consoles,
|
|
+ host_devices=host_devices,
|
|
)
|
|
|
|
result = True if not __opts__["test"] else None
|
|
@@ -1264,21 +1267,64 @@ def network_defined(
|
|
connection=None,
|
|
username=None,
|
|
password=None,
|
|
+ mtu=None,
|
|
+ domain=None,
|
|
+ nat=None,
|
|
+ interfaces=None,
|
|
+ addresses=None,
|
|
+ physical_function=None,
|
|
+ dns=None,
|
|
):
|
|
"""
|
|
Defines a new network with specified arguments.
|
|
|
|
+ :param name: Network name
|
|
:param bridge: Bridge name
|
|
:param forward: Forward mode(bridge, router, nat)
|
|
+
|
|
+ .. versionchanged:: Aluminium
|
|
+ a ``None`` value creates an isolated network with no forwarding at all
|
|
+
|
|
:param vport: Virtualport type (Default: ``'None'``)
|
|
+ The value can also be a dictionary with ``type`` and ``parameters`` keys.
|
|
+ The ``parameters`` value is a dictionary of virtual port parameters.
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - vport:
|
|
+ type: openvswitch
|
|
+ parameters:
|
|
+ interfaceid: 09b11c53-8b5c-4eeb-8f00-d84eaa0aaa4f
|
|
+
|
|
+ .. versionchanged:: Aluminium
|
|
+ possible dictionary value
|
|
+
|
|
:param tag: Vlan tag (Default: ``'None'``)
|
|
+ The value can also be a dictionary with the ``tags`` and optional ``trunk`` keys.
|
|
+ ``trunk`` is a boolean value indicating whether to use VLAN trunking.
|
|
+ ``tags`` is a list of dictionaries with keys ``id`` and ``nativeMode``.
|
|
+ The ``nativeMode`` value can be one of ``tagged`` or ``untagged``.
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - tag:
|
|
+ trunk: True
|
|
+ tags:
|
|
+ - id: 42
|
|
+ nativeMode: untagged
|
|
+ - id: 47
|
|
+
|
|
+ .. versionchanged:: Aluminium
|
|
+ possible dictionary value
|
|
+
|
|
:param ipv4_config:
|
|
- IPv4 network configuration. See the :py:func`virt.network_define
|
|
- <salt.modules.virt.network_define>` function corresponding parameter documentation
|
|
+ IPv4 network configuration. See the
|
|
+ :py:func:`virt.network_define <salt.modules.virt.network_define>`
|
|
+ function corresponding parameter documentation
|
|
for more details on this dictionary.
|
|
(Default: None).
|
|
:param ipv6_config:
|
|
- IPv6 network configuration. See the :py:func`virt.network_define
|
|
+ IPv6 network configuration. See the :py:func:`virt.network_define
|
|
<salt.modules.virt.network_define>` function corresponding parameter documentation
|
|
for more details on this dictionary.
|
|
(Default: None).
|
|
@@ -1286,6 +1332,100 @@ def network_defined(
|
|
:param connection: libvirt connection URI, overriding defaults
|
|
:param username: username to connect with, overriding defaults
|
|
:param password: password to connect with, overriding defaults
|
|
+ :param mtu: size of the Maximum Transmission Unit (MTU) of the network.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param domain: DNS domain name of the DHCP server.
|
|
+ The value is a dictionary with a mandatory ``name`` property and an optional ``localOnly`` boolean one.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - domain:
|
|
+ name: lab.acme.org
|
|
+ localOnly: True
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param nat: addresses and ports to route in NAT forward mode.
|
|
+ The value is a dictionary with optional keys ``address`` and ``port``.
|
|
+ Both values are a dictionary with ``start`` and ``end`` values.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: nat
|
|
+ - nat:
|
|
+ address:
|
|
+ start: 1.2.3.4
|
|
+ end: 1.2.3.10
|
|
+ port:
|
|
+ start: 500
|
|
+ end: 1000
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param interfaces: whitespace separated list of network interfaces devices that can be used for this network.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: passthrough
|
|
+ - interfaces: "eth10 eth11 eth12"
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param addresses: whitespace separated list of addreses of PCI devices that can be used for this network in `hostdev` forward mode.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: hostdev
|
|
+ - interfaces: "0000:04:00.1 0000:e3:01.2"
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param physical_function: device name of the physical interface to use in ``hostdev`` forward mode.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: hostdev
|
|
+ - physical_function: "eth0"
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param dns: virtual network DNS configuration
|
|
+ The value is a dictionary described in :ref:`net-define-dns`.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - dns:
|
|
+ forwarders:
|
|
+ - domain: example.com
|
|
+ addr: 192.168.1.1
|
|
+ - addr: 8.8.8.8
|
|
+ - domain: www.example.com
|
|
+ txt:
|
|
+ example.com: "v=spf1 a -all"
|
|
+ _http.tcp.example.com: "name=value,paper=A4"
|
|
+ hosts:
|
|
+ 192.168.1.2:
|
|
+ - mirror.acme.lab
|
|
+ - test.acme.lab
|
|
+ srvs:
|
|
+ - name: ldap
|
|
+ protocol: tcp
|
|
+ domain: ldapserver.example.com
|
|
+ target: .
|
|
+ port: 389
|
|
+ priority: 1
|
|
+ weight: 10
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
|
|
.. versionadded:: sodium
|
|
|
|
@@ -1332,9 +1472,62 @@ def network_defined(
|
|
name, connection=connection, username=username, password=password
|
|
)
|
|
if info and info[name]:
|
|
- ret["comment"] = "Network {} exists".format(name)
|
|
- ret["result"] = True
|
|
+ needs_autostart = (
|
|
+ info[name]["autostart"]
|
|
+ and not autostart
|
|
+ or not info[name]["autostart"]
|
|
+ and autostart
|
|
+ )
|
|
+ needs_update = __salt__["virt.network_update"](
|
|
+ name,
|
|
+ bridge,
|
|
+ forward,
|
|
+ vport=vport,
|
|
+ tag=tag,
|
|
+ ipv4_config=ipv4_config,
|
|
+ ipv6_config=ipv6_config,
|
|
+ mtu=mtu,
|
|
+ domain=domain,
|
|
+ nat=nat,
|
|
+ interfaces=interfaces,
|
|
+ addresses=addresses,
|
|
+ physical_function=physical_function,
|
|
+ dns=dns,
|
|
+ test=True,
|
|
+ connection=connection,
|
|
+ username=username,
|
|
+ password=password,
|
|
+ )
|
|
+ if needs_update:
|
|
+ if not __opts__["test"]:
|
|
+ __salt__["virt.network_update"](
|
|
+ name,
|
|
+ bridge,
|
|
+ forward,
|
|
+ vport=vport,
|
|
+ tag=tag,
|
|
+ ipv4_config=ipv4_config,
|
|
+ ipv6_config=ipv6_config,
|
|
+ mtu=mtu,
|
|
+ domain=domain,
|
|
+ nat=nat,
|
|
+ interfaces=interfaces,
|
|
+ addresses=addresses,
|
|
+ physical_function=physical_function,
|
|
+ dns=dns,
|
|
+ test=False,
|
|
+ connection=connection,
|
|
+ username=username,
|
|
+ password=password,
|
|
+ )
|
|
+ action = ", autostart flag changed" if needs_autostart else ""
|
|
+ ret["changes"][name] = "Network updated{}".format(action)
|
|
+ ret["comment"] = "Network {} updated{}".format(name, action)
|
|
+ else:
|
|
+ ret["comment"] = "Network {} unchanged".format(name)
|
|
+ ret["result"] = True
|
|
else:
|
|
+ needs_autostart = autostart
|
|
if not __opts__["test"]:
|
|
__salt__["virt.network_define"](
|
|
name,
|
|
@@ -1344,14 +1537,35 @@ def network_defined(
|
|
tag=tag,
|
|
ipv4_config=ipv4_config,
|
|
ipv6_config=ipv6_config,
|
|
- autostart=autostart,
|
|
+ mtu=mtu,
|
|
+ domain=domain,
|
|
+ nat=nat,
|
|
+ interfaces=interfaces,
|
|
+ addresses=addresses,
|
|
+ physical_function=physical_function,
|
|
+ dns=dns,
|
|
+ autostart=False,
|
|
start=False,
|
|
connection=connection,
|
|
username=username,
|
|
password=password,
|
|
)
|
|
- ret["changes"][name] = "Network defined"
|
|
- ret["comment"] = "Network {} defined".format(name)
|
|
+ if needs_autostart:
|
|
+ ret["changes"][name] = "Network defined, marked for autostart"
|
|
+ ret["comment"] = "Network {} defined, marked for autostart".format(name)
|
|
+ else:
|
|
+ ret["changes"][name] = "Network defined"
|
|
+ ret["comment"] = "Network {} defined".format(name)
|
|
+
|
|
+ if needs_autostart:
|
|
+ if not __opts__["test"]:
|
|
+ __salt__["virt.network_set_autostart"](
|
|
+ name,
|
|
+ state="on" if autostart else "off",
|
|
+ connection=connection,
|
|
+ username=username,
|
|
+ password=password,
|
|
+ )
|
|
except libvirt.libvirtError as err:
|
|
ret["result"] = False
|
|
ret["comment"] = err.get_error_message()
|
|
@@ -1371,14 +1585,56 @@ def network_running(
|
|
connection=None,
|
|
username=None,
|
|
password=None,
|
|
+ mtu=None,
|
|
+ domain=None,
|
|
+ nat=None,
|
|
+ interfaces=None,
|
|
+ addresses=None,
|
|
+ physical_function=None,
|
|
+ dns=None,
|
|
):
|
|
"""
|
|
Defines and starts a new network with specified arguments.
|
|
|
|
+ :param name: Network name
|
|
:param bridge: Bridge name
|
|
:param forward: Forward mode(bridge, router, nat)
|
|
+
|
|
+ .. versionchanged:: Aluminium
|
|
+ a ``None`` value creates an isolated network with no forwarding at all
|
|
+
|
|
:param vport: Virtualport type (Default: ``'None'``)
|
|
+ The value can also be a dictionary with ``type`` and ``parameters`` keys.
|
|
+ The ``parameters`` value is a dictionary of virtual port parameters.
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - vport:
|
|
+ type: openvswitch
|
|
+ parameters:
|
|
+ interfaceid: 09b11c53-8b5c-4eeb-8f00-d84eaa0aaa4f
|
|
+
|
|
+ .. versionchanged:: Aluminium
|
|
+ possible dictionary value
|
|
+
|
|
:param tag: Vlan tag (Default: ``'None'``)
|
|
+ The value can also be a dictionary with the ``tags`` and optional ``trunk`` keys.
|
|
+ ``trunk`` is a boolean value indicating whether to use VLAN trunking.
|
|
+ ``tags`` is a list of dictionaries with keys ``id`` and ``nativeMode``.
|
|
+ The ``nativeMode`` value can be one of ``tagged`` or ``untagged``.
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - tag:
|
|
+ trunk: True
|
|
+ tags:
|
|
+ - id: 42
|
|
+ nativeMode: untagged
|
|
+ - id: 47
|
|
+
|
|
+ .. versionchanged:: Aluminium
|
|
+ possible dictionary value
|
|
+
|
|
:param ipv4_config:
|
|
IPv4 network configuration. See the :py:func`virt.network_define
|
|
<salt.modules.virt.network_define>` function corresponding parameter documentation
|
|
@@ -1403,6 +1659,100 @@ def network_running(
|
|
:param password: password to connect with, overriding defaults
|
|
|
|
.. versionadded:: 2019.2.0
|
|
+ :param mtu: size of the Maximum Transmission Unit (MTU) of the network.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param domain: DNS domain name of the DHCP server.
|
|
+ The value is a dictionary with a mandatory ``name`` property and an optional ``localOnly`` boolean one.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - domain:
|
|
+ name: lab.acme.org
|
|
+ localOnly: True
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param nat: addresses and ports to route in NAT forward mode.
|
|
+ The value is a dictionary with optional keys ``address`` and ``port``.
|
|
+ Both values are a dictionary with ``start`` and ``end`` values.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: nat
|
|
+ - nat:
|
|
+ address:
|
|
+ start: 1.2.3.4
|
|
+ end: 1.2.3.10
|
|
+ port:
|
|
+ start: 500
|
|
+ end: 1000
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param interfaces: whitespace separated list of network interfaces devices that can be used for this network.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: passthrough
|
|
+ - interfaces: "eth10 eth11 eth12"
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param addresses: whitespace separated list of addreses of PCI devices that can be used for this network in `hostdev` forward mode.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: hostdev
|
|
+ - interfaces: "0000:04:00.1 0000:e3:01.2"
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param physical_function: device name of the physical interface to use in ``hostdev`` forward mode.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - forward: hostdev
|
|
+ - physical_function: "eth0"
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
+
|
|
+ :param dns: virtual network DNS configuration
|
|
+ The value is a dictionary described in :ref:`net-define-dns`.
|
|
+ (default ``None``)
|
|
+
|
|
+ .. code-block:: yaml
|
|
+
|
|
+ - dns:
|
|
+ forwarders:
|
|
+ - domain: example.com
|
|
+ addr: 192.168.1.1
|
|
+ - addr: 8.8.8.8
|
|
+ - domain: www.example.com
|
|
+ txt:
|
|
+ host.widgets.com.: "printer=lpr5"
|
|
+ example.com.: "This domain name is reserved for use in documentation"
|
|
+ hosts:
|
|
+ 192.168.1.2:
|
|
+ - mirror.acme.lab
|
|
+ - test.acme.lab
|
|
+ srvs:
|
|
+ - name: ldap
|
|
+ protocol: tcp
|
|
+ domain: ldapserver.example.com
|
|
+ target: .
|
|
+ port: 389
|
|
+ priority: 1
|
|
+ weight: 10
|
|
+
|
|
+ .. versionadded:: Aluminium
|
|
|
|
.. code-block:: yaml
|
|
|
|
@@ -1443,6 +1793,13 @@ def network_running(
|
|
tag=tag,
|
|
ipv4_config=ipv4_config,
|
|
ipv6_config=ipv6_config,
|
|
+ mtu=mtu,
|
|
+ domain=domain,
|
|
+ nat=nat,
|
|
+ interfaces=interfaces,
|
|
+ addresses=addresses,
|
|
+ physical_function=physical_function,
|
|
+ dns=dns,
|
|
autostart=autostart,
|
|
connection=connection,
|
|
username=username,
|
|
diff --git a/salt/templates/virt/libvirt_domain.jinja b/salt/templates/virt/libvirt_domain.jinja
|
|
index 6ac3e867b9..4603dfd8de 100644
|
|
--- a/salt/templates/virt/libvirt_domain.jinja
|
|
+++ b/salt/templates/virt/libvirt_domain.jinja
|
|
@@ -1,342 +1,336 @@
|
|
{%- import 'libvirt_disks.jinja' as libvirt_disks -%}
|
|
+{%- from 'libvirt_macros.jinja' import opt_attribute as opt_attribute -%}
|
|
{%- macro opt_attribute(obj, name, conv=none) %}
|
|
{%- if obj.get(name) is not none %} {{ name }}='{{ obj[name] if conv is none else conv(obj[name]) }}'{% endif -%}
|
|
{%- endmacro %}
|
|
{%- import 'libvirt_chardevs.jinja' as libvirt_chardevs -%}
|
|
<domain type='{{ hypervisor }}'>
|
|
- <name>{{ name }}</name>
|
|
- {%- if cpu %}
|
|
- <vcpu {{ opt_attribute(cpu, 'placement') }} {{ opt_attribute(cpu, 'cpuset') }} {{ opt_attribute(cpu, 'current') }}>{{ cpu.get('maximum', '') }}</vcpu>
|
|
- {%- endif %}
|
|
- {%- if cpu.get('vcpus') %}
|
|
- <vcpus>
|
|
- {%- for vcpu_id in cpu["vcpus"].keys() %}
|
|
- <vcpu id='{{ vcpu_id }}' {{ opt_attribute(cpu.vcpus[vcpu_id], 'enabled', yesno) }} {{ opt_attribute(cpu.vcpus[vcpu_id], 'hotpluggable', yesno) }} {{ opt_attribute(cpu.vcpus[vcpu_id], 'order') }}/>
|
|
- {%- endfor %}
|
|
- </vcpus>
|
|
- {%- endif %}
|
|
- {%- if cpu %}
|
|
- <cpu {{ opt_attribute(cpu, 'match') }} {{ opt_attribute(cpu, 'mode') }} {{ opt_attribute(cpu, 'check') }} >
|
|
- {%- if cpu.model %}
|
|
- <model {{ opt_attribute(cpu.model, 'fallback') }} {{ opt_attribute(cpu.model, 'vendor_id') }}>{{ cpu.model.get('name', '') }}</model>
|
|
- {%- endif %}
|
|
- {%- if cpu.vendor %}
|
|
- <vendor>{{ cpu.get('vendor', '') }}</vendor>
|
|
- {%- endif %}
|
|
- {%- if cpu.topology %}
|
|
- <topology {{ opt_attribute(cpu.topology, 'sockets') }} {{ opt_attribute(cpu.topology, 'dies') }} {{ opt_attribute(cpu.topology, 'cores') }} {{ opt_attribute(cpu.topology, 'threads') }}/>
|
|
- {%- endif %}
|
|
- {%- if cpu.cache %}
|
|
- <cache {{ opt_attribute(cpu.cache, 'level') }} {{ opt_attribute(cpu.cache, 'mode') }}/>
|
|
- {%- endif %}
|
|
- {%- if cpu.features %}
|
|
- {%- for k, v in cpu.features.items() %}
|
|
- <feature policy='{{ v }}' name='{{ k }}'/>
|
|
- {%- endfor %}
|
|
- {%- endif %}
|
|
- {%- if cpu.numa %}
|
|
- <numa>
|
|
- {%- for numa_id in cpu.numa.keys() %}
|
|
- {%- if cpu.numa.get(numa_id) %}
|
|
- <cell id='{{ numa_id }}' {{ opt_attribute(cpu.numa[numa_id], 'cpus') }} {{ opt_attribute(cpu.numa[numa_id], 'memory', to_kib) }} {{ opt_attribute(cpu.numa[numa_id], 'discard', yesno) }} {{ opt_attribute(cpu.numa[numa_id], 'memAccess') }}>
|
|
- {%- if cpu.numa[numa_id].distances %}
|
|
- <distances>
|
|
- {%- for sibling_id in cpu.numa[numa_id].distances %}
|
|
- <sibling id='{{ sibling_id }}' value='{{ cpu.numa[numa_id].distances[sibling_id] }}'/>
|
|
- {%- endfor %}
|
|
- </distances>
|
|
- {%- endif %}
|
|
- </cell>
|
|
- {%- endif %}
|
|
- {%- endfor %}
|
|
- </numa>
|
|
- {%- endif %}
|
|
- </cpu>
|
|
- {%- if cpu.iothreads %}
|
|
- <iothreads>{{ cpu.iothreads }}</iothreads>
|
|
- {%- endif %}
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning %}
|
|
- <cputune>
|
|
- {%- if cpu.tuning.vcpupin %}
|
|
- {%- for vcpu_id, cpuset in cpu.tuning.vcpupin.items() %}
|
|
- <vcpupin vcpu='{{ vcpu_id }}' cpuset='{{ cpuset }}'/>
|
|
- {%- endfor %}
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.emulatorpin %}
|
|
- <emulatorpin cpuset="{{ cpu.tuning.emulatorpin }}"/>
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.iothreadpin %}
|
|
- {%- for thread_id, cpuset in cpu.tuning.iothreadpin.items() %}
|
|
- <iothreadpin iothread='{{ thread_id }}' cpuset='{{ cpuset }}'/>
|
|
- {%- endfor %}
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.shares %}
|
|
- <shares>{{ cpu.tuning.shares }}</shares>
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.period %}
|
|
- <period>{{ cpu.tuning.period }}</period>
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.quota %}
|
|
- <quota>{{ cpu.tuning.quota }}</quota>
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.global_period %}
|
|
- <global_period>{{ cpu.tuning.global_period }}</global_period>
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.global_quota %}
|
|
- <global_quota>{{ cpu.tuning.global_quota }}</global_quota>
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.emulator_period %}
|
|
- <emulator_period>{{ cpu.tuning.emulator_period }}</emulator_period>
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.emulator_quota %}
|
|
- <emulator_quota>{{ cpu.tuning.emulator_quota }}</emulator_quota>
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.iothread_period %}
|
|
- <iothread_period>{{ cpu.tuning.iothread_period }}</iothread_period>
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.iothread_quota %}
|
|
- <iothread_quota>{{ cpu.tuning.iothread_quota }}</iothread_quota>
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.vcpusched %}
|
|
- {%- for sched in cpu.tuning.vcpusched %}
|
|
- <vcpusched scheduler='{{ sched.scheduler }}'
|
|
- {%- if sched.get("vcpus") %} vcpus='{{ sched.get("vcpus") }}'{% endif -%}
|
|
- {%- if sched.get("priority") is not none %} priority='{{ sched.get("priority") }}'{% endif -%}
|
|
- />
|
|
- {%- endfor %}
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.iothreadsched %}
|
|
- {%- for sched in cpu.tuning.iothreadsched %}
|
|
- <iothreadsched scheduler='{{ sched.scheduler }}'
|
|
- {%- if sched.get("iothreads") %} iothreads='{{ sched.get("iothreads") }}'{% endif -%}
|
|
- {%- if sched.get("priority") is not none %} priority='{{ sched.get("priority") }}'{% endif -%}
|
|
- />
|
|
- {%- endfor %}
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.emulatorsched %}
|
|
- <emulatorsched scheduler='{{ cpu.tuning.emulatorsched.scheduler }}'
|
|
- {%- if cpu.tuning.emulatorsched.get("priority") is not none %} priority='{{ cpu.tuning.emulatorsched.get("priority") }}'{% endif -%}
|
|
- />
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.cachetune %}
|
|
- {%- for k, v in cpu.tuning.cachetune.items() %}
|
|
- <cachetune vcpus='{{ k }}'>
|
|
- {%- for e, atrs in v.items() %}
|
|
- {%- if e is number and atrs %}
|
|
- <cache id='{{ e }}' {%- for atr, val in atrs.items() %} {{ atr }}='{{ val }}' {%- endfor %} />
|
|
- {%- elif e is not number %}
|
|
- {%- for atr, val in atrs.items() %}
|
|
- <monitor level='{{ val }}' vcpus='{{ atr }}'/>
|
|
- {%- endfor %}
|
|
- {%- endif %}
|
|
- {%- endfor %}
|
|
- </cachetune>
|
|
- {%- endfor %}
|
|
- {%- endif %}
|
|
- {%- if cpu.tuning.memorytune %}
|
|
- {%- for vcpus, nodes in cpu.tuning.memorytune.items() %}
|
|
- <memorytune vcpus='{{ vcpus}}'>
|
|
- {%- for id, bandwidth in nodes.items() %}
|
|
- <node id='{{ id }}' bandwidth='{{ bandwidth }}'/>
|
|
- {%- endfor %}
|
|
- </memorytune>
|
|
- {%- endfor %}
|
|
- {%- endif %}
|
|
- </cputune>
|
|
- {%- endif %}
|
|
- {%- if mem.max %}
|
|
- <maxMemory {{ opt_attribute(mem, 'slots') }} unit='KiB'>{{ to_kib(mem.max) }}</maxMemory>
|
|
- {%- endif %}
|
|
- {%- if mem.boot %}
|
|
- <memory unit='KiB'>{{ to_kib(mem.boot) }}</memory>
|
|
- {%- endif %}
|
|
- {%- if mem.current %}
|
|
- <currentMemory unit='KiB'>{{ to_kib(mem.current) }}</currentMemory>
|
|
- {%- endif %}
|
|
- {%- if mem %}
|
|
- <memtune>
|
|
- {%- if 'hard_limit' in mem and mem.hard_limit %}
|
|
- <hard_limit unit="KiB">{{ to_kib(mem.hard_limit) }}</hard_limit>
|
|
- {%- endif %}
|
|
- {%- if 'soft_limit' in mem and mem.soft_limit %}
|
|
- <soft_limit unit="KiB">{{ to_kib(mem.soft_limit) }}</soft_limit>
|
|
- {%- endif %}
|
|
- {%- if 'swap_hard_limit' in mem and mem.swap_hard_limit %}
|
|
- <swap_hard_limit unit="KiB">{{ to_kib(mem.swap_hard_limit) }}</swap_hard_limit>
|
|
- {%- endif %}
|
|
- {%- if 'min_guarantee' in mem and mem.min_guarantee %}
|
|
- <min_guarantee unit="KiB">{{ to_kib(mem.min_guarantee) }}</min_guarantee>
|
|
- {%- endif %}
|
|
- </memtune>
|
|
- {%- endif %}
|
|
- {%- if numatune %}
|
|
- <numatune>
|
|
- {%- if 'memory' in numatune and numatune.memory %}
|
|
- <memory mode='{{ numatune.memory.mode }}'
|
|
- {%- if numatune.memory.nodeset %} nodeset='{{ numatune.memory.nodeset }}'{%- endif %}
|
|
- />
|
|
- {%- endif %}
|
|
- {%- if 'memnodes' in numatune and numatune.memnodes %}
|
|
- {%- for cell_id in numatune['memnodes'] %}
|
|
- <memnode cellid='{{ cell_id }}' mode='{{ numatune.memnodes[cell_id].mode }}' nodeset='{{ numatune.memnodes[cell_id].nodeset }}'/>
|
|
- {%- endfor %}
|
|
- {%- endif %}
|
|
- </numatune>
|
|
+ <name>{{ name }}</name>
|
|
+{%- if cpu %}
|
|
+ <vcpu {{ opt_attribute(cpu, 'placement') }} {{ opt_attribute(cpu, 'cpuset') }} {{ opt_attribute(cpu, 'current') }}>{{ cpu.get('maximum', '') }}</vcpu>
|
|
+{%- endif %}
|
|
+{%- if cpu.get('vcpus') %}
|
|
+ <vcpus>
|
|
+ {%- for vcpu_id in cpu["vcpus"].keys() %}
|
|
+ <vcpu id='{{ vcpu_id }}' {{ opt_attribute(cpu.vcpus[vcpu_id], 'enabled', yesno) }} {{ opt_attribute(cpu.vcpus[vcpu_id], 'hotpluggable', yesno) }} {{ opt_attribute(cpu.vcpus[vcpu_id], 'order') }}/>
|
|
+ {%- endfor %}
|
|
+ </vcpus>
|
|
+{%- endif %}
|
|
+{%- if cpu %}
|
|
+ <cpu {{ opt_attribute(cpu, 'match') }} {{ opt_attribute(cpu, 'mode') }} {{ opt_attribute(cpu, 'check') }}>
|
|
+ {%- if cpu.model %}
|
|
+ <model {{ opt_attribute(cpu.model, 'fallback') }} {{ opt_attribute(cpu.model, 'vendor_id') }}>{{ cpu.model.get('name', '') }}</model>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.vendor %}
|
|
+ <vendor>{{ cpu.get('vendor', '') }}</vendor>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.topology %}
|
|
+ <topology {{ opt_attribute(cpu.topology, 'sockets') }} {{ opt_attribute(cpu.topology, 'dies') }} {{ opt_attribute(cpu.topology, 'cores') }} {{ opt_attribute(cpu.topology, 'threads') }}/>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.cache %}
|
|
+ <cache {{ opt_attribute(cpu.cache, 'level') }} {{ opt_attribute(cpu.cache, 'mode') }}/>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.features %}
|
|
+ {%- for k, v in cpu.features.items() %}
|
|
+ <feature policy='{{ v }}' name='{{ k }}'/>
|
|
+ {%- endfor %}
|
|
+ {%- endif %}
|
|
+ {%- if cpu.numa %}
|
|
+ <numa>
|
|
+ {%- for numa_id in cpu.numa.keys() %}
|
|
+ {%- if cpu.numa.get(numa_id) %}
|
|
+ <cell id='{{ numa_id }}' {{ opt_attribute(cpu.numa[numa_id], 'cpus') }} {{ opt_attribute(cpu.numa[numa_id], 'memory', to_kib) }} {{ opt_attribute(cpu.numa[numa_id], 'discard', yesno) }} {{ opt_attribute(cpu.numa[numa_id], 'memAccess') }}>
|
|
+ {%- if cpu.numa[numa_id].distances %}
|
|
+ <distances>
|
|
+ {%- for sibling_id in cpu.numa[numa_id].distances %}
|
|
+ <sibling id='{{ sibling_id }}' value='{{ cpu.numa[numa_id].distances[sibling_id] }}'/>
|
|
+ {%- endfor %}
|
|
+ </distances>
|
|
{%- endif %}
|
|
- {%- if mem %}
|
|
- <memoryBacking>
|
|
- {%- if mem.hugepages %}
|
|
- <hugepages>
|
|
- {%- for page in mem.hugepages %}
|
|
- <page size="{{ to_kib(page.get("size")) }}" unit="KiB"
|
|
- {%- if page.get("nodeset") or page.get("nodeset") == 0 %} nodeset='{{ page.get("nodeset") }}'{% endif -%}
|
|
- />
|
|
- {%- endfor %}
|
|
- </hugepages>
|
|
- {%- if mem.nosharepages %}
|
|
- <nosharepages/>
|
|
- {%- endif %}
|
|
- {%- if mem.locked %}
|
|
- <locked/>
|
|
- {%- endif %}
|
|
- {%- if mem.source %}
|
|
- <source type="{{ mem.source }}"/>
|
|
- {%- endif %}
|
|
- {%- if mem.access %}
|
|
- <access mode="{{ mem.access }}"/>
|
|
- {%- endif %}
|
|
- {%- if mem.allocation %}
|
|
- <allocation mode="{{ mem.allocation }}"/>
|
|
- {%- endif %}
|
|
- {%- if mem.discard %}
|
|
- <discard/>
|
|
- {%- endif %}
|
|
- {%- endif %}
|
|
- </memoryBacking>
|
|
+ </cell>
|
|
+ {%- endif %}
|
|
+ {%- endfor %}
|
|
+ </numa>
|
|
+ {%- endif %}
|
|
+ </cpu>
|
|
+ {%- if cpu.iothreads %}
|
|
+ <iothreads>{{ cpu.iothreads }}</iothreads>
|
|
+ {%- endif %}
|
|
+{%- endif %}
|
|
+{%- if cpu.tuning %}
|
|
+ <cputune>
|
|
+ {%- if cpu.tuning.vcpupin %}
|
|
+ {%- for vcpu_id, cpuset in cpu.tuning.vcpupin.items() %}
|
|
+ <vcpupin vcpu='{{ vcpu_id }}' cpuset='{{ cpuset }}'/>
|
|
+ {%- endfor %}
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.emulatorpin %}
|
|
+ <emulatorpin cpuset="{{ cpu.tuning.emulatorpin }}"/>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.iothreadpin %}
|
|
+ {%- for thread_id, cpuset in cpu.tuning.iothreadpin.items() %}
|
|
+ <iothreadpin iothread='{{ thread_id }}' cpuset='{{ cpuset }}'/>
|
|
+ {%- endfor %}
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.shares %}
|
|
+ <shares>{{ cpu.tuning.shares }}</shares>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.period %}
|
|
+ <period>{{ cpu.tuning.period }}</period>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.quota %}
|
|
+ <quota>{{ cpu.tuning.quota }}</quota>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.global_period %}
|
|
+ <global_period>{{ cpu.tuning.global_period }}</global_period>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.global_quota %}
|
|
+ <global_quota>{{ cpu.tuning.global_quota }}</global_quota>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.emulator_period %}
|
|
+ <emulator_period>{{ cpu.tuning.emulator_period }}</emulator_period>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.emulator_quota %}
|
|
+ <emulator_quota>{{ cpu.tuning.emulator_quota }}</emulator_quota>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.iothread_period %}
|
|
+ <iothread_period>{{ cpu.tuning.iothread_period }}</iothread_period>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.iothread_quota %}
|
|
+ <iothread_quota>{{ cpu.tuning.iothread_quota }}</iothread_quota>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.vcpusched %}
|
|
+ {%- for sched in cpu.tuning.vcpusched %}
|
|
+ <vcpusched scheduler='{{ sched.scheduler }}'{{ opt_attribute(sched, "vcpus") }}{{ opt_attribute(sched, "priority") }}/>
|
|
+ {%- endfor %}
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.iothreadsched %}
|
|
+ {%- for sched in cpu.tuning.iothreadsched %}
|
|
+ <iothreadsched scheduler='{{ sched.scheduler }}'{{ opt_attribute(sched, "iothreads") }}{{ opt_attribute(sched, "priority") }}/>
|
|
+ {%- endfor %}
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.emulatorsched %}
|
|
+ <emulatorsched scheduler='{{ cpu.tuning.emulatorsched.scheduler }}'{{ opt_attribute(cpu.tuning.emulatorsched, "priority") }}/>
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.cachetune %}
|
|
+ {%- for k, v in cpu.tuning.cachetune.items() %}
|
|
+ <cachetune vcpus='{{ k }}'>
|
|
+ {%- for e, atrs in v.items() %}
|
|
+ {%- if e is number and atrs %}
|
|
+ <cache id='{{ e }}' {%- for atr, val in atrs.items() %} {{ atr }}='{{ val }}' {%- endfor %} />
|
|
+ {%- elif e is not number %}
|
|
+ {%- for atr, val in atrs.items() %}
|
|
+ <monitor level='{{ val }}' vcpus='{{ atr }}'/>
|
|
+ {%- endfor %}
|
|
{%- endif %}
|
|
- <os {{ boot.os_attrib }}>
|
|
- <type arch='{{ arch }}'>{{ os_type }}</type>
|
|
- {% if boot %}
|
|
- {% if 'kernel' in boot %}
|
|
- <kernel>{{ boot.kernel }}</kernel>
|
|
- {% endif %}
|
|
- {% if 'initrd' in boot %}
|
|
- <initrd>{{ boot.initrd }}</initrd>
|
|
- {% endif %}
|
|
- {% if 'cmdline' in boot %}
|
|
- <cmdline>{{ boot.cmdline }}</cmdline>
|
|
- {% endif %}
|
|
- {% if 'loader' in boot %}
|
|
- <loader readonly='yes' type='pflash'>{{ boot.loader }}</loader>
|
|
- {% endif %}
|
|
- {% if 'nvram' in boot %}
|
|
- <nvram template='{{boot.nvram}}'></nvram>
|
|
- {% endif %}
|
|
- {% endif %}
|
|
- {% for dev in boot_dev %}
|
|
- <boot dev='{{ dev }}' />
|
|
- {% endfor %}
|
|
- </os>
|
|
+ {%- endfor %}
|
|
+ </cachetune>
|
|
+ {%- endfor %}
|
|
+ {%- endif %}
|
|
+ {%- if cpu.tuning.memorytune %}
|
|
+ {%- for vcpus, nodes in cpu.tuning.memorytune.items() %}
|
|
+ <memorytune vcpus='{{ vcpus}}'>
|
|
+ {%- for id, bandwidth in nodes.items() %}
|
|
+ <node id='{{ id }}' bandwidth='{{ bandwidth }}'/>
|
|
+ {%- endfor %}
|
|
+ </memorytune>
|
|
+ {%- endfor %}
|
|
+ {%- endif %}
|
|
+ </cputune>
|
|
+{%- endif %}
|
|
+{%- if mem.max %}
|
|
+ <maxMemory {{ opt_attribute(mem, 'slots') }} unit='KiB'>{{ to_kib(mem.max) }}</maxMemory>
|
|
+{%- endif %}
|
|
+{%- if mem.boot %}
|
|
+ <memory unit='KiB'>{{ to_kib(mem.boot) }}</memory>
|
|
+{%- endif %}
|
|
+{%- if mem.current %}
|
|
+ <currentMemory unit='KiB'>{{ to_kib(mem.current) }}</currentMemory>
|
|
+{%- endif %}
|
|
+{%- if mem %}
|
|
+ <memtune>
|
|
+ {%- if 'hard_limit' in mem and mem.hard_limit %}
|
|
+ <hard_limit unit="KiB">{{ to_kib(mem.hard_limit) }}</hard_limit>
|
|
+ {%- endif %}
|
|
+ {%- if 'soft_limit' in mem and mem.soft_limit %}
|
|
+ <soft_limit unit="KiB">{{ to_kib(mem.soft_limit) }}</soft_limit>
|
|
+ {%- endif %}
|
|
+ {%- if 'swap_hard_limit' in mem and mem.swap_hard_limit %}
|
|
+ <swap_hard_limit unit="KiB">{{ to_kib(mem.swap_hard_limit) }}</swap_hard_limit>
|
|
+ {%- endif %}
|
|
+ {%- if 'min_guarantee' in mem and mem.min_guarantee %}
|
|
+ <min_guarantee unit="KiB">{{ to_kib(mem.min_guarantee) }}</min_guarantee>
|
|
+ {%- endif %}
|
|
+ </memtune>
|
|
+{%- endif %}
|
|
+{%- if numatune %}
|
|
+ <numatune>
|
|
+ {%- if 'memory' in numatune and numatune.memory %}
|
|
+ <memory mode='{{ numatune.memory.mode }}'{{ opt_attribute(numatune.memory, "nodeset") }}/>
|
|
+ {%- endif %}
|
|
+ {%- if 'memnodes' in numatune and numatune.memnodes %}
|
|
+ {%- for cell_id in numatune['memnodes'] %}
|
|
+ <memnode cellid='{{ cell_id }}' mode='{{ numatune.memnodes[cell_id].mode }}' nodeset='{{ numatune.memnodes[cell_id].nodeset }}'/>
|
|
+ {%- endfor %}
|
|
+ {%- endif %}
|
|
+ </numatune>
|
|
+{%- endif %}
|
|
+{%- if mem %}
|
|
+ <memoryBacking>
|
|
+ {%- if mem.hugepages %}
|
|
+ <hugepages>
|
|
+ {%- for page in mem.hugepages %}
|
|
+ <page size="{{ to_kib(page.get("size")) }}" unit="KiB"{{ opt_attribute(page, "nodeset" )}}/>
|
|
+ {%- endfor %}
|
|
+ </hugepages>
|
|
+ {%- if mem.nosharepages %}
|
|
+ <nosharepages/>
|
|
+ {%- endif %}
|
|
+ {%- if mem.locked %}
|
|
+ <locked/>
|
|
+ {%- endif %}
|
|
+ {%- if mem.source %}
|
|
+ <source type="{{ mem.source }}"/>
|
|
+ {%- endif %}
|
|
+ {%- if mem.access %}
|
|
+ <access mode="{{ mem.access }}"/>
|
|
+ {%- endif %}
|
|
+ {%- if mem.allocation %}
|
|
+ <allocation mode="{{ mem.allocation }}"/>
|
|
+ {%- endif %}
|
|
+ {%- if mem.discard %}
|
|
+ <discard/>
|
|
+ {%- endif %}
|
|
+ {%- endif %}
|
|
+ </memoryBacking>
|
|
+{%- endif %}
|
|
+ <os {{ boot.os_attrib }}>
|
|
+ <type arch='{{ arch }}'>{{ os_type }}</type>
|
|
+{%- if boot %}
|
|
+ {%- if 'kernel' in boot %}
|
|
+ <kernel>{{ boot.kernel }}</kernel>
|
|
+ {%- endif %}
|
|
+ {%- if 'initrd' in boot %}
|
|
+ <initrd>{{ boot.initrd }}</initrd>
|
|
+ {%- endif %}
|
|
+ {%- if 'cmdline' in boot %}
|
|
+ <cmdline>{{ boot.cmdline }}</cmdline>
|
|
+ {%- endif %}
|
|
+ {%- if 'loader' in boot %}
|
|
+ <loader readonly='yes' type='pflash'>{{ boot.loader }}</loader>
|
|
+ {%- endif %}
|
|
+ {%- if 'nvram' in boot %}
|
|
+ <nvram template='{{boot.nvram}}'></nvram>
|
|
+ {%- endif %}
|
|
+{%- endif %}
|
|
+{%- for dev in boot_dev %}
|
|
+ <boot dev='{{ dev }}' />
|
|
+{%- endfor %}
|
|
+ </os>
|
|
{%- if clock %}
|
|
- <clock offset="{{ clock.offset }}"{{ opt_attribute(clock, "adjustment") }}{{ opt_attribute(clock, "timezone") }}>
|
|
- {%- for timer_name in clock.timers %}
|
|
+ <clock offset="{{ clock.offset }}"{{ opt_attribute(clock, "adjustment") }}{{ opt_attribute(clock, "timezone") }}>
|
|
+ {%- for timer_name in clock.timers %}
|
|
{%- set timer = clock.timers[timer_name] %}
|
|
- <timer name='{{ timer_name }}'{{ opt_attribute(timer, "track") }}{{ opt_attribute(timer, "tickpolicy") }}{{ opt_attribute(timer, "frequency") }}{{ opt_attribute(timer, "mode") }}{{ opt_attribute(timer, "present", yesno) }}>
|
|
- {%- if "threshold" in timer or "slew" in timer or "limit" in timer %}
|
|
- <catchup{{ opt_attribute(timer, "slew") }}{{ opt_attribute(timer, "threshold") }}{{ opt_attribute(timer, "limit") }}/>
|
|
- {%- endif %}
|
|
- </timer>
|
|
- {%- endfor %}
|
|
- </clock>
|
|
+ <timer name='{{ timer_name }}'{{ opt_attribute(timer, "track") }}{{ opt_attribute(timer, "tickpolicy") }}{{ opt_attribute(timer, "frequency") }}{{ opt_attribute(timer, "mode") }}{{ opt_attribute(timer, "present", yesno) }}>
|
|
+ {%- if "threshold" in timer or "slew" in timer or "limit" in timer %}
|
|
+ <catchup{{ opt_attribute(timer, "slew") }}{{ opt_attribute(timer, "threshold") }}{{ opt_attribute(timer, "limit") }}/>
|
|
+ {%- endif %}
|
|
+ </timer>
|
|
+ {%- endfor %}
|
|
+ </clock>
|
|
+{%- endif %}
|
|
+ <on_reboot>{{ on_reboot }}</on_reboot>
|
|
+ <devices>
|
|
+{%- for disk in disks %}
|
|
+ <disk type='{{ disk.type }}' device='{{ disk.device }}'>
|
|
+ {%- if disk.type == 'file' and 'source_file' in disk -%}
|
|
+ <source file='{{ disk.source_file }}' />
|
|
+ {%- endif %}
|
|
+ {%- if disk.type == 'block' -%}
|
|
+ <source dev='{{ disk.source_file }}' />
|
|
+ {%- endif %}
|
|
+ {%- if disk.type == 'volume' and 'pool' in disk -%}
|
|
+ <source pool='{{ disk.pool }}' volume='{{ disk.volume }}' />
|
|
+ {%- endif %}
|
|
+ {%- if disk.type == 'network' %}{{ libvirt_disks.network_source(disk) }}{%- endif %}
|
|
+ <target dev='{{ disk.target_dev }}' bus='{{ disk.disk_bus }}' />
|
|
+ {%- if disk.address -%}
|
|
+ <address type='drive' controller='0' bus='0' target='0' unit='{{ disk.index }}' />
|
|
+ {%- endif %}
|
|
+ {%- if disk.driver -%}
|
|
+ <driver name='qemu' type='{{ disk.format}}' cache='none' io='{{ disk.io }}'{{ opt_attribute(disk, "iothread") }}/>
|
|
+ {%- endif %}
|
|
+ </disk>
|
|
+{%- endfor %}
|
|
+{%- if controller_model %}
|
|
+ <controller type='scsi' index='0' model='{{ controller_model }}' />
|
|
+{%- endif %}
|
|
+{%- for nic in nics %}
|
|
+ <interface type='{{ nic.type }}'>
|
|
+ <source {{ nic.type }}='{{ nic.source }}'/>
|
|
+ {%- if nic.get('mac') -%}
|
|
+ <mac address='{{ nic.mac }}'/>
|
|
+ {%- endif %}
|
|
+ {%- if nic.model %}<model type='{{ nic.model }}'/>{% endif %}
|
|
+ </interface>
|
|
+{%- endfor %}
|
|
+{%- if graphics %}
|
|
+ <graphics type='{{ graphics.type }}'{{ opt_attribute(graphics, "port") }}
|
|
+ {%- if graphics.listen.address %}
|
|
+ listen='{{ graphics.listen.address }}'
|
|
+ {%- endif %}
|
|
+ {%- if graphics.type == 'spice' and graphics.tls_port %}
|
|
+ tlsPort='{{ graphics.tls_port }}'
|
|
+ {%- endif %}
|
|
+ autoport='{{ yesno(not graphics.port and not graphics.tls_port) }}'>
|
|
+ <listen type='{{ graphics.listen.type }}'{{ opt_attribute(graphics.listen, "address") }}/>
|
|
+ </graphics>
|
|
+ {%- if graphics.type == "spice" %}
|
|
+ <channel type='spicevmc'>
|
|
+ <target type='virtio' name='com.redhat.spice.0'/>
|
|
+ </channel>
|
|
+ {%- endif %}
|
|
{%- endif %}
|
|
- <on_reboot>{{ on_reboot }}</on_reboot>
|
|
- <devices>
|
|
- {% for disk in disks %}
|
|
- <disk type='{{ disk.type }}' device='{{ disk.device }}'>
|
|
- {% if disk.type == 'file' and 'source_file' in disk -%}
|
|
- <source file='{{ disk.source_file }}' />
|
|
- {% endif %}
|
|
- {% if disk.type == 'block' -%}
|
|
- <source dev='{{ disk.source_file }}' />
|
|
- {% endif %}
|
|
- {% if disk.type == 'volume' and 'pool' in disk -%}
|
|
- <source pool='{{ disk.pool }}' volume='{{ disk.volume }}' />
|
|
- {% endif %}
|
|
- {%- if disk.type == 'network' %}{{ libvirt_disks.network_source(disk) }}{%- endif %}
|
|
- <target dev='{{ disk.target_dev }}' bus='{{ disk.disk_bus }}' />
|
|
- {% if disk.address -%}
|
|
- <address type='drive' controller='0' bus='0' target='0' unit='{{ disk.index }}' />
|
|
- {% endif %}
|
|
- {% if disk.driver -%}
|
|
- <driver name='qemu' type='{{ disk.format}}' cache='none' io='{{ disk.io }}'/>
|
|
- {% endif %}
|
|
- </disk>
|
|
- {% endfor %}
|
|
-
|
|
- {% if controller_model %}
|
|
- <controller type='scsi' index='0' model='{{ controller_model }}' />
|
|
- {% endif %}
|
|
-
|
|
- {% for nic in nics %}
|
|
- <interface type='{{ nic.type }}'>
|
|
- <source {{ nic.type }}='{{ nic.source }}'/>
|
|
- {% if nic.get('mac') -%}
|
|
- <mac address='{{ nic.mac }}'/>
|
|
- {%- endif %}
|
|
- {% if nic.model %}<model type='{{ nic.model }}'/>{% endif %}
|
|
- </interface>
|
|
- {% endfor %}
|
|
- {% if graphics %}
|
|
- <graphics type='{{ graphics.type }}'
|
|
- {% if graphics.listen.address %}
|
|
- listen='{{ graphics.listen.address }}'
|
|
- {% endif %}
|
|
- {% if graphics.port %}
|
|
- port='{{ graphics.port }}'
|
|
- {% endif %}
|
|
- {% if graphics.type == 'spice' and graphics.tls_port %}
|
|
- tlsPort='{{ graphics.tls_port }}'
|
|
- {% endif %}
|
|
- autoport='{{ 'no' if graphics.port or graphics.tls_port else 'yes' }}'>
|
|
- <listen type='{{ graphics.listen.type }}'
|
|
- {% if graphics.listen.address %}
|
|
- address='{{ graphics.listen.address }}'
|
|
- {% endif %}/>
|
|
- </graphics>
|
|
-
|
|
- {% if graphics.type == "spice" -%}
|
|
- <channel type='spicevmc'>
|
|
- <target type='virtio' name='com.redhat.spice.0'/>
|
|
- </channel>
|
|
- {%- endif %}
|
|
- {% endif %}
|
|
-
|
|
- {%- for serial in serials %}
|
|
- <serial type='{{ serial.type }}'>
|
|
- {{ libvirt_chardevs.chardev(serial) }}
|
|
- </serial>
|
|
- {%- endfor %}
|
|
-
|
|
- {%- for console in consoles %}
|
|
- <console type='{{ console.type }}'>
|
|
- {{ libvirt_chardevs.chardev(console) }}
|
|
- </console>
|
|
- {% endfor %}
|
|
+{%- for serial in serials %}
|
|
+ <serial type='{{ serial.type }}'>
|
|
+ {{ libvirt_chardevs.chardev(serial) }}
|
|
+ </serial>
|
|
+{%- endfor %}
|
|
+{%- for console in consoles %}
|
|
+ <console type='{{ console.type }}'>
|
|
+ {{ libvirt_chardevs.chardev(console) }}
|
|
+ </console>
|
|
+{%- endfor %}
|
|
{%- if hypervisor in ["qemu", "kvm"] %}
|
|
- <channel type='unix'>
|
|
- <target type='virtio' name='org.qemu.guest_agent.0'/>
|
|
- </channel>
|
|
+ <channel type='unix'>
|
|
+ <target type='virtio' name='org.qemu.guest_agent.0'/>
|
|
+ </channel>
|
|
{%- endif %}
|
|
- </devices>
|
|
- <features>
|
|
- <acpi />
|
|
- <apic />
|
|
- <pae />
|
|
+{%- for hostdev in hostdevs %}
|
|
+ <hostdev mode='subsystem' type='{{ hostdev["type"] }}'{% if hostdev["type"] == "pci" %} managed='yes'{% endif %}>
|
|
+ <source>
|
|
+ {%- if hostdev["type"] == "usb" %}
|
|
+ <vendor id='{{ hostdev["vendor"] }}'/>
|
|
+ <product id='{{ hostdev["product"] }}'/>
|
|
+ {%- elif hostdev["type"] == "pci" %}
|
|
+ <address
|
|
+ domain='{{ hostdev["domain"] }}'
|
|
+ bus='{{ hostdev["bus"] }}'
|
|
+ slot='{{ hostdev["slot"] }}'
|
|
+ function='{{ hostdev["function"] }}'/>
|
|
+ {%- endif %}
|
|
+ </source>
|
|
+ </hostdev>
|
|
+{%- endfor %}
|
|
+ </devices>
|
|
+ <features>
|
|
+ <acpi />
|
|
+ <apic />
|
|
+ <pae />
|
|
{%- if hypervisor_features.get("kvm-hint-dedicated") %}
|
|
- <kvm>
|
|
- <hint-dedicated state="on"/>
|
|
- </kvm>
|
|
+ <kvm>
|
|
+ <hint-dedicated state="on"/>
|
|
+ </kvm>
|
|
{%- endif %}
|
|
- </features>
|
|
+ </features>
|
|
</domain>
|
|
diff --git a/salt/templates/virt/libvirt_macros.jinja b/salt/templates/virt/libvirt_macros.jinja
|
|
new file mode 100644
|
|
index 0000000000..d2e2fc213d
|
|
--- /dev/null
|
|
+++ b/salt/templates/virt/libvirt_macros.jinja
|
|
@@ -0,0 +1,3 @@
|
|
+{%- macro opt_attribute(obj, name, conv=none) %}
|
|
+{%- if obj.get(name) is not none %} {{ name }}='{{ obj[name] if conv is none else conv(obj[name]) }}'{% endif -%}
|
|
+{%- endmacro %}
|
|
diff --git a/salt/templates/virt/libvirt_network.jinja b/salt/templates/virt/libvirt_network.jinja
|
|
index 2f11e64559..ab14408712 100644
|
|
--- a/salt/templates/virt/libvirt_network.jinja
|
|
+++ b/salt/templates/virt/libvirt_network.jinja
|
|
@@ -1,20 +1,98 @@
|
|
+{%- from 'libvirt_macros.jinja' import opt_attribute as opt_attribute -%}
|
|
<network>
|
|
<name>{{ name }}</name>
|
|
+{%- if bridge %}
|
|
<bridge name='{{ bridge }}'/>
|
|
- <forward mode='{{ forward }}'/>{% if vport != None %}
|
|
- <virtualport type='{{ vport }}'/>{% endif %}{% if tag != None %}
|
|
- <vlan>
|
|
- <tag id='{{ tag }}'/>
|
|
- </vlan>{% endif %}
|
|
- {% for ip_config in ip_configs %}
|
|
+{%- endif %}
|
|
+{%- if mtu %}
|
|
+ <mtu size='{{ mtu }}'/>
|
|
+{%- endif %}
|
|
+{%- if domain %}
|
|
+ <domain name='{{ domain.name }}'{{ opt_attribute(domain, "localOnly", yesno) }}/>
|
|
+{%- endif %}
|
|
+{%- if forward %}
|
|
+ <forward mode='{{ forward }}'{% if forward == 'hostdev' %} managed='yes'{% endif %}>
|
|
+{%- endif %}
|
|
+{%- if nat %}
|
|
+ <nat>
|
|
+ {%- if nat.address %}
|
|
+ <address start='{{ nat.address.start }}' end='{{ nat.address.end }}'/>
|
|
+ {%- endif %}
|
|
+ {%- if nat.port %}
|
|
+ <port start='{{ nat.port.start }}' end='{{ nat.port.end }}'/>
|
|
+ {%- endif %}
|
|
+ </nat>
|
|
+{%- endif %}
|
|
+{%- for iface in interfaces %}
|
|
+ <interface dev='{{ iface }}'/>
|
|
+{%- endfor %}
|
|
+{%- for addr in addresses %}
|
|
+ <address type='pci' domain='0x{{ addr.domain }}' bus='0x{{ addr.bus }}' slot='0x{{ addr.slot }}' function='0x{{ addr.function }}'/>
|
|
+{%- endfor %}
|
|
+{%- if pf %}
|
|
+ <pf dev='{{ pf }}'/>
|
|
+{%- endif %}
|
|
+{%- if forward %}
|
|
+ </forward>
|
|
+{%- endif %}
|
|
+{%- if vport %}
|
|
+ <virtualport type='{{ vport.type }}'>
|
|
+ {%- if vport.parameters %}
|
|
+ <parameters{%- for atr, val in vport.parameters.items() %} {{ atr }}='{{ val }}' {%- endfor %}/>
|
|
+ {%- endif %}
|
|
+ </virtualport>
|
|
+{%- endif %}
|
|
+{%- if vlan %}
|
|
+ <vlan{{ opt_attribute(vlan, "trunk", yesno) }}>
|
|
+ {%- for tag in vlan.tags %}
|
|
+ <tag id='{{ tag.id }}'{{ opt_attribute(tag, "nativeMode") }}/>
|
|
+ {%- endfor %}
|
|
+ </vlan>
|
|
+{%- endif %}
|
|
+{%- if dns %}
|
|
+ <dns>
|
|
+ {%- for forwarder in dns.forwarders %}
|
|
+ <forwarder{{ opt_attribute(forwarder, "domain") }}{{ opt_attribute(forwarder, "addr") }}/>
|
|
+ {%- endfor %}
|
|
+ {%- for key in dns.txt.keys()|sort %}
|
|
+ <txt name='{{ key }}' value='{{ dns.txt[key] }}'/>
|
|
+ {%- endfor %}
|
|
+ {%- for ip in dns.hosts.keys()|sort %}
|
|
+ <host ip='{{ ip }}'>
|
|
+ {%- for hostname in dns.hosts[ip] %}
|
|
+ <hostname>{{ hostname }}</hostname>
|
|
+ {%- endfor %}
|
|
+ </host>
|
|
+ {%- endfor %}
|
|
+ {%- for srv in dns.srvs %}
|
|
+ <srv name='{{ srv.name }}' protocol='{{ srv.protocol }}'
|
|
+ {{ opt_attribute(srv, "port") }}
|
|
+ {{ opt_attribute(srv, "target") }}
|
|
+ {{ opt_attribute(srv, "priority") }}
|
|
+ {{ opt_attribute(srv, "weight") }}
|
|
+ {{ opt_attribute(srv, "domain") }}/>
|
|
+ {%- endfor %}
|
|
+ </dns>
|
|
+{%- endif %}
|
|
+{%- for ip_config in ip_configs %}
|
|
<ip family='ipv{{ ip_config.address.version }}'
|
|
- address='{{ ip_config.address.network_address }}'
|
|
+ address='{{ ip_config.address.hosts()|first }}'
|
|
prefix='{{ ip_config.address.prefixlen }}'>
|
|
<dhcp>
|
|
- {% for range in ip_config.dhcp_ranges %}
|
|
+ {%- for range in ip_config.dhcp_ranges %}
|
|
<range start='{{ range.start }}' end='{{ range.end }}' />
|
|
- {% endfor %}
|
|
+ {%- endfor %}
|
|
+ {%- for ip in ip_config.hosts.keys()|sort %}
|
|
+ {%- set host = ip_config.hosts[ip] %}
|
|
+ <host ip='{{ ip }}'{{ opt_attribute(host, 'mac') }}{{ opt_attribute(host, 'id') }}{{ opt_attribute(host, 'name') }}/>
|
|
+ {%- endfor %}
|
|
+ {%- if ip_config.bootp %}
|
|
+ <bootp file='{{ ip_config.bootp.file }}'{{ opt_attribute(ip_config.bootp, "server") }}/>
|
|
+ {%- endif %}
|
|
</dhcp>
|
|
+ {%- if ip_config.tftp %}
|
|
+ <tftp root='{{ ip_config.tftp }}'/>
|
|
+ {%- endif %}
|
|
</ip>
|
|
- {% endfor %}
|
|
+{%- endfor %}
|
|
</network>
|
|
diff --git a/salt/utils/xmlutil.py b/salt/utils/xmlutil.py
|
|
index 5c187ca7e5..c91c3f6275 100644
|
|
--- a/salt/utils/xmlutil.py
|
|
+++ b/salt/utils/xmlutil.py
|
|
@@ -380,3 +380,32 @@ def change_xml(doc, data, mapping):
|
|
deleted = del_fn(parent_map, node)
|
|
need_update = need_update or deleted
|
|
return need_update
|
|
+
|
|
+
|
|
+def strip_spaces(node):
|
|
+ """
|
|
+ Remove all spaces and line breaks before and after nodes.
|
|
+ This helps comparing XML trees.
|
|
+
|
|
+ :param node: the XML node to remove blanks from
|
|
+ :return: the node
|
|
+ """
|
|
+
|
|
+ if node.tail is not None:
|
|
+ node.tail = node.tail.strip(" \t\n")
|
|
+ if node.text is not None:
|
|
+ node.text = node.text.strip(" \t\n")
|
|
+ try:
|
|
+ for child in node:
|
|
+ strip_spaces(child)
|
|
+ except RecursionError:
|
|
+ raise Exception("Failed to recurse on the node")
|
|
+
|
|
+ return node
|
|
+
|
|
+
|
|
+def element_to_str(node):
|
|
+ """
|
|
+ Serialize an XML node into a string
|
|
+ """
|
|
+ return salt.utils.stringutils.to_str(ElementTree.tostring(node))
|
|
diff --git a/tests/conftest.py b/tests/conftest.py
|
|
index 6922f626f8..27df1f272d 100644
|
|
--- a/tests/conftest.py
|
|
+++ b/tests/conftest.py
|
|
@@ -27,10 +27,10 @@ import _pytest.logging
|
|
import _pytest.skipping
|
|
import psutil
|
|
import pytest
|
|
+import salt._logging.impl
|
|
import salt.config
|
|
import salt.loader
|
|
import salt.log.mixins
|
|
-import salt.log.setup
|
|
import salt.utils.files
|
|
import salt.utils.path
|
|
import salt.utils.platform
|
|
diff --git a/tests/pytests/functional/modules/test_opkg.py b/tests/pytests/functional/modules/test_opkg.py
|
|
index 4e1d2f9c20..8b5a690de8 100644
|
|
--- a/tests/pytests/functional/modules/test_opkg.py
|
|
+++ b/tests/pytests/functional/modules/test_opkg.py
|
|
@@ -8,14 +8,12 @@ from tests.support.mock import patch
|
|
pytestmark = pytest.mark.skip_if_binaries_missing("stat", "md5sum", "uname")
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {
|
|
opkg: {"__salt__": {"cmd.shell": cmd.shell, "cmd.run_stdout": cmd.run_stdout}},
|
|
cmd: {},
|
|
}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
|
|
|
|
def test_conf_d_path_does_not_exist_not_created_by_restart_check(tmp_path):
|
|
diff --git a/tests/pytests/unit/beacons/test_sensehat.py b/tests/pytests/unit/beacons/test_sensehat.py
|
|
index 501cb1c69b..b4b964b443 100644
|
|
--- a/tests/pytests/unit/beacons/test_sensehat.py
|
|
+++ b/tests/pytests/unit/beacons/test_sensehat.py
|
|
@@ -3,9 +3,9 @@ import salt.beacons.sensehat as sensehat
|
|
from tests.support.mock import MagicMock
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {
|
|
sensehat: {
|
|
"__salt__": {
|
|
"sensehat.get_humidity": MagicMock(return_value=80),
|
|
@@ -14,8 +14,6 @@ def setup_loader():
|
|
},
|
|
}
|
|
}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
|
|
|
|
def test_non_list_config():
|
|
diff --git a/tests/pytests/unit/beacons/test_status.py b/tests/pytests/unit/beacons/test_status.py
|
|
index bb32253c3e..6c010ddd80 100644
|
|
--- a/tests/pytests/unit/beacons/test_status.py
|
|
+++ b/tests/pytests/unit/beacons/test_status.py
|
|
@@ -10,16 +10,14 @@ import salt.modules.status as status_module
|
|
from salt.beacons import status
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {
|
|
status: {
|
|
"__salt__": pytest.helpers.salt_loader_module_functions(status_module)
|
|
},
|
|
status_module: {"__grains__": {"kernel": "Linux"}, "__salt__": {}},
|
|
}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
|
|
|
|
def test_empty_config():
|
|
diff --git a/tests/pytests/unit/modules/test_alternatives.py b/tests/pytests/unit/modules/test_alternatives.py
|
|
index 49c6c5e415..aa05c3f0f4 100644
|
|
--- a/tests/pytests/unit/modules/test_alternatives.py
|
|
+++ b/tests/pytests/unit/modules/test_alternatives.py
|
|
@@ -4,11 +4,9 @@ from tests.support.helpers import TstSuiteLoggingHandler
|
|
from tests.support.mock import MagicMock, patch
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {alternatives: {}}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {alternatives: {}}
|
|
|
|
|
|
def test_display():
|
|
diff --git a/tests/pytests/unit/modules/test_ansiblegate.py b/tests/pytests/unit/modules/test_ansiblegate.py
|
|
index ca5a6ab1ef..42c0968a6e 100644
|
|
--- a/tests/pytests/unit/modules/test_ansiblegate.py
|
|
+++ b/tests/pytests/unit/modules/test_ansiblegate.py
|
|
@@ -1,4 +1,3 @@
|
|
-#
|
|
# Author: Bo Maryniuk <bo@suse.de>
|
|
|
|
|
|
@@ -17,6 +16,11 @@ pytestmark = pytest.mark.skipif(
|
|
)
|
|
|
|
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {ansible: {}}
|
|
+
|
|
+
|
|
@pytest.fixture
|
|
def resolver():
|
|
_resolver = ansible.AnsibleModuleResolver({})
|
|
@@ -28,13 +32,6 @@ def resolver():
|
|
return _resolver
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {ansible: {}}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
-
|
|
-
|
|
def test_ansible_module_help(resolver):
|
|
"""
|
|
Test help extraction from the module
|
|
diff --git a/tests/pytests/unit/modules/test_archive.py b/tests/pytests/unit/modules/test_archive.py
|
|
index c2a7f24d1d..a4dfca8c84 100644
|
|
--- a/tests/pytests/unit/modules/test_archive.py
|
|
+++ b/tests/pytests/unit/modules/test_archive.py
|
|
@@ -18,11 +18,9 @@ class ZipFileMock(MagicMock):
|
|
return self._files
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {archive: {"__grains__": {"id": 0}}}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {archive: {"__grains__": {"id": 0}}}
|
|
|
|
|
|
def test_tar():
|
|
diff --git a/tests/pytests/unit/modules/test_azurearm_dns.py b/tests/pytests/unit/modules/test_azurearm_dns.py
|
|
index de096915a1..d1f42a60d7 100644
|
|
--- a/tests/pytests/unit/modules/test_azurearm_dns.py
|
|
+++ b/tests/pytests/unit/modules/test_azurearm_dns.py
|
|
@@ -109,8 +109,8 @@ def credentials():
|
|
}
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
"""
|
|
setup loader modules and override the azurearm.get_client utility
|
|
"""
|
|
@@ -120,11 +120,9 @@ def setup_loader():
|
|
minion_config, utils=utils, whitelist=["azurearm_dns", "config"]
|
|
)
|
|
utils["azurearm.get_client"] = AzureClientMock()
|
|
- setup_loader_modules = {
|
|
+ return {
|
|
azurearm_dns: {"__utils__": utils, "__salt__": funcs},
|
|
}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
|
|
|
|
def test_record_set_create_or_update(credentials):
|
|
diff --git a/tests/pytests/unit/modules/test_nilrt_ip.py b/tests/pytests/unit/modules/test_nilrt_ip.py
|
|
index adf08531dd..3e4bd414e9 100644
|
|
--- a/tests/pytests/unit/modules/test_nilrt_ip.py
|
|
+++ b/tests/pytests/unit/modules/test_nilrt_ip.py
|
|
@@ -5,11 +5,9 @@ import salt.modules.nilrt_ip as nilrt_ip
|
|
from tests.support.mock import patch
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {nilrt_ip: {}}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {nilrt_ip: {}}
|
|
|
|
|
|
@pytest.fixture
|
|
diff --git a/tests/pytests/unit/modules/test_opkg.py b/tests/pytests/unit/modules/test_opkg.py
|
|
index e5817eef38..7fd12015e5 100644
|
|
--- a/tests/pytests/unit/modules/test_opkg.py
|
|
+++ b/tests/pytests/unit/modules/test_opkg.py
|
|
@@ -3,11 +3,9 @@ import salt.modules.opkg as opkg
|
|
from tests.support.mock import patch
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {opkg: {}}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {opkg: {}}
|
|
|
|
|
|
def test_when_os_is_NILinuxRT_and_creation_of_RESTART_CHECK_STATE_PATH_fails_virtual_should_be_False():
|
|
diff --git a/tests/pytests/unit/modules/test_restartcheck.py b/tests/pytests/unit/modules/test_restartcheck.py
|
|
index b0c55dd0fe..8b4dc01bca 100644
|
|
--- a/tests/pytests/unit/modules/test_restartcheck.py
|
|
+++ b/tests/pytests/unit/modules/test_restartcheck.py
|
|
@@ -8,11 +8,9 @@ import salt.modules.systemd_service as service
|
|
from tests.support.mock import create_autospec, patch
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {restartcheck: {}}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {restartcheck: {}}
|
|
|
|
|
|
def test_when_timestamp_file_does_not_exist_then_file_changed_nilrt_should_be_True():
|
|
diff --git a/tests/pytests/unit/modules/test_slackware_service.py b/tests/pytests/unit/modules/test_slackware_service.py
|
|
index 047582e668..2fe38c5232 100644
|
|
--- a/tests/pytests/unit/modules/test_slackware_service.py
|
|
+++ b/tests/pytests/unit/modules/test_slackware_service.py
|
|
@@ -8,6 +8,11 @@ import salt.modules.slackware_service as slackware_service
|
|
from tests.support.mock import MagicMock, patch
|
|
|
|
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {slackware_service: {}}
|
|
+
|
|
+
|
|
@pytest.fixture
|
|
def mocked_rcd():
|
|
glob_output = [
|
|
@@ -39,13 +44,6 @@ def mocked_rcd():
|
|
return glob_mock, os_path_exists_mock, os_access_mock
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {slackware_service: {}}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
-
|
|
-
|
|
def test_get_all_rc_services_minus_system_and_config_files(mocked_rcd):
|
|
"""
|
|
In Slackware, the services are started, stopped, enabled or disabled
|
|
diff --git a/tests/pytests/unit/modules/test_swarm.py b/tests/pytests/unit/modules/test_swarm.py
|
|
index e474f89f62..6259d0bd17 100644
|
|
--- a/tests/pytests/unit/modules/test_swarm.py
|
|
+++ b/tests/pytests/unit/modules/test_swarm.py
|
|
@@ -16,15 +16,13 @@ pytestmark = pytest.mark.skipif(
|
|
)
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {swarm: {"__context__": {}}}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {swarm: {"__context__": {}}}
|
|
|
|
|
|
@pytest.fixture
|
|
-def fake_context_client():
|
|
+def fake_context_client(setup_loader_mock):
|
|
fake_swarm_client = MagicMock()
|
|
patch_context = patch.dict(
|
|
swarm.__context__, {"client": fake_swarm_client, "server_name": "test swarm"}
|
|
diff --git a/tests/pytests/unit/modules/test_tls.py b/tests/pytests/unit/modules/test_tls.py
|
|
index d7e79d91ad..a1db1930ee 100644
|
|
--- a/tests/pytests/unit/modules/test_tls.py
|
|
+++ b/tests/pytests/unit/modules/test_tls.py
|
|
@@ -5,6 +5,11 @@ import salt.modules.tls as tls
|
|
from tests.support.mock import MagicMock, patch
|
|
|
|
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {tls: {}}
|
|
+
|
|
+
|
|
@pytest.fixture(scope="module")
|
|
def tls_test_data():
|
|
return {
|
|
@@ -23,13 +28,6 @@ def tls_test_data():
|
|
}
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {tls: {}}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
-
|
|
-
|
|
def test_create_ca_permissions_on_cert_and_key(tmpdir, tls_test_data):
|
|
ca_name = "test_ca"
|
|
certp = tmpdir.join(ca_name).join("{}_ca_cert.crt".format(ca_name)).strpath
|
|
diff --git a/tests/pytests/unit/modules/virt/conftest.py b/tests/pytests/unit/modules/virt/conftest.py
|
|
index ec56bdff24..3bacd734a7 100644
|
|
--- a/tests/pytests/unit/modules/virt/conftest.py
|
|
+++ b/tests/pytests/unit/modules/virt/conftest.py
|
|
@@ -43,32 +43,29 @@ class MappedResultMock(MagicMock):
|
|
|
|
super().__init__(side_effect=mapped_results)
|
|
|
|
- def add(self, name):
|
|
- self._instances[name] = MagicMock()
|
|
+ def add(self, name, value=None):
|
|
+ self._instances[name] = value or MagicMock()
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader(request):
|
|
+def loader_modules_config():
|
|
# Create libvirt mock and connection mock
|
|
mock_libvirt = LibvirtMock()
|
|
mock_conn = MagicMock()
|
|
mock_conn.getStoragePoolCapabilities.return_value = "<storagepoolCapabilities/>"
|
|
|
|
mock_libvirt.openAuth.return_value = mock_conn
|
|
- setup_loader_modules = {
|
|
+ return {
|
|
virt: {
|
|
"libvirt": mock_libvirt,
|
|
"__salt__": {"config.get": config.get, "config.option": config.option},
|
|
},
|
|
config: {},
|
|
}
|
|
- with pytest.helpers.loader_mock(request, setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
|
|
|
|
@pytest.fixture
|
|
def make_mock_vm():
|
|
- def _make_mock_vm(xml_def):
|
|
+ def _make_mock_vm(xml_def, running=False, inactive_def=None):
|
|
mocked_conn = virt.libvirt.openAuth.return_value
|
|
|
|
doc = ET.fromstring(xml_def)
|
|
@@ -81,17 +78,21 @@ def make_mock_vm():
|
|
mocked_conn.listDefinedDomains.return_value = [name]
|
|
|
|
# Configure the mocked domain
|
|
- domain_mock = virt.libvirt.virDomain()
|
|
if not isinstance(mocked_conn.lookupByName, MappedResultMock):
|
|
mocked_conn.lookupByName = MappedResultMock()
|
|
mocked_conn.lookupByName.add(name)
|
|
domain_mock = mocked_conn.lookupByName(name)
|
|
- domain_mock.XMLDesc.return_value = xml_def
|
|
+
|
|
+ domain_mock.XMLDesc = MappedResultMock()
|
|
+ domain_mock.XMLDesc.add(0, xml_def)
|
|
+ domain_mock.XMLDesc.add(
|
|
+ virt.libvirt.VIR_DOMAIN_XML_INACTIVE, inactive_def or xml_def
|
|
+ )
|
|
domain_mock.OSType.return_value = os_type
|
|
|
|
# Return state as shutdown
|
|
domain_mock.info.return_value = [
|
|
- 4,
|
|
+ 0 if running else 4,
|
|
2048 * 1024,
|
|
1024 * 1024,
|
|
2,
|
|
@@ -103,6 +104,8 @@ def make_mock_vm():
|
|
domain_mock.attachDevice.return_value = 0
|
|
domain_mock.detachDevice.return_value = 0
|
|
|
|
+ domain_mock.connect.return_value = mocked_conn
|
|
+
|
|
return domain_mock
|
|
|
|
return _make_mock_vm
|
|
@@ -315,3 +318,66 @@ def make_capabilities():
|
|
</capabilities>"""
|
|
|
|
return _make_capabilities
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def make_mock_network():
|
|
+ def _make_mock_net(xml_def):
|
|
+ mocked_conn = virt.libvirt.openAuth.return_value
|
|
+
|
|
+ doc = ET.fromstring(xml_def)
|
|
+ name = doc.find("name").text
|
|
+
|
|
+ if not isinstance(mocked_conn.networkLookupByName, MappedResultMock):
|
|
+ mocked_conn.networkLookupByName = MappedResultMock()
|
|
+ mocked_conn.networkLookupByName.add(name)
|
|
+ net_mock = mocked_conn.networkLookupByName(name)
|
|
+ net_mock.XMLDesc.return_value = xml_def
|
|
+
|
|
+ # libvirt defaults the autostart to unset
|
|
+ net_mock.autostart.return_value = 0
|
|
+
|
|
+ # Append the network to listAllNetworks return value
|
|
+ all_nets = mocked_conn.listAllNetworks.return_value
|
|
+ if not isinstance(all_nets, list):
|
|
+ all_nets = []
|
|
+ all_nets.append(net_mock)
|
|
+ mocked_conn.listAllNetworks.return_value = all_nets
|
|
+
|
|
+ return net_mock
|
|
+
|
|
+ return _make_mock_net
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def make_mock_device():
|
|
+ """
|
|
+ Create a mock host device
|
|
+ """
|
|
+
|
|
+ def _make_mock_device(xml_def):
|
|
+ mocked_conn = virt.libvirt.openAuth.return_value
|
|
+ if not isinstance(mocked_conn.nodeDeviceLookupByName, MappedResultMock):
|
|
+ mocked_conn.nodeDeviceLookupByName = MappedResultMock()
|
|
+
|
|
+ doc = ET.fromstring(xml_def)
|
|
+ name = doc.find("./name").text
|
|
+
|
|
+ mocked_conn.nodeDeviceLookupByName.add(name)
|
|
+ mocked_device = mocked_conn.nodeDeviceLookupByName(name)
|
|
+ mocked_device.name.return_value = name
|
|
+ mocked_device.XMLDesc.return_value = xml_def
|
|
+ mocked_device.listCaps.return_value = [
|
|
+ cap.get("type") for cap in doc.findall("./capability")
|
|
+ ]
|
|
+ return mocked_device
|
|
+
|
|
+ return _make_mock_device
|
|
+
|
|
+
|
|
+@pytest.fixture(params=[True, False], ids=["test", "notest"])
|
|
+def test(request):
|
|
+ """
|
|
+ Run the test with both True and False test values
|
|
+ """
|
|
+ return request.param
|
|
diff --git a/tests/pytests/unit/modules/virt/test_domain.py b/tests/pytests/unit/modules/virt/test_domain.py
|
|
index 347c3bcd88..0bde881403 100644
|
|
--- a/tests/pytests/unit/modules/virt/test_domain.py
|
|
+++ b/tests/pytests/unit/modules/virt/test_domain.py
|
|
@@ -1,8 +1,16 @@
|
|
+import pytest
|
|
import salt.modules.virt as virt
|
|
+import salt.utils.xmlutil as xmlutil
|
|
from salt._compat import ElementTree as ET
|
|
from tests.support.mock import MagicMock, patch
|
|
|
|
-from .test_helpers import append_to_XMLDesc
|
|
+from .conftest import loader_modules_config
|
|
+from .test_helpers import append_to_XMLDesc, assert_called, strip_xml
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return loader_modules_config()
|
|
|
|
|
|
def test_update_xen_disk_volumes(make_mock_vm, make_mock_storage_pool):
|
|
@@ -589,3 +597,466 @@ def test_init_stop_on_reboot(make_capabilities):
|
|
define_mock = virt.libvirt.openAuth().defineXML
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
assert "destroy" == setxml.find("./on_reboot").text
|
|
+
|
|
+
|
|
+def test_init_hostdev_usb(make_capabilities, make_mock_device):
|
|
+ """
|
|
+ Test virt.init with USB host device passed through
|
|
+ """
|
|
+ make_capabilities()
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>usb_3_1_3</name>
|
|
+ <path>/sys/devices/pci0000:00/0000:00:1d.6/0000:06:00.0/0000:07:02.0/0000:3e:00.0/usb3/3-1/3-1.3</path>
|
|
+ <devnode type='dev'>/dev/bus/usb/003/004</devnode>
|
|
+ <parent>usb_3_1</parent>
|
|
+ <driver>
|
|
+ <name>usb</name>
|
|
+ </driver>
|
|
+ <capability type='usb_device'>
|
|
+ <bus>3</bus>
|
|
+ <device>4</device>
|
|
+ <product id='0x6006'>AUKEY PC-LM1E Camera</product>
|
|
+ <vendor id='0x0458'>KYE Systems Corp. (Mouse Systems)</vendor>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ )
|
|
+ with patch.dict(virt.os.__dict__, {"chmod": MagicMock(), "makedirs": MagicMock()}):
|
|
+ with patch.dict(virt.__salt__, {"cmd.run": MagicMock()}):
|
|
+ virt.init("test_vm", 2, 2048, host_devices=["usb_3_1_3"], start=False)
|
|
+ define_mock = virt.libvirt.openAuth().defineXML
|
|
+ setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
+ expected_xml = strip_xml(
|
|
+ """
|
|
+ <hostdev mode='subsystem' type='usb'>
|
|
+ <source>
|
|
+ <vendor id='0x0458'/>
|
|
+ <product id='0x6006'/>
|
|
+ </source>
|
|
+ </hostdev>
|
|
+ """
|
|
+ )
|
|
+ assert expected_xml == strip_xml(
|
|
+ ET.tostring(setxml.find("./devices/hostdev"))
|
|
+ )
|
|
+
|
|
+
|
|
+def test_init_hostdev_pci(make_capabilities, make_mock_device):
|
|
+ """
|
|
+ Test virt.init with PCI host device passed through
|
|
+ """
|
|
+ make_capabilities()
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>pci_1002_71c4</name>
|
|
+ <parent>pci_8086_27a1</parent>
|
|
+ <capability type='pci'>
|
|
+ <class>0xffffff</class>
|
|
+ <domain>0</domain>
|
|
+ <bus>1</bus>
|
|
+ <slot>0</slot>
|
|
+ <function>0</function>
|
|
+ <product id='0x71c4'>M56GL [Mobility FireGL V5200]</product>
|
|
+ <vendor id='0x1002'>ATI Technologies Inc</vendor>
|
|
+ <numa node='1'/>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ )
|
|
+ with patch.dict(virt.os.__dict__, {"chmod": MagicMock(), "makedirs": MagicMock()}):
|
|
+ with patch.dict(virt.__salt__, {"cmd.run": MagicMock()}):
|
|
+ virt.init("test_vm", 2, 2048, host_devices=["pci_1002_71c4"], start=False)
|
|
+ define_mock = virt.libvirt.openAuth().defineXML
|
|
+ setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
+ expected_xml = strip_xml(
|
|
+ """
|
|
+ <hostdev mode='subsystem' type='pci' managed='yes'>
|
|
+ <source>
|
|
+ <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
|
|
+ </source>
|
|
+ </hostdev>
|
|
+ """
|
|
+ )
|
|
+ assert expected_xml == strip_xml(
|
|
+ ET.tostring(setxml.find("./devices/hostdev"))
|
|
+ )
|
|
+
|
|
+
|
|
+def test_update_hostdev_nochange(make_mock_device, make_mock_vm):
|
|
+ """
|
|
+ Test the virt.update function with no host device changes
|
|
+ """
|
|
+ xml_def = """
|
|
+ <domain type='kvm'>
|
|
+ <name>my_vm</name>
|
|
+ <memory unit='KiB'>524288</memory>
|
|
+ <currentMemory unit='KiB'>524288</currentMemory>
|
|
+ <vcpu placement='static'>1</vcpu>
|
|
+ <os>
|
|
+ <type arch='x86_64'>hvm</type>
|
|
+ </os>
|
|
+ <on_reboot>restart</on_reboot>
|
|
+ <devices>
|
|
+ <hostdev mode='subsystem' type='pci' managed='yes'>
|
|
+ <source>
|
|
+ <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
|
|
+ </source>
|
|
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
+ </hostdev>
|
|
+ <hostdev mode='subsystem' type='usb' managed='no'>
|
|
+ <source>
|
|
+ <vendor id='0x0458'/>
|
|
+ <product id='0x6006'/>
|
|
+ <address bus='3' device='4'/>
|
|
+ </source>
|
|
+ <alias name='hostdev0'/>
|
|
+ <address type='usb' bus='0' port='1'/>
|
|
+ </hostdev>
|
|
+ </devices>
|
|
+ </domain>"""
|
|
+ domain_mock = make_mock_vm(xml_def)
|
|
+
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>usb_3_1_3</name>
|
|
+ <path>/sys/devices/pci0000:00/0000:00:1d.6/0000:06:00.0/0000:07:02.0/0000:3e:00.0/usb3/3-1/3-1.3</path>
|
|
+ <devnode type='dev'>/dev/bus/usb/003/004</devnode>
|
|
+ <parent>usb_3_1</parent>
|
|
+ <driver>
|
|
+ <name>usb</name>
|
|
+ </driver>
|
|
+ <capability type='usb_device'>
|
|
+ <bus>3</bus>
|
|
+ <device>4</device>
|
|
+ <product id='0x6006'>AUKEY PC-LM1E Camera</product>
|
|
+ <vendor id='0x0458'>KYE Systems Corp. (Mouse Systems)</vendor>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ )
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>pci_1002_71c4</name>
|
|
+ <parent>pci_8086_27a1</parent>
|
|
+ <capability type='pci'>
|
|
+ <class>0xffffff</class>
|
|
+ <domain>0</domain>
|
|
+ <bus>1</bus>
|
|
+ <slot>0</slot>
|
|
+ <function>0</function>
|
|
+ <product id='0x71c4'>M56GL [Mobility FireGL V5200]</product>
|
|
+ <vendor id='0x1002'>ATI Technologies Inc</vendor>
|
|
+ <numa node='1'/>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ )
|
|
+
|
|
+ ret = virt.update("my_vm", host_devices=["pci_1002_71c4", "usb_3_1_3"])
|
|
+
|
|
+ assert not ret["definition"]
|
|
+ define_mock = virt.libvirt.openAuth().defineXML
|
|
+ define_mock.assert_not_called()
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize(
|
|
+ "running,live",
|
|
+ [(False, False), (True, False), (True, True)],
|
|
+ ids=["stopped, no live", "running, no live", "running, live"],
|
|
+)
|
|
+def test_update_hostdev_changes(running, live, make_mock_device, make_mock_vm, test):
|
|
+ """
|
|
+ Test the virt.update function with host device changes
|
|
+ """
|
|
+ xml_def = """
|
|
+ <domain type='kvm'>
|
|
+ <name>my_vm</name>
|
|
+ <memory unit='KiB'>524288</memory>
|
|
+ <currentMemory unit='KiB'>524288</currentMemory>
|
|
+ <vcpu placement='static'>1</vcpu>
|
|
+ <os>
|
|
+ <type arch='x86_64'>hvm</type>
|
|
+ </os>
|
|
+ <on_reboot>restart</on_reboot>
|
|
+ <devices>
|
|
+ <hostdev mode='subsystem' type='pci' managed='yes'>
|
|
+ <source>
|
|
+ <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
|
|
+ </source>
|
|
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
+ </hostdev>
|
|
+ </devices>
|
|
+ </domain>"""
|
|
+ domain_mock = make_mock_vm(xml_def, running)
|
|
+
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>usb_3_1_3</name>
|
|
+ <path>/sys/devices/pci0000:00/0000:00:1d.6/0000:06:00.0/0000:07:02.0/0000:3e:00.0/usb3/3-1/3-1.3</path>
|
|
+ <devnode type='dev'>/dev/bus/usb/003/004</devnode>
|
|
+ <parent>usb_3_1</parent>
|
|
+ <driver>
|
|
+ <name>usb</name>
|
|
+ </driver>
|
|
+ <capability type='usb_device'>
|
|
+ <bus>3</bus>
|
|
+ <device>4</device>
|
|
+ <product id='0x6006'>AUKEY PC-LM1E Camera</product>
|
|
+ <vendor id='0x0458'>KYE Systems Corp. (Mouse Systems)</vendor>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ )
|
|
+
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>pci_1002_71c4</name>
|
|
+ <parent>pci_8086_27a1</parent>
|
|
+ <capability type='pci'>
|
|
+ <class>0xffffff</class>
|
|
+ <domain>0</domain>
|
|
+ <bus>1</bus>
|
|
+ <slot>0</slot>
|
|
+ <function>0</function>
|
|
+ <product id='0x71c4'>M56GL [Mobility FireGL V5200]</product>
|
|
+ <vendor id='0x1002'>ATI Technologies Inc</vendor>
|
|
+ <numa node='1'/>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ )
|
|
+
|
|
+ ret = virt.update("my_vm", host_devices=["usb_3_1_3"], test=test, live=live)
|
|
+ define_mock = virt.libvirt.openAuth().defineXML
|
|
+ assert_called(define_mock, not test)
|
|
+
|
|
+ # Test that the XML is updated with the proper devices
|
|
+ usb_device_xml = strip_xml(
|
|
+ """
|
|
+ <hostdev mode="subsystem" type="usb">
|
|
+ <source>
|
|
+ <vendor id="0x0458" />
|
|
+ <product id="0x6006" />
|
|
+ </source>
|
|
+ </hostdev>
|
|
+ """
|
|
+ )
|
|
+ if not test:
|
|
+ set_xml = ET.fromstring(define_mock.call_args[0][0])
|
|
+ actual_hostdevs = [
|
|
+ ET.tostring(xmlutil.strip_spaces(node))
|
|
+ for node in set_xml.findall("./devices/hostdev")
|
|
+ ]
|
|
+ assert [usb_device_xml] == actual_hostdevs
|
|
+
|
|
+ if not test and live:
|
|
+ attach_xml = strip_xml(domain_mock.attachDevice.call_args[0][0])
|
|
+ assert usb_device_xml == attach_xml
|
|
+
|
|
+ pci_device_xml = strip_xml(
|
|
+ """
|
|
+ <hostdev mode='subsystem' type='pci' managed='yes'>
|
|
+ <source>
|
|
+ <address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
|
|
+ </source>
|
|
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
+ </hostdev>
|
|
+ """
|
|
+ )
|
|
+ detach_xml = strip_xml(domain_mock.detachDevice.call_args[0][0])
|
|
+ assert pci_device_xml == detach_xml
|
|
+ else:
|
|
+ domain_mock.attachDevice.assert_not_called()
|
|
+ domain_mock.detachDevice.assert_not_called()
|
|
+
|
|
+
|
|
+def test_diff_nics():
|
|
+ """
|
|
+ Test virt._diff_nics()
|
|
+ """
|
|
+ old_nics = ET.fromstring(
|
|
+ """
|
|
+ <devices>
|
|
+ <interface type='network'>
|
|
+ <mac address='52:54:00:39:02:b1'/>
|
|
+ <source network='default'/>
|
|
+ <model type='virtio'/>
|
|
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
+ </interface>
|
|
+ <interface type='network'>
|
|
+ <mac address='52:54:00:39:02:b2'/>
|
|
+ <source network='admin'/>
|
|
+ <model type='virtio'/>
|
|
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
+ </interface>
|
|
+ <interface type='network'>
|
|
+ <mac address='52:54:00:39:02:b3'/>
|
|
+ <source network='admin'/>
|
|
+ <model type='virtio'/>
|
|
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
+ </interface>
|
|
+ </devices>
|
|
+ """
|
|
+ ).findall("interface")
|
|
+
|
|
+ new_nics = ET.fromstring(
|
|
+ """
|
|
+ <devices>
|
|
+ <interface type='network'>
|
|
+ <mac address='52:54:00:39:02:b1'/>
|
|
+ <source network='default'/>
|
|
+ <model type='virtio'/>
|
|
+ </interface>
|
|
+ <interface type='network'>
|
|
+ <mac address='52:54:00:39:02:b2'/>
|
|
+ <source network='default'/>
|
|
+ <model type='virtio'/>
|
|
+ </interface>
|
|
+ <interface type='network'>
|
|
+ <mac address='52:54:00:39:02:b4'/>
|
|
+ <source network='admin'/>
|
|
+ <model type='virtio'/>
|
|
+ </interface>
|
|
+ </devices>
|
|
+ """
|
|
+ ).findall("interface")
|
|
+ ret = virt._diff_interface_lists(old_nics, new_nics)
|
|
+ assert ["52:54:00:39:02:b1"] == [
|
|
+ nic.find("mac").get("address") for nic in ret["unchanged"]
|
|
+ ]
|
|
+ assert ["52:54:00:39:02:b2", "52:54:00:39:02:b4"] == [
|
|
+ nic.find("mac").get("address") for nic in ret["new"]
|
|
+ ]
|
|
+ assert ["52:54:00:39:02:b2", "52:54:00:39:02:b3"] == [
|
|
+ nic.find("mac").get("address") for nic in ret["deleted"]
|
|
+ ]
|
|
+
|
|
+
|
|
+def test_diff_nics_live_nochange():
|
|
+ """
|
|
+ Libvirt alters the NICs of network type when running the guest, test the virt._diff_nics()
|
|
+ function with no change in such a case.
|
|
+ """
|
|
+ old_nics = ET.fromstring(
|
|
+ """
|
|
+ <devices>
|
|
+ <interface type='direct'>
|
|
+ <mac address='52:54:00:03:02:15'/>
|
|
+ <source network='test-vepa' portid='8377df4f-7c72-45f3-9ba4-a76306333396' dev='eth1' mode='vepa'/>
|
|
+ <target dev='macvtap0'/>
|
|
+ <model type='virtio'/>
|
|
+ <alias name='net0'/>
|
|
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x05' function='0x0'/>
|
|
+ </interface>
|
|
+ <interface type='bridge'>
|
|
+ <mac address='52:54:00:ea:2e:89'/>
|
|
+ <source network='default' portid='b97ec5b7-25fd-4697-ae45-06af8cc1a964' bridge='br0'/>
|
|
+ <target dev='vnet0'/>
|
|
+ <model type='virtio'/>
|
|
+ <alias name='net0'/>
|
|
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
+ </interface>
|
|
+ </devices>
|
|
+ """
|
|
+ ).findall("interface")
|
|
+
|
|
+ new_nics = ET.fromstring(
|
|
+ """
|
|
+ <devices>
|
|
+ <interface type='network'>
|
|
+ <source network='test-vepa'/>
|
|
+ <model type='virtio'/>
|
|
+ </interface>
|
|
+ <interface type='network'>
|
|
+ <source network='default'/>
|
|
+ <model type='virtio'/>
|
|
+ </interface>
|
|
+ </devices>
|
|
+ """
|
|
+ )
|
|
+ ret = virt._diff_interface_lists(old_nics, new_nics)
|
|
+ assert ["52:54:00:03:02:15", "52:54:00:ea:2e:89"] == [
|
|
+ nic.find("mac").get("address") for nic in ret["unchanged"]
|
|
+ ]
|
|
+
|
|
+
|
|
+def test_update_nic_hostdev_nochange(make_mock_network, make_mock_vm, test):
|
|
+ """
|
|
+ Test the virt.update function with a running host with hostdev nic
|
|
+ """
|
|
+ xml_def_template = """
|
|
+ <domain type='kvm'>
|
|
+ <name>my_vm</name>
|
|
+ <memory unit='KiB'>524288</memory>
|
|
+ <currentMemory unit='KiB'>524288</currentMemory>
|
|
+ <vcpu placement='static'>1</vcpu>
|
|
+ <os>
|
|
+ <type arch='x86_64'>hvm</type>
|
|
+ </os>
|
|
+ <on_reboot>restart</on_reboot>
|
|
+ <devices>
|
|
+ {}
|
|
+ </devices>
|
|
+ </domain>
|
|
+ """
|
|
+ inactive_nic = """
|
|
+ <interface type='hostdev' managed='yes'>
|
|
+ <mac address='52:54:00:67:b2:08'/>
|
|
+ <driver name='vfio'/>
|
|
+ <source network="test-hostdev"/>
|
|
+ <model type='virtio'/>
|
|
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
+ </interface>
|
|
+ """
|
|
+ running_nic = """
|
|
+ <interface type='hostdev' managed='yes'>
|
|
+ <mac address='52:54:00:67:b2:08'/>
|
|
+ <driver name='vfio'/>
|
|
+ <source>
|
|
+ <address type='pci' domain='0x0000' bus='0x3d' slot='0x02' function='0x0'/>
|
|
+ </source>
|
|
+ <model type='virtio'/>
|
|
+ <alias name='hostdev0'/>
|
|
+ <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
+ </interface>
|
|
+ """
|
|
+ domain_mock = make_mock_vm(
|
|
+ xml_def_template.format(running_nic),
|
|
+ running="running",
|
|
+ inactive_def=xml_def_template.format(inactive_nic),
|
|
+ )
|
|
+
|
|
+ make_mock_network(
|
|
+ """
|
|
+ <network connections='1'>
|
|
+ <name>test-hostdev</name>
|
|
+ <uuid>51d0aaa5-7530-4c60-8498-5bc3ab8c655b</uuid>
|
|
+ <forward mode='hostdev' managed='yes'>
|
|
+ <pf dev='eth0'/>
|
|
+ <address type='pci' domain='0x0000' bus='0x3d' slot='0x02' function='0x0'/>
|
|
+ <address type='pci' domain='0x0000' bus='0x3d' slot='0x02' function='0x1'/>
|
|
+ </forward>
|
|
+ </network>
|
|
+ """
|
|
+ )
|
|
+
|
|
+ ret = virt.update(
|
|
+ "my_vm",
|
|
+ interfaces=[{"name": "eth0", "type": "network", "source": "test-hostdev"}],
|
|
+ test=test,
|
|
+ live=True,
|
|
+ )
|
|
+ assert not ret.get("definition")
|
|
+ assert not ret.get("interface").get("attached")
|
|
+ assert not ret.get("interface").get("detached")
|
|
+ define_mock = virt.libvirt.openAuth().defineXML
|
|
+ define_mock.assert_not_called()
|
|
+ domain_mock.attachDevice.assert_not_called()
|
|
+ domain_mock.detachDevice.assert_not_called()
|
|
diff --git a/tests/pytests/unit/modules/virt/test_helpers.py b/tests/pytests/unit/modules/virt/test_helpers.py
|
|
index f64aee2821..5410f45603 100644
|
|
--- a/tests/pytests/unit/modules/virt/test_helpers.py
|
|
+++ b/tests/pytests/unit/modules/virt/test_helpers.py
|
|
@@ -1,3 +1,4 @@
|
|
+import salt.utils.xmlutil as xmlutil
|
|
from salt._compat import ElementTree as ET
|
|
|
|
|
|
@@ -9,3 +10,27 @@ def append_to_XMLDesc(mocked, fragment):
|
|
xml_fragment = ET.fromstring(fragment)
|
|
xml_doc.append(xml_fragment)
|
|
mocked.XMLDesc.return_value = ET.tostring(xml_doc)
|
|
+
|
|
+
|
|
+def assert_xml_equals(expected, actual):
|
|
+ """
|
|
+ Assert that two ElementTree nodes are equal
|
|
+ """
|
|
+ assert xmlutil.to_dict(xmlutil.strip_spaces(expected), True) == xmlutil.to_dict(
|
|
+ xmlutil.strip_spaces(actual), True
|
|
+ )
|
|
+
|
|
+
|
|
+def strip_xml(xml_str):
|
|
+ """
|
|
+ Remove all spaces and formatting from an XML string
|
|
+ """
|
|
+ return ET.tostring(xmlutil.strip_spaces(ET.fromstring(xml_str)))
|
|
+
|
|
+
|
|
+def assert_called(mock, condition):
|
|
+ """
|
|
+ Assert that the mock has been called if not in test mode, and vice-versa.
|
|
+ I know it's a simple XOR, but makes the tests easier to read
|
|
+ """
|
|
+ assert not condition and not mock.called or condition and mock.called
|
|
diff --git a/tests/pytests/unit/modules/virt/test_host.py b/tests/pytests/unit/modules/virt/test_host.py
|
|
new file mode 100644
|
|
index 0000000000..555deb23bb
|
|
--- /dev/null
|
|
+++ b/tests/pytests/unit/modules/virt/test_host.py
|
|
@@ -0,0 +1,219 @@
|
|
+import pytest
|
|
+import salt.modules.virt as virt
|
|
+
|
|
+from .conftest import loader_modules_config
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return loader_modules_config()
|
|
+
|
|
+
|
|
+def test_node_devices(make_mock_device):
|
|
+ """
|
|
+ Test the virt.node_devices() function
|
|
+ """
|
|
+ mock_devs = [
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>pci_1002_71c4</name>
|
|
+ <parent>pci_8086_27a1</parent>
|
|
+ <capability type='pci'>
|
|
+ <class>0xffffff</class>
|
|
+ <domain>0</domain>
|
|
+ <bus>1</bus>
|
|
+ <slot>0</slot>
|
|
+ <function>0</function>
|
|
+ <product id='0x71c4'>M56GL [Mobility FireGL V5200]</product>
|
|
+ <vendor id='0x1002'>ATI Technologies Inc</vendor>
|
|
+ <numa node='1'/>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ ),
|
|
+ # Linux USB hub to be ignored
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>usb_device_1d6b_1_0000_00_1d_0</name>
|
|
+ <parent>pci_8086_27c8</parent>
|
|
+ <capability type='usb_device'>
|
|
+ <bus>2</bus>
|
|
+ <device>1</device>
|
|
+ <product id='0x0001'>1.1 root hub</product>
|
|
+ <vendor id='0x1d6b'>Linux Foundation</vendor>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ ),
|
|
+ # SR-IOV PCI device with multiple capabilities
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>pci_0000_02_10_7</name>
|
|
+ <parent>pci_0000_00_04_0</parent>
|
|
+ <capability type='pci'>
|
|
+ <domain>0</domain>
|
|
+ <bus>2</bus>
|
|
+ <slot>16</slot>
|
|
+ <function>7</function>
|
|
+ <product id='0x10ca'>82576 Virtual Function</product>
|
|
+ <vendor id='0x8086'>Intel Corporation</vendor>
|
|
+ <capability type='phys_function'>
|
|
+ <address domain='0x0000' bus='0x02' slot='0x00' function='0x1'/>
|
|
+ </capability>
|
|
+ <capability type='virt_functions' maxCount='7'>
|
|
+ <address domain='0x0000' bus='0x02' slot='0x00' function='0x2'/>
|
|
+ <address domain='0x0000' bus='0x02' slot='0x00' function='0x3'/>
|
|
+ <address domain='0x0000' bus='0x02' slot='0x00' function='0x4'/>
|
|
+ <address domain='0x0000' bus='0x02' slot='0x00' function='0x5'/>
|
|
+ </capability>
|
|
+ <iommuGroup number='31'>
|
|
+ <address domain='0x0000' bus='0x02' slot='0x10' function='0x7'/>
|
|
+ </iommuGroup>
|
|
+ <numa node='0'/>
|
|
+ <pci-express>
|
|
+ <link validity='cap' port='0' speed='2.5' width='4'/>
|
|
+ <link validity='sta' width='0'/>
|
|
+ </pci-express>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ ),
|
|
+ # PCI bridge to be ignored
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>pci_0000_00_1c_0</name>
|
|
+ <parent>computer</parent>
|
|
+ <capability type='pci'>
|
|
+ <class>0xffffff</class>
|
|
+ <domain>0</domain>
|
|
+ <bus>0</bus>
|
|
+ <slot>28</slot>
|
|
+ <function>0</function>
|
|
+ <product id='0x8c10'>8 Series/C220 Series Chipset Family PCI Express Root Port #1</product>
|
|
+ <vendor id='0x8086'>Intel Corporation</vendor>
|
|
+ <capability type='pci-bridge'/>
|
|
+ <iommuGroup number='8'>
|
|
+ <address domain='0x0000' bus='0x00' slot='0x1c' function='0x0'/>
|
|
+ </iommuGroup>
|
|
+ <pci-express>
|
|
+ <link validity='cap' port='1' speed='5' width='1'/>
|
|
+ <link validity='sta' speed='2.5' width='1'/>
|
|
+ </pci-express>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ ),
|
|
+ # Other device to be ignored
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>mdev_3627463d_b7f0_4fea_b468_f1da537d301b</name>
|
|
+ <parent>computer</parent>
|
|
+ <capability type='mdev'>
|
|
+ <type id='mtty-1'/>
|
|
+ <iommuGroup number='12'/>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ ),
|
|
+ # USB device to be listed
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>usb_3_1_3</name>
|
|
+ <path>/sys/devices/pci0000:00/0000:00:1d.6/0000:06:00.0/0000:07:02.0/0000:3e:00.0/usb3/3-1/3-1.3</path>
|
|
+ <devnode type='dev'>/dev/bus/usb/003/004</devnode>
|
|
+ <parent>usb_3_1</parent>
|
|
+ <driver>
|
|
+ <name>usb</name>
|
|
+ </driver>
|
|
+ <capability type='usb_device'>
|
|
+ <bus>3</bus>
|
|
+ <device>4</device>
|
|
+ <product id='0x6006'>AUKEY PC-LM1E Camera</product>
|
|
+ <vendor id='0x0458'>KYE Systems Corp. (Mouse Systems)</vendor>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ ),
|
|
+ # Network device to be listed
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>net_eth8_e6_86_48_46_c5_29</name>
|
|
+ <path>/sys/devices/pci0000:3a/0000:3a:00.0/0000:3b:00.0/0000:3c:03.0/0000:3d:02.2/net/eth8</path>
|
|
+ <parent>pci_0000_02_10_7</parent>
|
|
+ <capability type='net'>
|
|
+ <interface>eth8</interface>
|
|
+ <address>e6:86:48:46:c5:29</address>
|
|
+ <link state='down'/>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ ),
|
|
+ # Network device to be ignored
|
|
+ make_mock_device(
|
|
+ """
|
|
+ <device>
|
|
+ <name>net_lo_00_00_00_00_00_00</name>
|
|
+ <path>/sys/devices/virtual/net/lo</path>
|
|
+ <parent>computer</parent>
|
|
+ <capability type='net'>
|
|
+ <interface>lo</interface>
|
|
+ <address>00:00:00:00:00:00</address>
|
|
+ <link state='unknown'/>
|
|
+ </capability>
|
|
+ </device>
|
|
+ """
|
|
+ ),
|
|
+ ]
|
|
+ virt.libvirt.openAuth().listAllDevices.return_value = mock_devs
|
|
+
|
|
+ assert [
|
|
+ {
|
|
+ "name": "pci_1002_71c4",
|
|
+ "caps": "pci",
|
|
+ "vendor_id": "0x1002",
|
|
+ "vendor": "ATI Technologies Inc",
|
|
+ "product_id": "0x71c4",
|
|
+ "product": "M56GL [Mobility FireGL V5200]",
|
|
+ "address": "0000:01:00.0",
|
|
+ "PCI class": "0xffffff",
|
|
+ },
|
|
+ {
|
|
+ "name": "pci_0000_02_10_7",
|
|
+ "caps": "pci",
|
|
+ "vendor_id": "0x8086",
|
|
+ "vendor": "Intel Corporation",
|
|
+ "product_id": "0x10ca",
|
|
+ "product": "82576 Virtual Function",
|
|
+ "address": "0000:02:10.7",
|
|
+ "physical function": "0000:02:00.1",
|
|
+ "virtual functions": [
|
|
+ "0000:02:00.2",
|
|
+ "0000:02:00.3",
|
|
+ "0000:02:00.4",
|
|
+ "0000:02:00.5",
|
|
+ ],
|
|
+ },
|
|
+ {
|
|
+ "name": "usb_3_1_3",
|
|
+ "caps": "usb_device",
|
|
+ "vendor": "KYE Systems Corp. (Mouse Systems)",
|
|
+ "vendor_id": "0x0458",
|
|
+ "product": "AUKEY PC-LM1E Camera",
|
|
+ "product_id": "0x6006",
|
|
+ "address": "003:004",
|
|
+ },
|
|
+ {
|
|
+ "name": "eth8",
|
|
+ "caps": "net",
|
|
+ "address": "e6:86:48:46:c5:29",
|
|
+ "state": "down",
|
|
+ "device name": "pci_0000_02_10_7",
|
|
+ },
|
|
+ ] == virt.node_devices()
|
|
diff --git a/tests/pytests/unit/modules/virt/test_network.py b/tests/pytests/unit/modules/virt/test_network.py
|
|
new file mode 100644
|
|
index 0000000000..e7e544c580
|
|
--- /dev/null
|
|
+++ b/tests/pytests/unit/modules/virt/test_network.py
|
|
@@ -0,0 +1,450 @@
|
|
+import pytest
|
|
+import salt.modules.virt as virt
|
|
+import salt.utils.xmlutil as xmlutil
|
|
+from salt._compat import ElementTree as ET
|
|
+
|
|
+from .conftest import loader_modules_config
|
|
+from .test_helpers import assert_called, assert_xml_equals, strip_xml
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return loader_modules_config()
|
|
+
|
|
+
|
|
+def test_gen_xml():
|
|
+ """
|
|
+ Test virt._get_net_xml()
|
|
+ """
|
|
+ xml_data = virt._gen_net_xml("network", "main", "bridge", "openvswitch")
|
|
+ root = ET.fromstring(xml_data)
|
|
+ assert "network" == root.find("name").text
|
|
+ assert "main" == root.find("bridge").attrib["name"]
|
|
+ assert "bridge" == root.find("forward").attrib["mode"]
|
|
+ assert "openvswitch" == root.find("virtualport").attrib["type"]
|
|
+
|
|
+
|
|
+def test_gen_xml_nat():
|
|
+ """
|
|
+ Test virt._get_net_xml() in a nat setup
|
|
+ """
|
|
+ xml_data = virt._gen_net_xml(
|
|
+ "network",
|
|
+ "main",
|
|
+ "nat",
|
|
+ None,
|
|
+ ip_configs=[
|
|
+ {
|
|
+ "cidr": "192.168.2.0/24",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
+ {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
+ ],
|
|
+ "hosts": {
|
|
+ "192.168.2.10": {
|
|
+ "mac": "00:16:3e:77:e2:ed",
|
|
+ "name": "foo.example.com",
|
|
+ },
|
|
+ },
|
|
+ "bootp": {"file": "pxeboot.img", "server": "192.168.2.1"},
|
|
+ "tftp": "/path/to/tftp",
|
|
+ },
|
|
+ {
|
|
+ "cidr": "2001:db8:ca2:2::/64",
|
|
+ "hosts": {
|
|
+ "2001:db8:ca2:2:3::1": {"name": "paul"},
|
|
+ "2001:db8:ca2:2:3::2": {
|
|
+ "id": "0:3:0:1:0:16:3e:11:22:33",
|
|
+ "name": "ralph",
|
|
+ },
|
|
+ },
|
|
+ },
|
|
+ ],
|
|
+ nat={
|
|
+ "address": {"start": "1.2.3.4", "end": "1.2.3.10"},
|
|
+ "port": {"start": 500, "end": 1000},
|
|
+ },
|
|
+ domain={"name": "acme.lab", "localOnly": True},
|
|
+ mtu=9000,
|
|
+ )
|
|
+ root = ET.fromstring(xml_data)
|
|
+ assert "network" == root.find("name").text
|
|
+ assert "main" == root.find("bridge").attrib["name"]
|
|
+ assert "nat" == root.find("forward").attrib["mode"]
|
|
+ expected_ipv4 = ET.fromstring(
|
|
+ """
|
|
+ <ip family='ipv4' address='192.168.2.1' prefix='24'>
|
|
+ <dhcp>
|
|
+ <range start='192.168.2.10' end='192.168.2.25'/>
|
|
+ <range start='192.168.2.110' end='192.168.2.125'/>
|
|
+ <host ip='192.168.2.10' mac='00:16:3e:77:e2:ed' name='foo.example.com'/>
|
|
+ <bootp file='pxeboot.img' server='192.168.2.1'/>
|
|
+ </dhcp>
|
|
+ <tftp root='/path/to/tftp'/>
|
|
+ </ip>
|
|
+ """
|
|
+ )
|
|
+ assert_xml_equals(expected_ipv4, root.find("./ip[@address='192.168.2.1']"))
|
|
+
|
|
+ expected_ipv6 = ET.fromstring(
|
|
+ """
|
|
+ <ip family='ipv6' address='2001:db8:ca2:2::1' prefix='64'>
|
|
+ <dhcp>
|
|
+ <host ip='2001:db8:ca2:2:3::1' name='paul'/>
|
|
+ <host ip='2001:db8:ca2:2:3::2' id='0:3:0:1:0:16:3e:11:22:33' name='ralph'/>
|
|
+ </dhcp>
|
|
+ </ip>
|
|
+ """
|
|
+ )
|
|
+ assert_xml_equals(expected_ipv6, root.find("./ip[@address='2001:db8:ca2:2::1']"))
|
|
+
|
|
+ actual_nat = ET.tostring(xmlutil.strip_spaces(root.find("./forward/nat")))
|
|
+ expected_nat = strip_xml(
|
|
+ """
|
|
+ <nat>
|
|
+ <address start='1.2.3.4' end='1.2.3.10'/>
|
|
+ <port start='500' end='1000'/>
|
|
+ </nat>
|
|
+ """
|
|
+ )
|
|
+ assert expected_nat == actual_nat
|
|
+
|
|
+ assert {"name": "acme.lab", "localOnly": "yes"} == root.find("./domain").attrib
|
|
+ assert "9000" == root.find("mtu").get("size")
|
|
+
|
|
+
|
|
+def test_gen_xml_dns():
|
|
+ """
|
|
+ Test virt._get_net_xml() with DNS configuration
|
|
+ """
|
|
+ xml_data = virt._gen_net_xml(
|
|
+ "network",
|
|
+ "main",
|
|
+ "nat",
|
|
+ None,
|
|
+ ip_configs=[
|
|
+ {
|
|
+ "cidr": "192.168.2.0/24",
|
|
+ "dhcp_ranges": [{"start": "192.168.2.10", "end": "192.168.2.25"}],
|
|
+ }
|
|
+ ],
|
|
+ dns={
|
|
+ "forwarders": [
|
|
+ {"domain": "example.com", "addr": "192.168.1.1"},
|
|
+ {"addr": "8.8.8.8"},
|
|
+ {"domain": "www.example.com"},
|
|
+ ],
|
|
+ "txt": {
|
|
+ "host.widgets.com.": "printer=lpr5",
|
|
+ "example.com.": "reserved for doc",
|
|
+ },
|
|
+ "hosts": {"192.168.1.2": ["mirror.acme.lab", "test.acme.lab"]},
|
|
+ "srvs": [
|
|
+ {
|
|
+ "name": "srv1",
|
|
+ "protocol": "tcp",
|
|
+ "domain": "test-domain-name",
|
|
+ "target": ".",
|
|
+ "port": 1024,
|
|
+ "priority": 10,
|
|
+ "weight": 10,
|
|
+ },
|
|
+ {"name": "srv2", "protocol": "udp"},
|
|
+ ],
|
|
+ },
|
|
+ )
|
|
+ root = ET.fromstring(xml_data)
|
|
+ expected_xml = ET.fromstring(
|
|
+ """
|
|
+ <dns>
|
|
+ <forwarder domain='example.com' addr='192.168.1.1'/>
|
|
+ <forwarder addr='8.8.8.8'/>
|
|
+ <forwarder domain='www.example.com'/>
|
|
+ <txt name='example.com.' value='reserved for doc'/>
|
|
+ <txt name='host.widgets.com.' value='printer=lpr5'/>
|
|
+ <host ip='192.168.1.2'>
|
|
+ <hostname>mirror.acme.lab</hostname>
|
|
+ <hostname>test.acme.lab</hostname>
|
|
+ </host>
|
|
+ <srv name='srv1' protocol='tcp' port='1024' target='.' priority='10' weight='10' domain='test-domain-name'/>
|
|
+ <srv name='srv2' protocol='udp'/>
|
|
+ </dns>
|
|
+ """
|
|
+ )
|
|
+ assert_xml_equals(expected_xml, root.find("./dns"))
|
|
+
|
|
+
|
|
+def test_gen_xml_isolated():
|
|
+ """
|
|
+ Test the virt._gen_net_xml() function for an isolated network
|
|
+ """
|
|
+ xml_data = virt._gen_net_xml("network", "main", None, None)
|
|
+ assert ET.fromstring(xml_data).find("forward") is None
|
|
+
|
|
+
|
|
+def test_gen_xml_passthrough_interfaces():
|
|
+ """
|
|
+ Test the virt._gen_net_xml() function for a passthrough forward mode
|
|
+ """
|
|
+ xml_data = virt._gen_net_xml(
|
|
+ "network", "virbr0", "passthrough", None, interfaces="eth10 eth11 eth12",
|
|
+ )
|
|
+ root = ET.fromstring(xml_data)
|
|
+ assert "passthrough" == root.find("forward").get("mode")
|
|
+ assert ["eth10", "eth11", "eth12"] == [
|
|
+ n.get("dev") for n in root.findall("forward/interface")
|
|
+ ]
|
|
+
|
|
+
|
|
+def test_gen_xml_hostdev_addresses():
|
|
+ """
|
|
+ Test the virt._gen_net_xml() function for a hostdev forward mode with PCI addresses
|
|
+ """
|
|
+ xml_data = virt._gen_net_xml(
|
|
+ "network", "virbr0", "hostdev", None, addresses="0000:04:00.1 0000:e3:01.2",
|
|
+ )
|
|
+ root = ET.fromstring(xml_data)
|
|
+ expected_forward = ET.fromstring(
|
|
+ """
|
|
+ <forward mode='hostdev' managed='yes'>
|
|
+ <address type='pci' domain='0x0000' bus='0x04' slot='0x00' function='0x1'/>
|
|
+ <address type='pci' domain='0x0000' bus='0xe3' slot='0x01' function='0x2'/>
|
|
+ </forward>
|
|
+ """
|
|
+ )
|
|
+ assert_xml_equals(expected_forward, root.find("./forward"))
|
|
+
|
|
+
|
|
+def test_gen_xml_hostdev_pf():
|
|
+ """
|
|
+ Test the virt._gen_net_xml() function for a hostdev forward mode with physical function
|
|
+ """
|
|
+ xml_data = virt._gen_net_xml(
|
|
+ "network", "virbr0", "hostdev", None, physical_function="eth0"
|
|
+ )
|
|
+ root = ET.fromstring(xml_data)
|
|
+ expected_forward = strip_xml(
|
|
+ """
|
|
+ <forward mode='hostdev' managed='yes'>
|
|
+ <pf dev='eth0'/>
|
|
+ </forward>
|
|
+ """
|
|
+ )
|
|
+ actual_forward = ET.tostring(xmlutil.strip_spaces(root.find("./forward")))
|
|
+ assert expected_forward == actual_forward
|
|
+
|
|
+
|
|
+def test_gen_xml_openvswitch():
|
|
+ """
|
|
+ Test the virt._gen_net_xml() function for an openvswitch setup with virtualport and vlan
|
|
+ """
|
|
+ xml_data = virt._gen_net_xml(
|
|
+ "network",
|
|
+ "ovsbr0",
|
|
+ "bridge",
|
|
+ {
|
|
+ "type": "openvswitch",
|
|
+ "parameters": {"interfaceid": "09b11c53-8b5c-4eeb-8f00-d84eaa0aaa4f"},
|
|
+ },
|
|
+ tag={
|
|
+ "trunk": True,
|
|
+ "tags": [{"id": 42, "nativeMode": "untagged"}, {"id": 47}],
|
|
+ },
|
|
+ )
|
|
+ expected_xml = ET.fromstring(
|
|
+ """
|
|
+ <network>
|
|
+ <name>network</name>
|
|
+ <bridge name='ovsbr0'/>
|
|
+ <forward mode='bridge'/>
|
|
+ <virtualport type='openvswitch'>
|
|
+ <parameters interfaceid='09b11c53-8b5c-4eeb-8f00-d84eaa0aaa4f'/>
|
|
+ </virtualport>
|
|
+ <vlan trunk='yes'>
|
|
+ <tag id='42' nativeMode='untagged'/>
|
|
+ <tag id='47'/>
|
|
+ </vlan>
|
|
+ </network>
|
|
+ """
|
|
+ )
|
|
+ assert_xml_equals(expected_xml, ET.fromstring(xml_data))
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize(
|
|
+ "autostart, start", [(True, True), (False, True), (False, False)],
|
|
+)
|
|
+def test_define(make_mock_network, autostart, start):
|
|
+ """
|
|
+ Test the virt.defined function
|
|
+ """
|
|
+ # We create a network mock to fake the autostart flag at start
|
|
+ # and allow checking everything went fine. This doesn't mess up with the network define part
|
|
+ mock_network = make_mock_network("<network><name>default</name></network>")
|
|
+ assert virt.network_define(
|
|
+ "default",
|
|
+ "test-br0",
|
|
+ "nat",
|
|
+ ipv4_config={
|
|
+ "cidr": "192.168.124.0/24",
|
|
+ "dhcp_ranges": [{"start": "192.168.124.2", "end": "192.168.124.254"}],
|
|
+ },
|
|
+ autostart=autostart,
|
|
+ start=start,
|
|
+ )
|
|
+
|
|
+ expected_xml = strip_xml(
|
|
+ """
|
|
+ <network>
|
|
+ <name>default</name>
|
|
+ <bridge name='test-br0'/>
|
|
+ <forward mode='nat'/>
|
|
+ <ip family='ipv4' address='192.168.124.1' prefix='24'>
|
|
+ <dhcp>
|
|
+ <range start='192.168.124.2' end='192.168.124.254'/>
|
|
+ </dhcp>
|
|
+ </ip>
|
|
+ </network>
|
|
+ """
|
|
+ )
|
|
+ define_mock = virt.libvirt.openAuth().networkDefineXML
|
|
+ assert expected_xml == strip_xml(define_mock.call_args[0][0])
|
|
+
|
|
+ if autostart:
|
|
+ mock_network.setAutostart.assert_called_with(1)
|
|
+ else:
|
|
+ mock_network.setAutostart.assert_not_called()
|
|
+
|
|
+ assert_called(mock_network.create, autostart or start)
|
|
+
|
|
+
|
|
+def test_update_nat_nochange(make_mock_network):
|
|
+ """
|
|
+ Test updating a NAT network without changes
|
|
+ """
|
|
+ net_mock = make_mock_network(
|
|
+ """
|
|
+ <network>
|
|
+ <name>default</name>
|
|
+ <uuid>d6c95a31-16a2-473a-b8cd-7ad2fe2dd855</uuid>
|
|
+ <forward mode='nat'>
|
|
+ <nat>
|
|
+ <port start='1024' end='65535'/>
|
|
+ </nat>
|
|
+ </forward>
|
|
+ <bridge name='virbr0' stp='on' delay='0'/>
|
|
+ <mac address='52:54:00:cd:49:6b'/>
|
|
+ <domain name='my.lab' localOnly='yes'/>
|
|
+ <ip address='192.168.122.1' netmask='255.255.255.0'>
|
|
+ <dhcp>
|
|
+ <range start='192.168.122.2' end='192.168.122.254'/>
|
|
+ <host mac='52:54:00:46:4d:9e' name='mirror' ip='192.168.122.136'/>
|
|
+ <bootp file='pxelinux.0' server='192.168.122.110'/>
|
|
+ </dhcp>
|
|
+ </ip>
|
|
+ </network>
|
|
+ """
|
|
+ )
|
|
+ assert not virt.network_update(
|
|
+ "default",
|
|
+ None,
|
|
+ "nat",
|
|
+ ipv4_config={
|
|
+ "cidr": "192.168.122.0/24",
|
|
+ "dhcp_ranges": [{"start": "192.168.122.2", "end": "192.168.122.254"}],
|
|
+ "hosts": {
|
|
+ "192.168.122.136": {"mac": "52:54:00:46:4d:9e", "name": "mirror"},
|
|
+ },
|
|
+ "bootp": {"file": "pxelinux.0", "server": "192.168.122.110"},
|
|
+ },
|
|
+ domain={"name": "my.lab", "localOnly": True},
|
|
+ nat={"port": {"start": 1024, "end": "65535"}},
|
|
+ )
|
|
+ define_mock = virt.libvirt.openAuth().networkDefineXML
|
|
+ define_mock.assert_not_called()
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize("test", [True, False])
|
|
+def test_update_nat_change(make_mock_network, test):
|
|
+ """
|
|
+ Test updating a NAT network with changes
|
|
+ """
|
|
+ net_mock = make_mock_network(
|
|
+ """
|
|
+ <network>
|
|
+ <name>default</name>
|
|
+ <uuid>d6c95a31-16a2-473a-b8cd-7ad2fe2dd855</uuid>
|
|
+ <forward mode='nat'/>
|
|
+ <bridge name='virbr0' stp='on' delay='0'/>
|
|
+ <mac address='52:54:00:cd:49:6b'/>
|
|
+ <domain name='my.lab' localOnly='yes'/>
|
|
+ <ip address='192.168.122.1' netmask='255.255.255.0'>
|
|
+ <dhcp>
|
|
+ <range start='192.168.122.2' end='192.168.122.254'/>
|
|
+ </dhcp>
|
|
+ </ip>
|
|
+ </network>
|
|
+ """
|
|
+ )
|
|
+ assert virt.network_update(
|
|
+ "default",
|
|
+ "test-br0",
|
|
+ "nat",
|
|
+ ipv4_config={
|
|
+ "cidr": "192.168.124.0/24",
|
|
+ "dhcp_ranges": [{"start": "192.168.124.2", "end": "192.168.124.254"}],
|
|
+ },
|
|
+ test=test,
|
|
+ )
|
|
+ define_mock = virt.libvirt.openAuth().networkDefineXML
|
|
+ assert_called(define_mock, not test)
|
|
+
|
|
+ if not test:
|
|
+ # Test the passed new XML
|
|
+ expected_xml = strip_xml(
|
|
+ """
|
|
+ <network>
|
|
+ <name>default</name>
|
|
+ <mac address='52:54:00:cd:49:6b'/>
|
|
+ <uuid>d6c95a31-16a2-473a-b8cd-7ad2fe2dd855</uuid>
|
|
+ <bridge name='test-br0'/>
|
|
+ <forward mode='nat'/>
|
|
+ <ip family='ipv4' address='192.168.124.1' prefix='24'>
|
|
+ <dhcp>
|
|
+ <range start='192.168.124.2' end='192.168.124.254'/>
|
|
+ </dhcp>
|
|
+ </ip>
|
|
+ </network>
|
|
+ """
|
|
+ )
|
|
+ assert expected_xml == strip_xml(define_mock.call_args[0][0])
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize("change", [True, False], ids=["changed", "unchanged"])
|
|
+def test_update_hostdev_pf(make_mock_network, change):
|
|
+ """
|
|
+ Test updating a hostdev network without changes
|
|
+ """
|
|
+ net_mock = make_mock_network(
|
|
+ """
|
|
+ <network connections='1'>
|
|
+ <name>test-hostdev</name>
|
|
+ <uuid>51d0aaa5-7530-4c60-8498-5bc3ab8c655b</uuid>
|
|
+ <forward mode='hostdev' managed='yes'>
|
|
+ <pf dev='eth0'/>
|
|
+ <address type='pci' domain='0x0000' bus='0x3d' slot='0x02' function='0x0'/>
|
|
+ <address type='pci' domain='0x0000' bus='0x3d' slot='0x02' function='0x1'/>
|
|
+ </forward>
|
|
+ </network>
|
|
+ """
|
|
+ )
|
|
+ assert change == virt.network_update(
|
|
+ "test-hostdev",
|
|
+ None,
|
|
+ "hostdev",
|
|
+ physical_function="eth0" if not change else "eth1",
|
|
+ )
|
|
+ define_mock = virt.libvirt.openAuth().networkDefineXML
|
|
+ if change:
|
|
+ define_mock.assert_called()
|
|
+ else:
|
|
+ define_mock.assert_not_called()
|
|
diff --git a/tests/pytests/unit/output/test_highstate.py b/tests/pytests/unit/output/test_highstate.py
|
|
index 8336208bae..53eaf6fde7 100644
|
|
--- a/tests/pytests/unit/output/test_highstate.py
|
|
+++ b/tests/pytests/unit/output/test_highstate.py
|
|
@@ -2,11 +2,9 @@ import pytest
|
|
import salt.output.highstate as highstate
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {highstate: {"__opts__": {"strip_colors": True}}}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {highstate: {"__opts__": {"strip_colors": True}}}
|
|
|
|
|
|
@pytest.mark.parametrize("data", [None, {"return": None}, {"return": {"data": None}}])
|
|
diff --git a/tests/pytests/unit/states/test_alternatives.py b/tests/pytests/unit/states/test_alternatives.py
|
|
index 7bdcdb97cb..de0bc509b9 100644
|
|
--- a/tests/pytests/unit/states/test_alternatives.py
|
|
+++ b/tests/pytests/unit/states/test_alternatives.py
|
|
@@ -7,11 +7,9 @@ import salt.states.alternatives as alternatives
|
|
from tests.support.mock import MagicMock, patch
|
|
|
|
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {alternatives: {}}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
+@pytest.fixture
|
|
+def configure_loader_modules():
|
|
+ return {alternatives: {}}
|
|
|
|
|
|
# 'install' function tests: 1
|
|
diff --git a/tests/pytests/unit/states/test_ini_manage.py b/tests/pytests/unit/states/test_ini_manage.py
|
|
index b0030793da..2b44e2ffd6 100644
|
|
--- a/tests/pytests/unit/states/test_ini_manage.py
|
|
+++ b/tests/pytests/unit/states/test_ini_manage.py
|
|
@@ -9,17 +9,8 @@ from tests.support.mock import patch
|
|
|
|
|
|
@pytest.fixture
|
|
-def sections():
|
|
- sections = OrderedDict()
|
|
- sections["general"] = OrderedDict()
|
|
- sections["general"]["hostname"] = "myserver.com"
|
|
- sections["general"]["port"] = "1234"
|
|
- return sections
|
|
-
|
|
-
|
|
-@pytest.fixture(autouse=True)
|
|
-def setup_loader():
|
|
- setup_loader_modules = {
|
|
+def configure_loader_modules():
|
|
+ return {
|
|
ini_manage: {
|
|
"__salt__": {
|
|
"ini.get_ini": mod_ini_manage.get_ini,
|
|
@@ -29,8 +20,15 @@ def setup_loader():
|
|
},
|
|
mod_ini_manage: {"__opts__": {"test": False}},
|
|
}
|
|
- with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
- yield loader_mock
|
|
+
|
|
+
|
|
+@pytest.fixture
|
|
+def sections():
|
|
+ sections = OrderedDict()
|
|
+ sections["general"] = OrderedDict()
|
|
+ sections["general"]["hostname"] = "myserver.com"
|
|
+ sections["general"]["port"] = "1234"
|
|
+ return sections
|
|
|
|
|
|
def test_options_present(tmpdir, sections):
|
|
diff --git a/tests/pytests/unit/states/virt/__init__.py b/tests/pytests/unit/states/virt/__init__.py
|
|
new file mode 100644
|
|
index 0000000000..e69de29bb2
|
|
diff --git a/tests/pytests/unit/states/virt/conftest.py b/tests/pytests/unit/states/virt/conftest.py
|
|
new file mode 100644
|
|
index 0000000000..cc975fddbf
|
|
--- /dev/null
|
|
+++ b/tests/pytests/unit/states/virt/conftest.py
|
|
@@ -0,0 +1,36 @@
|
|
+import pytest
|
|
+import salt.states.virt as virt
|
|
+from tests.support.mock import MagicMock
|
|
+
|
|
+
|
|
+class LibvirtMock(MagicMock): # pylint: disable=too-many-ancestors
|
|
+ """
|
|
+ Libvirt library mock
|
|
+ """
|
|
+
|
|
+ class libvirtError(Exception):
|
|
+ """
|
|
+ libvirtError mock
|
|
+ """
|
|
+
|
|
+ def __init__(self, msg):
|
|
+ super().__init__(msg)
|
|
+ self.msg = msg
|
|
+
|
|
+ def get_error_message(self):
|
|
+ return self.msg
|
|
+
|
|
+
|
|
+@pytest.fixture(autouse=True)
|
|
+def setup_loader():
|
|
+ setup_loader_modules = {virt: {"libvirt": LibvirtMock()}}
|
|
+ with pytest.helpers.loader_mock(setup_loader_modules) as loader_mock:
|
|
+ yield loader_mock
|
|
+
|
|
+
|
|
+@pytest.fixture(params=[True, False], ids=["test", "notest"])
|
|
+def test(request):
|
|
+ """
|
|
+ Run the test with both True and False test values
|
|
+ """
|
|
+ return request.param
|
|
diff --git a/tests/pytests/unit/states/virt/test_domain.py b/tests/pytests/unit/states/virt/test_domain.py
|
|
new file mode 100644
|
|
index 0000000000..a4ae8c0694
|
|
--- /dev/null
|
|
+++ b/tests/pytests/unit/states/virt/test_domain.py
|
|
@@ -0,0 +1,840 @@
|
|
+import pytest
|
|
+import salt.states.virt as virt
|
|
+from salt.exceptions import CommandExecutionError
|
|
+from tests.support.mock import MagicMock, patch
|
|
+
|
|
+from .test_helpers import domain_update_call
|
|
+
|
|
+
|
|
+def test_defined_no_change(test):
|
|
+ """
|
|
+ defined state test, no change required case.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ init_mock = MagicMock(return_value=True)
|
|
+ update_mock = MagicMock(return_value={"definition": False})
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
+ "virt.update": update_mock,
|
|
+ "virt.init": init_mock,
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": {"myvm": {"definition": False}},
|
|
+ "result": True,
|
|
+ "comment": "Domain myvm unchanged",
|
|
+ } == virt.defined("myvm")
|
|
+ init_mock.assert_not_called()
|
|
+ assert [domain_update_call("myvm", test=test)] == update_mock.call_args_list
|
|
+
|
|
+
|
|
+def test_defined_new_with_connection(test):
|
|
+ """
|
|
+ defined state test, new guest with connection details passed case.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ init_mock = MagicMock(return_value=True)
|
|
+ update_mock = MagicMock(side_effect=CommandExecutionError("not found"))
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=[]),
|
|
+ "virt.init": init_mock,
|
|
+ "virt.update": update_mock,
|
|
+ },
|
|
+ ):
|
|
+ disks = [
|
|
+ {
|
|
+ "name": "system",
|
|
+ "size": 8192,
|
|
+ "overlay_image": True,
|
|
+ "pool": "default",
|
|
+ "image": "/path/to/image.qcow2",
|
|
+ },
|
|
+ {"name": "data", "size": 16834},
|
|
+ ]
|
|
+ ifaces = [
|
|
+ {"name": "eth0", "mac": "01:23:45:67:89:AB"},
|
|
+ {"name": "eth1", "type": "network", "source": "admin"},
|
|
+ ]
|
|
+ graphics = {
|
|
+ "type": "spice",
|
|
+ "listen": {"type": "address", "address": "192.168.0.1"},
|
|
+ }
|
|
+ serials = [
|
|
+ {"type": "tcp", "port": 22223, "protocol": "telnet"},
|
|
+ {"type": "pty"},
|
|
+ ]
|
|
+ consoles = [
|
|
+ {"type": "tcp", "port": 22223, "protocol": "telnet"},
|
|
+ {"type": "pty"},
|
|
+ ]
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "result": True if not test else None,
|
|
+ "changes": {"myvm": {"definition": True}},
|
|
+ "comment": "Domain myvm defined",
|
|
+ } == virt.defined(
|
|
+ "myvm",
|
|
+ cpu=2,
|
|
+ mem=2048,
|
|
+ boot_dev="cdrom hd",
|
|
+ os_type="linux",
|
|
+ arch="i686",
|
|
+ vm_type="qemu",
|
|
+ disk_profile="prod",
|
|
+ disks=disks,
|
|
+ nic_profile="prod",
|
|
+ interfaces=ifaces,
|
|
+ graphics=graphics,
|
|
+ seed=False,
|
|
+ install=False,
|
|
+ pub_key="/path/to/key.pub",
|
|
+ priv_key="/path/to/key",
|
|
+ hypervisor_features={"kvm-hint-dedicated": True},
|
|
+ clock={"utc": True},
|
|
+ stop_on_reboot=True,
|
|
+ connection="someconnection",
|
|
+ username="libvirtuser",
|
|
+ password="supersecret",
|
|
+ serials=serials,
|
|
+ consoles=consoles,
|
|
+ host_devices=["pci_0000_00_17_0"],
|
|
+ )
|
|
+ if not test:
|
|
+ init_mock.assert_called_with(
|
|
+ "myvm",
|
|
+ cpu=2,
|
|
+ mem=2048,
|
|
+ boot_dev="cdrom hd",
|
|
+ os_type="linux",
|
|
+ arch="i686",
|
|
+ disk="prod",
|
|
+ disks=disks,
|
|
+ nic="prod",
|
|
+ interfaces=ifaces,
|
|
+ graphics=graphics,
|
|
+ hypervisor="qemu",
|
|
+ seed=False,
|
|
+ boot=None,
|
|
+ numatune=None,
|
|
+ install=False,
|
|
+ start=False,
|
|
+ pub_key="/path/to/key.pub",
|
|
+ priv_key="/path/to/key",
|
|
+ hypervisor_features={"kvm-hint-dedicated": True},
|
|
+ clock={"utc": True},
|
|
+ stop_on_reboot=True,
|
|
+ connection="someconnection",
|
|
+ username="libvirtuser",
|
|
+ password="supersecret",
|
|
+ serials=serials,
|
|
+ consoles=consoles,
|
|
+ host_devices=["pci_0000_00_17_0"],
|
|
+ )
|
|
+ else:
|
|
+ init_mock.assert_not_called()
|
|
+ update_mock.assert_not_called()
|
|
+
|
|
+
|
|
+def test_defined_update(test):
|
|
+ """
|
|
+ defined state test, with change required case.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ init_mock = MagicMock(return_value=True)
|
|
+ update_mock = MagicMock(return_value={"definition": True, "cpu": True})
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
+ "virt.update": update_mock,
|
|
+ "virt.init": init_mock,
|
|
+ },
|
|
+ ):
|
|
+ boot = {
|
|
+ "kernel": "/root/f8-i386-vmlinuz",
|
|
+ "initrd": "/root/f8-i386-initrd",
|
|
+ "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/",
|
|
+ }
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": {"myvm": {"definition": True, "cpu": True}},
|
|
+ "result": True if not test else None,
|
|
+ "comment": "Domain myvm updated",
|
|
+ } == virt.defined("myvm", cpu=2, boot=boot,)
|
|
+ init_mock.assert_not_called()
|
|
+ assert [
|
|
+ domain_update_call("myvm", cpu=2, test=test, boot=boot)
|
|
+ ] == update_mock.call_args_list
|
|
+
|
|
+
|
|
+def test_defined_update_error(test):
|
|
+ """
|
|
+ defined state test, with error during the update.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ init_mock = MagicMock(return_value=True)
|
|
+ update_mock = MagicMock(
|
|
+ return_value={"definition": True, "cpu": False, "errors": ["some error"]}
|
|
+ )
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
+ "virt.update": update_mock,
|
|
+ "virt.init": init_mock,
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": {
|
|
+ "myvm": {
|
|
+ "definition": True,
|
|
+ "cpu": False,
|
|
+ "errors": ["some error"],
|
|
+ }
|
|
+ },
|
|
+ "result": True if not test else None,
|
|
+ "comment": "Domain myvm updated with live update(s) failures",
|
|
+ } == virt.defined("myvm", cpu=2, boot_dev="cdrom hd")
|
|
+ init_mock.assert_not_called()
|
|
+ update_mock.assert_called_with(
|
|
+ "myvm",
|
|
+ cpu=2,
|
|
+ boot_dev="cdrom hd",
|
|
+ mem=None,
|
|
+ disk_profile=None,
|
|
+ disks=None,
|
|
+ nic_profile=None,
|
|
+ interfaces=None,
|
|
+ graphics=None,
|
|
+ live=True,
|
|
+ connection=None,
|
|
+ username=None,
|
|
+ password=None,
|
|
+ boot=None,
|
|
+ numatune=None,
|
|
+ test=test,
|
|
+ hypervisor_features=None,
|
|
+ clock=None,
|
|
+ serials=None,
|
|
+ consoles=None,
|
|
+ stop_on_reboot=False,
|
|
+ host_devices=None,
|
|
+ )
|
|
+
|
|
+
|
|
+def test_defined_update_definition_error(test):
|
|
+ """
|
|
+ defined state test, with definition update failure
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ init_mock = MagicMock(return_value=True)
|
|
+ update_mock = MagicMock(
|
|
+ side_effect=[virt.libvirt.libvirtError("error message")]
|
|
+ )
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
+ "virt.update": update_mock,
|
|
+ "virt.init": init_mock,
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": {},
|
|
+ "result": False,
|
|
+ "comment": "error message",
|
|
+ } == virt.defined("myvm", cpu=2)
|
|
+ init_mock.assert_not_called()
|
|
+ assert [
|
|
+ domain_update_call("myvm", cpu=2, test=test)
|
|
+ ] == update_mock.call_args_list
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize("running", ["running", "shutdown"])
|
|
+def test_running_no_change(test, running):
|
|
+ """
|
|
+ running state test, no change required case.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ update_mock = MagicMock(return_value={"definition": False})
|
|
+ start_mock = MagicMock(return_value=0)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.vm_state": MagicMock(return_value={"myvm": running}),
|
|
+ "virt.start": start_mock,
|
|
+ "virt.update": MagicMock(return_value={"definition": False}),
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
+ },
|
|
+ ):
|
|
+ changes = {"definition": False}
|
|
+ comment = "Domain myvm exists and is running"
|
|
+ if running == "shutdown":
|
|
+ changes["started"] = True
|
|
+ comment = "Domain myvm started"
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "result": True,
|
|
+ "changes": {"myvm": changes},
|
|
+ "comment": comment,
|
|
+ } == virt.running("myvm")
|
|
+ if running == "shutdown" and not test:
|
|
+ start_mock.assert_called()
|
|
+ else:
|
|
+ start_mock.assert_not_called()
|
|
+
|
|
+
|
|
+def test_running_define(test):
|
|
+ """
|
|
+ running state test, defining and start a guest the old way
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ init_mock = MagicMock(return_value=True)
|
|
+ start_mock = MagicMock(return_value=0)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}),
|
|
+ "virt.init": init_mock,
|
|
+ "virt.start": start_mock,
|
|
+ "virt.list_domains": MagicMock(return_value=[]),
|
|
+ },
|
|
+ ):
|
|
+ disks = [
|
|
+ {
|
|
+ "name": "system",
|
|
+ "size": 8192,
|
|
+ "overlay_image": True,
|
|
+ "pool": "default",
|
|
+ "image": "/path/to/image.qcow2",
|
|
+ },
|
|
+ {"name": "data", "size": 16834},
|
|
+ ]
|
|
+ ifaces = [
|
|
+ {"name": "eth0", "mac": "01:23:45:67:89:AB"},
|
|
+ {"name": "eth1", "type": "network", "source": "admin"},
|
|
+ ]
|
|
+ graphics = {
|
|
+ "type": "spice",
|
|
+ "listen": {"type": "address", "address": "192.168.0.1"},
|
|
+ }
|
|
+
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "result": True if not test else None,
|
|
+ "changes": {"myvm": {"definition": True, "started": True}},
|
|
+ "comment": "Domain myvm defined and started",
|
|
+ } == virt.running(
|
|
+ "myvm",
|
|
+ cpu=2,
|
|
+ mem=2048,
|
|
+ os_type="linux",
|
|
+ arch="i686",
|
|
+ vm_type="qemu",
|
|
+ disk_profile="prod",
|
|
+ disks=disks,
|
|
+ nic_profile="prod",
|
|
+ interfaces=ifaces,
|
|
+ graphics=graphics,
|
|
+ seed=False,
|
|
+ install=False,
|
|
+ pub_key="/path/to/key.pub",
|
|
+ priv_key="/path/to/key",
|
|
+ boot_dev="network hd",
|
|
+ stop_on_reboot=True,
|
|
+ host_devices=["pci_0000_00_17_0"],
|
|
+ connection="someconnection",
|
|
+ username="libvirtuser",
|
|
+ password="supersecret",
|
|
+ )
|
|
+ if not test:
|
|
+ init_mock.assert_called_with(
|
|
+ "myvm",
|
|
+ cpu=2,
|
|
+ mem=2048,
|
|
+ os_type="linux",
|
|
+ arch="i686",
|
|
+ disk="prod",
|
|
+ disks=disks,
|
|
+ nic="prod",
|
|
+ interfaces=ifaces,
|
|
+ graphics=graphics,
|
|
+ hypervisor="qemu",
|
|
+ seed=False,
|
|
+ boot=None,
|
|
+ numatune=None,
|
|
+ install=False,
|
|
+ start=False,
|
|
+ pub_key="/path/to/key.pub",
|
|
+ priv_key="/path/to/key",
|
|
+ boot_dev="network hd",
|
|
+ hypervisor_features=None,
|
|
+ clock=None,
|
|
+ stop_on_reboot=True,
|
|
+ connection="someconnection",
|
|
+ username="libvirtuser",
|
|
+ password="supersecret",
|
|
+ serials=None,
|
|
+ consoles=None,
|
|
+ host_devices=["pci_0000_00_17_0"],
|
|
+ )
|
|
+ start_mock.assert_called_with(
|
|
+ "myvm",
|
|
+ connection="someconnection",
|
|
+ username="libvirtuser",
|
|
+ password="supersecret",
|
|
+ )
|
|
+ else:
|
|
+ init_mock.assert_not_called()
|
|
+ start_mock.assert_not_called()
|
|
+
|
|
+
|
|
+def test_running_start_error():
|
|
+ """
|
|
+ running state test, start an existing guest raising an error
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": False}):
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}),
|
|
+ "virt.update": MagicMock(return_value={"definition": False}),
|
|
+ "virt.start": MagicMock(
|
|
+ side_effect=[virt.libvirt.libvirtError("libvirt error msg")]
|
|
+ ),
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": {"myvm": {"definition": False}},
|
|
+ "result": False,
|
|
+ "comment": "libvirt error msg",
|
|
+ } == virt.running("myvm")
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize("running", ["running", "shutdown"])
|
|
+def test_running_update(test, running):
|
|
+ """
|
|
+ running state test, update an existing guest
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ start_mock = MagicMock(return_value=0)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.vm_state": MagicMock(return_value={"myvm": running}),
|
|
+ "virt.update": MagicMock(
|
|
+ return_value={"definition": True, "cpu": True}
|
|
+ ),
|
|
+ "virt.start": start_mock,
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
+ },
|
|
+ ):
|
|
+ changes = {"myvm": {"definition": True, "cpu": True}}
|
|
+ if running == "shutdown":
|
|
+ changes["myvm"]["started"] = True
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": changes,
|
|
+ "result": True if not test else None,
|
|
+ "comment": "Domain myvm updated"
|
|
+ if running == "running"
|
|
+ else "Domain myvm updated and started",
|
|
+ } == virt.running("myvm", cpu=2)
|
|
+ if running == "shutdown" and not test:
|
|
+ start_mock.assert_called()
|
|
+ else:
|
|
+ start_mock.assert_not_called()
|
|
+
|
|
+
|
|
+def test_running_definition_error():
|
|
+ """
|
|
+ running state test, update an existing guest raising an error when setting the XML
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": False}):
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
+ "virt.update": MagicMock(
|
|
+ side_effect=[virt.libvirt.libvirtError("error message")]
|
|
+ ),
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": {},
|
|
+ "result": False,
|
|
+ "comment": "error message",
|
|
+ } == virt.running("myvm", cpu=3)
|
|
+
|
|
+
|
|
+def test_running_update_error():
|
|
+ """
|
|
+ running state test, update an existing guest raising an error
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": False}):
|
|
+ update_mock = MagicMock(
|
|
+ return_value={"definition": True, "cpu": False, "errors": ["some error"]}
|
|
+ )
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
+ "virt.update": update_mock,
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": {
|
|
+ "myvm": {
|
|
+ "definition": True,
|
|
+ "cpu": False,
|
|
+ "errors": ["some error"],
|
|
+ }
|
|
+ },
|
|
+ "result": True,
|
|
+ "comment": "Domain myvm updated with live update(s) failures",
|
|
+ } == virt.running("myvm", cpu=2)
|
|
+ update_mock.assert_called_with(
|
|
+ "myvm",
|
|
+ cpu=2,
|
|
+ mem=None,
|
|
+ disk_profile=None,
|
|
+ disks=None,
|
|
+ nic_profile=None,
|
|
+ interfaces=None,
|
|
+ graphics=None,
|
|
+ live=True,
|
|
+ connection=None,
|
|
+ username=None,
|
|
+ password=None,
|
|
+ boot=None,
|
|
+ numatune=None,
|
|
+ test=False,
|
|
+ boot_dev=None,
|
|
+ hypervisor_features=None,
|
|
+ clock=None,
|
|
+ serials=None,
|
|
+ consoles=None,
|
|
+ stop_on_reboot=False,
|
|
+ host_devices=None,
|
|
+ )
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize("running", ["running", "shutdown"])
|
|
+def test_stopped(test, running):
|
|
+ """
|
|
+ stopped state test, running guest
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ shutdown_mock = MagicMock(return_value=True)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
+ "virt.vm_state": MagicMock(return_value={"myvm": running}),
|
|
+ "virt.shutdown": shutdown_mock,
|
|
+ },
|
|
+ ):
|
|
+ changes = {}
|
|
+ comment = "No changes had happened"
|
|
+ if running == "running":
|
|
+ changes = {"stopped": [{"domain": "myvm", "shutdown": True}]}
|
|
+ comment = "Machine has been shut down"
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": changes,
|
|
+ "comment": comment,
|
|
+ "result": True if not test or running == "shutdown" else None,
|
|
+ } == virt.stopped(
|
|
+ "myvm", connection="myconnection", username="user", password="secret",
|
|
+ )
|
|
+ if not test and running == "running":
|
|
+ shutdown_mock.assert_called_with(
|
|
+ "myvm",
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ else:
|
|
+ shutdown_mock.assert_not_called()
|
|
+
|
|
+
|
|
+def test_stopped_error():
|
|
+ """
|
|
+ stopped state test, error while stopping guest
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": False}):
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
+ "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
+ "virt.shutdown": MagicMock(
|
|
+ side_effect=virt.libvirt.libvirtError("Some error")
|
|
+ ),
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]},
|
|
+ "result": False,
|
|
+ "comment": "No changes had happened",
|
|
+ } == virt.stopped("myvm")
|
|
+
|
|
+
|
|
+def test_stopped_not_existing(test):
|
|
+ """
|
|
+ stopped state test, non existing guest
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ shutdown_mock = MagicMock(return_value=True)
|
|
+ with patch.dict(
|
|
+ virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])},
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": {},
|
|
+ "comment": "No changes had happened",
|
|
+ "result": False,
|
|
+ } == virt.stopped("myvm")
|
|
+
|
|
+
|
|
+@pytest.mark.parametrize("running", ["running", "shutdown"])
|
|
+def test_powered_off(test, running):
|
|
+ """
|
|
+ powered_off state test
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ stop_mock = MagicMock(return_value=True)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
+ "virt.vm_state": MagicMock(return_value={"myvm": running}),
|
|
+ "virt.stop": stop_mock,
|
|
+ },
|
|
+ ):
|
|
+ changes = {}
|
|
+ comment = "No changes had happened"
|
|
+ if running == "running":
|
|
+ changes = {"unpowered": [{"domain": "myvm", "stop": True}]}
|
|
+ comment = "Machine has been powered off"
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "result": True if not test or running == "shutdown" else None,
|
|
+ "changes": changes,
|
|
+ "comment": comment,
|
|
+ } == virt.powered_off(
|
|
+ "myvm", connection="myconnection", username="user", password="secret",
|
|
+ )
|
|
+ if not test and running == "running":
|
|
+ stop_mock.assert_called_with(
|
|
+ "myvm",
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ else:
|
|
+ stop_mock.assert_not_called()
|
|
+
|
|
+
|
|
+def test_powered_off_error():
|
|
+ """
|
|
+ powered_off state test, error case
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": False}):
|
|
+ stop_mock = MagicMock(return_value=True)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
+ "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
+ "virt.stop": MagicMock(
|
|
+ side_effect=virt.libvirt.libvirtError("Some error")
|
|
+ ),
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "result": False,
|
|
+ "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]},
|
|
+ "comment": "No changes had happened",
|
|
+ } == virt.powered_off("myvm")
|
|
+
|
|
+
|
|
+def test_powered_off_not_existing():
|
|
+ """
|
|
+ powered_off state test cases.
|
|
+ """
|
|
+ ret = {"name": "myvm", "changes": {}, "result": True}
|
|
+ with patch.dict(virt.__opts__, {"test": False}):
|
|
+ with patch.dict(
|
|
+ virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])}
|
|
+ ): # pylint: disable=no-member
|
|
+ ret.update(
|
|
+ {"changes": {}, "result": False, "comment": "No changes had happened"}
|
|
+ )
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": {},
|
|
+ "result": False,
|
|
+ "comment": "No changes had happened",
|
|
+ } == virt.powered_off("myvm")
|
|
+
|
|
+
|
|
+def test_snapshot(test):
|
|
+ """
|
|
+ snapshot state test
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ snapshot_mock = MagicMock(return_value=True)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
+ "virt.snapshot": snapshot_mock,
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "result": True if not test else None,
|
|
+ "changes": {"saved": [{"domain": "myvm", "snapshot": True}]},
|
|
+ "comment": "Snapshot has been taken",
|
|
+ } == virt.snapshot(
|
|
+ "myvm",
|
|
+ suffix="snap",
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ if not test:
|
|
+ snapshot_mock.assert_called_with(
|
|
+ "myvm",
|
|
+ suffix="snap",
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ else:
|
|
+ snapshot_mock.assert_not_called()
|
|
+
|
|
+
|
|
+def test_snapshot_error():
|
|
+ """
|
|
+ snapshot state test, error case
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": False}):
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
+ "virt.snapshot": MagicMock(
|
|
+ side_effect=virt.libvirt.libvirtError("Some error")
|
|
+ ),
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "result": False,
|
|
+ "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]},
|
|
+ "comment": "No changes had happened",
|
|
+ } == virt.snapshot("myvm")
|
|
+
|
|
+
|
|
+def test_snapshot_not_existing(test):
|
|
+ """
|
|
+ snapshot state test, guest not existing.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ with patch.dict(
|
|
+ virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])}
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": {},
|
|
+ "result": False,
|
|
+ "comment": "No changes had happened",
|
|
+ } == virt.snapshot("myvm")
|
|
+
|
|
+
|
|
+def test_rebooted(test):
|
|
+ """
|
|
+ rebooted state test
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ reboot_mock = MagicMock(return_value=True)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
+ "virt.reboot": reboot_mock,
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "result": True if not test else None,
|
|
+ "changes": {"rebooted": [{"domain": "myvm", "reboot": True}]},
|
|
+ "comment": "Machine has been rebooted",
|
|
+ } == virt.rebooted(
|
|
+ "myvm", connection="myconnection", username="user", password="secret",
|
|
+ )
|
|
+ if not test:
|
|
+ reboot_mock.assert_called_with(
|
|
+ "myvm",
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ else:
|
|
+ reboot_mock.assert_not_called()
|
|
+
|
|
+
|
|
+def test_rebooted_error():
|
|
+ """
|
|
+ rebooted state test, error case.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": False}):
|
|
+ reboot_mock = MagicMock(return_value=True)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
+ "virt.reboot": MagicMock(
|
|
+ side_effect=virt.libvirt.libvirtError("Some error")
|
|
+ ),
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "result": False,
|
|
+ "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]},
|
|
+ "comment": "No changes had happened",
|
|
+ } == virt.rebooted("myvm")
|
|
+
|
|
+
|
|
+def test_rebooted_not_existing(test):
|
|
+ """
|
|
+ rebooted state test cases.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ with patch.dict(
|
|
+ virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])}
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "myvm",
|
|
+ "changes": {},
|
|
+ "result": False,
|
|
+ "comment": "No changes had happened",
|
|
+ } == virt.rebooted("myvm")
|
|
diff --git a/tests/pytests/unit/states/virt/test_helpers.py b/tests/pytests/unit/states/virt/test_helpers.py
|
|
new file mode 100644
|
|
index 0000000000..b8e2cb06e2
|
|
--- /dev/null
|
|
+++ b/tests/pytests/unit/states/virt/test_helpers.py
|
|
@@ -0,0 +1,99 @@
|
|
+from tests.support.mock import call
|
|
+
|
|
+
|
|
+def network_update_call(
|
|
+ name,
|
|
+ bridge,
|
|
+ forward,
|
|
+ vport=None,
|
|
+ tag=None,
|
|
+ ipv4_config=None,
|
|
+ ipv6_config=None,
|
|
+ connection=None,
|
|
+ username=None,
|
|
+ password=None,
|
|
+ mtu=None,
|
|
+ domain=None,
|
|
+ nat=None,
|
|
+ interfaces=None,
|
|
+ addresses=None,
|
|
+ physical_function=None,
|
|
+ dns=None,
|
|
+ test=False,
|
|
+):
|
|
+ """
|
|
+ Create a call object with the missing default parameters from virt.network_update()
|
|
+ """
|
|
+ return call(
|
|
+ name,
|
|
+ bridge,
|
|
+ forward,
|
|
+ vport=vport,
|
|
+ tag=tag,
|
|
+ ipv4_config=ipv4_config,
|
|
+ ipv6_config=ipv6_config,
|
|
+ mtu=mtu,
|
|
+ domain=domain,
|
|
+ nat=nat,
|
|
+ interfaces=interfaces,
|
|
+ addresses=addresses,
|
|
+ physical_function=physical_function,
|
|
+ dns=dns,
|
|
+ test=test,
|
|
+ connection=connection,
|
|
+ username=username,
|
|
+ password=password,
|
|
+ )
|
|
+
|
|
+
|
|
+def domain_update_call(
|
|
+ name,
|
|
+ cpu=None,
|
|
+ mem=None,
|
|
+ disk_profile=None,
|
|
+ disks=None,
|
|
+ nic_profile=None,
|
|
+ interfaces=None,
|
|
+ graphics=None,
|
|
+ connection=None,
|
|
+ username=None,
|
|
+ password=None,
|
|
+ boot=None,
|
|
+ numatune=None,
|
|
+ boot_dev=None,
|
|
+ hypervisor_features=None,
|
|
+ clock=None,
|
|
+ serials=None,
|
|
+ consoles=None,
|
|
+ stop_on_reboot=False,
|
|
+ live=True,
|
|
+ host_devices=None,
|
|
+ test=False,
|
|
+):
|
|
+ """
|
|
+ Create a call object with the missing default parameters from virt.update()
|
|
+ """
|
|
+ return call(
|
|
+ name,
|
|
+ cpu=cpu,
|
|
+ mem=mem,
|
|
+ disk_profile=disk_profile,
|
|
+ disks=disks,
|
|
+ nic_profile=nic_profile,
|
|
+ interfaces=interfaces,
|
|
+ graphics=graphics,
|
|
+ live=live,
|
|
+ connection=connection,
|
|
+ username=username,
|
|
+ password=password,
|
|
+ boot=boot,
|
|
+ numatune=numatune,
|
|
+ serials=serials,
|
|
+ consoles=consoles,
|
|
+ test=test,
|
|
+ boot_dev=boot_dev,
|
|
+ hypervisor_features=hypervisor_features,
|
|
+ clock=clock,
|
|
+ stop_on_reboot=stop_on_reboot,
|
|
+ host_devices=host_devices,
|
|
+ )
|
|
diff --git a/tests/pytests/unit/states/virt/test_network.py b/tests/pytests/unit/states/virt/test_network.py
|
|
new file mode 100644
|
|
index 0000000000..668eee0c64
|
|
--- /dev/null
|
|
+++ b/tests/pytests/unit/states/virt/test_network.py
|
|
@@ -0,0 +1,476 @@
|
|
+import salt.states.virt as virt
|
|
+from tests.support.mock import MagicMock, patch
|
|
+
|
|
+from .test_helpers import network_update_call
|
|
+
|
|
+
|
|
+def test_network_defined_not_existing(test):
|
|
+ """
|
|
+ network_defined state tests if the network doesn't exist yet.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ define_mock = MagicMock(return_value=True)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.network_info": MagicMock(
|
|
+ side_effect=[{}, {"mynet": {"active": False}}]
|
|
+ ),
|
|
+ "virt.network_define": define_mock,
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "mynet",
|
|
+ "changes": {"mynet": "Network defined"},
|
|
+ "result": None if test else True,
|
|
+ "comment": "Network mynet defined",
|
|
+ } == virt.network_defined(
|
|
+ "mynet",
|
|
+ "br2",
|
|
+ "bridge",
|
|
+ vport="openvswitch",
|
|
+ tag=180,
|
|
+ ipv4_config={
|
|
+ "cidr": "192.168.2.0/24",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
+ {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
+ ],
|
|
+ },
|
|
+ ipv6_config={
|
|
+ "cidr": "2001:db8:ca2:2::1/64",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "2001:db8:ca2:1::10", "end": "2001:db8:ca2::1f"},
|
|
+ ],
|
|
+ },
|
|
+ mtu=9000,
|
|
+ domain={"name": "acme.lab"},
|
|
+ nat={"ports": {"start": 1024, "end": 2048}},
|
|
+ interfaces="eth0 eth1",
|
|
+ addresses="0000:01:02.4 0000:01:02.5",
|
|
+ physical_function="eth4",
|
|
+ dns={
|
|
+ "hosts": {
|
|
+ "192.168.2.10": {"name": "web", "mac": "de:ad:be:ef:00:00"}
|
|
+ }
|
|
+ },
|
|
+ autostart=False,
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ if not test:
|
|
+ define_mock.assert_called_with(
|
|
+ "mynet",
|
|
+ "br2",
|
|
+ "bridge",
|
|
+ vport="openvswitch",
|
|
+ tag=180,
|
|
+ autostart=False,
|
|
+ start=False,
|
|
+ ipv4_config={
|
|
+ "cidr": "192.168.2.0/24",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
+ {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
+ ],
|
|
+ },
|
|
+ ipv6_config={
|
|
+ "cidr": "2001:db8:ca2:2::1/64",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "2001:db8:ca2:1::10", "end": "2001:db8:ca2::1f"},
|
|
+ ],
|
|
+ },
|
|
+ mtu=9000,
|
|
+ domain={"name": "acme.lab"},
|
|
+ nat={"ports": {"start": 1024, "end": 2048}},
|
|
+ interfaces="eth0 eth1",
|
|
+ addresses="0000:01:02.4 0000:01:02.5",
|
|
+ physical_function="eth4",
|
|
+ dns={
|
|
+ "hosts": {
|
|
+ "192.168.2.10": {"name": "web", "mac": "de:ad:be:ef:00:00"}
|
|
+ }
|
|
+ },
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ else:
|
|
+ define_mock.assert_not_called()
|
|
+
|
|
+
|
|
+def test_network_defined_no_change(test):
|
|
+ """
|
|
+ network_defined state tests if the network doesn't need update.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ define_mock = MagicMock(return_value=True)
|
|
+ update_mock = MagicMock(return_value=False)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.network_info": MagicMock(
|
|
+ return_value={"mynet": {"active": True, "autostart": True}}
|
|
+ ),
|
|
+ "virt.network_define": define_mock,
|
|
+ "virt.network_update": update_mock,
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "mynet",
|
|
+ "changes": {},
|
|
+ "result": True,
|
|
+ "comment": "Network mynet unchanged",
|
|
+ } == virt.network_defined("mynet", "br2", "bridge")
|
|
+ define_mock.assert_not_called()
|
|
+ assert [
|
|
+ network_update_call("mynet", "br2", "bridge", test=True)
|
|
+ ] == update_mock.call_args_list
|
|
+
|
|
+
|
|
+def test_network_defined_change(test):
|
|
+ """
|
|
+ network_defined state tests if the network needs update.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ define_mock = MagicMock(return_value=True)
|
|
+ update_mock = MagicMock(return_value=True)
|
|
+ autostart_mock = MagicMock(return_value=True)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.network_info": MagicMock(
|
|
+ return_value={"mynet": {"active": True, "autostart": True}}
|
|
+ ),
|
|
+ "virt.network_define": define_mock,
|
|
+ "virt.network_update": update_mock,
|
|
+ "virt.network_set_autostart": autostart_mock,
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "mynet",
|
|
+ "changes": {"mynet": "Network updated, autostart flag changed"},
|
|
+ "result": None if test else True,
|
|
+ "comment": "Network mynet updated, autostart flag changed",
|
|
+ } == virt.network_defined(
|
|
+ "mynet",
|
|
+ "br2",
|
|
+ "bridge",
|
|
+ vport="openvswitch",
|
|
+ tag=180,
|
|
+ ipv4_config={
|
|
+ "cidr": "192.168.2.0/24",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
+ {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
+ ],
|
|
+ },
|
|
+ ipv6_config={
|
|
+ "cidr": "2001:db8:ca2:2::1/64",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "2001:db8:ca2:1::10", "end": "2001:db8:ca2::1f"},
|
|
+ ],
|
|
+ },
|
|
+ mtu=9000,
|
|
+ domain={"name": "acme.lab"},
|
|
+ nat={"ports": {"start": 1024, "end": 2048}},
|
|
+ interfaces="eth0 eth1",
|
|
+ addresses="0000:01:02.4 0000:01:02.5",
|
|
+ physical_function="eth4",
|
|
+ dns={
|
|
+ "hosts": {
|
|
+ "192.168.2.10": {"name": "web", "mac": "de:ad:be:ef:00:00"}
|
|
+ }
|
|
+ },
|
|
+ autostart=False,
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ define_mock.assert_not_called()
|
|
+ expected_update_kwargs = {
|
|
+ "vport": "openvswitch",
|
|
+ "tag": 180,
|
|
+ "ipv4_config": {
|
|
+ "cidr": "192.168.2.0/24",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
+ {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
+ ],
|
|
+ },
|
|
+ "ipv6_config": {
|
|
+ "cidr": "2001:db8:ca2:2::1/64",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "2001:db8:ca2:1::10", "end": "2001:db8:ca2::1f"},
|
|
+ ],
|
|
+ },
|
|
+ "mtu": 9000,
|
|
+ "domain": {"name": "acme.lab"},
|
|
+ "nat": {"ports": {"start": 1024, "end": 2048}},
|
|
+ "interfaces": "eth0 eth1",
|
|
+ "addresses": "0000:01:02.4 0000:01:02.5",
|
|
+ "physical_function": "eth4",
|
|
+ "dns": {
|
|
+ "hosts": {
|
|
+ "192.168.2.10": {"name": "web", "mac": "de:ad:be:ef:00:00"}
|
|
+ }
|
|
+ },
|
|
+ "connection": "myconnection",
|
|
+ "username": "user",
|
|
+ "password": "secret",
|
|
+ }
|
|
+ calls = [
|
|
+ network_update_call(
|
|
+ "mynet", "br2", "bridge", **expected_update_kwargs, test=True
|
|
+ )
|
|
+ ]
|
|
+ if test:
|
|
+ assert calls == update_mock.call_args_list
|
|
+ autostart_mock.assert_not_called()
|
|
+ else:
|
|
+ calls.append(
|
|
+ network_update_call(
|
|
+ "mynet", "br2", "bridge", **expected_update_kwargs, test=False
|
|
+ )
|
|
+ )
|
|
+ assert calls == update_mock.call_args_list
|
|
+ autostart_mock.assert_called_with(
|
|
+ "mynet",
|
|
+ state="off",
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+
|
|
+
|
|
+def test_network_defined_error(test):
|
|
+ """
|
|
+ network_defined state tests if an error is triggered by libvirt.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ define_mock = MagicMock(return_value=True)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.network_info": MagicMock(
|
|
+ side_effect=virt.libvirt.libvirtError("Some error")
|
|
+ )
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "mynet",
|
|
+ "changes": {},
|
|
+ "result": False,
|
|
+ "comment": "Some error",
|
|
+ } == virt.network_defined("mynet", "br2", "bridge")
|
|
+ define_mock.assert_not_called()
|
|
+
|
|
+
|
|
+def test_network_running_not_existing(test):
|
|
+ """
|
|
+ network_running state test cases, non-existing network case.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ define_mock = MagicMock(return_value=True)
|
|
+ start_mock = MagicMock(return_value=True)
|
|
+ # Non-existing network case
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.network_info": MagicMock(
|
|
+ side_effect=[{}, {"mynet": {"active": False}}]
|
|
+ ),
|
|
+ "virt.network_define": define_mock,
|
|
+ "virt.network_start": start_mock,
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "mynet",
|
|
+ "changes": {"mynet": "Network defined and started"},
|
|
+ "comment": "Network mynet defined and started",
|
|
+ "result": None if test else True,
|
|
+ } == virt.network_running(
|
|
+ "mynet",
|
|
+ "br2",
|
|
+ "bridge",
|
|
+ vport="openvswitch",
|
|
+ tag=180,
|
|
+ ipv4_config={
|
|
+ "cidr": "192.168.2.0/24",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
+ {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
+ ],
|
|
+ },
|
|
+ ipv6_config={
|
|
+ "cidr": "2001:db8:ca2:2::1/64",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "2001:db8:ca2:1::10", "end": "2001:db8:ca2::1f"},
|
|
+ ],
|
|
+ },
|
|
+ mtu=9000,
|
|
+ domain={"name": "acme.lab"},
|
|
+ nat={"ports": {"start": 1024, "end": 2048}},
|
|
+ interfaces="eth0 eth1",
|
|
+ addresses="0000:01:02.4 0000:01:02.5",
|
|
+ physical_function="eth4",
|
|
+ dns={
|
|
+ "hosts": {
|
|
+ "192.168.2.10": {"name": "web", "mac": "de:ad:be:ef:00:00"}
|
|
+ }
|
|
+ },
|
|
+ autostart=False,
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ if not test:
|
|
+ define_mock.assert_called_with(
|
|
+ "mynet",
|
|
+ "br2",
|
|
+ "bridge",
|
|
+ vport="openvswitch",
|
|
+ tag=180,
|
|
+ autostart=False,
|
|
+ start=False,
|
|
+ ipv4_config={
|
|
+ "cidr": "192.168.2.0/24",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
+ {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
+ ],
|
|
+ },
|
|
+ ipv6_config={
|
|
+ "cidr": "2001:db8:ca2:2::1/64",
|
|
+ "dhcp_ranges": [
|
|
+ {"start": "2001:db8:ca2:1::10", "end": "2001:db8:ca2::1f"},
|
|
+ ],
|
|
+ },
|
|
+ mtu=9000,
|
|
+ domain={"name": "acme.lab"},
|
|
+ nat={"ports": {"start": 1024, "end": 2048}},
|
|
+ interfaces="eth0 eth1",
|
|
+ addresses="0000:01:02.4 0000:01:02.5",
|
|
+ physical_function="eth4",
|
|
+ dns={
|
|
+ "hosts": {
|
|
+ "192.168.2.10": {"name": "web", "mac": "de:ad:be:ef:00:00"}
|
|
+ }
|
|
+ },
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ start_mock.assert_called_with(
|
|
+ "mynet",
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ else:
|
|
+ define_mock.assert_not_called()
|
|
+ start_mock.assert_not_called()
|
|
+
|
|
+
|
|
+def test_network_running_nochange(test):
|
|
+ """
|
|
+ network_running state test cases, no change case case.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ define_mock = MagicMock(return_value=True)
|
|
+ update_mock = MagicMock(return_value=False)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.network_info": MagicMock(
|
|
+ return_value={"mynet": {"active": True, "autostart": True}}
|
|
+ ),
|
|
+ "virt.network_define": define_mock,
|
|
+ "virt.network_update": update_mock,
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "mynet",
|
|
+ "changes": {},
|
|
+ "comment": "Network mynet unchanged and is running",
|
|
+ "result": None if test else True,
|
|
+ } == virt.network_running("mynet", "br2", "bridge")
|
|
+ assert [
|
|
+ network_update_call("mynet", "br2", "bridge", test=True)
|
|
+ ] == update_mock.call_args_list
|
|
+
|
|
+
|
|
+def test_network_running_stopped(test):
|
|
+ """
|
|
+ network_running state test cases, network stopped case.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ define_mock = MagicMock(return_value=True)
|
|
+ start_mock = MagicMock(return_value=True)
|
|
+ update_mock = MagicMock(return_value=False)
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ { # pylint: disable=no-member
|
|
+ "virt.network_info": MagicMock(
|
|
+ return_value={"mynet": {"active": False, "autostart": True}}
|
|
+ ),
|
|
+ "virt.network_start": start_mock,
|
|
+ "virt.network_define": define_mock,
|
|
+ "virt.network_update": update_mock,
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "mynet",
|
|
+ "changes": {"mynet": "Network started"},
|
|
+ "comment": "Network mynet unchanged and started",
|
|
+ "result": None if test else True,
|
|
+ } == virt.network_running(
|
|
+ "mynet",
|
|
+ "br2",
|
|
+ "bridge",
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ assert [
|
|
+ network_update_call(
|
|
+ "mynet",
|
|
+ "br2",
|
|
+ "bridge",
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ test=True,
|
|
+ )
|
|
+ ] == update_mock.call_args_list
|
|
+ if not test:
|
|
+ start_mock.assert_called_with(
|
|
+ "mynet",
|
|
+ connection="myconnection",
|
|
+ username="user",
|
|
+ password="secret",
|
|
+ )
|
|
+ else:
|
|
+ start_mock.assert_not_called()
|
|
+
|
|
+
|
|
+def test_network_running_error(test):
|
|
+ """
|
|
+ network_running state test cases, libvirt error case.
|
|
+ """
|
|
+ with patch.dict(virt.__opts__, {"test": test}):
|
|
+ with patch.dict(
|
|
+ virt.__salt__,
|
|
+ {
|
|
+ "virt.network_info": MagicMock(
|
|
+ side_effect=virt.libvirt.libvirtError("Some error")
|
|
+ ),
|
|
+ },
|
|
+ ):
|
|
+ assert {
|
|
+ "name": "mynet",
|
|
+ "changes": {},
|
|
+ "comment": "Some error",
|
|
+ "result": False,
|
|
+ } == virt.network_running("mynet", "br2", "bridge")
|
|
diff --git a/tests/pytests/unit/utils/test_xmlutil.py b/tests/pytests/unit/utils/test_xmlutil.py
|
|
index 2bcaff3a17..aed3e42e06 100644
|
|
--- a/tests/pytests/unit/utils/test_xmlutil.py
|
|
+++ b/tests/pytests/unit/utils/test_xmlutil.py
|
|
@@ -208,3 +208,17 @@ def test_change_xml_template_list(xml_doc):
|
|
assert ["1024", "512"] == [
|
|
n.get("size") for n in xml_doc.findall("memtune/hugepages/page")
|
|
]
|
|
+
|
|
+
|
|
+def test_strip_spaces():
|
|
+ xml_str = """<domain>
|
|
+ <name>test01</name>
|
|
+ <memory unit="MiB" >1024</memory>
|
|
+ </domain>
|
|
+ """
|
|
+ expected_str = (
|
|
+ b'<domain><name>test01</name><memory unit="MiB">1024</memory></domain>'
|
|
+ )
|
|
+
|
|
+ node = ET.fromstring(xml_str)
|
|
+ assert expected_str == ET.tostring(xml.strip_spaces(node))
|
|
diff --git a/tests/unit/modules/test_linux_sysctl.py b/tests/unit/modules/test_linux_sysctl.py
|
|
deleted file mode 100644
|
|
index 7f463bb7ab..0000000000
|
|
--- a/tests/unit/modules/test_linux_sysctl.py
|
|
+++ /dev/null
|
|
@@ -1,173 +0,0 @@
|
|
-"""
|
|
- :codeauthor: jmoney <justin@saltstack.com>
|
|
-"""
|
|
-
|
|
-
|
|
-import salt.modules.linux_sysctl as linux_sysctl
|
|
-import salt.modules.systemd_service as systemd
|
|
-from salt.exceptions import CommandExecutionError
|
|
-from tests.support.mixins import LoaderModuleMockMixin
|
|
-from tests.support.mock import MagicMock, mock_open, patch
|
|
-from tests.support.unit import TestCase
|
|
-
|
|
-
|
|
-class LinuxSysctlTestCase(TestCase, LoaderModuleMockMixin):
|
|
- """
|
|
- TestCase for salt.modules.linux_sysctl module
|
|
- """
|
|
-
|
|
- def setup_loader_modules(self):
|
|
- return {linux_sysctl: {}, systemd: {}}
|
|
-
|
|
- def test_get(self):
|
|
- """
|
|
- Tests the return of get function
|
|
- """
|
|
- mock_cmd = MagicMock(return_value=1)
|
|
- with patch.dict(linux_sysctl.__salt__, {"cmd.run": mock_cmd}):
|
|
- self.assertEqual(linux_sysctl.get("net.ipv4.ip_forward"), 1)
|
|
-
|
|
- def test_assign_proc_sys_failed(self):
|
|
- """
|
|
- Tests if /proc/sys/<kernel-subsystem> exists or not
|
|
- """
|
|
- with patch("os.path.exists", MagicMock(return_value=False)):
|
|
- cmd = {
|
|
- "pid": 1337,
|
|
- "retcode": 0,
|
|
- "stderr": "",
|
|
- "stdout": "net.ipv4.ip_forward = 1",
|
|
- }
|
|
- mock_cmd = MagicMock(return_value=cmd)
|
|
- with patch.dict(linux_sysctl.__salt__, {"cmd.run_all": mock_cmd}):
|
|
- self.assertRaises(
|
|
- CommandExecutionError, linux_sysctl.assign, "net.ipv4.ip_forward", 1
|
|
- )
|
|
-
|
|
- def test_assign_cmd_failed(self):
|
|
- """
|
|
- Tests if the assignment was successful or not
|
|
- """
|
|
- with patch("os.path.exists", MagicMock(return_value=True)):
|
|
- cmd = {
|
|
- "pid": 1337,
|
|
- "retcode": 0,
|
|
- "stderr": 'sysctl: setting key "net.ipv4.ip_forward": Invalid argument',
|
|
- "stdout": "net.ipv4.ip_forward = backward",
|
|
- }
|
|
- mock_cmd = MagicMock(return_value=cmd)
|
|
- with patch.dict(linux_sysctl.__salt__, {"cmd.run_all": mock_cmd}):
|
|
- self.assertRaises(
|
|
- CommandExecutionError,
|
|
- linux_sysctl.assign,
|
|
- "net.ipv4.ip_forward",
|
|
- "backward",
|
|
- )
|
|
-
|
|
- def test_assign_success(self):
|
|
- """
|
|
- Tests the return of successful assign function
|
|
- """
|
|
- with patch("os.path.exists", MagicMock(return_value=True)):
|
|
- cmd = {
|
|
- "pid": 1337,
|
|
- "retcode": 0,
|
|
- "stderr": "",
|
|
- "stdout": "net.ipv4.ip_forward = 1",
|
|
- }
|
|
- ret = {"net.ipv4.ip_forward": "1"}
|
|
- mock_cmd = MagicMock(return_value=cmd)
|
|
- with patch.dict(linux_sysctl.__salt__, {"cmd.run_all": mock_cmd}):
|
|
- self.assertEqual(linux_sysctl.assign("net.ipv4.ip_forward", 1), ret)
|
|
-
|
|
- def test_persist_no_conf_failure(self):
|
|
- """
|
|
- Tests adding of config file failure
|
|
- """
|
|
- asn_cmd = {
|
|
- "pid": 1337,
|
|
- "retcode": 0,
|
|
- "stderr": "sysctl: permission denied",
|
|
- "stdout": "",
|
|
- }
|
|
- mock_asn_cmd = MagicMock(return_value=asn_cmd)
|
|
- cmd = "sysctl -w net.ipv4.ip_forward=1"
|
|
- mock_cmd = MagicMock(return_value=cmd)
|
|
- with patch.dict(
|
|
- linux_sysctl.__salt__,
|
|
- {"cmd.run_stdout": mock_cmd, "cmd.run_all": mock_asn_cmd},
|
|
- ):
|
|
- with patch("salt.utils.files.fopen", mock_open()) as m_open:
|
|
- self.assertRaises(
|
|
- CommandExecutionError,
|
|
- linux_sysctl.persist,
|
|
- "net.ipv4.ip_forward",
|
|
- 1,
|
|
- config=None,
|
|
- )
|
|
-
|
|
- def test_persist_no_conf_success(self):
|
|
- """
|
|
- Tests successful add of config file when previously not one
|
|
- """
|
|
- config = "/etc/sysctl.conf"
|
|
- with patch("os.path.isfile", MagicMock(return_value=False)), patch(
|
|
- "os.path.exists", MagicMock(return_value=True)
|
|
- ):
|
|
- asn_cmd = {
|
|
- "pid": 1337,
|
|
- "retcode": 0,
|
|
- "stderr": "",
|
|
- "stdout": "net.ipv4.ip_forward = 1",
|
|
- }
|
|
- mock_asn_cmd = MagicMock(return_value=asn_cmd)
|
|
-
|
|
- sys_cmd = "systemd 208\n+PAM +LIBWRAP"
|
|
- mock_sys_cmd = MagicMock(return_value=sys_cmd)
|
|
-
|
|
- with patch("salt.utils.files.fopen", mock_open()) as m_open, patch.dict(
|
|
- linux_sysctl.__context__, {"salt.utils.systemd.version": 232}
|
|
- ), patch.dict(
|
|
- linux_sysctl.__salt__,
|
|
- {"cmd.run_stdout": mock_sys_cmd, "cmd.run_all": mock_asn_cmd},
|
|
- ), patch.dict(
|
|
- systemd.__context__,
|
|
- {"salt.utils.systemd.booted": True, "salt.utils.systemd.version": 232},
|
|
- ):
|
|
- linux_sysctl.persist("net.ipv4.ip_forward", 1, config=config)
|
|
- writes = m_open.write_calls()
|
|
- assert writes == ["#\n# Kernel sysctl configuration\n#\n"], writes
|
|
-
|
|
- def test_persist_read_conf_success(self):
|
|
- """
|
|
- Tests sysctl.conf read success
|
|
- """
|
|
- with patch("os.path.isfile", MagicMock(return_value=True)), patch(
|
|
- "os.path.exists", MagicMock(return_value=True)
|
|
- ):
|
|
- asn_cmd = {
|
|
- "pid": 1337,
|
|
- "retcode": 0,
|
|
- "stderr": "",
|
|
- "stdout": "net.ipv4.ip_forward = 1",
|
|
- }
|
|
- mock_asn_cmd = MagicMock(return_value=asn_cmd)
|
|
-
|
|
- sys_cmd = "systemd 208\n+PAM +LIBWRAP"
|
|
- mock_sys_cmd = MagicMock(return_value=sys_cmd)
|
|
-
|
|
- with patch("salt.utils.files.fopen", mock_open()):
|
|
- with patch.dict(
|
|
- linux_sysctl.__context__, {"salt.utils.systemd.version": 232}
|
|
- ):
|
|
- with patch.dict(
|
|
- linux_sysctl.__salt__,
|
|
- {"cmd.run_stdout": mock_sys_cmd, "cmd.run_all": mock_asn_cmd},
|
|
- ):
|
|
- with patch.dict(
|
|
- systemd.__context__, {"salt.utils.systemd.booted": True}
|
|
- ):
|
|
- self.assertEqual(
|
|
- linux_sysctl.persist("net.ipv4.ip_forward", 1),
|
|
- "Updated",
|
|
- )
|
|
diff --git a/tests/unit/modules/test_virt.py b/tests/unit/modules/test_virt.py
|
|
index 91dee2098d..f717513944 100644
|
|
--- a/tests/unit/modules/test_virt.py
|
|
+++ b/tests/unit/modules/test_virt.py
|
|
@@ -598,7 +598,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
"swap_hard_limit": "1g",
|
|
"min_guarantee": "256m",
|
|
"hugepages": [
|
|
- {"nodeset": "", "size": "128m"},
|
|
+ {"size": "128m"},
|
|
{"nodeset": "0", "size": "256m"},
|
|
{"nodeset": "1", "size": "512m"},
|
|
],
|
|
@@ -1881,70 +1881,6 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
],
|
|
)
|
|
|
|
- def test_diff_nics(self):
|
|
- """
|
|
- Test virt._diff_nics()
|
|
- """
|
|
- old_nics = ET.fromstring(
|
|
- """
|
|
- <devices>
|
|
- <interface type='network'>
|
|
- <mac address='52:54:00:39:02:b1'/>
|
|
- <source network='default'/>
|
|
- <model type='virtio'/>
|
|
- <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
- </interface>
|
|
- <interface type='network'>
|
|
- <mac address='52:54:00:39:02:b2'/>
|
|
- <source network='admin'/>
|
|
- <model type='virtio'/>
|
|
- <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
- </interface>
|
|
- <interface type='network'>
|
|
- <mac address='52:54:00:39:02:b3'/>
|
|
- <source network='admin'/>
|
|
- <model type='virtio'/>
|
|
- <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
- </interface>
|
|
- </devices>
|
|
- """
|
|
- ).findall("interface")
|
|
-
|
|
- new_nics = ET.fromstring(
|
|
- """
|
|
- <devices>
|
|
- <interface type='network'>
|
|
- <mac address='52:54:00:39:02:b1'/>
|
|
- <source network='default'/>
|
|
- <model type='virtio'/>
|
|
- </interface>
|
|
- <interface type='network'>
|
|
- <mac address='52:54:00:39:02:b2'/>
|
|
- <source network='default'/>
|
|
- <model type='virtio'/>
|
|
- </interface>
|
|
- <interface type='network'>
|
|
- <mac address='52:54:00:39:02:b4'/>
|
|
- <source network='admin'/>
|
|
- <model type='virtio'/>
|
|
- </interface>
|
|
- </devices>
|
|
- """
|
|
- ).findall("interface")
|
|
- ret = virt._diff_interface_lists(old_nics, new_nics)
|
|
- self.assertEqual(
|
|
- [nic.find("mac").get("address") for nic in ret["unchanged"]],
|
|
- ["52:54:00:39:02:b1"],
|
|
- )
|
|
- self.assertEqual(
|
|
- [nic.find("mac").get("address") for nic in ret["new"]],
|
|
- ["52:54:00:39:02:b2", "52:54:00:39:02:b4"],
|
|
- )
|
|
- self.assertEqual(
|
|
- [nic.find("mac").get("address") for nic in ret["deleted"]],
|
|
- ["52:54:00:39:02:b2", "52:54:00:39:02:b3"],
|
|
- )
|
|
-
|
|
def test_init(self):
|
|
"""
|
|
Test init() function
|
|
@@ -3160,7 +3096,12 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
"source_file": None,
|
|
"model": "ide",
|
|
},
|
|
- {"name": "added", "size": 2048, "iothreads": True},
|
|
+ {
|
|
+ "name": "added",
|
|
+ "size": 2048,
|
|
+ "io": "threads",
|
|
+ "iothread_id": 2,
|
|
+ },
|
|
],
|
|
)
|
|
added_disk_path = os.path.join(
|
|
@@ -3196,6 +3137,9 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
self.assertEqual(
|
|
"threads", setxml.find("devices/disk[3]/driver").get("io")
|
|
)
|
|
+ self.assertEqual(
|
|
+ "2", setxml.find("devices/disk[3]/driver").get("iothread")
|
|
+ )
|
|
|
|
# Update nics case
|
|
yaml_config = """
|
|
@@ -3245,7 +3189,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
devattach_mock.reset_mock()
|
|
devdetach_mock.reset_mock()
|
|
ret = virt.update("my_vm", nic_profile=None, interfaces=[])
|
|
- self.assertEqual([], ret["interface"]["attached"])
|
|
+ self.assertFalse(ret["interface"].get("attached"))
|
|
self.assertEqual(2, len(ret["interface"]["detached"]))
|
|
devattach_mock.assert_not_called()
|
|
devdetach_mock.assert_called()
|
|
@@ -3254,7 +3198,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
devattach_mock.reset_mock()
|
|
devdetach_mock.reset_mock()
|
|
ret = virt.update("my_vm", disk_profile=None, disks=[])
|
|
- self.assertEqual([], ret["disk"]["attached"])
|
|
+ self.assertFalse(ret["disk"].get("attached"))
|
|
self.assertEqual(3, len(ret["disk"]["detached"]))
|
|
devattach_mock.assert_not_called()
|
|
devdetach_mock.assert_called()
|
|
@@ -3540,8 +3484,8 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
)
|
|
|
|
self.assertTrue(ret["definition"])
|
|
- self.assertFalse(ret["disk"]["attached"])
|
|
- self.assertFalse(ret["disk"]["detached"])
|
|
+ self.assertFalse(ret["disk"].get("attached"))
|
|
+ self.assertFalse(ret["disk"].get("detached"))
|
|
self.assertEqual(
|
|
[
|
|
{
|
|
@@ -6119,59 +6063,6 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
}
|
|
self.assertEqual(expected, caps)
|
|
|
|
- def test_network(self):
|
|
- """
|
|
- Test virt._get_net_xml()
|
|
- """
|
|
- xml_data = virt._gen_net_xml("network", "main", "bridge", "openvswitch")
|
|
- root = ET.fromstring(xml_data)
|
|
- self.assertEqual(root.find("name").text, "network")
|
|
- self.assertEqual(root.find("bridge").attrib["name"], "main")
|
|
- self.assertEqual(root.find("forward").attrib["mode"], "bridge")
|
|
- self.assertEqual(root.find("virtualport").attrib["type"], "openvswitch")
|
|
-
|
|
- def test_network_nat(self):
|
|
- """
|
|
- Test virt._get_net_xml() in a nat setup
|
|
- """
|
|
- xml_data = virt._gen_net_xml(
|
|
- "network",
|
|
- "main",
|
|
- "nat",
|
|
- None,
|
|
- ip_configs=[
|
|
- {
|
|
- "cidr": "192.168.2.0/24",
|
|
- "dhcp_ranges": [
|
|
- {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
- {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
- ],
|
|
- }
|
|
- ],
|
|
- )
|
|
- root = ET.fromstring(xml_data)
|
|
- self.assertEqual(root.find("name").text, "network")
|
|
- self.assertEqual(root.find("bridge").attrib["name"], "main")
|
|
- self.assertEqual(root.find("forward").attrib["mode"], "nat")
|
|
- self.assertEqual(
|
|
- root.find("./ip[@address='192.168.2.0']").attrib["prefix"], "24"
|
|
- )
|
|
- self.assertEqual(
|
|
- root.find("./ip[@address='192.168.2.0']").attrib["family"], "ipv4"
|
|
- )
|
|
- self.assertEqual(
|
|
- root.find(
|
|
- "./ip[@address='192.168.2.0']/dhcp/range[@start='192.168.2.10']"
|
|
- ).attrib["end"],
|
|
- "192.168.2.25",
|
|
- )
|
|
- self.assertEqual(
|
|
- root.find(
|
|
- "./ip[@address='192.168.2.0']/dhcp/range[@start='192.168.2.110']"
|
|
- ).attrib["end"],
|
|
- "192.168.2.125",
|
|
- )
|
|
-
|
|
def test_domain_capabilities(self):
|
|
"""
|
|
Test the virt.domain_capabilities parsing
|
|
diff --git a/tests/unit/states/test_virt.py b/tests/unit/states/test_virt.py
|
|
index dadc6dd08e..2ab73f8af4 100644
|
|
--- a/tests/unit/states/test_virt.py
|
|
+++ b/tests/unit/states/test_virt.py
|
|
@@ -7,7 +7,7 @@ import tempfile
|
|
|
|
import salt.states.virt as virt
|
|
import salt.utils.files
|
|
-from salt.exceptions import CommandExecutionError, SaltInvocationError
|
|
+from salt.exceptions import SaltInvocationError
|
|
from tests.support.mixins import LoaderModuleMockMixin
|
|
from tests.support.mock import MagicMock, mock_open, patch
|
|
from tests.support.runtests import RUNTIME_VARS
|
|
@@ -263,1707 +263,6 @@ class LibvirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
ret,
|
|
)
|
|
|
|
- def test_defined(self):
|
|
- """
|
|
- defined state test cases.
|
|
- """
|
|
- ret = {
|
|
- "name": "myvm",
|
|
- "changes": {},
|
|
- "result": True,
|
|
- "comment": "myvm is running",
|
|
- }
|
|
- with patch.dict(virt.__opts__, {"test": False}):
|
|
- # no change test
|
|
- init_mock = MagicMock(return_value=True)
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- "virt.update": MagicMock(return_value={"definition": False}),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": False}},
|
|
- "comment": "Domain myvm unchanged",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.defined("myvm"), ret)
|
|
-
|
|
- # Test defining a guest with connection details
|
|
- init_mock.reset_mock()
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=[]),
|
|
- "virt.init": init_mock,
|
|
- "virt.update": MagicMock(
|
|
- side_effect=CommandExecutionError("not found")
|
|
- ),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True}},
|
|
- "comment": "Domain myvm defined",
|
|
- }
|
|
- )
|
|
- disks = [
|
|
- {
|
|
- "name": "system",
|
|
- "size": 8192,
|
|
- "overlay_image": True,
|
|
- "pool": "default",
|
|
- "image": "/path/to/image.qcow2",
|
|
- },
|
|
- {"name": "data", "size": 16834},
|
|
- ]
|
|
- ifaces = [
|
|
- {"name": "eth0", "mac": "01:23:45:67:89:AB"},
|
|
- {"name": "eth1", "type": "network", "source": "admin"},
|
|
- ]
|
|
- graphics = {
|
|
- "type": "spice",
|
|
- "listen": {"type": "address", "address": "192.168.0.1"},
|
|
- }
|
|
- serials = [
|
|
- {"type": "tcp", "port": 22223, "protocol": "telnet"},
|
|
- {"type": "pty"},
|
|
- ]
|
|
- consoles = [
|
|
- {"type": "tcp", "port": 22223, "protocol": "telnet"},
|
|
- {"type": "pty"},
|
|
- ]
|
|
- self.assertDictEqual(
|
|
- virt.defined(
|
|
- "myvm",
|
|
- cpu=2,
|
|
- mem=2048,
|
|
- boot_dev="cdrom hd",
|
|
- os_type="linux",
|
|
- arch="i686",
|
|
- vm_type="qemu",
|
|
- disk_profile="prod",
|
|
- disks=disks,
|
|
- nic_profile="prod",
|
|
- interfaces=ifaces,
|
|
- graphics=graphics,
|
|
- seed=False,
|
|
- install=False,
|
|
- pub_key="/path/to/key.pub",
|
|
- priv_key="/path/to/key",
|
|
- hypervisor_features={"kvm-hint-dedicated": True},
|
|
- clock={"utc": True},
|
|
- stop_on_reboot=True,
|
|
- connection="someconnection",
|
|
- username="libvirtuser",
|
|
- password="supersecret",
|
|
- serials=serials,
|
|
- consoles=consoles,
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- init_mock.assert_called_with(
|
|
- "myvm",
|
|
- cpu=2,
|
|
- mem=2048,
|
|
- boot_dev="cdrom hd",
|
|
- os_type="linux",
|
|
- arch="i686",
|
|
- disk="prod",
|
|
- disks=disks,
|
|
- nic="prod",
|
|
- interfaces=ifaces,
|
|
- graphics=graphics,
|
|
- hypervisor="qemu",
|
|
- seed=False,
|
|
- boot=None,
|
|
- numatune=None,
|
|
- install=False,
|
|
- start=False,
|
|
- pub_key="/path/to/key.pub",
|
|
- priv_key="/path/to/key",
|
|
- hypervisor_features={"kvm-hint-dedicated": True},
|
|
- clock={"utc": True},
|
|
- stop_on_reboot=True,
|
|
- connection="someconnection",
|
|
- username="libvirtuser",
|
|
- password="supersecret",
|
|
- serials=serials,
|
|
- consoles=consoles,
|
|
- )
|
|
-
|
|
- # Working update case when running
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- "virt.update": MagicMock(
|
|
- return_value={"definition": True, "cpu": True}
|
|
- ),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True, "cpu": True}},
|
|
- "result": True,
|
|
- "comment": "Domain myvm updated",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.defined("myvm", cpu=2), ret)
|
|
-
|
|
- # Working update case when running with boot params
|
|
- boot = {
|
|
- "kernel": "/root/f8-i386-vmlinuz",
|
|
- "initrd": "/root/f8-i386-initrd",
|
|
- "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/",
|
|
- }
|
|
-
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- "virt.update": MagicMock(
|
|
- return_value={"definition": True, "cpu": True}
|
|
- ),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True, "cpu": True}},
|
|
- "result": True,
|
|
- "comment": "Domain myvm updated",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.defined("myvm", boot=boot), ret)
|
|
-
|
|
- # Working update case when stopped
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- "virt.update": MagicMock(return_value={"definition": True}),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True}},
|
|
- "result": True,
|
|
- "comment": "Domain myvm updated",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.defined("myvm", cpu=2), ret)
|
|
-
|
|
- # Failed live update case
|
|
- update_mock = MagicMock(
|
|
- return_value={
|
|
- "definition": True,
|
|
- "cpu": False,
|
|
- "errors": ["some error"],
|
|
- }
|
|
- )
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- "virt.update": update_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {
|
|
- "myvm": {
|
|
- "definition": True,
|
|
- "cpu": False,
|
|
- "errors": ["some error"],
|
|
- }
|
|
- },
|
|
- "result": True,
|
|
- "comment": "Domain myvm updated with live update(s) failures",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(
|
|
- virt.defined("myvm", cpu=2, boot_dev="cdrom hd"), ret
|
|
- )
|
|
- update_mock.assert_called_with(
|
|
- "myvm",
|
|
- cpu=2,
|
|
- boot_dev="cdrom hd",
|
|
- mem=None,
|
|
- disk_profile=None,
|
|
- disks=None,
|
|
- nic_profile=None,
|
|
- interfaces=None,
|
|
- graphics=None,
|
|
- live=True,
|
|
- connection=None,
|
|
- username=None,
|
|
- password=None,
|
|
- boot=None,
|
|
- numatune=None,
|
|
- test=False,
|
|
- hypervisor_features=None,
|
|
- clock=None,
|
|
- serials=None,
|
|
- consoles=None,
|
|
- stop_on_reboot=False,
|
|
- )
|
|
-
|
|
- # Failed definition update case
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- "virt.update": MagicMock(
|
|
- side_effect=[self.mock_libvirt.libvirtError("error message")]
|
|
- ),
|
|
- },
|
|
- ):
|
|
- ret.update({"changes": {}, "result": False, "comment": "error message"})
|
|
- self.assertDictEqual(virt.defined("myvm", cpu=2), ret)
|
|
-
|
|
- # Test dry-run mode
|
|
- with patch.dict(virt.__opts__, {"test": True}):
|
|
- # Guest defined case
|
|
- init_mock = MagicMock(return_value=True)
|
|
- update_mock = MagicMock(side_effect=CommandExecutionError("not found"))
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=[]),
|
|
- "virt.init": init_mock,
|
|
- "virt.update": update_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True}},
|
|
- "result": None,
|
|
- "comment": "Domain myvm defined",
|
|
- }
|
|
- )
|
|
- disks = [
|
|
- {
|
|
- "name": "system",
|
|
- "size": 8192,
|
|
- "overlay_image": True,
|
|
- "pool": "default",
|
|
- "image": "/path/to/image.qcow2",
|
|
- },
|
|
- {"name": "data", "size": 16834},
|
|
- ]
|
|
- ifaces = [
|
|
- {"name": "eth0", "mac": "01:23:45:67:89:AB"},
|
|
- {"name": "eth1", "type": "network", "source": "admin"},
|
|
- ]
|
|
- graphics = {
|
|
- "type": "spice",
|
|
- "listen": {"type": "address", "address": "192.168.0.1"},
|
|
- }
|
|
- self.assertDictEqual(
|
|
- virt.defined(
|
|
- "myvm",
|
|
- cpu=2,
|
|
- mem=2048,
|
|
- os_type="linux",
|
|
- arch="i686",
|
|
- vm_type="qemu",
|
|
- disk_profile="prod",
|
|
- disks=disks,
|
|
- nic_profile="prod",
|
|
- interfaces=ifaces,
|
|
- graphics=graphics,
|
|
- seed=False,
|
|
- install=False,
|
|
- pub_key="/path/to/key.pub",
|
|
- priv_key="/path/to/key",
|
|
- stop_on_reboot=False,
|
|
- connection="someconnection",
|
|
- username="libvirtuser",
|
|
- password="supersecret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- init_mock.assert_not_called()
|
|
- update_mock.assert_not_called()
|
|
-
|
|
- # Guest update case
|
|
- update_mock = MagicMock(return_value={"definition": True})
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- "virt.update": update_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True}},
|
|
- "result": None,
|
|
- "comment": "Domain myvm updated",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.defined("myvm", cpu=2), ret)
|
|
- update_mock.assert_called_with(
|
|
- "myvm",
|
|
- cpu=2,
|
|
- mem=None,
|
|
- disk_profile=None,
|
|
- disks=None,
|
|
- nic_profile=None,
|
|
- interfaces=None,
|
|
- graphics=None,
|
|
- live=True,
|
|
- connection=None,
|
|
- username=None,
|
|
- password=None,
|
|
- boot=None,
|
|
- numatune=None,
|
|
- test=True,
|
|
- boot_dev=None,
|
|
- hypervisor_features=None,
|
|
- clock=None,
|
|
- serials=None,
|
|
- consoles=None,
|
|
- stop_on_reboot=False,
|
|
- )
|
|
-
|
|
- # No changes case
|
|
- update_mock = MagicMock(return_value={"definition": False})
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- "virt.update": update_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": False}},
|
|
- "result": True,
|
|
- "comment": "Domain myvm unchanged",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.defined("myvm"), ret)
|
|
- update_mock.assert_called_with(
|
|
- "myvm",
|
|
- cpu=None,
|
|
- mem=None,
|
|
- disk_profile=None,
|
|
- disks=None,
|
|
- nic_profile=None,
|
|
- interfaces=None,
|
|
- graphics=None,
|
|
- live=True,
|
|
- connection=None,
|
|
- username=None,
|
|
- password=None,
|
|
- boot=None,
|
|
- numatune=None,
|
|
- test=True,
|
|
- boot_dev=None,
|
|
- hypervisor_features=None,
|
|
- clock=None,
|
|
- serials=None,
|
|
- consoles=None,
|
|
- stop_on_reboot=False,
|
|
- )
|
|
-
|
|
- def test_running(self):
|
|
- """
|
|
- running state test cases.
|
|
- """
|
|
- ret = {
|
|
- "name": "myvm",
|
|
- "changes": {},
|
|
- "result": True,
|
|
- "comment": "myvm is running",
|
|
- }
|
|
- with patch.dict(virt.__opts__, {"test": False}):
|
|
- # Test starting an existing guest without changing it
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}),
|
|
- "virt.start": MagicMock(return_value=0),
|
|
- "virt.update": MagicMock(return_value={"definition": False}),
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"started": True}},
|
|
- "comment": "Domain myvm started",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.running("myvm"), ret)
|
|
-
|
|
- # Test defining and starting a guest the old way
|
|
- init_mock = MagicMock(return_value=True)
|
|
- start_mock = MagicMock(return_value=0)
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}),
|
|
- "virt.init": init_mock,
|
|
- "virt.start": start_mock,
|
|
- "virt.list_domains": MagicMock(return_value=[]),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True, "started": True}},
|
|
- "comment": "Domain myvm defined and started",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(
|
|
- virt.running(
|
|
- "myvm",
|
|
- cpu=2,
|
|
- mem=2048,
|
|
- disks=[{"name": "system", "image": "/path/to/img.qcow2"}],
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- init_mock.assert_called_with(
|
|
- "myvm",
|
|
- cpu=2,
|
|
- mem=2048,
|
|
- os_type=None,
|
|
- arch=None,
|
|
- boot=None,
|
|
- numatune=None,
|
|
- disk=None,
|
|
- disks=[{"name": "system", "image": "/path/to/img.qcow2"}],
|
|
- nic=None,
|
|
- interfaces=None,
|
|
- graphics=None,
|
|
- hypervisor=None,
|
|
- start=False,
|
|
- seed=True,
|
|
- install=True,
|
|
- pub_key=None,
|
|
- priv_key=None,
|
|
- boot_dev=None,
|
|
- hypervisor_features=None,
|
|
- clock=None,
|
|
- stop_on_reboot=False,
|
|
- connection=None,
|
|
- username=None,
|
|
- password=None,
|
|
- serials=None,
|
|
- consoles=None,
|
|
- )
|
|
- start_mock.assert_called_with(
|
|
- "myvm", connection=None, username=None, password=None
|
|
- )
|
|
-
|
|
- # Test defining and starting a guest the new way with connection details
|
|
- init_mock.reset_mock()
|
|
- start_mock.reset_mock()
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}),
|
|
- "virt.init": init_mock,
|
|
- "virt.start": start_mock,
|
|
- "virt.list_domains": MagicMock(return_value=[]),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True, "started": True}},
|
|
- "comment": "Domain myvm defined and started",
|
|
- }
|
|
- )
|
|
- disks = [
|
|
- {
|
|
- "name": "system",
|
|
- "size": 8192,
|
|
- "overlay_image": True,
|
|
- "pool": "default",
|
|
- "image": "/path/to/image.qcow2",
|
|
- },
|
|
- {"name": "data", "size": 16834},
|
|
- ]
|
|
- ifaces = [
|
|
- {"name": "eth0", "mac": "01:23:45:67:89:AB"},
|
|
- {"name": "eth1", "type": "network", "source": "admin"},
|
|
- ]
|
|
- graphics = {
|
|
- "type": "spice",
|
|
- "listen": {"type": "address", "address": "192.168.0.1"},
|
|
- }
|
|
- self.assertDictEqual(
|
|
- virt.running(
|
|
- "myvm",
|
|
- cpu=2,
|
|
- mem=2048,
|
|
- os_type="linux",
|
|
- arch="i686",
|
|
- vm_type="qemu",
|
|
- disk_profile="prod",
|
|
- disks=disks,
|
|
- nic_profile="prod",
|
|
- interfaces=ifaces,
|
|
- graphics=graphics,
|
|
- seed=False,
|
|
- install=False,
|
|
- pub_key="/path/to/key.pub",
|
|
- priv_key="/path/to/key",
|
|
- boot_dev="network hd",
|
|
- stop_on_reboot=True,
|
|
- connection="someconnection",
|
|
- username="libvirtuser",
|
|
- password="supersecret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- init_mock.assert_called_with(
|
|
- "myvm",
|
|
- cpu=2,
|
|
- mem=2048,
|
|
- os_type="linux",
|
|
- arch="i686",
|
|
- disk="prod",
|
|
- disks=disks,
|
|
- nic="prod",
|
|
- interfaces=ifaces,
|
|
- graphics=graphics,
|
|
- hypervisor="qemu",
|
|
- seed=False,
|
|
- boot=None,
|
|
- numatune=None,
|
|
- install=False,
|
|
- start=False,
|
|
- pub_key="/path/to/key.pub",
|
|
- priv_key="/path/to/key",
|
|
- boot_dev="network hd",
|
|
- hypervisor_features=None,
|
|
- clock=None,
|
|
- stop_on_reboot=True,
|
|
- connection="someconnection",
|
|
- username="libvirtuser",
|
|
- password="supersecret",
|
|
- serials=None,
|
|
- consoles=None,
|
|
- )
|
|
- start_mock.assert_called_with(
|
|
- "myvm",
|
|
- connection="someconnection",
|
|
- username="libvirtuser",
|
|
- password="supersecret",
|
|
- )
|
|
-
|
|
- # Test with existing guest, but start raising an error
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}),
|
|
- "virt.update": MagicMock(return_value={"definition": False}),
|
|
- "virt.start": MagicMock(
|
|
- side_effect=[
|
|
- self.mock_libvirt.libvirtError("libvirt error msg")
|
|
- ]
|
|
- ),
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {}},
|
|
- "result": False,
|
|
- "comment": "libvirt error msg",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.running("myvm"), ret)
|
|
-
|
|
- # Working update case when running
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
- "virt.update": MagicMock(
|
|
- return_value={"definition": True, "cpu": True}
|
|
- ),
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True, "cpu": True}},
|
|
- "result": True,
|
|
- "comment": "Domain myvm updated",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.running("myvm", cpu=2, update=True), ret)
|
|
-
|
|
- # Working update case when running with boot params
|
|
- boot = {
|
|
- "kernel": "/root/f8-i386-vmlinuz",
|
|
- "initrd": "/root/f8-i386-initrd",
|
|
- "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/",
|
|
- }
|
|
-
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
- "virt.update": MagicMock(
|
|
- return_value={"definition": True, "cpu": True}
|
|
- ),
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True, "cpu": True}},
|
|
- "result": True,
|
|
- "comment": "Domain myvm updated",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.running("myvm", boot=boot, update=True), ret)
|
|
-
|
|
- # Working update case when stopped
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}),
|
|
- "virt.start": MagicMock(return_value=0),
|
|
- "virt.update": MagicMock(return_value={"definition": True}),
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True, "started": True}},
|
|
- "result": True,
|
|
- "comment": "Domain myvm updated and started",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.running("myvm", cpu=2, update=True), ret)
|
|
-
|
|
- # Failed live update case
|
|
- update_mock = MagicMock(
|
|
- return_value={
|
|
- "definition": True,
|
|
- "cpu": False,
|
|
- "errors": ["some error"],
|
|
- }
|
|
- )
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
- "virt.update": update_mock,
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {
|
|
- "myvm": {
|
|
- "definition": True,
|
|
- "cpu": False,
|
|
- "errors": ["some error"],
|
|
- }
|
|
- },
|
|
- "result": True,
|
|
- "comment": "Domain myvm updated with live update(s) failures",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.running("myvm", cpu=2, update=True), ret)
|
|
- update_mock.assert_called_with(
|
|
- "myvm",
|
|
- cpu=2,
|
|
- mem=None,
|
|
- disk_profile=None,
|
|
- disks=None,
|
|
- nic_profile=None,
|
|
- interfaces=None,
|
|
- graphics=None,
|
|
- live=True,
|
|
- connection=None,
|
|
- username=None,
|
|
- password=None,
|
|
- boot=None,
|
|
- numatune=None,
|
|
- test=False,
|
|
- boot_dev=None,
|
|
- hypervisor_features=None,
|
|
- clock=None,
|
|
- serials=None,
|
|
- consoles=None,
|
|
- stop_on_reboot=False,
|
|
- )
|
|
-
|
|
- # Failed definition update case
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
- "virt.update": MagicMock(
|
|
- side_effect=[self.mock_libvirt.libvirtError("error message")]
|
|
- ),
|
|
- "virt.list_domains": MagicMock(return_value=["myvm"]),
|
|
- },
|
|
- ):
|
|
- ret.update({"changes": {}, "result": False, "comment": "error message"})
|
|
- self.assertDictEqual(virt.running("myvm", cpu=2, update=True), ret)
|
|
-
|
|
- # Test dry-run mode
|
|
- with patch.dict(virt.__opts__, {"test": True}):
|
|
- # Guest defined case
|
|
- init_mock = MagicMock(return_value=True)
|
|
- start_mock = MagicMock(return_value=0)
|
|
- list_mock = MagicMock(return_value=[])
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}),
|
|
- "virt.init": init_mock,
|
|
- "virt.start": start_mock,
|
|
- "virt.list_domains": list_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True, "started": True}},
|
|
- "result": None,
|
|
- "comment": "Domain myvm defined and started",
|
|
- }
|
|
- )
|
|
- disks = [
|
|
- {
|
|
- "name": "system",
|
|
- "size": 8192,
|
|
- "overlay_image": True,
|
|
- "pool": "default",
|
|
- "image": "/path/to/image.qcow2",
|
|
- },
|
|
- {"name": "data", "size": 16834},
|
|
- ]
|
|
- ifaces = [
|
|
- {"name": "eth0", "mac": "01:23:45:67:89:AB"},
|
|
- {"name": "eth1", "type": "network", "source": "admin"},
|
|
- ]
|
|
- graphics = {
|
|
- "type": "spice",
|
|
- "listen": {"type": "address", "address": "192.168.0.1"},
|
|
- }
|
|
- self.assertDictEqual(
|
|
- virt.running(
|
|
- "myvm",
|
|
- cpu=2,
|
|
- mem=2048,
|
|
- os_type="linux",
|
|
- arch="i686",
|
|
- vm_type="qemu",
|
|
- disk_profile="prod",
|
|
- disks=disks,
|
|
- nic_profile="prod",
|
|
- interfaces=ifaces,
|
|
- graphics=graphics,
|
|
- seed=False,
|
|
- install=False,
|
|
- pub_key="/path/to/key.pub",
|
|
- priv_key="/path/to/key",
|
|
- stop_on_reboot=True,
|
|
- connection="someconnection",
|
|
- username="libvirtuser",
|
|
- password="supersecret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- init_mock.assert_not_called()
|
|
- start_mock.assert_not_called()
|
|
-
|
|
- # Guest update case
|
|
- update_mock = MagicMock(return_value={"definition": True})
|
|
- start_mock = MagicMock(return_value=0)
|
|
- list_mock = MagicMock(return_value=["myvm"])
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "stopped"}),
|
|
- "virt.start": start_mock,
|
|
- "virt.update": update_mock,
|
|
- "virt.list_domains": list_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": True, "started": True}},
|
|
- "result": None,
|
|
- "comment": "Domain myvm updated and started",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.running("myvm", cpu=2, update=True), ret)
|
|
- update_mock.assert_called_with(
|
|
- "myvm",
|
|
- cpu=2,
|
|
- mem=None,
|
|
- disk_profile=None,
|
|
- disks=None,
|
|
- nic_profile=None,
|
|
- interfaces=None,
|
|
- graphics=None,
|
|
- live=True,
|
|
- connection=None,
|
|
- username=None,
|
|
- password=None,
|
|
- boot=None,
|
|
- numatune=None,
|
|
- test=True,
|
|
- boot_dev=None,
|
|
- hypervisor_features=None,
|
|
- clock=None,
|
|
- serials=None,
|
|
- consoles=None,
|
|
- stop_on_reboot=False,
|
|
- )
|
|
- start_mock.assert_not_called()
|
|
-
|
|
- # No changes case
|
|
- update_mock = MagicMock(return_value={"definition": False})
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
- "virt.update": update_mock,
|
|
- "virt.list_domains": list_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"myvm": {"definition": False}},
|
|
- "result": True,
|
|
- "comment": "Domain myvm exists and is running",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.running("myvm", update=True), ret)
|
|
- update_mock.assert_called_with(
|
|
- "myvm",
|
|
- cpu=None,
|
|
- mem=None,
|
|
- disk_profile=None,
|
|
- disks=None,
|
|
- nic_profile=None,
|
|
- interfaces=None,
|
|
- graphics=None,
|
|
- live=True,
|
|
- connection=None,
|
|
- username=None,
|
|
- password=None,
|
|
- boot=None,
|
|
- numatune=None,
|
|
- test=True,
|
|
- boot_dev=None,
|
|
- hypervisor_features=None,
|
|
- clock=None,
|
|
- serials=None,
|
|
- consoles=None,
|
|
- stop_on_reboot=False,
|
|
- )
|
|
-
|
|
- def test_stopped(self):
|
|
- """
|
|
- stopped state test cases.
|
|
- """
|
|
- ret = {"name": "myvm", "changes": {}, "result": True}
|
|
-
|
|
- shutdown_mock = MagicMock(return_value=True)
|
|
-
|
|
- # Normal case
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
- "virt.shutdown": shutdown_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"stopped": [{"domain": "myvm", "shutdown": True}]},
|
|
- "comment": "Machine has been shut down",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.stopped("myvm"), ret)
|
|
- shutdown_mock.assert_called_with(
|
|
- "myvm", connection=None, username=None, password=None
|
|
- )
|
|
-
|
|
- # Normal case with user-provided connection parameters
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
- "virt.shutdown": shutdown_mock,
|
|
- },
|
|
- ):
|
|
- self.assertDictEqual(
|
|
- virt.stopped(
|
|
- "myvm",
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- shutdown_mock.assert_called_with(
|
|
- "myvm", connection="myconnection", username="user", password="secret"
|
|
- )
|
|
-
|
|
- # Case where an error occurred during the shutdown
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
- "virt.shutdown": MagicMock(
|
|
- side_effect=self.mock_libvirt.libvirtError("Some error")
|
|
- ),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]},
|
|
- "result": False,
|
|
- "comment": "No changes had happened",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.stopped("myvm"), ret)
|
|
-
|
|
- # Case there the domain doesn't exist
|
|
- with patch.dict(
|
|
- virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])}
|
|
- ): # pylint: disable=no-member
|
|
- ret.update(
|
|
- {"changes": {}, "result": False, "comment": "No changes had happened"}
|
|
- )
|
|
- self.assertDictEqual(virt.stopped("myvm"), ret)
|
|
-
|
|
- # Case where the domain is already stopped
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "shutdown"}),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {"changes": {}, "result": True, "comment": "No changes had happened"}
|
|
- )
|
|
- self.assertDictEqual(virt.stopped("myvm"), ret)
|
|
-
|
|
- def test_powered_off(self):
|
|
- """
|
|
- powered_off state test cases.
|
|
- """
|
|
- ret = {"name": "myvm", "changes": {}, "result": True}
|
|
-
|
|
- stop_mock = MagicMock(return_value=True)
|
|
-
|
|
- # Normal case
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
- "virt.stop": stop_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"unpowered": [{"domain": "myvm", "stop": True}]},
|
|
- "comment": "Machine has been powered off",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.powered_off("myvm"), ret)
|
|
- stop_mock.assert_called_with(
|
|
- "myvm", connection=None, username=None, password=None
|
|
- )
|
|
-
|
|
- # Normal case with user-provided connection parameters
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
- "virt.stop": stop_mock,
|
|
- },
|
|
- ):
|
|
- self.assertDictEqual(
|
|
- virt.powered_off(
|
|
- "myvm",
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- stop_mock.assert_called_with(
|
|
- "myvm", connection="myconnection", username="user", password="secret"
|
|
- )
|
|
-
|
|
- # Case where an error occurred during the poweroff
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "running"}),
|
|
- "virt.stop": MagicMock(
|
|
- side_effect=self.mock_libvirt.libvirtError("Some error")
|
|
- ),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]},
|
|
- "result": False,
|
|
- "comment": "No changes had happened",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.powered_off("myvm"), ret)
|
|
-
|
|
- # Case there the domain doesn't exist
|
|
- with patch.dict(
|
|
- virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])}
|
|
- ): # pylint: disable=no-member
|
|
- ret.update(
|
|
- {"changes": {}, "result": False, "comment": "No changes had happened"}
|
|
- )
|
|
- self.assertDictEqual(virt.powered_off("myvm"), ret)
|
|
-
|
|
- # Case where the domain is already stopped
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.vm_state": MagicMock(return_value={"myvm": "shutdown"}),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {"changes": {}, "result": True, "comment": "No changes had happened"}
|
|
- )
|
|
- self.assertDictEqual(virt.powered_off("myvm"), ret)
|
|
-
|
|
- def test_snapshot(self):
|
|
- """
|
|
- snapshot state test cases.
|
|
- """
|
|
- ret = {"name": "myvm", "changes": {}, "result": True}
|
|
-
|
|
- snapshot_mock = MagicMock(return_value=True)
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.snapshot": snapshot_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"saved": [{"domain": "myvm", "snapshot": True}]},
|
|
- "comment": "Snapshot has been taken",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.snapshot("myvm"), ret)
|
|
- snapshot_mock.assert_called_with(
|
|
- "myvm", suffix=None, connection=None, username=None, password=None
|
|
- )
|
|
-
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.snapshot": snapshot_mock,
|
|
- },
|
|
- ):
|
|
- self.assertDictEqual(
|
|
- virt.snapshot(
|
|
- "myvm",
|
|
- suffix="snap",
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- snapshot_mock.assert_called_with(
|
|
- "myvm",
|
|
- suffix="snap",
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- )
|
|
-
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.snapshot": MagicMock(
|
|
- side_effect=self.mock_libvirt.libvirtError("Some error")
|
|
- ),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]},
|
|
- "result": False,
|
|
- "comment": "No changes had happened",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.snapshot("myvm"), ret)
|
|
-
|
|
- with patch.dict(
|
|
- virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])}
|
|
- ): # pylint: disable=no-member
|
|
- ret.update(
|
|
- {"changes": {}, "result": False, "comment": "No changes had happened"}
|
|
- )
|
|
- self.assertDictEqual(virt.snapshot("myvm"), ret)
|
|
-
|
|
- def test_rebooted(self):
|
|
- """
|
|
- rebooted state test cases.
|
|
- """
|
|
- ret = {"name": "myvm", "changes": {}, "result": True}
|
|
-
|
|
- reboot_mock = MagicMock(return_value=True)
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.reboot": reboot_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"rebooted": [{"domain": "myvm", "reboot": True}]},
|
|
- "comment": "Machine has been rebooted",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.rebooted("myvm"), ret)
|
|
- reboot_mock.assert_called_with(
|
|
- "myvm", connection=None, username=None, password=None
|
|
- )
|
|
-
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.reboot": reboot_mock,
|
|
- },
|
|
- ):
|
|
- self.assertDictEqual(
|
|
- virt.rebooted(
|
|
- "myvm",
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- reboot_mock.assert_called_with(
|
|
- "myvm", connection="myconnection", username="user", password="secret"
|
|
- )
|
|
-
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.list_domains": MagicMock(return_value=["myvm", "vm1"]),
|
|
- "virt.reboot": MagicMock(
|
|
- side_effect=self.mock_libvirt.libvirtError("Some error")
|
|
- ),
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"ignored": [{"domain": "myvm", "issue": "Some error"}]},
|
|
- "result": False,
|
|
- "comment": "No changes had happened",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(virt.rebooted("myvm"), ret)
|
|
-
|
|
- with patch.dict(
|
|
- virt.__salt__, {"virt.list_domains": MagicMock(return_value=[])}
|
|
- ): # pylint: disable=no-member
|
|
- ret.update(
|
|
- {"changes": {}, "result": False, "comment": "No changes had happened"}
|
|
- )
|
|
- self.assertDictEqual(virt.rebooted("myvm"), ret)
|
|
-
|
|
- def test_network_defined(self):
|
|
- """
|
|
- network_defined state test cases.
|
|
- """
|
|
- ret = {"name": "mynet", "changes": {}, "result": True, "comment": ""}
|
|
- with patch.dict(virt.__opts__, {"test": False}):
|
|
- define_mock = MagicMock(return_value=True)
|
|
- # Non-existing network case
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(
|
|
- side_effect=[{}, {"mynet": {"active": False}}]
|
|
- ),
|
|
- "virt.network_define": define_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"mynet": "Network defined"},
|
|
- "comment": "Network mynet defined",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(
|
|
- virt.network_defined(
|
|
- "mynet",
|
|
- "br2",
|
|
- "bridge",
|
|
- vport="openvswitch",
|
|
- tag=180,
|
|
- ipv4_config={
|
|
- "cidr": "192.168.2.0/24",
|
|
- "dhcp_ranges": [
|
|
- {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
- {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
- ],
|
|
- },
|
|
- ipv6_config={
|
|
- "cidr": "2001:db8:ca2:2::1/64",
|
|
- "dhcp_ranges": [
|
|
- {
|
|
- "start": "2001:db8:ca2:1::10",
|
|
- "end": "2001:db8:ca2::1f",
|
|
- },
|
|
- ],
|
|
- },
|
|
- autostart=False,
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- define_mock.assert_called_with(
|
|
- "mynet",
|
|
- "br2",
|
|
- "bridge",
|
|
- vport="openvswitch",
|
|
- tag=180,
|
|
- autostart=False,
|
|
- start=False,
|
|
- ipv4_config={
|
|
- "cidr": "192.168.2.0/24",
|
|
- "dhcp_ranges": [
|
|
- {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
- {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
- ],
|
|
- },
|
|
- ipv6_config={
|
|
- "cidr": "2001:db8:ca2:2::1/64",
|
|
- "dhcp_ranges": [
|
|
- {"start": "2001:db8:ca2:1::10", "end": "2001:db8:ca2::1f"},
|
|
- ],
|
|
- },
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- )
|
|
-
|
|
- # Case where there is nothing to be done
|
|
- define_mock.reset_mock()
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(
|
|
- return_value={"mynet": {"active": True}}
|
|
- ),
|
|
- "virt.network_define": define_mock,
|
|
- },
|
|
- ):
|
|
- ret.update({"changes": {}, "comment": "Network mynet exists"})
|
|
- self.assertDictEqual(
|
|
- virt.network_defined("mynet", "br2", "bridge"), ret
|
|
- )
|
|
-
|
|
- # Error case
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(return_value={}),
|
|
- "virt.network_define": MagicMock(
|
|
- side_effect=self.mock_libvirt.libvirtError("Some error")
|
|
- ),
|
|
- },
|
|
- ):
|
|
- ret.update({"changes": {}, "comment": "Some error", "result": False})
|
|
- self.assertDictEqual(
|
|
- virt.network_defined("mynet", "br2", "bridge"), ret
|
|
- )
|
|
-
|
|
- # Test cases with __opt__['test'] set to True
|
|
- with patch.dict(virt.__opts__, {"test": True}):
|
|
- ret.update({"result": None})
|
|
-
|
|
- # Non-existing network case
|
|
- define_mock.reset_mock()
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(return_value={}),
|
|
- "virt.network_define": define_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"mynet": "Network defined"},
|
|
- "comment": "Network mynet defined",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(
|
|
- virt.network_defined(
|
|
- "mynet",
|
|
- "br2",
|
|
- "bridge",
|
|
- vport="openvswitch",
|
|
- tag=180,
|
|
- ipv4_config={
|
|
- "cidr": "192.168.2.0/24",
|
|
- "dhcp_ranges": [
|
|
- {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
- {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
- ],
|
|
- },
|
|
- ipv6_config={
|
|
- "cidr": "2001:db8:ca2:2::1/64",
|
|
- "dhcp_ranges": [
|
|
- {
|
|
- "start": "2001:db8:ca2:1::10",
|
|
- "end": "2001:db8:ca2::1f",
|
|
- },
|
|
- ],
|
|
- },
|
|
- autostart=False,
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- define_mock.assert_not_called()
|
|
-
|
|
- # Case where there is nothing to be done
|
|
- define_mock.reset_mock()
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(
|
|
- return_value={"mynet": {"active": True}}
|
|
- ),
|
|
- "virt.network_define": define_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {"changes": {}, "comment": "Network mynet exists", "result": True}
|
|
- )
|
|
- self.assertDictEqual(
|
|
- virt.network_defined("mynet", "br2", "bridge"), ret
|
|
- )
|
|
-
|
|
- # Error case
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(
|
|
- side_effect=self.mock_libvirt.libvirtError("Some error")
|
|
- )
|
|
- },
|
|
- ):
|
|
- ret.update({"changes": {}, "comment": "Some error", "result": False})
|
|
- self.assertDictEqual(
|
|
- virt.network_defined("mynet", "br2", "bridge"), ret
|
|
- )
|
|
-
|
|
- def test_network_running(self):
|
|
- """
|
|
- network_running state test cases.
|
|
- """
|
|
- ret = {"name": "mynet", "changes": {}, "result": True, "comment": ""}
|
|
- with patch.dict(virt.__opts__, {"test": False}):
|
|
- define_mock = MagicMock(return_value=True)
|
|
- start_mock = MagicMock(return_value=True)
|
|
- # Non-existing network case
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(
|
|
- side_effect=[{}, {"mynet": {"active": False}}]
|
|
- ),
|
|
- "virt.network_define": define_mock,
|
|
- "virt.network_start": start_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"mynet": "Network defined and started"},
|
|
- "comment": "Network mynet defined and started",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(
|
|
- virt.network_running(
|
|
- "mynet",
|
|
- "br2",
|
|
- "bridge",
|
|
- vport="openvswitch",
|
|
- tag=180,
|
|
- ipv4_config={
|
|
- "cidr": "192.168.2.0/24",
|
|
- "dhcp_ranges": [
|
|
- {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
- {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
- ],
|
|
- },
|
|
- ipv6_config={
|
|
- "cidr": "2001:db8:ca2:2::1/64",
|
|
- "dhcp_ranges": [
|
|
- {
|
|
- "start": "2001:db8:ca2:1::10",
|
|
- "end": "2001:db8:ca2::1f",
|
|
- },
|
|
- ],
|
|
- },
|
|
- autostart=False,
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- define_mock.assert_called_with(
|
|
- "mynet",
|
|
- "br2",
|
|
- "bridge",
|
|
- vport="openvswitch",
|
|
- tag=180,
|
|
- autostart=False,
|
|
- start=False,
|
|
- ipv4_config={
|
|
- "cidr": "192.168.2.0/24",
|
|
- "dhcp_ranges": [
|
|
- {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
- {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
- ],
|
|
- },
|
|
- ipv6_config={
|
|
- "cidr": "2001:db8:ca2:2::1/64",
|
|
- "dhcp_ranges": [
|
|
- {"start": "2001:db8:ca2:1::10", "end": "2001:db8:ca2::1f"},
|
|
- ],
|
|
- },
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- )
|
|
- start_mock.assert_called_with(
|
|
- "mynet",
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- )
|
|
-
|
|
- # Case where there is nothing to be done
|
|
- define_mock.reset_mock()
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(
|
|
- return_value={"mynet": {"active": True}}
|
|
- ),
|
|
- "virt.network_define": define_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {"changes": {}, "comment": "Network mynet exists and is running"}
|
|
- )
|
|
- self.assertDictEqual(
|
|
- virt.network_running("mynet", "br2", "bridge"), ret
|
|
- )
|
|
-
|
|
- # Network existing and stopped case
|
|
- start_mock = MagicMock(return_value=True)
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(
|
|
- return_value={"mynet": {"active": False}}
|
|
- ),
|
|
- "virt.network_start": start_mock,
|
|
- "virt.network_define": define_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"mynet": "Network started"},
|
|
- "comment": "Network mynet exists and started",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(
|
|
- virt.network_running(
|
|
- "mynet",
|
|
- "br2",
|
|
- "bridge",
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- start_mock.assert_called_with(
|
|
- "mynet",
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- )
|
|
-
|
|
- # Error case
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(return_value={}),
|
|
- "virt.network_define": MagicMock(
|
|
- side_effect=self.mock_libvirt.libvirtError("Some error")
|
|
- ),
|
|
- },
|
|
- ):
|
|
- ret.update({"changes": {}, "comment": "Some error", "result": False})
|
|
- self.assertDictEqual(
|
|
- virt.network_running("mynet", "br2", "bridge"), ret
|
|
- )
|
|
-
|
|
- # Test cases with __opt__['test'] set to True
|
|
- with patch.dict(virt.__opts__, {"test": True}):
|
|
- ret.update({"result": None})
|
|
-
|
|
- # Non-existing network case
|
|
- define_mock.reset_mock()
|
|
- start_mock.reset_mock()
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(return_value={}),
|
|
- "virt.network_define": define_mock,
|
|
- "virt.network_start": start_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"mynet": "Network defined and started"},
|
|
- "comment": "Network mynet defined and started",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(
|
|
- virt.network_running(
|
|
- "mynet",
|
|
- "br2",
|
|
- "bridge",
|
|
- vport="openvswitch",
|
|
- tag=180,
|
|
- ipv4_config={
|
|
- "cidr": "192.168.2.0/24",
|
|
- "dhcp_ranges": [
|
|
- {"start": "192.168.2.10", "end": "192.168.2.25"},
|
|
- {"start": "192.168.2.110", "end": "192.168.2.125"},
|
|
- ],
|
|
- },
|
|
- ipv6_config={
|
|
- "cidr": "2001:db8:ca2:2::1/64",
|
|
- "dhcp_ranges": [
|
|
- {
|
|
- "start": "2001:db8:ca2:1::10",
|
|
- "end": "2001:db8:ca2::1f",
|
|
- },
|
|
- ],
|
|
- },
|
|
- autostart=False,
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- define_mock.assert_not_called()
|
|
- start_mock.assert_not_called()
|
|
-
|
|
- # Case where there is nothing to be done
|
|
- define_mock.reset_mock()
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(
|
|
- return_value={"mynet": {"active": True}}
|
|
- ),
|
|
- "virt.network_define": define_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {"changes": {}, "comment": "Network mynet exists and is running"}
|
|
- )
|
|
- self.assertDictEqual(
|
|
- virt.network_running("mynet", "br2", "bridge"), ret
|
|
- )
|
|
-
|
|
- # Network existing and stopped case
|
|
- start_mock = MagicMock(return_value=True)
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(
|
|
- return_value={"mynet": {"active": False}}
|
|
- ),
|
|
- "virt.network_start": start_mock,
|
|
- "virt.network_define": define_mock,
|
|
- },
|
|
- ):
|
|
- ret.update(
|
|
- {
|
|
- "changes": {"mynet": "Network started"},
|
|
- "comment": "Network mynet exists and started",
|
|
- }
|
|
- )
|
|
- self.assertDictEqual(
|
|
- virt.network_running(
|
|
- "mynet",
|
|
- "br2",
|
|
- "bridge",
|
|
- connection="myconnection",
|
|
- username="user",
|
|
- password="secret",
|
|
- ),
|
|
- ret,
|
|
- )
|
|
- start_mock.assert_not_called()
|
|
-
|
|
- # Error case
|
|
- with patch.dict(
|
|
- virt.__salt__,
|
|
- { # pylint: disable=no-member
|
|
- "virt.network_info": MagicMock(
|
|
- side_effect=self.mock_libvirt.libvirtError("Some error")
|
|
- )
|
|
- },
|
|
- ):
|
|
- ret.update({"changes": {}, "comment": "Some error", "result": False})
|
|
- self.assertDictEqual(
|
|
- virt.network_running("mynet", "br2", "bridge"), ret
|
|
- )
|
|
-
|
|
def test_pool_defined(self):
|
|
"""
|
|
pool_defined state test cases.
|
|
--
|
|
2.29.2
|
|
|
|
|