mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
7020 lines
255 KiB
Python
7020 lines
255 KiB
Python
"""
|
|
virt execution module unit tests
|
|
"""
|
|
|
|
import datetime
|
|
import os
|
|
import shutil
|
|
import tempfile
|
|
import xml.etree.ElementTree as ET
|
|
|
|
import pytest
|
|
|
|
import salt.config
|
|
import salt.modules.config as config
|
|
import salt.modules.virt as virt
|
|
import salt.syspaths
|
|
import salt.utils.yaml
|
|
from salt.exceptions import CommandExecutionError, SaltInvocationError
|
|
from tests.support.helpers import dedent
|
|
from tests.support.mixins import LoaderModuleMockMixin
|
|
from tests.support.mock import MagicMock, patch
|
|
from tests.support.unit import TestCase
|
|
|
|
|
|
class LibvirtMock(MagicMock):
|
|
"""
|
|
Libvirt library mock
|
|
"""
|
|
|
|
class virDomain(MagicMock):
|
|
"""
|
|
virDomain mock
|
|
"""
|
|
|
|
class libvirtError(Exception):
|
|
"""
|
|
libvirtError mock
|
|
"""
|
|
|
|
def __init__(self, msg):
|
|
super().__init__(msg)
|
|
self.msg = msg
|
|
|
|
def get_error_message(self):
|
|
return self.msg
|
|
|
|
|
|
class VirtTestCase(TestCase, LoaderModuleMockMixin):
|
|
"""
|
|
Test cases for salt.module.virt
|
|
"""
|
|
|
|
def setup_loader_modules(self):
|
|
self.mock_libvirt = LibvirtMock()
|
|
self.mock_conn = MagicMock()
|
|
self.mock_conn.getStoragePoolCapabilities.return_value = (
|
|
"<storagepoolCapabilities/>"
|
|
)
|
|
self.mock_libvirt.openAuth.return_value = self.mock_conn
|
|
self.mock_popen = MagicMock()
|
|
self.addCleanup(delattr, self, "mock_libvirt")
|
|
self.addCleanup(delattr, self, "mock_conn")
|
|
self.addCleanup(delattr, self, "mock_popen")
|
|
self.mock_subprocess = MagicMock()
|
|
self.mock_subprocess.return_value = (
|
|
self.mock_subprocess
|
|
) # pylint: disable=no-member
|
|
self.mock_subprocess.Popen.return_value = (
|
|
self.mock_popen
|
|
) # pylint: disable=no-member
|
|
loader_globals = {
|
|
"__salt__": {"config.get": config.get, "config.option": config.option},
|
|
"libvirt": self.mock_libvirt,
|
|
"subprocess": self.mock_subprocess,
|
|
}
|
|
return {virt: loader_globals, config: loader_globals}
|
|
|
|
def set_mock_vm(self, name, xml):
|
|
"""
|
|
Define VM to use in tests
|
|
"""
|
|
self.mock_conn.listDefinedDomains.return_value = [
|
|
name
|
|
] # pylint: disable=no-member
|
|
mock_domain = self.mock_libvirt.virDomain()
|
|
self.mock_conn.lookupByName.return_value = (
|
|
mock_domain # pylint: disable=no-member
|
|
)
|
|
mock_domain.XMLDesc.return_value = xml # pylint: disable=no-member
|
|
|
|
# Return state as shutdown
|
|
mock_domain.info.return_value = [
|
|
4,
|
|
2048 * 1024,
|
|
1024 * 1024,
|
|
2,
|
|
1234,
|
|
] # pylint: disable=no-member
|
|
mock_domain.ID.return_value = 1
|
|
mock_domain.name.return_value = name
|
|
return mock_domain
|
|
|
|
def assert_equal_unit(self, actual, expected, unit="KiB"):
|
|
self.assertEqual(actual.get("unit"), unit)
|
|
self.assertEqual(actual.text, str(expected))
|
|
|
|
def test_disk_profile_merge(self):
|
|
"""
|
|
Test virt._disk_profile() when merging with user-defined disks
|
|
"""
|
|
root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images")
|
|
userdisks = [
|
|
{"name": "system", "image": "/path/to/image"},
|
|
{"name": "data", "size": 16384, "format": "raw"},
|
|
]
|
|
|
|
disks = virt._disk_profile(self.mock_conn, "default", "kvm", userdisks, "myvm")
|
|
self.assertEqual(
|
|
[
|
|
{
|
|
"name": "system",
|
|
"device": "disk",
|
|
"size": 8192,
|
|
"format": "qcow2",
|
|
"model": "virtio",
|
|
"filename": "myvm_system.qcow2",
|
|
"image": "/path/to/image",
|
|
"source_file": "{}{}myvm_system.qcow2".format(root_dir, os.sep),
|
|
},
|
|
{
|
|
"name": "data",
|
|
"device": "disk",
|
|
"size": 16384,
|
|
"format": "raw",
|
|
"model": "virtio",
|
|
"filename": "myvm_data.raw",
|
|
"source_file": "{}{}myvm_data.raw".format(root_dir, os.sep),
|
|
},
|
|
],
|
|
disks,
|
|
)
|
|
|
|
def test_boot_default_dev(self):
|
|
"""
|
|
Test virt._gen_xml() default boot device
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64"
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("os/boot").attrib["dev"], "hd")
|
|
self.assertEqual(root.find("os/type").attrib["arch"], "x86_64")
|
|
self.assertEqual(root.find("os/type").text, "hvm")
|
|
|
|
def test_boot_custom_dev(self):
|
|
"""
|
|
Test virt._gen_xml() custom boot device
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
boot_dev="cdrom",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("os/boot").attrib["dev"], "cdrom")
|
|
|
|
def test_boot_multiple_devs(self):
|
|
"""
|
|
Test virt._gen_xml() multiple boot devices
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
boot_dev="cdrom network",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
devs = root.findall(".//boot")
|
|
self.assertTrue(len(devs) == 2)
|
|
|
|
def test_gen_xml_no_nic(self):
|
|
"""
|
|
Test virt._gen_xml() serial console
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
serials=[{"type": "pty"}],
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("devices/serial").attrib["type"], "pty")
|
|
|
|
def test_gen_xml_for_telnet_serial(self):
|
|
"""
|
|
Test virt._gen_xml() telnet serial
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
serials=[{"type": "tcp", "port": 22223, "protocol": "telnet"}],
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("devices/serial").attrib["type"], "tcp")
|
|
self.assertEqual(root.find("devices/serial/source").attrib["service"], "22223")
|
|
self.assertEqual(root.find("devices/serial/protocol").attrib["type"], "telnet")
|
|
|
|
def test_gen_xml_for_telnet_serial_unspecified_port(self):
|
|
"""
|
|
Test virt._gen_xml() telnet serial without any specified port
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
serials=[{"type": "tcp"}],
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("devices/serial").attrib["type"], "tcp")
|
|
self.assertEqual(root.find("devices/serial/source").attrib["service"], "23023")
|
|
self.assertFalse("tls" in root.find("devices/serial/source").keys())
|
|
self.assertEqual(root.find("devices/serial/protocol").attrib["type"], "telnet")
|
|
|
|
def test_gen_xml_for_chardev_types(self):
|
|
"""
|
|
Test virt._gen_xml() consoles and serials of various types
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
consoles=[
|
|
{"type": "pty", "path": "/dev/pts/2", "target_port": 2},
|
|
{"type": "pty", "target_type": "usb-serial"},
|
|
{"type": "stdio"},
|
|
{"type": "file", "path": "/path/to/serial.log"},
|
|
],
|
|
serials=[
|
|
{"type": "pipe", "path": "/tmp/mypipe"},
|
|
{"type": "udp", "host": "127.0.0.1", "port": 1234},
|
|
{"type": "tcp", "port": 22223, "protocol": "raw", "tls": True},
|
|
{"type": "unix", "path": "/path/to/socket"},
|
|
],
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
|
|
self.assertEqual(root.find("devices/console[1]").attrib["type"], "pty")
|
|
self.assertEqual(
|
|
root.find("devices/console[1]/source").attrib["path"], "/dev/pts/2"
|
|
)
|
|
self.assertEqual(root.find("devices/console[1]/target").attrib["port"], "2")
|
|
|
|
self.assertEqual(root.find("devices/console[2]").attrib["type"], "pty")
|
|
self.assertIsNone(root.find("devices/console[2]/source"))
|
|
self.assertEqual(
|
|
root.find("devices/console[2]/target").attrib["type"], "usb-serial"
|
|
)
|
|
|
|
self.assertEqual(root.find("devices/console[3]").attrib["type"], "stdio")
|
|
self.assertIsNone(root.find("devices/console[3]/source"))
|
|
|
|
self.assertEqual(root.find("devices/console[4]").attrib["type"], "file")
|
|
self.assertEqual(
|
|
root.find("devices/console[4]/source").attrib["path"], "/path/to/serial.log"
|
|
)
|
|
|
|
self.assertEqual(root.find("devices/serial[1]").attrib["type"], "pipe")
|
|
self.assertEqual(
|
|
root.find("devices/serial[1]/source").attrib["path"], "/tmp/mypipe"
|
|
)
|
|
|
|
self.assertEqual(root.find("devices/serial[2]").attrib["type"], "udp")
|
|
self.assertEqual(root.find("devices/serial[2]/source").attrib["mode"], "bind")
|
|
self.assertEqual(
|
|
root.find("devices/serial[2]/source").attrib["service"], "1234"
|
|
)
|
|
self.assertEqual(
|
|
root.find("devices/serial[2]/source").attrib["host"], "127.0.0.1"
|
|
)
|
|
|
|
self.assertEqual(root.find("devices/serial[3]").attrib["type"], "tcp")
|
|
self.assertEqual(root.find("devices/serial[3]/source").attrib["mode"], "bind")
|
|
self.assertEqual(
|
|
root.find("devices/serial[3]/source").attrib["service"], "22223"
|
|
)
|
|
self.assertEqual(root.find("devices/serial[3]/source").attrib["tls"], "yes")
|
|
self.assertEqual(root.find("devices/serial[3]/protocol").attrib["type"], "raw")
|
|
|
|
self.assertEqual(root.find("devices/serial[4]").attrib["type"], "unix")
|
|
self.assertEqual(
|
|
root.find("devices/serial[4]/source").attrib["path"], "/path/to/socket"
|
|
)
|
|
|
|
def test_gen_xml_no_nic_console(self):
|
|
"""
|
|
Test virt._gen_xml() console
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
consoles=[{"type": "pty"}],
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("devices/console").attrib["type"], "pty")
|
|
|
|
def test_gen_xml_for_telnet_console(self):
|
|
"""
|
|
Test virt._gen_xml() telnet console
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
consoles=[{"type": "tcp", "port": 22223, "protocol": "telnet"}],
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("devices/console").attrib["type"], "tcp")
|
|
self.assertEqual(root.find("devices/console/source").attrib["service"], "22223")
|
|
self.assertEqual(root.find("devices/console/protocol").attrib["type"], "telnet")
|
|
|
|
def test_gen_xml_for_telnet_console_unspecified_port(self):
|
|
"""
|
|
Test virt._gen_xml() telnet console without any specified port
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
consoles=[{"type": "tcp"}],
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("devices/console").attrib["type"], "tcp")
|
|
self.assertEqual(root.find("devices/console/source").attrib["service"], "23023")
|
|
self.assertEqual(root.find("devices/console/protocol").attrib["type"], "telnet")
|
|
|
|
def test_gen_xml_for_serial_no_console(self):
|
|
"""
|
|
Test virt._gen_xml() with no serial console
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
serials=[{"type": "pty"}],
|
|
consoles=[],
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("devices/serial").attrib["type"], "pty")
|
|
self.assertEqual(root.find("devices/console"), None)
|
|
|
|
def test_gen_xml_for_telnet_no_console(self):
|
|
"""
|
|
Test virt._gen_xml() with no telnet console
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
serials=[{"type": "tcp", "port": 22223, "protocol": "telnet"}],
|
|
consoles=[],
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("devices/serial").attrib["type"], "tcp")
|
|
self.assertEqual(root.find("devices/console"), None)
|
|
|
|
def test_gen_xml_nographics_default(self):
|
|
"""
|
|
Test virt._gen_xml() with default no graphics device
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64"
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertIsNone(root.find("devices/graphics"))
|
|
|
|
def test_gen_xml_noloader_default(self):
|
|
"""
|
|
Test virt._gen_xml() with default no loader
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64"
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertIsNone(root.find("os/loader"))
|
|
|
|
def test_gen_xml_vnc_default(self):
|
|
"""
|
|
Test virt._gen_xml() with default vnc graphics device
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
graphics={
|
|
"type": "vnc",
|
|
"port": 1234,
|
|
"tlsPort": 5678,
|
|
"listen": {"type": "address", "address": "myhost"},
|
|
},
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("devices/graphics").attrib["type"], "vnc")
|
|
self.assertEqual(root.find("devices/graphics").attrib["autoport"], "no")
|
|
self.assertEqual(root.find("devices/graphics").attrib["port"], "1234")
|
|
self.assertFalse("tlsPort" in root.find("devices/graphics").attrib)
|
|
self.assertEqual(root.find("devices/graphics").attrib["listen"], "myhost")
|
|
self.assertEqual(root.find("devices/graphics/listen").attrib["type"], "address")
|
|
self.assertEqual(
|
|
root.find("devices/graphics/listen").attrib["address"], "myhost"
|
|
)
|
|
|
|
def test_gen_xml_memory(self):
|
|
"""
|
|
Test virt._gen_xml() with advanced memory settings
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
{
|
|
"boot": "512m",
|
|
"current": "256m",
|
|
"max": "1g",
|
|
"hard_limit": "1024",
|
|
"soft_limit": "512m",
|
|
"swap_hard_limit": "1g",
|
|
"min_guarantee": "256m",
|
|
"hugepages": [
|
|
{"size": "128m"},
|
|
{"nodeset": "0", "size": "256m"},
|
|
{"nodeset": "1", "size": "512m"},
|
|
],
|
|
"nosharepages": True,
|
|
"locked": True,
|
|
"source": "file",
|
|
"access": "shared",
|
|
"allocation": "immediate",
|
|
"discard": True,
|
|
},
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assert_equal_unit(root.find("memory"), 512 * 1024)
|
|
self.assert_equal_unit(root.find("currentMemory"), 256 * 1024)
|
|
self.assert_equal_unit(root.find("maxMemory"), 1024 * 1024)
|
|
self.assertFalse("slots" in root.find("maxMemory").keys())
|
|
self.assert_equal_unit(root.find("memtune/hard_limit"), 1024 * 1024)
|
|
self.assert_equal_unit(root.find("memtune/soft_limit"), 512 * 1024)
|
|
self.assert_equal_unit(root.find("memtune/swap_hard_limit"), 1024**2)
|
|
self.assert_equal_unit(root.find("memtune/min_guarantee"), 256 * 1024)
|
|
self.assertEqual(
|
|
[
|
|
{"nodeset": page.get("nodeset"), "size": page.get("size")}
|
|
for page in root.findall("memoryBacking/hugepages/page")
|
|
],
|
|
[
|
|
{"nodeset": None, "size": str(128 * 1024)},
|
|
{"nodeset": "0", "size": str(256 * 1024)},
|
|
{"nodeset": "1", "size": str(512 * 1024)},
|
|
],
|
|
)
|
|
self.assertIsNotNone(root.find("memoryBacking/nosharepages"))
|
|
self.assertIsNotNone(root.find("memoryBacking/locked"))
|
|
self.assertIsNotNone(root.find("memoryBacking/discard"))
|
|
self.assertEqual(root.find("memoryBacking/source").get("type"), "file")
|
|
self.assertEqual(root.find("memoryBacking/access").get("mode"), "shared")
|
|
self.assertEqual(root.find("memoryBacking/allocation").get("mode"), "immediate")
|
|
|
|
def test_gen_xml_cpu(self):
|
|
"""
|
|
Test virt._gen_xml() with CPU advanced properties
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
{
|
|
"maximum": 12,
|
|
"placement": "static",
|
|
"cpuset": "0-11",
|
|
"current": 5,
|
|
"mode": "custom",
|
|
"match": "minimum",
|
|
"check": "full",
|
|
"vendor": "Intel",
|
|
"model": {
|
|
"name": "core2duo",
|
|
"fallback": "allow",
|
|
"vendor_id": "GenuineIntel",
|
|
},
|
|
"cache": {"level": 3, "mode": "emulate"},
|
|
"features": {"lahf": "optional", "vmx": "require"},
|
|
"vcpus": {
|
|
0: {"enabled": True, "hotpluggable": True},
|
|
1: {"enabled": False},
|
|
},
|
|
},
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("vcpu").get("current"), "5")
|
|
self.assertEqual(root.find("vcpu").get("placement"), "static")
|
|
self.assertEqual(root.find("vcpu").get("cpuset"), "0-11")
|
|
self.assertEqual(root.find("vcpu").text, "12")
|
|
self.assertEqual(root.find("cpu").get("match"), "minimum")
|
|
self.assertEqual(root.find("cpu").get("mode"), "custom")
|
|
self.assertEqual(root.find("cpu").get("check"), "full")
|
|
self.assertEqual(root.find("cpu/vendor").text, "Intel")
|
|
self.assertEqual(root.find("cpu/model").text, "core2duo")
|
|
self.assertEqual(root.find("cpu/model").get("fallback"), "allow")
|
|
self.assertEqual(root.find("cpu/model").get("vendor_id"), "GenuineIntel")
|
|
self.assertEqual(root.find("cpu/cache").get("level"), "3")
|
|
self.assertEqual(root.find("cpu/cache").get("mode"), "emulate")
|
|
self.assertEqual(
|
|
{f.get("name"): f.get("policy") for f in root.findall("cpu/feature")},
|
|
{"lahf": "optional", "vmx": "require"},
|
|
)
|
|
self.assertEqual(
|
|
{
|
|
v.get("id"): {
|
|
"enabled": v.get("enabled"),
|
|
"hotpluggable": v.get("hotpluggable"),
|
|
}
|
|
for v in root.findall("vcpus/vcpu")
|
|
},
|
|
{
|
|
"0": {"enabled": "yes", "hotpluggable": "yes"},
|
|
"1": {"enabled": "no", "hotpluggable": None},
|
|
},
|
|
)
|
|
|
|
def test_gen_xml_cpu_topology(self):
|
|
"""
|
|
Test virt._gen_xml() with CPU topology
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
{"maximum": 1, "topology": {"sockets": 4, "cores": 16, "threads": 2}},
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("cpu/topology").get("sockets"), "4")
|
|
self.assertEqual(root.find("cpu/topology").get("cores"), "16")
|
|
self.assertEqual(root.find("cpu/topology").get("threads"), "2")
|
|
|
|
def test_gen_xml_cpu_numa(self):
|
|
"""
|
|
Test virt._gen_xml() with CPU numa settings
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
{
|
|
"maximum": 1,
|
|
"numa": {
|
|
0: {
|
|
"cpus": "0-3",
|
|
"memory": "1g",
|
|
"discard": True,
|
|
"distances": {0: 10, 1: 20},
|
|
},
|
|
1: {"cpus": "4-7", "memory": "2g", "distances": {0: 20, 1: 10}},
|
|
},
|
|
},
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
cell0 = root.find("cpu/numa/cell[@id='0']")
|
|
self.assertEqual(cell0.get("cpus"), "0-3")
|
|
self.assertIsNone(cell0.get("unit"))
|
|
self.assertEqual(cell0.get("memory"), str(1024**2))
|
|
self.assertEqual(cell0.get("discard"), "yes")
|
|
self.assertEqual(
|
|
{d.get("id"): d.get("value") for d in cell0.findall("distances/sibling")},
|
|
{"0": "10", "1": "20"},
|
|
)
|
|
|
|
cell1 = root.find("cpu/numa/cell[@id='1']")
|
|
self.assertEqual(cell1.get("cpus"), "4-7")
|
|
self.assertIsNone(cell0.get("unit"))
|
|
self.assertEqual(cell1.get("memory"), str(2 * 1024**2))
|
|
self.assertFalse("discard" in cell1.keys())
|
|
self.assertEqual(
|
|
{d.get("id"): d.get("value") for d in cell1.findall("distances/sibling")},
|
|
{"0": "20", "1": "10"},
|
|
)
|
|
|
|
def test_gen_xml_cputune(self):
|
|
"""
|
|
Test virt._gen_xml() with CPU tuning
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
cputune = {
|
|
"shares": 2048,
|
|
"period": 122000,
|
|
"quota": -1,
|
|
"global_period": 1000000,
|
|
"global_quota": -3,
|
|
"emulator_period": 1200000,
|
|
"emulator_quota": -10,
|
|
"iothread_period": 133000,
|
|
"iothread_quota": -1,
|
|
"vcpupin": {0: "1-4,^2", 1: "0,1", 2: "2,3", 3: "0,4"},
|
|
"emulatorpin": "1-3",
|
|
"iothreadpin": {1: "5-6", 2: "7-8"},
|
|
"vcpusched": [
|
|
{"scheduler": "fifo", "priority": 1, "vcpus": "0"},
|
|
{"scheduler": "fifo", "priority": 2, "vcpus": "1"},
|
|
{"scheduler": "idle", "priority": 3, "vcpus": "2"},
|
|
],
|
|
"iothreadsched": [
|
|
{"scheduler": "idle"},
|
|
{"scheduler": "batch", "iothreads": "5-7", "priority": 1},
|
|
],
|
|
"emulatorsched": {"scheduler": "rr", "priority": 2},
|
|
"cachetune": {
|
|
"0-3": {
|
|
0: {"level": 3, "type": "both", "size": 3},
|
|
1: {"level": 3, "type": "both", "size": 3},
|
|
"monitor": {1: 3, "0-3": 3},
|
|
},
|
|
"4-5": {"monitor": {4: 3, 5: 2}},
|
|
},
|
|
"memorytune": {"0-2": {0: 60}, "3-4": {0: 50, 1: 70}},
|
|
}
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
{"maximum": 1, "tuning": cputune, "iothreads": 2},
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("cputune").find("shares").text, "2048")
|
|
self.assertEqual(root.find("cputune").find("period").text, "122000")
|
|
self.assertEqual(root.find("cputune").find("quota").text, "-1")
|
|
self.assertEqual(root.find("cputune").find("global_period").text, "1000000")
|
|
self.assertEqual(root.find("cputune").find("global_quota").text, "-3")
|
|
self.assertEqual(root.find("cputune").find("emulator_period").text, "1200000")
|
|
self.assertEqual(root.find("cputune").find("emulator_quota").text, "-10")
|
|
self.assertEqual(root.find("cputune").find("iothread_period").text, "133000")
|
|
self.assertEqual(root.find("cputune").find("iothread_quota").text, "-1")
|
|
self.assertEqual(
|
|
root.find("cputune").find("vcpupin[@vcpu='0']").attrib.get("cpuset"),
|
|
"1-4,^2",
|
|
)
|
|
self.assertEqual(
|
|
root.find("cputune").find("vcpupin[@vcpu='1']").attrib.get("cpuset"),
|
|
"0,1",
|
|
)
|
|
self.assertEqual(
|
|
root.find("cputune").find("vcpupin[@vcpu='2']").attrib.get("cpuset"),
|
|
"2,3",
|
|
)
|
|
self.assertEqual(
|
|
root.find("cputune").find("vcpupin[@vcpu='3']").attrib.get("cpuset"),
|
|
"0,4",
|
|
)
|
|
self.assertEqual(
|
|
root.find("cputune").find("emulatorpin").attrib.get("cpuset"), "1-3"
|
|
)
|
|
self.assertEqual(
|
|
root.find("cputune")
|
|
.find("iothreadpin[@iothread='1']")
|
|
.attrib.get("cpuset"),
|
|
"5-6",
|
|
)
|
|
self.assertEqual(
|
|
root.find("cputune")
|
|
.find("iothreadpin[@iothread='2']")
|
|
.attrib.get("cpuset"),
|
|
"7-8",
|
|
)
|
|
self.assertDictEqual(
|
|
{
|
|
s.get("vcpus"): {
|
|
"scheduler": s.get("scheduler"),
|
|
"priority": s.get("priority"),
|
|
}
|
|
for s in root.findall("cputune/vcpusched")
|
|
},
|
|
{
|
|
"0": {"scheduler": "fifo", "priority": "1"},
|
|
"1": {"scheduler": "fifo", "priority": "2"},
|
|
"2": {"scheduler": "idle", "priority": "3"},
|
|
},
|
|
)
|
|
self.assertDictEqual(
|
|
{
|
|
s.get("iothreads"): {
|
|
"scheduler": s.get("scheduler"),
|
|
"priority": s.get("priority"),
|
|
}
|
|
for s in root.findall("cputune/iothreadsched")
|
|
},
|
|
{
|
|
None: {"scheduler": "idle", "priority": None},
|
|
"5-7": {"scheduler": "batch", "priority": "1"},
|
|
},
|
|
)
|
|
self.assertEqual(root.find("cputune/emulatorsched").get("scheduler"), "rr")
|
|
self.assertEqual(root.find("cputune/emulatorsched").get("priority"), "2")
|
|
self.assertEqual(
|
|
root.find("./cputune/cachetune[@vcpus='0-3']").attrib.get("vcpus"), "0-3"
|
|
)
|
|
self.assertEqual(
|
|
root.find("./cputune/cachetune[@vcpus='0-3']/cache[@id='0']").attrib.get(
|
|
"level"
|
|
),
|
|
"3",
|
|
)
|
|
self.assertEqual(
|
|
root.find("./cputune/cachetune[@vcpus='0-3']/cache[@id='0']").attrib.get(
|
|
"type"
|
|
),
|
|
"both",
|
|
)
|
|
self.assertEqual(
|
|
root.find(
|
|
"./cputune/cachetune[@vcpus='0-3']/monitor[@vcpus='1']"
|
|
).attrib.get("level"),
|
|
"3",
|
|
)
|
|
self.assertNotEqual(
|
|
root.find("./cputune/cachetune[@vcpus='0-3']/monitor[@vcpus='1']"), None
|
|
)
|
|
self.assertNotEqual(
|
|
root.find("./cputune/cachetune[@vcpus='4-5']").attrib.get("vcpus"), None
|
|
)
|
|
self.assertEqual(
|
|
root.find("./cputune/cachetune[@vcpus='4-5']/cache[@id='0']"), None
|
|
)
|
|
self.assertEqual(
|
|
root.find(
|
|
"./cputune/cachetune[@vcpus='4-5']/monitor[@vcpus='4']"
|
|
).attrib.get("level"),
|
|
"3",
|
|
)
|
|
self.assertEqual(
|
|
root.find(
|
|
"./cputune/cachetune[@vcpus='4-5']/monitor[@vcpus='5']"
|
|
).attrib.get("level"),
|
|
"2",
|
|
)
|
|
self.assertNotEqual(root.find("./cputune/memorytune[@vcpus='0-2']"), None)
|
|
self.assertEqual(
|
|
root.find("./cputune/memorytune[@vcpus='0-2']/node[@id='0']").attrib.get(
|
|
"bandwidth"
|
|
),
|
|
"60",
|
|
)
|
|
self.assertNotEqual(root.find("./cputune/memorytune[@vcpus='3-4']"), None)
|
|
self.assertEqual(
|
|
root.find("./cputune/memorytune[@vcpus='3-4']/node[@id='0']").attrib.get(
|
|
"bandwidth"
|
|
),
|
|
"50",
|
|
)
|
|
self.assertEqual(
|
|
root.find("./cputune/memorytune[@vcpus='3-4']/node[@id='1']").attrib.get(
|
|
"bandwidth"
|
|
),
|
|
"70",
|
|
)
|
|
self.assertEqual(root.find("iothreads").text, "2")
|
|
|
|
def test_default_disk_profile_hypervisor_esxi(self):
|
|
"""
|
|
Test virt._disk_profile() default ESXi profile
|
|
"""
|
|
mock = MagicMock(return_value={})
|
|
with patch.dict(
|
|
virt.__salt__, {"config.get": mock} # pylint: disable=no-member
|
|
):
|
|
ret = virt._disk_profile(
|
|
self.mock_conn, "nonexistent", "vmware", None, "test-vm"
|
|
)
|
|
self.assertTrue(len(ret) == 1)
|
|
found = [disk for disk in ret if disk["name"] == "system"]
|
|
self.assertTrue(bool(found))
|
|
system = found[0]
|
|
self.assertEqual(system["format"], "vmdk")
|
|
self.assertEqual(system["model"], "scsi")
|
|
self.assertTrue(int(system["size"]) >= 1)
|
|
|
|
def test_default_disk_profile_hypervisor_kvm(self):
|
|
"""
|
|
Test virt._disk_profile() default KVM profile
|
|
"""
|
|
mock = MagicMock(side_effect=[{}, "/images/dir"])
|
|
with patch.dict(
|
|
virt.__salt__, {"config.get": mock} # pylint: disable=no-member
|
|
):
|
|
ret = virt._disk_profile(
|
|
self.mock_conn, "nonexistent", "kvm", None, "test-vm"
|
|
)
|
|
self.assertTrue(len(ret) == 1)
|
|
found = [disk for disk in ret if disk["name"] == "system"]
|
|
self.assertTrue(bool(found))
|
|
system = found[0]
|
|
self.assertEqual(system["format"], "qcow2")
|
|
self.assertEqual(system["model"], "virtio")
|
|
self.assertTrue(int(system["size"]) >= 1)
|
|
|
|
def test_default_disk_profile_hypervisor_xen(self):
|
|
"""
|
|
Test virt._disk_profile() default XEN profile
|
|
"""
|
|
mock = MagicMock(side_effect=[{}, "/images/dir"])
|
|
with patch.dict(
|
|
virt.__salt__, {"config.get": mock} # pylint: disable=no-member
|
|
):
|
|
ret = virt._disk_profile(
|
|
self.mock_conn, "nonexistent", "xen", None, "test-vm"
|
|
)
|
|
self.assertTrue(len(ret) == 1)
|
|
found = [disk for disk in ret if disk["name"] == "system"]
|
|
self.assertTrue(bool(found))
|
|
system = found[0]
|
|
self.assertEqual(system["format"], "qcow2")
|
|
self.assertEqual(system["model"], "xen")
|
|
self.assertTrue(int(system["size"]) >= 1)
|
|
|
|
def test_default_nic_profile_hypervisor_esxi(self):
|
|
"""
|
|
Test virt._nic_profile() default ESXi profile
|
|
"""
|
|
mock = MagicMock(return_value={})
|
|
with patch.dict(
|
|
virt.__salt__, {"config.get": mock} # pylint: disable=no-member
|
|
):
|
|
ret = virt._nic_profile("nonexistent", "vmware")
|
|
self.assertTrue(len(ret) == 1)
|
|
eth0 = ret[0]
|
|
self.assertEqual(eth0["name"], "eth0")
|
|
self.assertEqual(eth0["type"], "bridge")
|
|
self.assertEqual(eth0["source"], "DEFAULT")
|
|
self.assertEqual(eth0["model"], "e1000")
|
|
|
|
def test_default_nic_profile_hypervisor_kvm(self):
|
|
"""
|
|
Test virt._nic_profile() default KVM profile
|
|
"""
|
|
mock = MagicMock(return_value={})
|
|
with patch.dict(
|
|
virt.__salt__, {"config.get": mock} # pylint: disable=no-member
|
|
):
|
|
ret = virt._nic_profile("nonexistent", "kvm")
|
|
self.assertTrue(len(ret) == 1)
|
|
eth0 = ret[0]
|
|
self.assertEqual(eth0["name"], "eth0")
|
|
self.assertEqual(eth0["type"], "bridge")
|
|
self.assertEqual(eth0["source"], "br0")
|
|
self.assertEqual(eth0["model"], "virtio")
|
|
|
|
def test_default_nic_profile_hypervisor_xen(self):
|
|
"""
|
|
Test virt._nic_profile() default XEN profile
|
|
"""
|
|
mock = MagicMock(return_value={})
|
|
with patch.dict(
|
|
virt.__salt__, {"config.get": mock} # pylint: disable=no-member
|
|
):
|
|
ret = virt._nic_profile("nonexistent", "xen")
|
|
self.assertTrue(len(ret) == 1)
|
|
eth0 = ret[0]
|
|
self.assertEqual(eth0["name"], "eth0")
|
|
self.assertEqual(eth0["type"], "bridge")
|
|
self.assertEqual(eth0["source"], "br0")
|
|
self.assertFalse(eth0["model"])
|
|
|
|
def test_gen_vol_xml_esx(self):
|
|
"""
|
|
Test virt._get_vol_xml() for the ESX case
|
|
"""
|
|
xml_data = virt._gen_vol_xml("vmname/system.vmdk", 8192, format="vmdk")
|
|
root = ET.fromstring(xml_data)
|
|
self.assertIsNone(root.get("type"))
|
|
self.assertEqual(root.find("name").text, "vmname/system.vmdk")
|
|
self.assertEqual(root.find("capacity").attrib["unit"], "KiB")
|
|
self.assertEqual(root.find("capacity").text, str(8192 * 1024))
|
|
self.assertEqual(root.find("allocation").text, str(0))
|
|
self.assertEqual(root.find("target/format").get("type"), "vmdk")
|
|
self.assertIsNone(root.find("target/permissions"))
|
|
self.assertIsNone(root.find("target/nocow"))
|
|
self.assertIsNone(root.find("backingStore"))
|
|
|
|
def test_gen_vol_xml_file(self):
|
|
"""
|
|
Test virt._get_vol_xml() for a file volume
|
|
"""
|
|
xml_data = virt._gen_vol_xml(
|
|
"myvm_system.qcow2",
|
|
8192,
|
|
format="qcow2",
|
|
allocation=4096,
|
|
type="file",
|
|
permissions={
|
|
"mode": "0775",
|
|
"owner": "123",
|
|
"group": "456",
|
|
"label": "sec_label",
|
|
},
|
|
backing_store={"path": "/backing/image", "format": "raw"},
|
|
nocow=True,
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.get("type"), "file")
|
|
self.assertEqual(root.find("name").text, "myvm_system.qcow2")
|
|
self.assertIsNone(root.find("key"))
|
|
self.assertIsNone(root.find("target/path"))
|
|
self.assertEqual(root.find("target/format").get("type"), "qcow2")
|
|
self.assertEqual(root.find("capacity").attrib["unit"], "KiB")
|
|
self.assertEqual(root.find("capacity").text, str(8192 * 1024))
|
|
self.assertEqual(root.find("capacity").attrib["unit"], "KiB")
|
|
self.assertEqual(root.find("allocation").text, str(4096 * 1024))
|
|
self.assertEqual(root.find("target/permissions/mode").text, "0775")
|
|
self.assertEqual(root.find("target/permissions/owner").text, "123")
|
|
self.assertEqual(root.find("target/permissions/group").text, "456")
|
|
self.assertEqual(root.find("target/permissions/label").text, "sec_label")
|
|
self.assertIsNotNone(root.find("target/nocow"))
|
|
self.assertEqual(root.find("backingStore/path").text, "/backing/image")
|
|
self.assertEqual(root.find("backingStore/format").get("type"), "raw")
|
|
|
|
def test_gen_xml_for_kvm_default_profile(self):
|
|
"""
|
|
Test virt._gen_xml(), KVM default profile case
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.attrib["type"], "kvm")
|
|
self.assertEqual(root.find("vcpu").text, "1")
|
|
self.assertEqual(root.find("memory").text, str(512 * 1024))
|
|
self.assertEqual(root.find("memory").attrib["unit"], "KiB")
|
|
|
|
disks = root.findall(".//disk")
|
|
self.assertEqual(len(disks), 1)
|
|
disk = disks[0]
|
|
root_dir = salt.config.DEFAULT_MINION_OPTS.get("root_dir")
|
|
self.assertTrue(disk.find("source").attrib["file"].startswith(root_dir))
|
|
self.assertTrue("hello_system" in disk.find("source").attrib["file"])
|
|
self.assertEqual(disk.find("target").attrib["dev"], "vda")
|
|
self.assertEqual(disk.find("target").attrib["bus"], "virtio")
|
|
self.assertEqual(disk.find("driver").attrib["name"], "qemu")
|
|
self.assertEqual(disk.find("driver").attrib["type"], "qcow2")
|
|
|
|
interfaces = root.findall(".//interface")
|
|
self.assertEqual(len(interfaces), 1)
|
|
iface = interfaces[0]
|
|
self.assertEqual(iface.attrib["type"], "bridge")
|
|
self.assertEqual(iface.find("source").attrib["bridge"], "br0")
|
|
self.assertEqual(iface.find("model").attrib["type"], "virtio")
|
|
|
|
def test_gen_xml_for_esxi_default_profile(self):
|
|
"""
|
|
Test virt._gen_xml(), ESXi/vmware default profile case
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "vmware", [], "hello")
|
|
nicp = virt._nic_profile("default", "vmware")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"vmware",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.attrib["type"], "vmware")
|
|
self.assertEqual(root.find("vcpu").text, "1")
|
|
self.assertEqual(root.find("memory").text, str(512 * 1024))
|
|
self.assertEqual(root.find("memory").attrib["unit"], "KiB")
|
|
|
|
disks = root.findall(".//disk")
|
|
self.assertEqual(len(disks), 1)
|
|
disk = disks[0]
|
|
self.assertTrue("[0]" in disk.find("source").attrib["file"])
|
|
self.assertTrue("hello_system" in disk.find("source").attrib["file"])
|
|
self.assertEqual(disk.find("target").attrib["dev"], "sda")
|
|
self.assertEqual(disk.find("target").attrib["bus"], "scsi")
|
|
self.assertEqual(disk.find("address").attrib["unit"], "0")
|
|
|
|
interfaces = root.findall(".//interface")
|
|
self.assertEqual(len(interfaces), 1)
|
|
iface = interfaces[0]
|
|
self.assertEqual(iface.attrib["type"], "bridge")
|
|
self.assertEqual(iface.find("source").attrib["bridge"], "DEFAULT")
|
|
self.assertEqual(iface.find("model").attrib["type"], "e1000")
|
|
|
|
def test_gen_xml_for_esxi_custom_profile(self):
|
|
"""
|
|
Test virt._gen_xml(), ESXi/vmware custom profile case
|
|
"""
|
|
disks = {
|
|
"noeffect": [
|
|
{"first": {"size": 8192, "pool": "datastore1"}},
|
|
{"second": {"size": 4096, "pool": "datastore2"}},
|
|
]
|
|
}
|
|
nics = {
|
|
"noeffect": [
|
|
{"name": "eth1", "source": "ONENET"},
|
|
{"name": "eth2", "source": "TWONET"},
|
|
]
|
|
}
|
|
with patch.dict(
|
|
virt.__salt__, # pylint: disable=no-member
|
|
{"config.get": MagicMock(side_effect=[disks, nics])},
|
|
):
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn, "noeffect", "vmware", [], "hello"
|
|
)
|
|
nicp = virt._nic_profile("noeffect", "vmware")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"vmware",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.attrib["type"], "vmware")
|
|
self.assertEqual(root.find("vcpu").text, "1")
|
|
self.assertEqual(root.find("memory").text, str(512 * 1024))
|
|
self.assertEqual(root.find("memory").attrib["unit"], "KiB")
|
|
self.assertTrue(len(root.findall(".//disk")) == 2)
|
|
self.assertTrue(len(root.findall(".//interface")) == 2)
|
|
|
|
def test_gen_xml_for_kvm_custom_profile(self):
|
|
"""
|
|
Test virt._gen_xml(), KVM custom profile case
|
|
"""
|
|
disks = {
|
|
"noeffect": [
|
|
{"first": {"size": 8192, "pool": "/var/lib/images"}},
|
|
{"second": {"size": 4096, "pool": "/var/lib/images"}},
|
|
]
|
|
}
|
|
nics = {
|
|
"noeffect": [
|
|
{"name": "eth1", "source": "b2"},
|
|
{"name": "eth2", "source": "b2"},
|
|
]
|
|
}
|
|
with patch.dict(
|
|
virt.__salt__, # pylint: disable=no-member
|
|
{"config.get": MagicMock(side_effect=[disks, nics])},
|
|
):
|
|
diskp = virt._disk_profile(self.mock_conn, "noeffect", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("noeffect", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.attrib["type"], "kvm")
|
|
self.assertEqual(root.find("vcpu").text, "1")
|
|
self.assertEqual(root.find("memory").text, str(512 * 1024))
|
|
self.assertEqual(root.find("memory").attrib["unit"], "KiB")
|
|
disks = root.findall(".//disk")
|
|
self.assertTrue(len(disks) == 2)
|
|
self.assertEqual(disks[0].find("target").get("dev"), "vda")
|
|
self.assertEqual(disks[1].find("target").get("dev"), "vdb")
|
|
self.assertTrue(len(root.findall(".//interface")) == 2)
|
|
|
|
def test_disk_profile_kvm_disk_pool(self):
|
|
"""
|
|
Test virt._disk_profile(), KVM case with pools defined.
|
|
"""
|
|
disks = {
|
|
"noeffect": [
|
|
{"first": {"size": 8192, "pool": "mypool"}},
|
|
{"second": {"size": 4096}},
|
|
]
|
|
}
|
|
|
|
# pylint: disable=no-member
|
|
with patch.dict(
|
|
virt.__salt__,
|
|
{
|
|
"config.get": MagicMock(
|
|
side_effect=[
|
|
disks,
|
|
os.path.join(salt.syspaths.ROOT_DIR, "default", "path"),
|
|
]
|
|
)
|
|
},
|
|
):
|
|
|
|
diskp = virt._disk_profile(self.mock_conn, "noeffect", "kvm", [], "hello")
|
|
|
|
pools_path = (
|
|
os.path.join(salt.syspaths.ROOT_DIR, "pools", "mypool") + os.sep
|
|
)
|
|
default_path = (
|
|
os.path.join(salt.syspaths.ROOT_DIR, "default", "path") + os.sep
|
|
)
|
|
|
|
self.assertEqual(len(diskp), 2)
|
|
self.assertTrue(diskp[1]["source_file"].startswith(default_path))
|
|
# pylint: enable=no-member
|
|
|
|
def test_disk_profile_kvm_disk_external_image(self):
|
|
"""
|
|
Test virt._gen_xml(), KVM case with an external image.
|
|
"""
|
|
with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=True)}):
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"kvm",
|
|
[{"name": "mydisk", "source_file": "/path/to/my/image.qcow2"}],
|
|
"hello",
|
|
)
|
|
|
|
self.assertEqual(len(diskp), 1)
|
|
self.assertEqual(diskp[0]["source_file"], "/path/to/my/image.qcow2")
|
|
|
|
def test_disk_profile_cdrom_default(self):
|
|
"""
|
|
Test virt._gen_xml(), KVM case with cdrom.
|
|
"""
|
|
with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=True)}):
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"kvm",
|
|
[
|
|
{
|
|
"name": "mydisk",
|
|
"device": "cdrom",
|
|
"source_file": "/path/to/my.iso",
|
|
}
|
|
],
|
|
"hello",
|
|
)
|
|
|
|
self.assertEqual(len(diskp), 1)
|
|
self.assertEqual(diskp[0]["model"], "ide")
|
|
self.assertEqual(diskp[0]["format"], "raw")
|
|
|
|
def test_disk_profile_pool_disk_type(self):
|
|
"""
|
|
Test virt._disk_profile(), with a disk pool of disk type
|
|
"""
|
|
self.mock_conn.listStoragePools.return_value = ["test-vdb"]
|
|
self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = """
|
|
<pool type="disk">
|
|
<name>test-vdb</name>
|
|
<source>
|
|
<device path='/dev/vdb'/>
|
|
</source>
|
|
<target>
|
|
<path>/dev</path>
|
|
</target>
|
|
</pool>
|
|
"""
|
|
|
|
# No existing disk case
|
|
self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = (
|
|
[]
|
|
)
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"kvm",
|
|
[{"name": "mydisk", "pool": "test-vdb"}],
|
|
"hello",
|
|
)
|
|
self.assertEqual(diskp[0]["filename"], "vdb1")
|
|
|
|
# Append to the end case
|
|
self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = [
|
|
"vdb1",
|
|
"vdb2",
|
|
]
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"kvm",
|
|
[{"name": "mydisk", "pool": "test-vdb"}],
|
|
"hello",
|
|
)
|
|
self.assertEqual(diskp[0]["filename"], "vdb3")
|
|
|
|
# Hole in the middle case
|
|
self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = [
|
|
"vdb1",
|
|
"vdb3",
|
|
]
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"kvm",
|
|
[{"name": "mydisk", "pool": "test-vdb"}],
|
|
"hello",
|
|
)
|
|
self.assertEqual(diskp[0]["filename"], "vdb2")
|
|
|
|
# Reuse existing volume case
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"kvm",
|
|
[{"name": "mydisk", "pool": "test-vdb", "source_file": "vdb1"}],
|
|
"hello",
|
|
)
|
|
self.assertEqual(diskp[0]["filename"], "vdb1")
|
|
|
|
def test_gen_xml_volume(self):
|
|
"""
|
|
Test virt._gen_xml(), generating a disk of volume type
|
|
"""
|
|
self.mock_conn.listStoragePools.return_value = ["default"]
|
|
self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = (
|
|
"<pool type='dir'/>"
|
|
)
|
|
self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = [
|
|
"myvolume"
|
|
]
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"kvm",
|
|
[
|
|
{"name": "system", "pool": "default"},
|
|
{"name": "data", "pool": "default", "source_file": "myvolume"},
|
|
],
|
|
"hello",
|
|
)
|
|
self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = (
|
|
"<pool type='dir'/>"
|
|
)
|
|
nicp = virt._nic_profile(None, "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
disk = root.findall(".//disk")[0]
|
|
self.assertEqual(disk.attrib["device"], "disk")
|
|
self.assertEqual(disk.attrib["type"], "volume")
|
|
source = disk.find("source")
|
|
self.assertEqual("default", source.attrib["pool"])
|
|
self.assertEqual("hello_system", source.attrib["volume"])
|
|
self.assertEqual("myvolume", root.find(".//disk[2]/source").get("volume"))
|
|
|
|
# RBD volume usage auth test case
|
|
self.mock_conn.listStoragePools.return_value = ["test-rbd"]
|
|
self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = """
|
|
<pool type='rbd'>
|
|
<name>test-rbd</name>
|
|
<uuid>ede33e0a-9df0-479f-8afd-55085a01b244</uuid>
|
|
<capacity unit='bytes'>526133493760</capacity>
|
|
<allocation unit='bytes'>589928</allocation>
|
|
<available unit='bytes'>515081306112</available>
|
|
<source>
|
|
<host name='ses2.tf.local'/>
|
|
<host name='ses3.tf.local' port='1234'/>
|
|
<name>libvirt-pool</name>
|
|
<auth type='ceph' username='libvirt'>
|
|
<secret usage='pool_test-rbd'/>
|
|
</auth>
|
|
</source>
|
|
</pool>
|
|
"""
|
|
self.mock_conn.getStoragePoolCapabilities.return_value = """
|
|
<storagepoolCapabilities>
|
|
<pool type='rbd' supported='yes'>
|
|
<volOptions>
|
|
<defaultFormat type='raw'/>
|
|
<enum name='targetFormatType'>
|
|
</enum>
|
|
</volOptions>
|
|
</pool>
|
|
</storagepoolCapabilities>
|
|
"""
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"kvm",
|
|
[{"name": "system", "pool": "test-rbd"}],
|
|
"test-vm",
|
|
)
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
disk = root.findall(".//disk")[0]
|
|
self.assertDictEqual(
|
|
{
|
|
"type": "network",
|
|
"device": "disk",
|
|
"source": {
|
|
"protocol": "rbd",
|
|
"name": "libvirt-pool/test-vm_system",
|
|
"host": [
|
|
{"name": "ses2.tf.local"},
|
|
{"name": "ses3.tf.local", "port": "1234"},
|
|
],
|
|
"auth": {
|
|
"username": "libvirt",
|
|
"secret": {"type": "ceph", "usage": "pool_test-rbd"},
|
|
},
|
|
},
|
|
"target": {"dev": "vda", "bus": "virtio"},
|
|
"driver": {
|
|
"name": "qemu",
|
|
"type": "raw",
|
|
"cache": "none",
|
|
"io": "native",
|
|
},
|
|
},
|
|
salt.utils.xmlutil.to_dict(disk, True),
|
|
)
|
|
|
|
# RBD volume UUID auth test case
|
|
self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = """
|
|
<pool type='rbd'>
|
|
<name>test-rbd</name>
|
|
<uuid>ede33e0a-9df0-479f-8afd-55085a01b244</uuid>
|
|
<capacity unit='bytes'>526133493760</capacity>
|
|
<allocation unit='bytes'>589928</allocation>
|
|
<available unit='bytes'>515081306112</available>
|
|
<source>
|
|
<host name='ses2.tf.local'/>
|
|
<host name='ses3.tf.local' port='1234'/>
|
|
<name>libvirt-pool</name>
|
|
<auth type='ceph' username='libvirt'>
|
|
<secret uuid='some-uuid'/>
|
|
</auth>
|
|
</source>
|
|
</pool>
|
|
"""
|
|
self.mock_conn.secretLookupByUUIDString.return_value.usageID.return_value = (
|
|
"pool_test-rbd"
|
|
)
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"kvm",
|
|
[{"name": "system", "pool": "test-rbd"}],
|
|
"test-vm",
|
|
)
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertDictEqual(
|
|
{
|
|
"username": "libvirt",
|
|
"secret": {"type": "ceph", "usage": "pool_test-rbd"},
|
|
},
|
|
salt.utils.xmlutil.to_dict(root.find(".//disk/source/auth"), True),
|
|
)
|
|
self.mock_conn.secretLookupByUUIDString.assert_called_once_with("some-uuid")
|
|
|
|
# Disk volume test case
|
|
self.mock_conn.getStoragePoolCapabilities.return_value = """
|
|
<storagepoolCapabilities>
|
|
<pool type='disk' supported='yes'>
|
|
<volOptions>
|
|
<defaultFormat type='none'/>
|
|
<enum name='targetFormatType'>
|
|
<value>none</value>
|
|
<value>linux</value>
|
|
<value>fat16</value>
|
|
</enum>
|
|
</volOptions>
|
|
</pool>
|
|
</storagepoolCapabilities>
|
|
"""
|
|
self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = """
|
|
<pool type='disk'>
|
|
<name>test-vdb</name>
|
|
<source>
|
|
<device path='/dev/vdb'/>
|
|
<format type='gpt'/>
|
|
</source>
|
|
</pool>
|
|
"""
|
|
self.mock_conn.listStoragePools.return_value = ["test-vdb"]
|
|
self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = [
|
|
"vdb1",
|
|
]
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"kvm",
|
|
[{"name": "system", "pool": "test-vdb"}],
|
|
"test-vm",
|
|
)
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
disk = root.findall(".//disk")[0]
|
|
self.assertEqual(disk.attrib["type"], "volume")
|
|
source = disk.find("source")
|
|
self.assertEqual("test-vdb", source.attrib["pool"])
|
|
self.assertEqual("vdb2", source.attrib["volume"])
|
|
self.assertEqual("raw", disk.find("driver").get("type"))
|
|
|
|
def test_get_xml_volume_xen_dir(self):
|
|
"""
|
|
Test virt._gen_xml generating disks for a Xen hypervisor
|
|
"""
|
|
self.mock_conn.listStoragePools.return_value = ["default"]
|
|
pool_mock = MagicMock()
|
|
pool_mock.XMLDesc.return_value = (
|
|
"<pool type='dir'><target><path>/path/to/images</path></target></pool>"
|
|
)
|
|
volume_xml = "<volume><target><path>/path/to/images/hello_system</path></target></volume>"
|
|
pool_mock.storageVolLookupByName.return_value.XMLDesc.return_value = volume_xml
|
|
self.mock_conn.storagePoolLookupByName.return_value = pool_mock
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"xen",
|
|
[{"name": "system", "pool": "default"}],
|
|
"hello",
|
|
)
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
[],
|
|
"xen",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
disk = root.findall(".//disk")[0]
|
|
self.assertEqual(disk.attrib["type"], "file")
|
|
self.assertEqual(
|
|
"/path/to/images/hello_system", disk.find("source").attrib["file"]
|
|
)
|
|
|
|
def test_get_xml_volume_xen_block(self):
|
|
"""
|
|
Test virt._gen_xml generating disks for a Xen hypervisor
|
|
"""
|
|
self.mock_conn.listStoragePools.return_value = ["default"]
|
|
pool_mock = MagicMock()
|
|
pool_mock.listVolumes.return_value = ["vol01"]
|
|
volume_xml = "<volume><target><path>/dev/to/vol01</path></target></volume>"
|
|
pool_mock.storageVolLookupByName.return_value.XMLDesc.return_value = volume_xml
|
|
self.mock_conn.storagePoolLookupByName.return_value = pool_mock
|
|
|
|
for pool_type in ["logical", "disk", "iscsi", "scsi"]:
|
|
pool_mock.XMLDesc.return_value = "<pool type='{}'><source><device path='/dev/sda'/></source></pool>".format(
|
|
pool_type
|
|
)
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"xen",
|
|
[{"name": "system", "pool": "default", "source_file": "vol01"}],
|
|
"hello",
|
|
)
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
[],
|
|
"xen",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
disk = root.findall(".//disk")[0]
|
|
self.assertEqual(disk.attrib["type"], "block")
|
|
self.assertEqual("/dev/to/vol01", disk.find("source").attrib["dev"])
|
|
|
|
def test_gen_xml_cdrom(self):
|
|
"""
|
|
Test virt._gen_xml(), generating a cdrom device (different disk type, no source)
|
|
"""
|
|
self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = (
|
|
"<pool type='dir'/>"
|
|
)
|
|
diskp = virt._disk_profile(
|
|
self.mock_conn,
|
|
None,
|
|
"kvm",
|
|
[
|
|
{"name": "system", "pool": "default"},
|
|
{
|
|
"name": "tested",
|
|
"device": "cdrom",
|
|
"source_file": None,
|
|
"model": "ide",
|
|
},
|
|
{
|
|
"name": "remote",
|
|
"device": "cdrom",
|
|
"source_file": (
|
|
"http://myhost:8080/url/to/image?query=foo&filter=bar"
|
|
),
|
|
"model": "ide",
|
|
},
|
|
],
|
|
"hello",
|
|
)
|
|
nicp = virt._nic_profile(None, "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
disk = root.findall(".//disk")[1]
|
|
self.assertEqual(disk.get("type"), "file")
|
|
self.assertEqual(disk.attrib["device"], "cdrom")
|
|
self.assertIsNone(disk.find("source"))
|
|
self.assertEqual(disk.find("target").get("dev"), "hda")
|
|
|
|
disk = root.findall(".//disk")[2]
|
|
self.assertEqual(disk.get("type"), "network")
|
|
self.assertEqual(disk.attrib["device"], "cdrom")
|
|
self.assertEqual(
|
|
{
|
|
"protocol": "http",
|
|
"name": "/url/to/image",
|
|
"query": "query=foo&filter=bar",
|
|
"host": {"name": "myhost", "port": "8080"},
|
|
},
|
|
salt.utils.xmlutil.to_dict(disk.find("source"), True),
|
|
)
|
|
|
|
def test_controller_for_esxi(self):
|
|
"""
|
|
Test virt._gen_xml() generated device controller for ESXi/vmware
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "vmware", [], "hello")
|
|
nicp = virt._nic_profile("default", "vmware")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"vmware",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
controllers = root.findall(".//devices/controller")
|
|
self.assertTrue(len(controllers) == 1)
|
|
controller = controllers[0]
|
|
self.assertEqual(controller.attrib["model"], "lsilogic")
|
|
|
|
def test_controller_for_kvm(self):
|
|
"""
|
|
Test virt._gen_xml() generated device controller for KVM
|
|
"""
|
|
diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
|
|
nicp = virt._nic_profile("default", "kvm")
|
|
xml_data = virt._gen_xml(
|
|
self.mock_conn,
|
|
"hello",
|
|
1,
|
|
512,
|
|
diskp,
|
|
nicp,
|
|
"kvm",
|
|
"hvm",
|
|
"x86_64",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
controllers = root.findall(".//devices/controller")
|
|
# There should be no controller
|
|
self.assertTrue(len(controllers) == 0)
|
|
|
|
def test_diff_disks(self):
|
|
"""
|
|
Test virt._diff_disks()
|
|
"""
|
|
old_disks = ET.fromstring(
|
|
"""
|
|
<devices>
|
|
<disk type='file' device='disk'>
|
|
<source file='/path/to/img0.qcow2'/>
|
|
<target dev='vda' bus='virtio'/>
|
|
</disk>
|
|
<disk type='file' device='disk'>
|
|
<source file='/path/to/img1.qcow2'/>
|
|
<target dev='vdb' bus='virtio'/>
|
|
</disk>
|
|
<disk type='file' device='disk'>
|
|
<source file='/path/to/img2.qcow2'/>
|
|
<target dev='hda' bus='ide'/>
|
|
</disk>
|
|
<disk type='file' device='disk'>
|
|
<source file='/path/to/img4.qcow2'/>
|
|
<target dev='hdb' bus='ide'/>
|
|
</disk>
|
|
<disk type='file' device='cdrom'>
|
|
<target dev='hdc' bus='ide'/>
|
|
</disk>
|
|
</devices>
|
|
"""
|
|
).findall("disk")
|
|
|
|
new_disks = ET.fromstring(
|
|
"""
|
|
<devices>
|
|
<disk type='file' device='disk'>
|
|
<source file='/path/to/img3.qcow2'/>
|
|
<target dev='vda' bus='virtio'/>
|
|
</disk>
|
|
<disk type='file' device='disk' cache='default'>
|
|
<source file='/path/to/img0.qcow2'/>
|
|
<target dev='vda' bus='virtio'/>
|
|
</disk>
|
|
<disk type='file' device='disk'>
|
|
<source file='/path/to/img4.qcow2'/>
|
|
<target dev='vda' bus='virtio'/>
|
|
</disk>
|
|
<disk type='file' device='cdrom'>
|
|
<target dev='hda' bus='ide'/>
|
|
</disk>
|
|
</devices>
|
|
"""
|
|
).findall("disk")
|
|
ret = virt._diff_disk_lists(old_disks, new_disks)
|
|
self.assertEqual(
|
|
[
|
|
disk.find("source").get("file")
|
|
if disk.find("source") is not None
|
|
else None
|
|
for disk in ret["unchanged"]
|
|
],
|
|
[],
|
|
)
|
|
self.assertEqual(
|
|
[
|
|
disk.find("source").get("file")
|
|
if disk.find("source") is not None
|
|
else None
|
|
for disk in ret["new"]
|
|
],
|
|
["/path/to/img3.qcow2", "/path/to/img0.qcow2", "/path/to/img4.qcow2", None],
|
|
)
|
|
self.assertEqual(
|
|
[disk.find("target").get("dev") for disk in ret["sorted"]],
|
|
["vda", "vdb", "vdc", "hda"],
|
|
)
|
|
self.assertEqual(
|
|
[
|
|
disk.find("source").get("file")
|
|
if disk.find("source") is not None
|
|
else None
|
|
for disk in ret["sorted"]
|
|
],
|
|
["/path/to/img3.qcow2", "/path/to/img0.qcow2", "/path/to/img4.qcow2", None],
|
|
)
|
|
self.assertEqual(ret["new"][1].find("target").get("bus"), "virtio")
|
|
self.assertEqual(
|
|
[
|
|
disk.find("source").get("file")
|
|
if disk.find("source") is not None
|
|
else None
|
|
for disk in ret["deleted"]
|
|
],
|
|
[
|
|
"/path/to/img0.qcow2",
|
|
"/path/to/img1.qcow2",
|
|
"/path/to/img2.qcow2",
|
|
"/path/to/img4.qcow2",
|
|
None,
|
|
],
|
|
)
|
|
|
|
@pytest.mark.skip_on_fips_enabled_platform
|
|
def test_init(self):
|
|
"""
|
|
Test init() function
|
|
"""
|
|
xml = """
|
|
<capabilities>
|
|
<host>
|
|
<uuid>44454c4c-3400-105a-8033-b3c04f4b344a</uuid>
|
|
<cpu>
|
|
<arch>x86_64</arch>
|
|
<model>Nehalem</model>
|
|
<vendor>Intel</vendor>
|
|
<microcode version='25'/>
|
|
<topology sockets='1' cores='4' threads='2'/>
|
|
<feature name='vme'/>
|
|
<feature name='ds'/>
|
|
<feature name='acpi'/>
|
|
<pages unit='KiB' size='4'/>
|
|
<pages unit='KiB' size='2048'/>
|
|
</cpu>
|
|
<power_management>
|
|
<suspend_mem/>
|
|
<suspend_disk/>
|
|
<suspend_hybrid/>
|
|
</power_management>
|
|
<migration_features>
|
|
<live/>
|
|
<uri_transports>
|
|
<uri_transport>tcp</uri_transport>
|
|
<uri_transport>rdma</uri_transport>
|
|
</uri_transports>
|
|
</migration_features>
|
|
<topology>
|
|
<cells num='1'>
|
|
<cell id='0'>
|
|
<memory unit='KiB'>12367120</memory>
|
|
<pages unit='KiB' size='4'>3091780</pages>
|
|
<pages unit='KiB' size='2048'>0</pages>
|
|
<distances>
|
|
<sibling id='0' value='10'/>
|
|
</distances>
|
|
<cpus num='8'>
|
|
<cpu id='0' socket_id='0' core_id='0' siblings='0,4'/>
|
|
<cpu id='1' socket_id='0' core_id='1' siblings='1,5'/>
|
|
<cpu id='2' socket_id='0' core_id='2' siblings='2,6'/>
|
|
<cpu id='3' socket_id='0' core_id='3' siblings='3,7'/>
|
|
<cpu id='4' socket_id='0' core_id='0' siblings='0,4'/>
|
|
<cpu id='5' socket_id='0' core_id='1' siblings='1,5'/>
|
|
<cpu id='6' socket_id='0' core_id='2' siblings='2,6'/>
|
|
<cpu id='7' socket_id='0' core_id='3' siblings='3,7'/>
|
|
</cpus>
|
|
</cell>
|
|
</cells>
|
|
</topology>
|
|
<cache>
|
|
<bank id='0' level='3' type='both' size='8' unit='MiB' cpus='0-7'/>
|
|
</cache>
|
|
<secmodel>
|
|
<model>apparmor</model>
|
|
<doi>0</doi>
|
|
</secmodel>
|
|
<secmodel>
|
|
<model>dac</model>
|
|
<doi>0</doi>
|
|
<baselabel type='kvm'>+487:+486</baselabel>
|
|
<baselabel type='qemu'>+487:+486</baselabel>
|
|
</secmodel>
|
|
</host>
|
|
|
|
<guest>
|
|
<os_type>hvm</os_type>
|
|
<arch name='i686'>
|
|
<wordsize>32</wordsize>
|
|
<emulator>/usr/bin/qemu-system-i386</emulator>
|
|
<machine maxCpus='255'>pc-i440fx-2.6</machine>
|
|
<machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
|
|
<machine maxCpus='255'>pc-0.12</machine>
|
|
<domain type='qemu'/>
|
|
<domain type='kvm'>
|
|
<emulator>/usr/bin/qemu-kvm</emulator>
|
|
<machine maxCpus='255'>pc-i440fx-2.6</machine>
|
|
<machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
|
|
<machine maxCpus='255'>pc-0.12</machine>
|
|
</domain>
|
|
</arch>
|
|
<features>
|
|
<cpuselection/>
|
|
<deviceboot/>
|
|
<disksnapshot default='on' toggle='no'/>
|
|
<acpi default='on' toggle='yes'/>
|
|
<apic default='on' toggle='no'/>
|
|
<pae/>
|
|
<nonpae/>
|
|
</features>
|
|
</guest>
|
|
|
|
<guest>
|
|
<os_type>hvm</os_type>
|
|
<arch name='x86_64'>
|
|
<wordsize>64</wordsize>
|
|
<emulator>/usr/bin/qemu-system-x86_64</emulator>
|
|
<machine maxCpus='255'>pc-i440fx-2.6</machine>
|
|
<machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
|
|
<machine maxCpus='255'>pc-0.12</machine>
|
|
<domain type='qemu'/>
|
|
<domain type='kvm'>
|
|
<emulator>/usr/bin/qemu-kvm</emulator>
|
|
<machine maxCpus='255'>pc-i440fx-2.6</machine>
|
|
<machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
|
|
<machine maxCpus='255'>pc-0.12</machine>
|
|
</domain>
|
|
</arch>
|
|
<features>
|
|
<cpuselection/>
|
|
<deviceboot/>
|
|
<disksnapshot default='on' toggle='no'/>
|
|
<acpi default='on' toggle='yes'/>
|
|
<apic default='on' toggle='no'/>
|
|
</features>
|
|
</guest>
|
|
|
|
</capabilities>
|
|
"""
|
|
self.mock_conn.getCapabilities.return_value = xml # pylint: disable=no-member
|
|
|
|
root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images")
|
|
|
|
defineMock = MagicMock(return_value=1)
|
|
self.mock_conn.defineXML = defineMock
|
|
mock_chmod = MagicMock()
|
|
mock_run = MagicMock()
|
|
with patch.dict(
|
|
os.__dict__, {"chmod": mock_chmod, "makedirs": MagicMock()}
|
|
): # pylint: disable=no-member
|
|
with patch.dict(
|
|
virt.__salt__, {"cmd.run": mock_run}
|
|
): # pylint: disable=no-member
|
|
|
|
# Ensure the init() function allows creating VM without NIC and disk
|
|
virt.init(
|
|
"test vm", 2, 1234, nic=None, disk=None, seed=False, start=False
|
|
)
|
|
definition = defineMock.call_args_list[0][0][0]
|
|
self.assertFalse("<interface" in definition)
|
|
self.assertFalse("<disk" in definition)
|
|
|
|
# Ensure the init() function allows creating VM without NIC and
|
|
# disk but with boot parameters.
|
|
|
|
defineMock.reset_mock()
|
|
mock_run.reset_mock()
|
|
boot = {
|
|
"kernel": "/root/f8-i386-vmlinuz",
|
|
"initrd": "/root/f8-i386-initrd",
|
|
"cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/",
|
|
}
|
|
retval = virt.init(
|
|
"test vm boot params",
|
|
2,
|
|
1234,
|
|
nic=None,
|
|
disk=None,
|
|
seed=False,
|
|
start=False,
|
|
boot=boot,
|
|
)
|
|
definition = defineMock.call_args_list[0][0][0]
|
|
self.assertEqual("<kernel" in definition, True)
|
|
self.assertEqual("<initrd" in definition, True)
|
|
self.assertEqual("<cmdline" in definition, True)
|
|
self.assertEqual(retval, True)
|
|
|
|
# Verify that remote paths are downloaded and the xml has been
|
|
# modified
|
|
mock_response = MagicMock()
|
|
mock_response.read = MagicMock(return_value="filecontent")
|
|
cache_dir = tempfile.mkdtemp()
|
|
|
|
with patch.dict(virt.__dict__, {"CACHE_DIR": cache_dir}):
|
|
with patch(
|
|
"urllib.request.urlopen",
|
|
MagicMock(return_value=mock_response),
|
|
):
|
|
with patch(
|
|
"salt.utils.files.fopen", return_value=mock_response
|
|
):
|
|
|
|
defineMock.reset_mock()
|
|
mock_run.reset_mock()
|
|
boot = {
|
|
"kernel": "https://www.example.com/download/vmlinuz",
|
|
"initrd": "",
|
|
"cmdline": (
|
|
"console=ttyS0 ks=http://example.com/f8-i386/os/"
|
|
),
|
|
}
|
|
|
|
retval = virt.init(
|
|
"test remote vm boot params",
|
|
2,
|
|
1234,
|
|
nic=None,
|
|
disk=None,
|
|
seed=False,
|
|
start=False,
|
|
boot=boot,
|
|
)
|
|
definition = defineMock.call_args_list[0][0][0]
|
|
self.assertEqual(cache_dir in definition, True)
|
|
|
|
shutil.rmtree(cache_dir)
|
|
|
|
# Test case creating disks
|
|
defineMock.reset_mock()
|
|
mock_run.reset_mock()
|
|
pool_mock = MagicMock()
|
|
pool_mock.XMLDesc.return_value = '<pool type="dir"/>'
|
|
self.mock_conn.storagePoolLookupByName.return_value = pool_mock
|
|
virt.init(
|
|
"test vm",
|
|
2,
|
|
1234,
|
|
nic=None,
|
|
disk=None,
|
|
disks=[
|
|
{"name": "system", "size": 10240},
|
|
{
|
|
"name": "cddrive",
|
|
"device": "cdrom",
|
|
"source_file": None,
|
|
"model": "ide",
|
|
},
|
|
],
|
|
seed=False,
|
|
start=False,
|
|
)
|
|
definition = ET.fromstring(defineMock.call_args_list[0][0][0])
|
|
expected_disk_path = os.path.join(root_dir, "test vm_system.qcow2")
|
|
self.assertEqual(
|
|
expected_disk_path,
|
|
definition.find("./devices/disk[1]/source").get("file"),
|
|
)
|
|
self.assertIsNone(definition.find("./devices/disk[2]/source"))
|
|
self.assertEqual(
|
|
mock_run.call_args[0][0],
|
|
'qemu-img create -f qcow2 "{}" 10240M'.format(expected_disk_path),
|
|
)
|
|
self.assertEqual(mock_chmod.call_args[0][0], expected_disk_path)
|
|
|
|
# Test case creating disks volumes
|
|
defineMock.reset_mock()
|
|
mock_run.reset_mock()
|
|
vol_mock = MagicMock()
|
|
pool_mock.storageVolLookupByName.return_value = vol_mock
|
|
pool_mock.listVolumes.return_value = ["test vm_data"]
|
|
stream_mock = MagicMock()
|
|
self.mock_conn.newStream.return_value = stream_mock
|
|
self.mock_conn.listStoragePools.return_value = ["default", "test"]
|
|
with patch.dict(
|
|
os.__dict__, {"open": MagicMock(), "close": MagicMock()}
|
|
):
|
|
cache_mock = MagicMock()
|
|
with patch.dict(virt.__salt__, {"cp.cache_file": cache_mock}):
|
|
virt.init(
|
|
"test vm",
|
|
2,
|
|
1234,
|
|
nic=None,
|
|
disk=None,
|
|
disks=[
|
|
{
|
|
"name": "system",
|
|
"size": 10240,
|
|
"image": "/path/to/image",
|
|
"pool": "test",
|
|
},
|
|
{"name": "data", "size": 10240, "pool": "default"},
|
|
{
|
|
"name": "test",
|
|
"size": 1024,
|
|
"pool": "default",
|
|
"format": "qcow2",
|
|
"backing_store_path": "/backing/path",
|
|
"backing_store_format": "raw",
|
|
},
|
|
],
|
|
seed=False,
|
|
start=False,
|
|
)
|
|
definition = ET.fromstring(defineMock.call_args_list[0][0][0])
|
|
self.assertTrue(
|
|
all(
|
|
[
|
|
disk.get("type") == "volume"
|
|
for disk in definition.findall("./devices/disk")
|
|
]
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
["test", "default", "default"],
|
|
[
|
|
src.get("pool")
|
|
for src in definition.findall("./devices/disk/source")
|
|
],
|
|
)
|
|
self.assertEqual(
|
|
["test vm_system", "test vm_data", "test vm_test"],
|
|
[
|
|
src.get("volume")
|
|
for src in definition.findall("./devices/disk/source")
|
|
],
|
|
)
|
|
|
|
create_calls = pool_mock.createXML.call_args_list
|
|
vol_names = [
|
|
ET.fromstring(call[0][0]).find("name").text
|
|
for call in create_calls
|
|
]
|
|
self.assertEqual(
|
|
["test vm_system", "test vm_test"],
|
|
vol_names,
|
|
)
|
|
|
|
stream_mock.sendAll.assert_called_once()
|
|
stream_mock.finish.assert_called_once()
|
|
vol_mock.upload.assert_called_once_with(stream_mock, 0, 0, 0)
|
|
|
|
def test_update_backing_store(self):
|
|
"""
|
|
Test updating a disk with a backing store
|
|
"""
|
|
xml = """
|
|
<domain type='kvm' id='7'>
|
|
<name>my_vm</name>
|
|
<memory unit='KiB'>1048576</memory>
|
|
<currentMemory unit='KiB'>1048576</currentMemory>
|
|
<vcpu placement='auto'>1</vcpu>
|
|
<on_reboot>restart</on_reboot>
|
|
<os>
|
|
<type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
|
|
</os>
|
|
<devices>
|
|
<disk type='volume' device='disk'>
|
|
<driver name='qemu' type='qcow2' cache='none' io='native'/>
|
|
<source pool='default' volume='my_vm_system' index='1'/>
|
|
<backingStore type='file' index='2'>
|
|
<format type='qcow2'/>
|
|
<source file='/path/to/base.qcow2'/>
|
|
<backingStore/>
|
|
</backingStore>
|
|
<target dev='vda' bus='virtio'/>
|
|
<alias name='virtio-disk0'/>
|
|
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
|
|
</disk>
|
|
</devices>
|
|
</domain>
|
|
"""
|
|
domain_mock = self.set_mock_vm("my_vm", xml)
|
|
domain_mock.OSType.return_value = "hvm"
|
|
self.mock_conn.defineXML.return_value = True
|
|
updatedev_mock = MagicMock(return_value=0)
|
|
domain_mock.updateDeviceFlags = updatedev_mock
|
|
self.mock_conn.listStoragePools.return_value = ["default"]
|
|
self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = (
|
|
"<pool type='dir'/>"
|
|
)
|
|
|
|
ret = virt.update(
|
|
"my_vm",
|
|
disks=[
|
|
{
|
|
"name": "system",
|
|
"pool": "default",
|
|
"backing_store_path": "/path/to/base.qcow2",
|
|
"backing_store_format": "qcow2",
|
|
},
|
|
],
|
|
)
|
|
self.assertFalse(ret["definition"])
|
|
self.assertFalse(ret["disk"]["attached"])
|
|
self.assertFalse(ret["disk"]["detached"])
|
|
|
|
def test_update_removables(self):
|
|
"""
|
|
Test attaching, detaching, changing removable devices
|
|
"""
|
|
xml = """
|
|
<domain type='kvm' id='7'>
|
|
<name>my_vm</name>
|
|
<memory unit='KiB'>1048576</memory>
|
|
<currentMemory unit='KiB'>1048576</currentMemory>
|
|
<vcpu placement='auto'>1</vcpu>
|
|
<os>
|
|
<type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
|
|
</os>
|
|
<devices>
|
|
<disk type='network' device='cdrom'>
|
|
<driver name='qemu' type='raw' cache='none' io='native'/>
|
|
<source protocol='https' name='/dvd-image-1.iso'>
|
|
<host name='test-srv.local' port='80'/>
|
|
</source>
|
|
<backingStore/>
|
|
<target dev='hda' bus='ide'/>
|
|
<readonly/>
|
|
<alias name='ide0-0-0'/>
|
|
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
|
|
</disk>
|
|
<disk type='file' device='cdrom'>
|
|
<driver name='qemu' type='raw' cache='none' io='native'/>
|
|
<target dev='hdb' bus='ide'/>
|
|
<readonly/>
|
|
<alias name='ide0-0-1'/>
|
|
<address type='drive' controller='0' bus='0' target='0' unit='1'/>
|
|
</disk>
|
|
<disk type='file' device='cdrom'>
|
|
<driver name='qemu' type='raw' cache='none' io='native'/>
|
|
<source file='/srv/dvd-image-2.iso'/>
|
|
<backingStore/>
|
|
<target dev='hdc' bus='ide'/>
|
|
<readonly/>
|
|
<alias name='ide0-0-2'/>
|
|
<address type='drive' controller='0' bus='0' target='0' unit='2'/>
|
|
</disk>
|
|
<disk type='file' device='cdrom'>
|
|
<driver name='qemu' type='raw' cache='none' io='native'/>
|
|
<source file='/srv/dvd-image-3.iso'/>
|
|
<backingStore/>
|
|
<target dev='hdd' bus='ide'/>
|
|
<readonly/>
|
|
<alias name='ide0-0-3'/>
|
|
<address type='drive' controller='0' bus='0' target='0' unit='3'/>
|
|
</disk>
|
|
<disk type='network' device='cdrom'>
|
|
<driver name='qemu' type='raw' cache='none' io='native'/>
|
|
<source protocol='https' name='/dvd-image-6.iso'>
|
|
<host name='test-srv.local' port='80'/>
|
|
</source>
|
|
<backingStore/>
|
|
<target dev='hde' bus='ide'/>
|
|
<readonly/>
|
|
</disk>
|
|
</devices>
|
|
</domain>
|
|
"""
|
|
domain_mock = self.set_mock_vm("my_vm", xml)
|
|
domain_mock.OSType.return_value = "hvm"
|
|
self.mock_conn.defineXML.return_value = True
|
|
updatedev_mock = MagicMock(return_value=0)
|
|
domain_mock.updateDeviceFlags = updatedev_mock
|
|
|
|
ret = virt.update(
|
|
"my_vm",
|
|
disks=[
|
|
{
|
|
"name": "dvd1",
|
|
"device": "cdrom",
|
|
"source_file": None,
|
|
"model": "ide",
|
|
},
|
|
{
|
|
"name": "dvd2",
|
|
"device": "cdrom",
|
|
"source_file": "/srv/dvd-image-4.iso",
|
|
"model": "ide",
|
|
},
|
|
{
|
|
"name": "dvd3",
|
|
"device": "cdrom",
|
|
"source_file": "/srv/dvd-image-2.iso",
|
|
"model": "ide",
|
|
},
|
|
{
|
|
"name": "dvd4",
|
|
"device": "cdrom",
|
|
"source_file": "/srv/dvd-image-5.iso",
|
|
"model": "ide",
|
|
},
|
|
{
|
|
"name": "dvd5",
|
|
"device": "cdrom",
|
|
"source_file": "/srv/dvd-image-6.iso",
|
|
"model": "ide",
|
|
},
|
|
],
|
|
)
|
|
|
|
self.assertTrue(ret["definition"])
|
|
self.assertFalse(ret["disk"].get("attached"))
|
|
self.assertFalse(ret["disk"].get("detached"))
|
|
self.assertEqual(
|
|
[
|
|
{
|
|
"type": "file",
|
|
"device": "cdrom",
|
|
"driver": {
|
|
"name": "qemu",
|
|
"type": "raw",
|
|
"cache": "none",
|
|
"io": "native",
|
|
},
|
|
"backingStore": None,
|
|
"target": {"dev": "hda", "bus": "ide"},
|
|
"readonly": None,
|
|
"alias": {"name": "ide0-0-0"},
|
|
"address": {
|
|
"type": "drive",
|
|
"controller": "0",
|
|
"bus": "0",
|
|
"target": "0",
|
|
"unit": "0",
|
|
},
|
|
},
|
|
{
|
|
"type": "file",
|
|
"device": "cdrom",
|
|
"driver": {
|
|
"name": "qemu",
|
|
"type": "raw",
|
|
"cache": "none",
|
|
"io": "native",
|
|
},
|
|
"target": {"dev": "hdb", "bus": "ide"},
|
|
"readonly": None,
|
|
"alias": {"name": "ide0-0-1"},
|
|
"address": {
|
|
"type": "drive",
|
|
"controller": "0",
|
|
"bus": "0",
|
|
"target": "0",
|
|
"unit": "1",
|
|
},
|
|
"source": {"file": "/srv/dvd-image-4.iso"},
|
|
},
|
|
{
|
|
"type": "file",
|
|
"device": "cdrom",
|
|
"driver": {
|
|
"name": "qemu",
|
|
"type": "raw",
|
|
"cache": "none",
|
|
"io": "native",
|
|
},
|
|
"backingStore": None,
|
|
"target": {"dev": "hdd", "bus": "ide"},
|
|
"readonly": None,
|
|
"alias": {"name": "ide0-0-3"},
|
|
"address": {
|
|
"type": "drive",
|
|
"controller": "0",
|
|
"bus": "0",
|
|
"target": "0",
|
|
"unit": "3",
|
|
},
|
|
"source": {"file": "/srv/dvd-image-5.iso"},
|
|
},
|
|
{
|
|
"type": "file",
|
|
"device": "cdrom",
|
|
"driver": {
|
|
"name": "qemu",
|
|
"type": "raw",
|
|
"cache": "none",
|
|
"io": "native",
|
|
},
|
|
"backingStore": None,
|
|
"target": {"dev": "hde", "bus": "ide"},
|
|
"readonly": None,
|
|
"source": {"file": "/srv/dvd-image-6.iso"},
|
|
},
|
|
],
|
|
[
|
|
salt.utils.xmlutil.to_dict(ET.fromstring(disk), True)
|
|
for disk in ret["disk"]["updated"]
|
|
],
|
|
)
|
|
|
|
def test_update_xen_boot_params(self):
|
|
"""
|
|
Test virt.update() a Xen definition no boot parameter.
|
|
"""
|
|
root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images")
|
|
xml_boot = """
|
|
<domain type='xen' id='8'>
|
|
<name>vm</name>
|
|
<memory unit='KiB'>1048576</memory>
|
|
<currentMemory unit='KiB'>1048576</currentMemory>
|
|
<vcpu placement='auto'>1</vcpu>
|
|
<os>
|
|
<type arch='x86_64' machine='xenfv'>hvm</type>
|
|
<loader type='rom'>/usr/lib/xen/boot/hvmloader</loader>
|
|
</os>
|
|
</domain>
|
|
"""
|
|
domain_mock_boot = self.set_mock_vm("vm", xml_boot)
|
|
domain_mock_boot.OSType = MagicMock(return_value="hvm")
|
|
define_mock_boot = MagicMock(return_value=True)
|
|
define_mock_boot.setVcpusFlags = MagicMock(return_value=0)
|
|
self.mock_conn.defineXML = define_mock_boot
|
|
self.assertEqual(
|
|
{
|
|
"cpu": False,
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm", cpu=2),
|
|
)
|
|
setxml = ET.fromstring(define_mock_boot.call_args[0][0])
|
|
self.assertEqual(setxml.find("os").find("loader").attrib.get("type"), "rom")
|
|
self.assertEqual(
|
|
setxml.find("os").find("loader").text, "/usr/lib/xen/boot/hvmloader"
|
|
)
|
|
|
|
def test_update_existing_boot_params(self):
|
|
"""
|
|
Test virt.update() with existing boot parameters.
|
|
"""
|
|
xml_boot = """
|
|
<domain type='kvm' id='8'>
|
|
<name>vm_with_boot_param</name>
|
|
<memory unit='KiB'>1048576</memory>
|
|
<currentMemory unit='KiB'>1048576</currentMemory>
|
|
<vcpu placement='auto'>1</vcpu>
|
|
<os>
|
|
<type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
|
|
<kernel>/boot/oldkernel</kernel>
|
|
<initrd>/boot/initrdold.img</initrd>
|
|
<cmdline>console=ttyS0 ks=http://example.com/old/os/</cmdline>
|
|
<loader>/usr/share/old/OVMF_CODE.fd</loader>
|
|
<nvram>/usr/share/old/OVMF_VARS.ms.fd</nvram>
|
|
</os>
|
|
</domain>
|
|
"""
|
|
domain_mock_boot = self.set_mock_vm("vm_with_boot_param", xml_boot)
|
|
domain_mock_boot.OSType = MagicMock(return_value="hvm")
|
|
define_mock_boot = MagicMock(return_value=True)
|
|
self.mock_conn.defineXML = define_mock_boot
|
|
boot_new = {
|
|
"kernel": "/root/new-vmlinuz",
|
|
"initrd": "/root/new-initrd",
|
|
"cmdline": "console=ttyS0 ks=http://example.com/new/os/",
|
|
}
|
|
|
|
uefi_boot_new = {
|
|
"loader": "/usr/share/new/OVMF_CODE.fd",
|
|
"nvram": "/usr/share/new/OVMF_VARS.ms.fd",
|
|
}
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_boot_param", boot=boot_new),
|
|
)
|
|
setxml_boot = ET.fromstring(define_mock_boot.call_args[0][0])
|
|
self.assertEqual(
|
|
setxml_boot.find("os").find("kernel").text, "/root/new-vmlinuz"
|
|
)
|
|
self.assertEqual(setxml_boot.find("os").find("initrd").text, "/root/new-initrd")
|
|
self.assertEqual(
|
|
setxml_boot.find("os").find("cmdline").text,
|
|
"console=ttyS0 ks=http://example.com/new/os/",
|
|
)
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_boot_param", boot=uefi_boot_new),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock_boot.call_args[0][0])
|
|
self.assertEqual(
|
|
setxml.find("os").find("loader").text, "/usr/share/new/OVMF_CODE.fd"
|
|
)
|
|
self.assertEqual(setxml.find("os").find("loader").attrib.get("readonly"), "yes")
|
|
self.assertEqual(setxml.find("os").find("loader").attrib["type"], "pflash")
|
|
self.assertEqual(
|
|
setxml.find("os").find("nvram").attrib["template"],
|
|
"/usr/share/new/OVMF_VARS.ms.fd",
|
|
)
|
|
|
|
kernel_none = {
|
|
"kernel": None,
|
|
"initrd": None,
|
|
"cmdline": None,
|
|
}
|
|
|
|
uefi_none = {"loader": None, "nvram": None}
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_boot_param", boot=kernel_none),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock_boot.call_args[0][0])
|
|
self.assertEqual(setxml.find("os").find("kernel"), None)
|
|
self.assertEqual(setxml.find("os").find("initrd"), None)
|
|
self.assertEqual(setxml.find("os").find("cmdline"), None)
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_boot_param", boot={"efi": False}),
|
|
)
|
|
setxml = ET.fromstring(define_mock_boot.call_args[0][0])
|
|
self.assertEqual(setxml.find("os").find("nvram"), None)
|
|
self.assertEqual(setxml.find("os").find("loader"), None)
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_boot_param", boot=uefi_none),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock_boot.call_args[0][0])
|
|
self.assertEqual(setxml.find("os").find("loader"), None)
|
|
self.assertEqual(setxml.find("os").find("nvram"), None)
|
|
|
|
def test_update_existing_numatune_params(self):
|
|
"""
|
|
Test virt.update() with existing numatune parameters.
|
|
"""
|
|
xml_numatune = """
|
|
<domain type='kvm' id='8'>
|
|
<name>vm_with_numatune_param</name>
|
|
<memory unit='KiB'>1048576</memory>
|
|
<currentMemory unit='KiB'>1048576</currentMemory>
|
|
<maxMemory slots="12" unit="bytes">1048576</maxMemory>
|
|
<vcpu placement='auto'>1</vcpu>
|
|
<numatune>
|
|
<memory mode="strict" nodeset="0-11"/>
|
|
<memnode cellid="1" mode="strict" nodeset="3"/>
|
|
<memnode cellid="3" mode="preferred" nodeset="7"/>
|
|
</numatune>
|
|
<os>
|
|
<type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
|
|
</os>
|
|
<on_reboot>restart</on_reboot>
|
|
</domain>
|
|
"""
|
|
domain_mock = self.set_mock_vm("vm_with_numatune_param", xml_numatune)
|
|
domain_mock.OSType = MagicMock(return_value="hvm")
|
|
define_mock = MagicMock(return_value=True)
|
|
self.mock_conn.defineXML = define_mock
|
|
|
|
# test update existing numatune node
|
|
numatune = {
|
|
"memory": {"mode": "preferred", "nodeset": "0-5"},
|
|
"memnodes": {
|
|
0: {"mode": "strict", "nodeset": "4"},
|
|
3: {"mode": "preferred", "nodeset": "7"},
|
|
4: {"mode": "strict", "nodeset": "6"},
|
|
},
|
|
}
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_numatune_param", numatune=numatune),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(
|
|
setxml.find("numatune").find("memory").attrib.get("mode"), "preferred"
|
|
)
|
|
|
|
self.assertEqual(
|
|
setxml.find("numatune").find("memory").attrib.get("nodeset"),
|
|
",".join([str(i) for i in range(0, 6)]),
|
|
)
|
|
|
|
self.assertEqual(
|
|
setxml.find("./numatune/memnode/[@cellid='0']").attrib.get("mode"), "strict"
|
|
)
|
|
|
|
self.assertEqual(
|
|
setxml.find("./numatune/memnode/[@cellid='0']").attrib.get("nodeset"), "4"
|
|
)
|
|
|
|
self.assertEqual(setxml.find("./numatune/memnode/[@cellid='1']"), None)
|
|
|
|
self.assertEqual(
|
|
setxml.find("./numatune/memnode/[@cellid='3']").attrib.get("mode"),
|
|
"preferred",
|
|
)
|
|
|
|
self.assertEqual(
|
|
setxml.find("./numatune/memnode/[@cellid='3']").attrib.get("nodeset"), "7"
|
|
)
|
|
|
|
self.assertEqual(
|
|
setxml.find("./numatune/memnode/[@cellid='4']").attrib.get("mode"), "strict"
|
|
)
|
|
|
|
self.assertEqual(
|
|
setxml.find("./numatune/memnode/[@cellid='4']").attrib.get("nodeset"), "6"
|
|
)
|
|
|
|
self.assertEqual(setxml.find("./numatune/memnode/[@cellid='2']"), None)
|
|
|
|
numatune_mem_none = {
|
|
"memory": None,
|
|
"memnodes": {
|
|
0: {"mode": "strict", "nodeset": "4"},
|
|
3: {"mode": "preferred", "nodeset": "7"},
|
|
4: {"mode": "strict", "nodeset": "6"},
|
|
},
|
|
}
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_numatune_param", numatune=numatune_mem_none),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("numatune").find("memory"), None)
|
|
|
|
self.assertEqual(
|
|
setxml.find("./numatune/memnode/[@cellid='0']").attrib.get("mode"), "strict"
|
|
)
|
|
|
|
self.assertEqual(
|
|
setxml.find("./numatune/memnode/[@cellid='0']").attrib.get("nodeset"), "4"
|
|
)
|
|
|
|
self.assertEqual(
|
|
setxml.find("./numatune/memnode/[@cellid='3']").attrib.get("mode"),
|
|
"preferred",
|
|
)
|
|
|
|
self.assertEqual(
|
|
setxml.find("./numatune/memnode/[@cellid='3']").attrib.get("nodeset"), "7"
|
|
)
|
|
|
|
self.assertEqual(setxml.find("./numatune/memnode/[@cellid='2']"), None)
|
|
|
|
numatune_mnodes_none = {
|
|
"memory": {"mode": "preferred", "nodeset": "0-5"},
|
|
"memnodes": None,
|
|
}
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_numatune_param", numatune=numatune_mnodes_none),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(
|
|
setxml.find("numatune").find("memory").attrib.get("mode"), "preferred"
|
|
)
|
|
|
|
self.assertEqual(
|
|
setxml.find("numatune").find("memory").attrib.get("nodeset"),
|
|
",".join([str(i) for i in range(0, 6)]),
|
|
)
|
|
|
|
self.assertEqual(setxml.find("./numatune/memnode"), None)
|
|
|
|
numatune_without_change = {
|
|
"memory": {"mode": "strict", "nodeset": "0-5,6,7-11"},
|
|
"memnodes": {
|
|
1: {"mode": "strict", "nodeset": "3"},
|
|
3: {"mode": "preferred", "nodeset": "7"},
|
|
},
|
|
}
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": False,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_numatune_param", numatune=numatune_without_change),
|
|
)
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update(
|
|
"vm_with_numatune_param", numatune={"memory": None, "memnodes": None}
|
|
),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("numatune"), None)
|
|
|
|
def test_update_existing_cpu_params(self):
|
|
"""
|
|
Test virt.update() with existing cpu-related parameters.
|
|
"""
|
|
xml_with_existing_params = """
|
|
<domain type='kvm' id='8'>
|
|
<name>vm_with_boot_param</name>
|
|
<memory unit='KiB'>1048576</memory>
|
|
<currentMemory unit='KiB'>1048576</currentMemory>
|
|
<vcpu placement="static" cpuset="0-11" current="3">6</vcpu>
|
|
<vcpus>
|
|
<vcpu id="0" enabled="yes" hotpluggable="no" order="1"/>
|
|
<vcpu id="1" enabled="no" hotpluggable="yes"/>
|
|
<vcpu id="2" enabled="no" hotpluggable="yes"/>
|
|
<vcpu id="3" enabled="no" hotpluggable="yes"/>
|
|
<vcpu id="4" enabled="no" hotpluggable="yes"/>
|
|
<vcpu id="5" enabled="no" hotpluggable="yes"/>
|
|
<vcpu id="6" enabled="no" hotpluggable="yes"/>
|
|
<vcpu id="7" enabled="no" hotpluggable="yes"/>
|
|
<vcpu id="8" enabled="no" hotpluggable="yes"/>
|
|
<vcpu id="9" enabled="no" hotpluggable="yes"/>
|
|
<vcpu id="10" enabled="no" hotpluggable="yes"/>
|
|
<vcpu id="11" enabled="no" hotpluggable="yes"/>
|
|
</vcpus>
|
|
<cpu mode="custom" match="exact" check="full">
|
|
<model fallback="allow" vendor_id="Genuine20201">core2duo</model>
|
|
<vendor>Intel</vendor>
|
|
<topology sockets="2" cores="5" threads="2"/>
|
|
<cache level="3" mode="emulate"/>
|
|
<feature policy="optional" name="lahf_lm"/>
|
|
<feature policy="require" name="pcid"/>
|
|
<numa>
|
|
<cell id="0" cpus="0-3" memory="1073741824" unit="KiB" discard="no">
|
|
<distances>
|
|
<sibling id="0" value="10"/>
|
|
<sibling id="1" value="21"/>
|
|
<sibling id="2" value="31"/>
|
|
<sibling id="3" value="41"/>
|
|
</distances>
|
|
</cell>
|
|
<cell id="1" cpus="4-6" memory="1073741824" unit="KiB" memAccess="private">
|
|
<distances>
|
|
<sibling id="0" value="21"/>
|
|
<sibling id="1" value="10"/>
|
|
<sibling id="2" value="21"/>
|
|
<sibling id="3" value="31"/>
|
|
</distances>
|
|
</cell>
|
|
</numa>
|
|
</cpu>
|
|
<os>
|
|
<type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
|
|
</os>
|
|
</domain>
|
|
"""
|
|
domain_mock = self.set_mock_vm(
|
|
"vm_with_existing_param", xml_with_existing_params
|
|
)
|
|
domain_mock.OSType = MagicMock(return_value="hvm")
|
|
define_mock = MagicMock(return_value=True)
|
|
self.mock_conn.defineXML = define_mock
|
|
|
|
# test update vcpu with existing attributes case
|
|
setvcpus_mock = MagicMock(return_value=0)
|
|
domain_mock.setVcpusFlags = setvcpus_mock
|
|
|
|
cpu_attr = {"placement": "static", "cpuset": "0-5", "current": 3, "maximum": 5}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"cpu": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_attr),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("vcpu").text, "5")
|
|
self.assertEqual(setxml.find("vcpu").attrib["placement"], "static")
|
|
self.assertEqual(
|
|
setxml.find("vcpu").attrib["cpuset"],
|
|
",".join([str(i) for i in range(0, 6)]),
|
|
)
|
|
self.assertEqual(setxml.find("vcpu").attrib["current"], "3")
|
|
|
|
# test removing vcpu attribute
|
|
cpu_none = {"placement": "auto", "cpuset": None, "current": 2, "maximum": 5}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"cpu": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_none),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("vcpu").text, "5")
|
|
self.assertEqual(setxml.find("vcpu").attrib["placement"], "auto")
|
|
self.assertEqual(setxml.find("vcpu").attrib.get("cpuset"), None)
|
|
self.assertEqual(setxml.find("vcpu").attrib.get("current"), "2")
|
|
|
|
# test update individual vcpu with exisiting attributes
|
|
vcpus = {
|
|
"vcpus": {
|
|
"0": {"enabled": False, "hotpluggable": True, "order": 5},
|
|
"3": {"enabled": True, "hotpluggable": False, "order": 3},
|
|
"7": {"enabled": True, "hotpluggable": False},
|
|
}
|
|
}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=vcpus),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("./vcpus/vcpu/[@id='0']").attrib["id"], "0")
|
|
self.assertEqual(setxml.find("./vcpus/vcpu/[@id='0']").attrib["enabled"], "no")
|
|
self.assertEqual(
|
|
setxml.find("./vcpus/vcpu/[@id='0']").attrib["hotpluggable"], "yes"
|
|
)
|
|
self.assertEqual(setxml.find("./vcpus/vcpu/[@id='0']").attrib["order"], "5")
|
|
self.assertEqual(setxml.find("./vcpus/vcpu/[@id='3']").attrib["id"], "3")
|
|
self.assertEqual(setxml.find("./vcpus/vcpu/[@id='3']").attrib["enabled"], "yes")
|
|
self.assertEqual(
|
|
setxml.find("./vcpus/vcpu/[@id='3']").attrib["hotpluggable"], "no"
|
|
)
|
|
self.assertEqual(setxml.find("./vcpus/vcpu/[@id='3']").attrib["order"], "3")
|
|
self.assertEqual(setxml.find("./vcpus/vcpu/[@id='7']").attrib["id"], "7")
|
|
self.assertEqual(setxml.find("./vcpus/vcpu/[@id='7']").attrib["enabled"], "yes")
|
|
self.assertEqual(
|
|
setxml.find("./vcpus/vcpu/[@id='7']").attrib["hotpluggable"], "no"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./vcpus/vcpu/[@id='7']").attrib.get("order"), None
|
|
)
|
|
|
|
# test removing vcpu element
|
|
ind_vcpu = {
|
|
"vcpus": {"3": {"enabled": True, "hotpluggable": False, "order": None}}
|
|
}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=ind_vcpu),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("./vcpus/vcpu/[@id='0']"), None)
|
|
self.assertEqual(setxml.find("./vcpus/vcpu/[@id='3']").attrib["enabled"], "yes")
|
|
self.assertEqual(
|
|
setxml.find("./vcpus/vcpu/[@id='3']").attrib["hotpluggable"], "no"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./vcpus/vcpu/[@id='3']").attrib.get("order"), None
|
|
)
|
|
|
|
# test removing vcpus element
|
|
vcpus_none = {"vcpus": None}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=vcpus_none),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("vcpus"), None)
|
|
|
|
# test removing cpu attrbutes
|
|
cpu_atr_none = {"match": None, "mode": None, "check": None}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_atr_none),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cpu").attrib, {})
|
|
|
|
cpu_atr_mn = {"match": None}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_atr_mn),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cpu").attrib.get("match"), None)
|
|
self.assertEqual(setxml.find("cpu").attrib.get("mode"), "custom")
|
|
self.assertEqual(setxml.find("cpu").attrib.get("check"), "full")
|
|
|
|
# test update existing cpu model
|
|
cpu_model_none = {"model": None}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_model_none),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cpu").find("model"), None)
|
|
|
|
cpu_model_atr_none = {
|
|
"model": {"name": "coresolo", "fallback": "forbid", "vendor_id": None}
|
|
}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_model_atr_none),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cpu").find("model").attrib.get("vendor_id"), None)
|
|
self.assertEqual(
|
|
setxml.find("cpu").find("model").attrib.get("fallback"), "forbid"
|
|
)
|
|
self.assertEqual(setxml.find("cpu").find("model").text, "coresolo")
|
|
|
|
cpu_model_atr = {
|
|
"model": {
|
|
"name": "coresolo",
|
|
"fallback": "forbid",
|
|
"vendor_id": "AuthenticAMD",
|
|
}
|
|
}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_model_atr),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(
|
|
setxml.find("cpu").find("model").attrib.get("fallback"), "forbid"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("cpu").find("model").attrib.get("vendor_id"), "AuthenticAMD"
|
|
)
|
|
self.assertEqual(setxml.find("cpu").find("model").text, "coresolo")
|
|
|
|
# test update existing cpu vendor
|
|
cpu_vendor = {"vendor": "AMD"}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_vendor),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cpu").find("vendor").text, "AMD")
|
|
|
|
cpu_vendor_none = {"vendor": None}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_vendor_none),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cpu").find("vendor"), None)
|
|
|
|
# test update exisiting cpu topology
|
|
cpu_topology = {"topology": {"sockets": 1, "cores": 12, "threads": 1}}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_topology),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cpu").find("topology").attrib.get("sockets"), "1")
|
|
self.assertEqual(setxml.find("cpu").find("topology").attrib.get("cores"), "12")
|
|
self.assertEqual(setxml.find("cpu").find("topology").attrib.get("threads"), "1")
|
|
|
|
cpu_topology_atr_none = {
|
|
"topology": {"sockets": None, "cores": 12, "threads": 1}
|
|
}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_topology_atr_none),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(
|
|
setxml.find("cpu").find("topology").attrib.get("sockets"), None
|
|
)
|
|
self.assertEqual(setxml.find("cpu").find("topology").attrib.get("cores"), "12")
|
|
self.assertEqual(setxml.find("cpu").find("topology").attrib.get("threads"), "1")
|
|
|
|
cpu_topology_none = {"topology": None}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_topology_none),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cpu").find("topology"), None)
|
|
|
|
# test update existing cache
|
|
cpu_cache = {"cache": {"mode": "passthrough", "level": 2}}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_cache),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cpu").find("cache").attrib.get("level"), "2")
|
|
self.assertEqual(
|
|
setxml.find("cpu").find("cache").attrib.get("mode"), "passthrough"
|
|
)
|
|
|
|
cpu_cache_atr_none = {"cache": {"mode": "passthrough", "level": None}}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_cache_atr_none),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cpu").find("cache").attrib.get("level"), None)
|
|
self.assertEqual(
|
|
setxml.find("cpu").find("cache").attrib.get("mode"), "passthrough"
|
|
)
|
|
|
|
cpu_cache_none = {"cache": None}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_cache_none),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cpu").find("cache"), None)
|
|
|
|
# test update existing feature
|
|
cpu_feature = {"features": {"lahf_lm": "require", "pcid": "optional"}}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_feature),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(
|
|
setxml.find("./cpu/feature[@name='pcid']").attrib.get("policy"), "optional"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/feature[@name='lahf_lm']").attrib.get("policy"),
|
|
"require",
|
|
)
|
|
|
|
cpu_feature_atr_none = {"features": {"pcid": "optional", "lahf_lm": "disable"}}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_feature_atr_none),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(
|
|
setxml.find("./cpu/feature[@name='lahf_lm']").attrib.get("policy"),
|
|
"disable",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/feature[@name='pcid']").attrib.get("policy"), "optional"
|
|
)
|
|
|
|
cpu_feature_none = {"features": {"lahf_lm": None, "pcid": None}}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=cpu_feature_none),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("./cpu/feature"), None)
|
|
|
|
# test update existing numa cell
|
|
numa_cell = {
|
|
"numa": {
|
|
0: {
|
|
"cpus": "0-6",
|
|
"memory": "512m",
|
|
"discard": True,
|
|
"distances": {0: 15, 1: 16, 2: 17, 3: 18},
|
|
},
|
|
1: {
|
|
"cpus": "7-12",
|
|
"memory": "2g",
|
|
"discard": True,
|
|
"memAccess": "shared",
|
|
"distances": {0: 23, 1: 24, 2: 25, 3: 26},
|
|
},
|
|
}
|
|
}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=numa_cell),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']").attrib["cpus"],
|
|
",".join([str(i) for i in range(0, 7)]),
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']").attrib["memory"],
|
|
str(512 * 1024**2),
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']").get("unit"),
|
|
"bytes",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']").attrib["discard"], "yes"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='0']").attrib[
|
|
"value"
|
|
],
|
|
"15",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='1']").attrib[
|
|
"value"
|
|
],
|
|
"16",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='2']").attrib[
|
|
"value"
|
|
],
|
|
"17",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='3']").attrib[
|
|
"value"
|
|
],
|
|
"18",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']").attrib["cpus"],
|
|
",".join([str(i) for i in range(7, 13)]),
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']").attrib["memory"],
|
|
str(int(2 * 1024**3)),
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']").get("unit"),
|
|
"bytes",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']").attrib["discard"], "yes"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']").attrib["memAccess"], "shared"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='0']").attrib[
|
|
"value"
|
|
],
|
|
"23",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='1']").attrib[
|
|
"value"
|
|
],
|
|
"24",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='2']").attrib[
|
|
"value"
|
|
],
|
|
"25",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='3']").attrib[
|
|
"value"
|
|
],
|
|
"26",
|
|
)
|
|
|
|
numa_cell_atr_none = {
|
|
"numa": {
|
|
"0": {
|
|
"cpus": "0-6",
|
|
"memory": "512m",
|
|
"discard": False,
|
|
"distances": {0: 15, 2: 17, 3: 18},
|
|
},
|
|
"1": {
|
|
"cpus": "7-12",
|
|
"memory": "2g",
|
|
"discard": True,
|
|
"distances": {0: 23, 1: 24, 2: 25},
|
|
},
|
|
}
|
|
}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_existing_param", cpu=numa_cell_atr_none),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']").attrib["cpus"],
|
|
",".join([str(i) for i in range(0, 7)]),
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']").attrib["memory"],
|
|
str(512 * 1024**2),
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']").get("unit"),
|
|
"bytes",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']").attrib.get("discard"), "no"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='0']").attrib[
|
|
"value"
|
|
],
|
|
"15",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='1']"), None
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='2']").attrib[
|
|
"value"
|
|
],
|
|
"17",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='0']/distances/sibling/[@id='3']").attrib[
|
|
"value"
|
|
],
|
|
"18",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']").attrib["cpus"],
|
|
",".join([str(i) for i in range(7, 13)]),
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']").attrib["memory"],
|
|
str(int(2 * 1024**3)),
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']").attrib["discard"], "yes"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='0']").attrib[
|
|
"value"
|
|
],
|
|
"23",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='1']").attrib[
|
|
"value"
|
|
],
|
|
"24",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='2']").attrib[
|
|
"value"
|
|
],
|
|
"25",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='3']"), None
|
|
)
|
|
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']").attrib["cpus"],
|
|
",".join([str(i) for i in range(7, 13)]),
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']").attrib["memory"],
|
|
str(int(1024**3 * 2)),
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']").attrib["discard"], "yes"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='0']").attrib[
|
|
"value"
|
|
],
|
|
"23",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='1']").attrib[
|
|
"value"
|
|
],
|
|
"24",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='2']").attrib[
|
|
"value"
|
|
],
|
|
"25",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cpu/numa/cell/[@id='1']/distances/sibling/[@id='3']"),
|
|
None,
|
|
)
|
|
|
|
def test_update_memtune_params(self):
|
|
"""
|
|
Test virt.update() with memory tuning parameters.
|
|
"""
|
|
xml_with_memtune_params = """
|
|
<domain type='kvm' id='8'>
|
|
<name>vm_with_boot_param</name>
|
|
<memory unit='KiB'>1048576</memory>
|
|
<currentMemory unit='KiB'>1048576</currentMemory>
|
|
<maxMemory slots="12" unit="bytes">1048576</maxMemory>
|
|
<vcpu placement='auto'>1</vcpu>
|
|
<memtune>
|
|
<hard_limit unit="KiB">1048576</hard_limit>
|
|
<soft_limit unit="KiB">2097152</soft_limit>
|
|
<swap_hard_limit unit="KiB">2621440</swap_hard_limit>
|
|
<min_guarantee unit='KiB'>671088</min_guarantee>
|
|
</memtune>
|
|
<os>
|
|
<type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
|
|
</os>
|
|
</domain>
|
|
"""
|
|
domain_mock = self.set_mock_vm("vm_with_memtune_param", xml_with_memtune_params)
|
|
domain_mock.OSType = MagicMock(return_value="hvm")
|
|
define_mock = MagicMock(return_value=True)
|
|
self.mock_conn.defineXML = define_mock
|
|
|
|
memtune_new_val = {
|
|
"boot": "0.7g",
|
|
"current": "2.5g",
|
|
"max": "3096m",
|
|
"slots": "10",
|
|
"soft_limit": "2048m",
|
|
"hard_limit": "1024",
|
|
"swap_hard_limit": "2.5g",
|
|
"min_guarantee": "1 g",
|
|
}
|
|
|
|
domain_mock.setMemoryFlags.return_value = 0
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
"mem": True,
|
|
},
|
|
virt.update("vm_with_memtune_param", mem=memtune_new_val),
|
|
)
|
|
self.assertEqual(
|
|
domain_mock.setMemoryFlags.call_args[0][0], int(2.5 * 1024**2)
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(
|
|
setxml.find("memtune").find("soft_limit").text, str(2048 * 1024)
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("memtune").find("hard_limit").text, str(1024 * 1024)
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("memtune").find("swap_hard_limit").text,
|
|
str(int(2.5 * 1024**2)),
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("memtune").find("swap_hard_limit").get("unit"),
|
|
"KiB",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("memtune").find("min_guarantee").text, str(1 * 1024**3)
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("memtune").find("min_guarantee").attrib.get("unit"), "bytes"
|
|
)
|
|
self.assertEqual(setxml.find("maxMemory").text, str(3096 * 1024**2))
|
|
self.assertEqual(setxml.find("maxMemory").attrib.get("slots"), "10")
|
|
self.assertEqual(setxml.find("currentMemory").text, str(int(2.5 * 1024**3)))
|
|
self.assertEqual(setxml.find("memory").text, str(int(0.7 * 1024**3)))
|
|
|
|
max_slot_reverse = {
|
|
"slots": "10",
|
|
"max": "3096m",
|
|
}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_memtune_param", mem=max_slot_reverse),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("maxMemory").text, str(3096 * 1024**2))
|
|
self.assertEqual(setxml.find("maxMemory").get("unit"), "bytes")
|
|
self.assertEqual(setxml.find("maxMemory").attrib.get("slots"), "10")
|
|
|
|
max_swap_none = {
|
|
"boot": "0.7g",
|
|
"current": "2.5g",
|
|
"max": None,
|
|
"slots": "10",
|
|
"soft_limit": "2048m",
|
|
"hard_limit": "1024",
|
|
"swap_hard_limit": None,
|
|
"min_guarantee": "1 g",
|
|
}
|
|
|
|
domain_mock.setMemoryFlags.reset_mock()
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
"mem": True,
|
|
},
|
|
virt.update("vm_with_memtune_param", mem=max_swap_none),
|
|
)
|
|
self.assertEqual(
|
|
domain_mock.setMemoryFlags.call_args[0][0], int(2.5 * 1024**2)
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(
|
|
setxml.find("memtune").find("soft_limit").text, str(2048 * 1024)
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("memtune").find("hard_limit").text, str(1024 * 1024)
|
|
)
|
|
self.assertEqual(setxml.find("memtune").find("swap_hard_limit"), None)
|
|
self.assertEqual(
|
|
setxml.find("memtune").find("min_guarantee").text, str(1 * 1024**3)
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("memtune").find("min_guarantee").attrib.get("unit"), "bytes"
|
|
)
|
|
self.assertEqual(setxml.find("maxMemory").text, None)
|
|
self.assertEqual(setxml.find("currentMemory").text, str(int(2.5 * 1024**3)))
|
|
self.assertEqual(setxml.find("memory").text, str(int(0.7 * 1024**3)))
|
|
|
|
memtune_none = {
|
|
"soft_limit": None,
|
|
"hard_limit": None,
|
|
"swap_hard_limit": None,
|
|
"min_guarantee": None,
|
|
}
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_memtune_param", mem=memtune_none),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("memtune").find("soft_limit"), None)
|
|
self.assertEqual(setxml.find("memtune").find("hard_limit"), None)
|
|
self.assertEqual(setxml.find("memtune").find("swap_hard_limit"), None)
|
|
self.assertEqual(setxml.find("memtune").find("min_guarantee"), None)
|
|
|
|
max_none = {
|
|
"max": None,
|
|
}
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_memtune_param", mem=max_none),
|
|
)
|
|
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("maxMemory"), None)
|
|
self.assertEqual(setxml.find("currentMemory").text, str(int(1 * 1024**2)))
|
|
self.assertEqual(setxml.find("memory").text, str(int(1 * 1024**2)))
|
|
|
|
def test_update_exist_memorybacking_params(self):
|
|
"""
|
|
Test virt.update() with memory backing parameters.
|
|
"""
|
|
xml_with_memback_params = """
|
|
<domain type='kvm' id='8'>
|
|
<name>vm_with_memback_param</name>
|
|
<memory unit='KiB'>1048576</memory>
|
|
<currentMemory unit='KiB'>1048576</currentMemory>
|
|
<maxMemory slots="12" unit="KiB">1048576</maxMemory>
|
|
<vcpu placement='auto'>1</vcpu>
|
|
<memoryBacking>
|
|
<hugepages>
|
|
<page size="2048" unit="KiB"/>
|
|
<page size="3145728" nodeset="1-4,^3" unit="KiB"/>
|
|
<page size="1048576" nodeset="3" unit="KiB"/>
|
|
</hugepages>
|
|
<nosharepages/>
|
|
<locked/>
|
|
<source type="file"/>
|
|
<access mode="shared"/>
|
|
<discard/>
|
|
</memoryBacking>
|
|
<os>
|
|
<type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
|
|
</os>
|
|
<on_reboot>restart</on_reboot>
|
|
</domain>
|
|
"""
|
|
domain_mock = self.set_mock_vm("vm_with_memback_param", xml_with_memback_params)
|
|
domain_mock.OSType = MagicMock(return_value="hvm")
|
|
define_mock = MagicMock(return_value=True)
|
|
self.mock_conn.defineXML = define_mock
|
|
|
|
# update memory backing case
|
|
mem_back_param = {
|
|
"hugepages": [
|
|
{"nodeset": "1-4,^3", "size": "1g"},
|
|
{"nodeset": "3", "size": "2g"},
|
|
],
|
|
"nosharepages": None,
|
|
"locked": None,
|
|
"source": "anonymous",
|
|
"access": "private",
|
|
"allocation": "ondemand",
|
|
"discard": None,
|
|
}
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_memback_param", mem=mem_back_param),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertDictEqual(
|
|
{
|
|
p.get("nodeset"): {"size": p.get("size"), "unit": p.get("unit")}
|
|
for p in setxml.findall("memoryBacking/hugepages/page")
|
|
},
|
|
{
|
|
"1,2,4": {"size": str(1024**3), "unit": "bytes"},
|
|
"3": {"size": str(2 * 1024**3), "unit": "bytes"},
|
|
},
|
|
)
|
|
self.assertEqual(setxml.find("./memoryBacking/nosharepages"), None)
|
|
self.assertEqual(setxml.find("./memoryBacking/locked"), None)
|
|
self.assertEqual(
|
|
setxml.find("./memoryBacking/source").attrib["type"], "anonymous"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./memoryBacking/access").attrib["mode"], "private"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./memoryBacking/allocation").attrib["mode"], "ondemand"
|
|
)
|
|
self.assertEqual(setxml.find("./memoryBacking/discard"), None)
|
|
|
|
unchanged_page = {
|
|
"hugepages": [
|
|
{"size": "2m"},
|
|
{"nodeset": "1-4,^3", "size": "3g"},
|
|
{"nodeset": "3", "size": "1g"},
|
|
],
|
|
}
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": False,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("vm_with_memback_param", mem=unchanged_page),
|
|
)
|
|
|
|
def test_update_iothreads_params(self):
|
|
"""
|
|
Test virt.update() with iothreads parameters.
|
|
"""
|
|
xml_with_iothreads_params = """
|
|
<domain type='kvm' id='8'>
|
|
<name>xml_with_iothreads_params</name>
|
|
<memory unit='KiB'>1048576</memory>
|
|
<currentMemory unit='KiB'>1048576</currentMemory>
|
|
<maxMemory slots="12" unit="KiB">1048576</maxMemory>
|
|
<vcpu placement='auto'>1</vcpu>
|
|
<iothreads>6</iothreads>
|
|
<os>
|
|
<type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
|
|
</os>
|
|
</domain>
|
|
"""
|
|
domain_mock = self.set_mock_vm(
|
|
"xml_with_iothreads_params", xml_with_iothreads_params
|
|
)
|
|
domain_mock.OSType = MagicMock(return_value="hvm")
|
|
define_mock = MagicMock(return_value=True)
|
|
self.mock_conn.defineXML = define_mock
|
|
|
|
# test updating existing iothreads
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("xml_with_iothreads_params", cpu={"iothreads": 7}),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("iothreads").text, "7")
|
|
|
|
def test_update_cputune_paramters(self):
|
|
"""
|
|
Test virt.update() with cputune parameters.
|
|
"""
|
|
xml_with_cputune_params = """
|
|
<domain type='kvm' id='8'>
|
|
<name>xml_with_cputune_params</name>
|
|
<memory unit='KiB'>1048576</memory>
|
|
<currentMemory unit='KiB'>1048576</currentMemory>
|
|
<maxMemory slots="12" unit="KiB">1048576</maxMemory>
|
|
<vcpu placement='auto'>1</vcpu>
|
|
<iothreads>4</iothreads>
|
|
<cputune>
|
|
<shares>2048</shares>
|
|
<period>1000000</period>
|
|
<quota>-1</quota>
|
|
<global_period>1000000</global_period>
|
|
<global_quota>-1</global_quota>
|
|
<emulator_period>1000000</emulator_period>
|
|
<emulator_quota>-1</emulator_quota>
|
|
<iothread_period>1000000</iothread_period>
|
|
<iothread_quota>-1</iothread_quota>
|
|
<vcpupin vcpu="0" cpuset="0-2"/>
|
|
<vcpupin vcpu="1" cpuset="3"/>
|
|
<vcpupin vcpu="2" cpuset="4"/>
|
|
<vcpupin vcpu="3" cpuset="5-7"/>
|
|
<emulatorpin cpuset="1-2"/>
|
|
<iothreadpin iothread="1" cpuset="1-5"/>
|
|
<iothreadpin iothread="2" cpuset="6-7"/>
|
|
<vcpusched vcpus="0" scheduler="idle" priority="3"/>
|
|
<vcpusched vcpus="1" scheduler="rr" priority="1"/>
|
|
<vcpusched vcpus="2" scheduler="fifo" priority="2"/>
|
|
<iothreadsched iothreads="4" scheduler="fifo"/>
|
|
<emulatorsched scheduler="idle"/>
|
|
<cachetune vcpus="0-4">
|
|
<cache id="0" level="2" type="both" size="4" unit="KiB"/>
|
|
<cache id="1" level="2" type="both" size="4" unit="KiB"/>
|
|
<monitor level="5" vcpus="0-2"/>
|
|
<monitor level="6" vcpus="1-3"/>
|
|
</cachetune>
|
|
<cachetune vcpus="5-8">
|
|
<monitor level="5" vcpus="5-6"/>
|
|
<monitor level="3" vcpus="7-8"/>
|
|
</cachetune>
|
|
<memorytune vcpus="0-6">
|
|
<node id="0" bandwidth="45"/>
|
|
</memorytune>
|
|
<memorytune vcpus="7-8">
|
|
<node id="0" bandwidth="120"/>
|
|
</memorytune>
|
|
</cputune>
|
|
<os>
|
|
<type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
|
|
</os>
|
|
</domain>
|
|
"""
|
|
domain_mock = self.set_mock_vm(
|
|
"xml_with_cputune_params", xml_with_cputune_params
|
|
)
|
|
domain_mock.OSType = MagicMock(return_value="hvm")
|
|
define_mock = MagicMock(return_value=True)
|
|
self.mock_conn.defineXML = define_mock
|
|
|
|
# test updating existing cputune parameters
|
|
cputune = {
|
|
"shares": 1024,
|
|
"period": 5000,
|
|
"quota": -20,
|
|
"global_period": 4000,
|
|
"global_quota": -30,
|
|
"emulator_period": 3000,
|
|
"emulator_quota": -4,
|
|
"iothread_period": 7000,
|
|
"iothread_quota": -5,
|
|
"vcpupin": {0: "1-4,^2", 1: "0,1", 2: "2,3", 3: "0,4"},
|
|
"emulatorpin": "1-3",
|
|
"iothreadpin": {1: "5-6", 2: "7-8"},
|
|
"vcpusched": [
|
|
{"scheduler": "fifo", "priority": 1, "vcpus": "0"},
|
|
{"scheduler": "fifo", "priority": 2, "vcpus": "1"},
|
|
{"scheduler": "idle", "priority": 3, "vcpus": "2"},
|
|
],
|
|
"iothreadsched": [
|
|
{"scheduler": "batch", "iothreads": "5-7", "priority": 1}
|
|
],
|
|
"emulatorsched": {"scheduler": "rr", "priority": 2},
|
|
"cachetune": {
|
|
"0-3": {
|
|
0: {"level": 3, "type": "both", "size": 3},
|
|
1: {"level": 3, "type": "both", "size": 3},
|
|
"monitor": {1: 3, "0-3": 3},
|
|
},
|
|
"4-5": {"monitor": {4: 3, 5: 2}},
|
|
},
|
|
"memorytune": {"0-2": {0: 60}, "3-4": {0: 50, 1: 70}},
|
|
}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("xml_with_cputune_params", cpu={"tuning": cputune}),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cputune").find("shares").text, "1024")
|
|
self.assertEqual(setxml.find("cputune").find("period").text, "5000")
|
|
self.assertEqual(setxml.find("cputune").find("quota").text, "-20")
|
|
self.assertEqual(setxml.find("cputune").find("global_period").text, "4000")
|
|
self.assertEqual(setxml.find("cputune").find("global_quota").text, "-30")
|
|
self.assertEqual(setxml.find("cputune").find("emulator_period").text, "3000")
|
|
self.assertEqual(setxml.find("cputune").find("emulator_quota").text, "-4")
|
|
self.assertEqual(setxml.find("cputune").find("iothread_period").text, "7000")
|
|
self.assertEqual(setxml.find("cputune").find("iothread_quota").text, "-5")
|
|
self.assertEqual(
|
|
setxml.find("cputune").find("vcpupin[@vcpu='0']").attrib.get("cpuset"),
|
|
"1,3,4",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("cputune").find("vcpupin[@vcpu='1']").attrib.get("cpuset"),
|
|
"0,1",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("cputune").find("vcpupin[@vcpu='2']").attrib.get("cpuset"),
|
|
"2,3",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("cputune").find("vcpupin[@vcpu='3']").attrib.get("cpuset"),
|
|
"0,4",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("cputune").find("emulatorpin").attrib.get("cpuset"), "1,2,3"
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("cputune")
|
|
.find("iothreadpin[@iothread='1']")
|
|
.attrib.get("cpuset"),
|
|
"5,6",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("cputune")
|
|
.find("iothreadpin[@iothread='2']")
|
|
.attrib.get("cpuset"),
|
|
"7,8",
|
|
)
|
|
self.assertDictEqual(
|
|
{
|
|
s.get("vcpus"): {
|
|
"scheduler": s.get("scheduler"),
|
|
"priority": s.get("priority"),
|
|
}
|
|
for s in setxml.findall("cputune/vcpusched")
|
|
},
|
|
{
|
|
"0": {"scheduler": "fifo", "priority": "1"},
|
|
"1": {"scheduler": "fifo", "priority": "2"},
|
|
"2": {"scheduler": "idle", "priority": "3"},
|
|
},
|
|
)
|
|
self.assertDictEqual(
|
|
{
|
|
s.get("iothreads"): {
|
|
"scheduler": s.get("scheduler"),
|
|
"priority": s.get("priority"),
|
|
}
|
|
for s in setxml.findall("cputune/iothreadsched")
|
|
},
|
|
{"5,6,7": {"scheduler": "batch", "priority": "1"}},
|
|
)
|
|
self.assertEqual(setxml.find("cputune/emulatorsched").get("scheduler"), "rr")
|
|
self.assertEqual(setxml.find("cputune/emulatorsched").get("priority"), "2")
|
|
self.assertIsNotNone(setxml.find("./cputune/cachetune[@vcpus='0,1,2,3']"))
|
|
self.assertEqual(
|
|
setxml.find(
|
|
"./cputune/cachetune[@vcpus='0,1,2,3']/cache[@id='0']"
|
|
).attrib.get("level"),
|
|
"3",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find(
|
|
"./cputune/cachetune[@vcpus='0,1,2,3']/cache[@id='0']"
|
|
).attrib.get("type"),
|
|
"both",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find(
|
|
"./cputune/cachetune[@vcpus='0,1,2,3']/monitor[@vcpus='1']"
|
|
).attrib.get("level"),
|
|
"3",
|
|
)
|
|
self.assertNotEqual(
|
|
setxml.find("./cputune/cachetune[@vcpus='0,1,2,3']/monitor[@vcpus='1']"),
|
|
None,
|
|
)
|
|
self.assertNotEqual(
|
|
setxml.find("./cputune/cachetune[@vcpus='4,5']").attrib.get("vcpus"), None
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cputune/cachetune[@vcpus='4,5']/cache[@id='0']"), None
|
|
)
|
|
self.assertEqual(
|
|
setxml.find(
|
|
"./cputune/cachetune[@vcpus='4,5']/monitor[@vcpus='4']"
|
|
).attrib.get("level"),
|
|
"3",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find(
|
|
"./cputune/cachetune[@vcpus='4,5']/monitor[@vcpus='5']"
|
|
).attrib.get("level"),
|
|
"2",
|
|
)
|
|
self.assertNotEqual(setxml.find("./cputune/memorytune[@vcpus='0,1,2']"), None)
|
|
self.assertEqual(
|
|
setxml.find(
|
|
"./cputune/memorytune[@vcpus='0,1,2']/node[@id='0']"
|
|
).attrib.get("bandwidth"),
|
|
"60",
|
|
)
|
|
self.assertNotEqual(setxml.find("./cputune/memorytune[@vcpus='3,4']"), None)
|
|
self.assertEqual(
|
|
setxml.find("./cputune/memorytune[@vcpus='3,4']/node[@id='0']").attrib.get(
|
|
"bandwidth"
|
|
),
|
|
"50",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cputune/memorytune[@vcpus='3,4']/node[@id='1']").attrib.get(
|
|
"bandwidth"
|
|
),
|
|
"70",
|
|
)
|
|
|
|
# test removing cputune attributes and sub elements
|
|
cputune = {
|
|
"shares": None,
|
|
"period": 20000,
|
|
"quota": None,
|
|
"global_period": 5000,
|
|
"global_quota": None,
|
|
"emulator_period": 2000,
|
|
"emulator_quota": -4,
|
|
"iothread_period": None,
|
|
"iothread_quota": -5,
|
|
"vcpupin": {0: "1-4,^2", 2: "2,4"},
|
|
"emulatorpin": None,
|
|
"iothreadpin": {1: "5-6"},
|
|
"vcpusched": [{"scheduler": "idle", "priority": 5, "vcpus": "1"}],
|
|
"iothreadsched": None,
|
|
"cachetune": {
|
|
"0-3": {
|
|
0: {"level": 4, "type": "data", "size": 7},
|
|
"monitor": {"1-2": 11},
|
|
},
|
|
},
|
|
"memorytune": {"3-4": {0: 37, 1: 73}},
|
|
}
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("xml_with_cputune_params", cpu={"tuning": cputune}),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cputune").find("shares"), None)
|
|
self.assertEqual(setxml.find("cputune").find("period").text, "20000")
|
|
self.assertEqual(setxml.find("cputune").find("quota"), None)
|
|
self.assertEqual(setxml.find("cputune").find("global_period").text, "5000")
|
|
self.assertEqual(setxml.find("cputune").find("global_quota"), None)
|
|
self.assertEqual(setxml.find("cputune").find("emulator_period").text, "2000")
|
|
self.assertEqual(setxml.find("cputune").find("emulator_quota").text, "-4")
|
|
self.assertEqual(setxml.find("cputune").find("iothread_period"), None)
|
|
self.assertEqual(setxml.find("cputune").find("iothread_quota").text, "-5")
|
|
self.assertEqual(
|
|
setxml.find("cputune").find("vcpupin[@vcpu='0']").attrib.get("cpuset"),
|
|
"1,3,4",
|
|
)
|
|
self.assertEqual(setxml.find("cputune").find("vcpupin[@vcpu='1']"), None)
|
|
self.assertEqual(
|
|
setxml.find("cputune").find("vcpupin[@vcpu='2']").attrib.get("cpuset"),
|
|
"2,4",
|
|
)
|
|
self.assertEqual(setxml.find("cputune").find("vcpupin[@vcpu='3']"), None)
|
|
self.assertEqual(setxml.find("cputune").find("emulatorpin"), None)
|
|
self.assertEqual(
|
|
setxml.find("cputune")
|
|
.find("iothreadpin[@iothread='1']")
|
|
.attrib.get("cpuset"),
|
|
"5,6",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("cputune").find("iothreadpin[@iothread='2']"), None
|
|
)
|
|
self.assertDictEqual(
|
|
{
|
|
s.get("vcpus"): {
|
|
"scheduler": s.get("scheduler"),
|
|
"priority": s.get("priority"),
|
|
}
|
|
for s in setxml.findall("cputune/vcpusched")
|
|
},
|
|
{"1": {"scheduler": "idle", "priority": "5"}},
|
|
)
|
|
self.assertEqual(setxml.find("cputune").find("iothreadsched"), None)
|
|
self.assertIsNotNone(setxml.find("./cputune/cachetune[@vcpus='0,1,2,3']"))
|
|
self.assertEqual(
|
|
setxml.find(
|
|
"./cputune/cachetune[@vcpus='0,1,2,3']/cache[@id='0']"
|
|
).attrib.get("size"),
|
|
"7",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find(
|
|
"./cputune/cachetune[@vcpus='0,1,2,3']/cache[@id='0']"
|
|
).attrib.get("level"),
|
|
"4",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find(
|
|
"./cputune/cachetune[@vcpus='0,1,2,3']/cache[@id='0']"
|
|
).attrib.get("type"),
|
|
"data",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find(
|
|
"./cputune/cachetune[@vcpus='0,1,2,3']/monitor[@vcpus='1,2']"
|
|
).attrib.get("level"),
|
|
"11",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cputune/cachetune[@vcpus='0,1,2,3']/monitor[@vcpus='3,4']"),
|
|
None,
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cputune/cachetune[@vcpus='0,1,2,3']/cache[@id='1']"), None
|
|
)
|
|
self.assertEqual(setxml.find("./cputune/cachetune[@vcpus='4,5']"), None)
|
|
self.assertEqual(setxml.find("./cputune/memorytune[@vcpus='0,1,2']"), None)
|
|
self.assertEqual(
|
|
setxml.find("./cputune/memorytune[@vcpus='3,4']/node[@id='0']").attrib.get(
|
|
"bandwidth"
|
|
),
|
|
"37",
|
|
)
|
|
self.assertEqual(
|
|
setxml.find("./cputune/memorytune[@vcpus='3,4']/node[@id='1']").attrib.get(
|
|
"bandwidth"
|
|
),
|
|
"73",
|
|
)
|
|
|
|
cputune_subelement = {
|
|
"vcpupin": None,
|
|
"iothreadpin": None,
|
|
"vcpusched": None,
|
|
"iothreadsched": None,
|
|
"cachetune": None,
|
|
"memorytune": None,
|
|
}
|
|
|
|
self.assertEqual(
|
|
{
|
|
"definition": True,
|
|
"disk": {"attached": [], "detached": [], "updated": []},
|
|
"interface": {"attached": [], "detached": []},
|
|
},
|
|
virt.update("xml_with_cputune_params", cpu={"tuning": cputune_subelement}),
|
|
)
|
|
setxml = ET.fromstring(define_mock.call_args[0][0])
|
|
self.assertEqual(setxml.find("cputune").find("vcpupin"), None)
|
|
self.assertEqual(setxml.find("cputune").find("iothreadpin"), None)
|
|
self.assertEqual(setxml.find("cputune").find("vcpusched"), None)
|
|
self.assertEqual(setxml.find("cputune").find("iothreadsched"), None)
|
|
self.assertEqual(setxml.find("cputune").find("cachetune"), None)
|
|
self.assertEqual(setxml.find("cputune").find("memorytune"), None)
|
|
|
|
def test_handle_unit(self):
|
|
"""
|
|
Test regex function for handling units
|
|
"""
|
|
valid_case = [
|
|
("2", 2097152),
|
|
("42", 44040192),
|
|
("5b", 5),
|
|
("2.3Kib", 2355),
|
|
("5.8Kb", 5800),
|
|
("16MiB", 16777216),
|
|
("20 GB", 20000000000),
|
|
("16KB", 16000),
|
|
(".5k", 512),
|
|
("2.k", 2048),
|
|
]
|
|
|
|
for key, val in valid_case:
|
|
self.assertEqual(virt._handle_unit(key), val)
|
|
|
|
invalid_case = [
|
|
("9ib", "invalid units"),
|
|
("8byte", "invalid units"),
|
|
("512bytes", "invalid units"),
|
|
("4 Kbytes", "invalid units"),
|
|
("3.4.MB", "invalid number"),
|
|
("", "invalid number"),
|
|
("bytes", "invalid number"),
|
|
("2HB", "invalid units"),
|
|
]
|
|
|
|
for key, val in invalid_case:
|
|
with self.assertRaises(SaltInvocationError):
|
|
virt._handle_unit(key)
|
|
|
|
def test_mixed_dict_and_list_as_profile_objects(self):
|
|
"""
|
|
Test virt._nic_profile with mixed dictionaries and lists as input.
|
|
"""
|
|
yaml_config = """
|
|
virt:
|
|
nic:
|
|
new-listonly-profile:
|
|
- bridge: br0
|
|
name: eth0
|
|
- model: virtio
|
|
name: eth1
|
|
source: test_network
|
|
type: network
|
|
new-list-with-legacy-names:
|
|
- eth0:
|
|
bridge: br0
|
|
- eth1:
|
|
bridge: br1
|
|
model: virtio
|
|
non-default-legacy-profile:
|
|
eth0:
|
|
bridge: br0
|
|
eth1:
|
|
bridge: br1
|
|
model: virtio
|
|
"""
|
|
mock_config = salt.utils.yaml.safe_load(yaml_config)
|
|
with patch.dict(
|
|
salt.modules.config.__opts__, mock_config # pylint: disable=no-member
|
|
):
|
|
|
|
for name in mock_config["virt"]["nic"].keys():
|
|
profile = salt.modules.virt._nic_profile(name, "kvm")
|
|
self.assertEqual(len(profile), 2)
|
|
|
|
interface_attrs = profile[0]
|
|
self.assertIn("source", interface_attrs)
|
|
self.assertIn("type", interface_attrs)
|
|
self.assertIn("name", interface_attrs)
|
|
self.assertIn("model", interface_attrs)
|
|
self.assertEqual(interface_attrs["model"], "virtio")
|
|
|
|
def test_get_xml(self):
|
|
"""
|
|
Test virt.get_xml()
|
|
"""
|
|
xml = """<domain type='kvm' id='7'>
|
|
<name>test-vm</name>
|
|
<devices>
|
|
<graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'>
|
|
<listen type='address' address='0.0.0.0'/>
|
|
</graphics>
|
|
</devices>
|
|
</domain>
|
|
"""
|
|
domain = self.set_mock_vm("test-vm", xml)
|
|
self.assertEqual(xml, virt.get_xml("test-vm"))
|
|
self.assertEqual(xml, virt.get_xml(domain))
|
|
|
|
def test_get_loader(self):
|
|
"""
|
|
Test virt.get_loader()
|
|
"""
|
|
xml = """<domain type='kvm' id='7'>
|
|
<name>test-vm</name>
|
|
<os>
|
|
<loader readonly='yes' type='pflash'>/foo/bar</loader>
|
|
</os>
|
|
</domain>
|
|
"""
|
|
self.set_mock_vm("test-vm", xml)
|
|
|
|
loader = virt.get_loader("test-vm")
|
|
self.assertEqual("/foo/bar", loader["path"])
|
|
self.assertEqual("yes", loader["readonly"])
|
|
|
|
def test_cpu_baseline(self):
|
|
"""
|
|
Test virt.cpu_baseline()
|
|
"""
|
|
capabilities_xml = dedent(
|
|
"""<capabilities>
|
|
<host>
|
|
<uuid>44454c4c-3400-105a-8033-b3c04f4b344a</uuid>
|
|
<cpu>
|
|
<arch>x86_64</arch>
|
|
<vendor>Intel</vendor>
|
|
</cpu>
|
|
</host>
|
|
</capabilities>"""
|
|
)
|
|
|
|
baseline_cpu_xml = b"""<cpu match="exact" mode="custom">
|
|
<vendor>Intel</vendor>
|
|
</cpu>"""
|
|
|
|
self.mock_conn.getCapabilities.return_value = capabilities_xml
|
|
self.mock_conn.baselineCPU.return_value = baseline_cpu_xml
|
|
self.assertMultiLineEqual(str(baseline_cpu_xml), str(virt.cpu_baseline()))
|
|
|
|
def test_parse_qemu_img_info(self):
|
|
"""
|
|
Make sure that qemu-img info output is properly parsed
|
|
"""
|
|
qemu_infos = """[{
|
|
"snapshots": [
|
|
{
|
|
"vm-clock-nsec": 0,
|
|
"name": "first-snap",
|
|
"date-sec": 1528877587,
|
|
"date-nsec": 380589000,
|
|
"vm-clock-sec": 0,
|
|
"id": "1",
|
|
"vm-state-size": 1234
|
|
},
|
|
{
|
|
"vm-clock-nsec": 0,
|
|
"name": "second snap",
|
|
"date-sec": 1528877592,
|
|
"date-nsec": 933509000,
|
|
"vm-clock-sec": 0,
|
|
"id": "2",
|
|
"vm-state-size": 4567
|
|
}
|
|
],
|
|
"virtual-size": 25769803776,
|
|
"filename": "/disks/test.qcow2",
|
|
"cluster-size": 65536,
|
|
"format": "qcow2",
|
|
"actual-size": 217088,
|
|
"format-specific": {
|
|
"type": "qcow2",
|
|
"data": {
|
|
"compat": "1.1",
|
|
"lazy-refcounts": false,
|
|
"refcount-bits": 16,
|
|
"corrupt": false
|
|
}
|
|
},
|
|
"full-backing-filename": "/disks/mybacking.qcow2",
|
|
"backing-filename": "mybacking.qcow2",
|
|
"dirty-flag": false
|
|
},
|
|
{
|
|
"virtual-size": 25769803776,
|
|
"filename": "/disks/mybacking.qcow2",
|
|
"cluster-size": 65536,
|
|
"format": "qcow2",
|
|
"actual-size": 393744384,
|
|
"format-specific": {
|
|
"type": "qcow2",
|
|
"data": {
|
|
"compat": "1.1",
|
|
"lazy-refcounts": false,
|
|
"refcount-bits": 16,
|
|
"corrupt": false
|
|
}
|
|
},
|
|
"full-backing-filename": "/disks/root.qcow2",
|
|
"backing-filename": "root.qcow2",
|
|
"dirty-flag": false
|
|
},
|
|
{
|
|
"virtual-size": 25769803776,
|
|
"filename": "/disks/root.qcow2",
|
|
"cluster-size": 65536,
|
|
"format": "qcow2",
|
|
"actual-size": 196872192,
|
|
"format-specific": {
|
|
"type": "qcow2",
|
|
"data": {
|
|
"compat": "1.1",
|
|
"lazy-refcounts": false,
|
|
"refcount-bits": 16,
|
|
"corrupt": false
|
|
}
|
|
},
|
|
"dirty-flag": false
|
|
}]"""
|
|
|
|
self.assertEqual(
|
|
{
|
|
"file": "/disks/test.qcow2",
|
|
"file format": "qcow2",
|
|
"backing file": {
|
|
"file": "/disks/mybacking.qcow2",
|
|
"file format": "qcow2",
|
|
"disk size": 393744384,
|
|
"virtual size": 25769803776,
|
|
"cluster size": 65536,
|
|
"backing file": {
|
|
"file": "/disks/root.qcow2",
|
|
"file format": "qcow2",
|
|
"disk size": 196872192,
|
|
"virtual size": 25769803776,
|
|
"cluster size": 65536,
|
|
},
|
|
},
|
|
"disk size": 217088,
|
|
"virtual size": 25769803776,
|
|
"cluster size": 65536,
|
|
"snapshots": [
|
|
{
|
|
"id": "1",
|
|
"tag": "first-snap",
|
|
"vmsize": 1234,
|
|
"date": datetime.datetime.fromtimestamp(
|
|
float("{}.{}".format(1528877587, 380589000))
|
|
).isoformat(),
|
|
"vmclock": "00:00:00",
|
|
},
|
|
{
|
|
"id": "2",
|
|
"tag": "second snap",
|
|
"vmsize": 4567,
|
|
"date": datetime.datetime.fromtimestamp(
|
|
float("{}.{}".format(1528877592, 933509000))
|
|
).isoformat(),
|
|
"vmclock": "00:00:00",
|
|
},
|
|
],
|
|
},
|
|
virt._parse_qemu_img_info(qemu_infos),
|
|
)
|
|
|
|
@patch("salt.modules.virt.stop", return_value=True)
|
|
@patch("salt.modules.virt.undefine")
|
|
@patch("os.remove")
|
|
def test_purge_default(self, mock_remove, mock_undefine, mock_stop):
|
|
"""
|
|
Test virt.purge() with default parameters
|
|
"""
|
|
xml = """<domain type='kvm' id='7'>
|
|
<name>test-vm</name>
|
|
<devices>
|
|
<disk type='file' device='disk'>
|
|
<driver name='qemu' type='qcow2'/>
|
|
<source file='/disks/test.qcow2'/>
|
|
<target dev='vda' bus='virtio'/>
|
|
</disk>
|
|
<disk type='file' device='cdrom'>
|
|
<driver name='qemu' type='raw'/>
|
|
<source file='/disks/test-cdrom.iso'/>
|
|
<target dev='hda' bus='ide'/>
|
|
<readonly/>
|
|
</disk>
|
|
</devices>
|
|
</domain>
|
|
"""
|
|
self.set_mock_vm("test-vm", xml)
|
|
|
|
qemu_infos = """[{
|
|
"virtual-size": 25769803776,
|
|
"filename": "/disks/test.qcow2",
|
|
"cluster-size": 65536,
|
|
"format": "qcow2",
|
|
"actual-size": 217088,
|
|
"format-specific": {
|
|
"type": "qcow2",
|
|
"data": {
|
|
"compat": "1.1",
|
|
"lazy-refcounts": false,
|
|
"refcount-bits": 16,
|
|
"corrupt": false
|
|
}
|
|
},
|
|
"dirty-flag": false
|
|
}]"""
|
|
|
|
self.mock_popen.communicate.return_value = [qemu_infos, ""]
|
|
self.mock_popen.returncode = 0
|
|
|
|
with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=True)}):
|
|
res = virt.purge("test-vm")
|
|
self.assertTrue(res)
|
|
mock_remove.assert_called_once()
|
|
mock_remove.assert_any_call("/disks/test.qcow2")
|
|
|
|
@patch("salt.modules.virt.stop", return_value=True)
|
|
@patch("salt.modules.virt.undefine")
|
|
def test_purge_volumes(self, mock_undefine, mock_stop):
|
|
"""
|
|
Test virt.purge() with volume disks
|
|
"""
|
|
xml = """<domain type='kvm' id='7'>
|
|
<name>test-vm</name>
|
|
<devices>
|
|
<disk type='volume' device='disk'>
|
|
<driver name='qemu' type='qcow2' cache='none' io='native'/>
|
|
<source pool='default' volume='vm05_system'/>
|
|
<backingStore type='file' index='1'>
|
|
<format type='qcow2'/>
|
|
<source file='/var/lib/libvirt/images/vm04_system.qcow2'/>
|
|
<backingStore type='file' index='2'>
|
|
<format type='qcow2'/>
|
|
<source file='/var/testsuite-data/disk-image-template.qcow2'/>
|
|
<backingStore/>
|
|
</backingStore>
|
|
</backingStore>
|
|
<target dev='vda' bus='virtio'/>
|
|
<alias name='virtio-disk0'/>
|
|
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
|
|
</disk>
|
|
</devices>
|
|
</domain>
|
|
"""
|
|
self.set_mock_vm("test-vm", xml)
|
|
|
|
pool_mock = MagicMock()
|
|
pool_mock.storageVolLookupByName.return_value.info.return_value = [
|
|
0,
|
|
1234567,
|
|
12345,
|
|
]
|
|
pool_mock.storageVolLookupByName.return_value.XMLDesc.return_value = [
|
|
"""
|
|
<volume type='file'>
|
|
<name>vm05_system</name>
|
|
<target>
|
|
<path>/var/lib/libvirt/images/vm05_system</path>
|
|
<format type='qcow2'/>
|
|
</target>
|
|
<backingStore>
|
|
<path>/var/lib/libvirt/images/vm04_system.qcow2</path>
|
|
<format type='qcow2'/>
|
|
</backingStore>
|
|
</volume>
|
|
""",
|
|
]
|
|
pool_mock.listVolumes.return_value = ["vm05_system", "vm04_system.qcow2"]
|
|
self.mock_conn.storagePoolLookupByName.return_value = pool_mock
|
|
self.mock_conn.listStoragePools.return_value = ["default"]
|
|
|
|
with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=False)}):
|
|
res = virt.purge("test-vm")
|
|
self.assertTrue(res)
|
|
pool_mock.storageVolLookupByName.return_value.delete.assert_called_once()
|
|
|
|
@patch("salt.modules.virt.stop", return_value=True)
|
|
@patch("salt.modules.virt.undefine")
|
|
def test_purge_rbd(self, mock_undefine, mock_stop):
|
|
"""
|
|
Test virt.purge() with RBD disks
|
|
"""
|
|
xml = """<domain type='kvm' id='7'>
|
|
<name>test-vm</name>
|
|
<devices>
|
|
<disk type="network" device="disk">
|
|
<driver name='raw' type='qcow2'/>
|
|
<source protocol='rbd' name='libvirt-pool/my_vm_data2'>
|
|
<host name='ses2.tf.local'/>
|
|
<host name='ses3.tf.local' port='1234'/>
|
|
<auth username='libvirt'>
|
|
<secret type='ceph' usage='pool_test-rbd'/>
|
|
</auth>
|
|
</source>
|
|
<target dev='vdc' bus='virtio'/>
|
|
<alias name='virtio-disk2'/>
|
|
<address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x2'/>
|
|
</disk>
|
|
</devices>
|
|
</domain>
|
|
"""
|
|
self.set_mock_vm("test-vm", xml)
|
|
|
|
pool_mock = MagicMock()
|
|
pool_mock.storageVolLookupByName.return_value.info.return_value = [
|
|
0,
|
|
1234567,
|
|
12345,
|
|
]
|
|
pool_mock.XMLDesc.return_value = """
|
|
<pool type='rbd'>
|
|
<name>test-ses</name>
|
|
<source>
|
|
<host name='ses2.tf.local'/>
|
|
<name>libvirt-pool</name>
|
|
<auth type='ceph' username='libvirt'>
|
|
<secret usage='pool_test-ses'/>
|
|
</auth>
|
|
</source>
|
|
</pool>
|
|
"""
|
|
pool_mock.name.return_value = "test-ses"
|
|
pool_mock.storageVolLookupByName.return_value.XMLDesc.return_value = [
|
|
"""
|
|
<volume type='network'>
|
|
<name>my_vm_data2</name>
|
|
<source>
|
|
</source>
|
|
<capacity unit='bytes'>536870912</capacity>
|
|
<allocation unit='bytes'>0</allocation>
|
|
<target>
|
|
<path>libvirt-pool/my_vm_data2</path>
|
|
<format type='raw'/>
|
|
</target>
|
|
</volume>
|
|
""",
|
|
]
|
|
pool_mock.listVolumes.return_value = ["my_vm_data2"]
|
|
self.mock_conn.listAllStoragePools.return_value = [pool_mock]
|
|
self.mock_conn.listStoragePools.return_value = ["test-ses"]
|
|
self.mock_conn.storagePoolLookupByName.return_value = pool_mock
|
|
|
|
with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=False)}):
|
|
res = virt.purge("test-vm")
|
|
self.assertTrue(res)
|
|
pool_mock.storageVolLookupByName.return_value.delete.assert_called_once()
|
|
|
|
@patch("salt.modules.virt.stop", return_value=True)
|
|
@patch("salt.modules.virt.undefine")
|
|
@patch("os.remove")
|
|
def test_purge_removable(self, mock_remove, mock_undefine, mock_stop):
|
|
"""
|
|
Test virt.purge(removables=True)
|
|
"""
|
|
xml = """<domain type="kvm" id="7">
|
|
<name>test-vm</name>
|
|
<devices>
|
|
<disk type='file' device='disk'>
|
|
<driver name='qemu' type='qcow2'/>
|
|
<source file='/disks/test.qcow2'/>
|
|
<target dev='vda' bus='virtio'/>
|
|
</disk>
|
|
<disk type='file' device='cdrom'>
|
|
<driver name='qemu' type='raw'/>
|
|
<source file='/disks/test-cdrom.iso'/>
|
|
<target dev='hda' bus='ide'/>
|
|
<readonly/>
|
|
</disk>
|
|
<disk type='file' device='floppy'>
|
|
<driver name='qemu' type='raw'/>
|
|
<source file='/disks/test-floppy.iso'/>
|
|
<target dev='hdb' bus='ide'/>
|
|
<readonly/>
|
|
</disk>
|
|
</devices>
|
|
</domain>
|
|
"""
|
|
self.set_mock_vm("test-vm", xml)
|
|
|
|
qemu_infos = """[{
|
|
"virtual-size": 25769803776,
|
|
"filename": "/disks/test.qcow2",
|
|
"cluster-size": 65536,
|
|
"format": "qcow2",
|
|
"actual-size": 217088,
|
|
"format-specific": {
|
|
"type": "qcow2",
|
|
"data": {
|
|
"compat": "1.1",
|
|
"lazy-refcounts": false,
|
|
"refcount-bits": 16,
|
|
"corrupt": false
|
|
}
|
|
},
|
|
"dirty-flag": false
|
|
}]"""
|
|
|
|
self.mock_popen.communicate.return_value = [qemu_infos, ""]
|
|
self.mock_popen.returncode = 0
|
|
|
|
with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=True)}):
|
|
res = virt.purge("test-vm", removables=True)
|
|
self.assertTrue(res)
|
|
mock_remove.assert_any_call("/disks/test.qcow2")
|
|
mock_remove.assert_any_call("/disks/test-cdrom.iso")
|
|
|
|
def test_capabilities(self):
|
|
"""
|
|
Test the virt.capabilities parsing
|
|
"""
|
|
xml = """
|
|
<capabilities>
|
|
<host>
|
|
<uuid>44454c4c-3400-105a-8033-b3c04f4b344a</uuid>
|
|
<cpu>
|
|
<arch>x86_64</arch>
|
|
<model>Nehalem</model>
|
|
<vendor>Intel</vendor>
|
|
<microcode version='25'/>
|
|
<topology sockets='1' cores='4' threads='2'/>
|
|
<feature name='vme'/>
|
|
<feature name='ds'/>
|
|
<feature name='acpi'/>
|
|
<pages unit='KiB' size='4'/>
|
|
<pages unit='KiB' size='2048'/>
|
|
</cpu>
|
|
<power_management>
|
|
<suspend_mem/>
|
|
<suspend_disk/>
|
|
<suspend_hybrid/>
|
|
</power_management>
|
|
<migration_features>
|
|
<live/>
|
|
<uri_transports>
|
|
<uri_transport>tcp</uri_transport>
|
|
<uri_transport>rdma</uri_transport>
|
|
</uri_transports>
|
|
</migration_features>
|
|
<topology>
|
|
<cells num='1'>
|
|
<cell id='0'>
|
|
<memory unit='KiB'>12367120</memory>
|
|
<pages unit='KiB' size='4'>3091780</pages>
|
|
<pages unit='KiB' size='2048'>0</pages>
|
|
<distances>
|
|
<sibling id='0' value='10'/>
|
|
</distances>
|
|
<cpus num='8'>
|
|
<cpu id='0' socket_id='0' core_id='0' siblings='0,4'/>
|
|
<cpu id='1' socket_id='0' core_id='1' siblings='1,5'/>
|
|
<cpu id='2' socket_id='0' core_id='2' siblings='2,6'/>
|
|
<cpu id='3' socket_id='0' core_id='3' siblings='3,7'/>
|
|
<cpu id='4' socket_id='0' core_id='0' siblings='0,4'/>
|
|
<cpu id='5' socket_id='0' core_id='1' siblings='1,5'/>
|
|
<cpu id='6' socket_id='0' core_id='2' siblings='2,6'/>
|
|
<cpu id='7' socket_id='0' core_id='3' siblings='3,7'/>
|
|
</cpus>
|
|
</cell>
|
|
</cells>
|
|
</topology>
|
|
<cache>
|
|
<bank id='0' level='3' type='both' size='8' unit='MiB' cpus='0-7'/>
|
|
</cache>
|
|
<secmodel>
|
|
<model>apparmor</model>
|
|
<doi>0</doi>
|
|
</secmodel>
|
|
<secmodel>
|
|
<model>dac</model>
|
|
<doi>0</doi>
|
|
<baselabel type='kvm'>+487:+486</baselabel>
|
|
<baselabel type='qemu'>+487:+486</baselabel>
|
|
</secmodel>
|
|
</host>
|
|
|
|
<guest>
|
|
<os_type>hvm</os_type>
|
|
<arch name='i686'>
|
|
<wordsize>32</wordsize>
|
|
<emulator>/usr/bin/qemu-system-i386</emulator>
|
|
<machine maxCpus='255'>pc-i440fx-2.6</machine>
|
|
<machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
|
|
<machine maxCpus='255'>pc-0.12</machine>
|
|
<domain type='qemu'/>
|
|
<domain type='kvm'>
|
|
<emulator>/usr/bin/qemu-kvm</emulator>
|
|
<machine maxCpus='255'>pc-i440fx-2.6</machine>
|
|
<machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
|
|
<machine maxCpus='255'>pc-0.12</machine>
|
|
</domain>
|
|
</arch>
|
|
<features>
|
|
<cpuselection/>
|
|
<deviceboot/>
|
|
<disksnapshot default='on' toggle='no'/>
|
|
<acpi default='off' toggle='yes'/>
|
|
<apic default='on' toggle='no'/>
|
|
<pae/>
|
|
<nonpae/>
|
|
</features>
|
|
</guest>
|
|
|
|
<guest>
|
|
<os_type>hvm</os_type>
|
|
<arch name='x86_64'>
|
|
<wordsize>64</wordsize>
|
|
<emulator>/usr/bin/qemu-system-x86_64</emulator>
|
|
<machine maxCpus='255'>pc-i440fx-2.6</machine>
|
|
<machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
|
|
<machine maxCpus='255'>pc-0.12</machine>
|
|
<domain type='qemu'/>
|
|
<domain type='kvm'>
|
|
<emulator>/usr/bin/qemu-kvm</emulator>
|
|
<machine maxCpus='255'>pc-i440fx-2.6</machine>
|
|
<machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
|
|
<machine maxCpus='255'>pc-0.12</machine>
|
|
</domain>
|
|
</arch>
|
|
<features>
|
|
<cpuselection/>
|
|
<deviceboot/>
|
|
<disksnapshot default='on' toggle='no'/>
|
|
<acpi default='on' toggle='yes'/>
|
|
<apic default='off' toggle='no'/>
|
|
</features>
|
|
</guest>
|
|
|
|
<guest>
|
|
<os_type>xen</os_type>
|
|
<arch name='x86_64'>
|
|
<wordsize>64</wordsize>
|
|
<emulator>/usr/bin/qemu-system-x86_64</emulator>
|
|
<machine>xenpv</machine>
|
|
<domain type='xen'/>
|
|
</arch>
|
|
</guest>
|
|
|
|
</capabilities>
|
|
"""
|
|
self.mock_conn.getCapabilities.return_value = xml # pylint: disable=no-member
|
|
caps = virt.capabilities()
|
|
|
|
expected = {
|
|
"host": {
|
|
"uuid": "44454c4c-3400-105a-8033-b3c04f4b344a",
|
|
"cpu": {
|
|
"arch": "x86_64",
|
|
"model": "Nehalem",
|
|
"vendor": "Intel",
|
|
"microcode": "25",
|
|
"sockets": 1,
|
|
"cores": 4,
|
|
"threads": 2,
|
|
"features": ["vme", "ds", "acpi"],
|
|
"pages": [{"size": "4 KiB"}, {"size": "2048 KiB"}],
|
|
},
|
|
"power_management": ["suspend_mem", "suspend_disk", "suspend_hybrid"],
|
|
"migration": {"live": True, "transports": ["tcp", "rdma"]},
|
|
"topology": {
|
|
"cells": [
|
|
{
|
|
"id": 0,
|
|
"memory": "12367120 KiB",
|
|
"pages": [
|
|
{"size": "4 KiB", "available": 3091780},
|
|
{"size": "2048 KiB", "available": 0},
|
|
],
|
|
"distances": {0: 10},
|
|
"cpus": [
|
|
{
|
|
"id": 0,
|
|
"socket_id": 0,
|
|
"core_id": 0,
|
|
"siblings": "0,4",
|
|
},
|
|
{
|
|
"id": 1,
|
|
"socket_id": 0,
|
|
"core_id": 1,
|
|
"siblings": "1,5",
|
|
},
|
|
{
|
|
"id": 2,
|
|
"socket_id": 0,
|
|
"core_id": 2,
|
|
"siblings": "2,6",
|
|
},
|
|
{
|
|
"id": 3,
|
|
"socket_id": 0,
|
|
"core_id": 3,
|
|
"siblings": "3,7",
|
|
},
|
|
{
|
|
"id": 4,
|
|
"socket_id": 0,
|
|
"core_id": 0,
|
|
"siblings": "0,4",
|
|
},
|
|
{
|
|
"id": 5,
|
|
"socket_id": 0,
|
|
"core_id": 1,
|
|
"siblings": "1,5",
|
|
},
|
|
{
|
|
"id": 6,
|
|
"socket_id": 0,
|
|
"core_id": 2,
|
|
"siblings": "2,6",
|
|
},
|
|
{
|
|
"id": 7,
|
|
"socket_id": 0,
|
|
"core_id": 3,
|
|
"siblings": "3,7",
|
|
},
|
|
],
|
|
}
|
|
]
|
|
},
|
|
"cache": {
|
|
"banks": [
|
|
{
|
|
"id": 0,
|
|
"level": 3,
|
|
"type": "both",
|
|
"size": "8 MiB",
|
|
"cpus": "0-7",
|
|
}
|
|
]
|
|
},
|
|
"security": [
|
|
{"model": "apparmor", "doi": "0", "baselabels": []},
|
|
{
|
|
"model": "dac",
|
|
"doi": "0",
|
|
"baselabels": [
|
|
{"type": "kvm", "label": "+487:+486"},
|
|
{"type": "qemu", "label": "+487:+486"},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
"guests": [
|
|
{
|
|
"os_type": "hvm",
|
|
"arch": {
|
|
"name": "i686",
|
|
"wordsize": 32,
|
|
"emulator": "/usr/bin/qemu-system-i386",
|
|
"machines": {
|
|
"pc-i440fx-2.6": {
|
|
"maxcpus": 255,
|
|
"alternate_names": ["pc"],
|
|
},
|
|
"pc-0.12": {"maxcpus": 255, "alternate_names": []},
|
|
},
|
|
"domains": {
|
|
"qemu": {"emulator": None, "machines": {}},
|
|
"kvm": {
|
|
"emulator": "/usr/bin/qemu-kvm",
|
|
"machines": {
|
|
"pc-i440fx-2.6": {
|
|
"maxcpus": 255,
|
|
"alternate_names": ["pc"],
|
|
},
|
|
"pc-0.12": {"maxcpus": 255, "alternate_names": []},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"features": {
|
|
"cpuselection": {"default": True, "toggle": False},
|
|
"deviceboot": {"default": True, "toggle": False},
|
|
"disksnapshot": {"default": True, "toggle": False},
|
|
"acpi": {"default": False, "toggle": True},
|
|
"apic": {"default": True, "toggle": False},
|
|
"pae": {"default": True, "toggle": False},
|
|
"nonpae": {"default": True, "toggle": False},
|
|
},
|
|
},
|
|
{
|
|
"os_type": "hvm",
|
|
"arch": {
|
|
"name": "x86_64",
|
|
"wordsize": 64,
|
|
"emulator": "/usr/bin/qemu-system-x86_64",
|
|
"machines": {
|
|
"pc-i440fx-2.6": {
|
|
"maxcpus": 255,
|
|
"alternate_names": ["pc"],
|
|
},
|
|
"pc-0.12": {"maxcpus": 255, "alternate_names": []},
|
|
},
|
|
"domains": {
|
|
"qemu": {"emulator": None, "machines": {}},
|
|
"kvm": {
|
|
"emulator": "/usr/bin/qemu-kvm",
|
|
"machines": {
|
|
"pc-i440fx-2.6": {
|
|
"maxcpus": 255,
|
|
"alternate_names": ["pc"],
|
|
},
|
|
"pc-0.12": {"maxcpus": 255, "alternate_names": []},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
"features": {
|
|
"cpuselection": {"default": True, "toggle": False},
|
|
"deviceboot": {"default": True, "toggle": False},
|
|
"disksnapshot": {"default": True, "toggle": False},
|
|
"acpi": {"default": True, "toggle": True},
|
|
"apic": {"default": False, "toggle": False},
|
|
},
|
|
},
|
|
{
|
|
"os_type": "xen",
|
|
"arch": {
|
|
"name": "x86_64",
|
|
"wordsize": 64,
|
|
"emulator": "/usr/bin/qemu-system-x86_64",
|
|
"machines": {"xenpv": {"alternate_names": []}},
|
|
"domains": {"xen": {"emulator": None, "machines": {}}},
|
|
},
|
|
},
|
|
],
|
|
}
|
|
self.assertEqual(expected, caps)
|
|
|
|
def test_domain_capabilities(self):
|
|
"""
|
|
Test the virt.domain_capabilities parsing
|
|
"""
|
|
xml = """
|
|
<domainCapabilities>
|
|
<path>/usr/bin/qemu-system-aarch64</path>
|
|
<domain>kvm</domain>
|
|
<machine>virt-2.12</machine>
|
|
<arch>aarch64</arch>
|
|
<vcpu max='255'/>
|
|
<iothreads supported='yes'/>
|
|
<os supported='yes'>
|
|
<loader supported='yes'>
|
|
<value>/usr/share/AAVMF/AAVMF_CODE.fd</value>
|
|
<value>/usr/share/AAVMF/AAVMF32_CODE.fd</value>
|
|
<value>/usr/share/OVMF/OVMF_CODE.fd</value>
|
|
<enum name='type'>
|
|
<value>rom</value>
|
|
<value>pflash</value>
|
|
</enum>
|
|
<enum name='readonly'>
|
|
<value>yes</value>
|
|
<value>no</value>
|
|
</enum>
|
|
</loader>
|
|
</os>
|
|
<cpu>
|
|
<mode name='host-passthrough' supported='yes'/>
|
|
<mode name='host-model' supported='yes'>
|
|
<model fallback='forbid'>sample-cpu</model>
|
|
<vendor>ACME</vendor>
|
|
<feature policy='require' name='vme'/>
|
|
<feature policy='require' name='ss'/>
|
|
</mode>
|
|
<mode name='custom' supported='yes'>
|
|
<model usable='unknown'>pxa262</model>
|
|
<model usable='yes'>pxa270-a0</model>
|
|
<model usable='no'>arm1136</model>
|
|
</mode>
|
|
</cpu>
|
|
<devices>
|
|
<disk supported='yes'>
|
|
<enum name='diskDevice'>
|
|
<value>disk</value>
|
|
<value>cdrom</value>
|
|
<value>floppy</value>
|
|
<value>lun</value>
|
|
</enum>
|
|
<enum name='bus'>
|
|
<value>fdc</value>
|
|
<value>scsi</value>
|
|
<value>virtio</value>
|
|
<value>usb</value>
|
|
<value>sata</value>
|
|
</enum>
|
|
</disk>
|
|
<graphics supported='yes'>
|
|
<enum name='type'>
|
|
<value>sdl</value>
|
|
<value>vnc</value>
|
|
</enum>
|
|
</graphics>
|
|
<video supported='yes'>
|
|
<enum name='modelType'>
|
|
<value>vga</value>
|
|
<value>virtio</value>
|
|
</enum>
|
|
</video>
|
|
<hostdev supported='yes'>
|
|
<enum name='mode'>
|
|
<value>subsystem</value>
|
|
</enum>
|
|
<enum name='startupPolicy'>
|
|
<value>default</value>
|
|
<value>mandatory</value>
|
|
<value>requisite</value>
|
|
<value>optional</value>
|
|
</enum>
|
|
<enum name='subsysType'>
|
|
<value>usb</value>
|
|
<value>pci</value>
|
|
<value>scsi</value>
|
|
</enum>
|
|
<enum name='capsType'/>
|
|
<enum name='pciBackend'>
|
|
<value>default</value>
|
|
<value>kvm</value>
|
|
<value>vfio</value>
|
|
</enum>
|
|
</hostdev>
|
|
</devices>
|
|
<features>
|
|
<gic supported='yes'>
|
|
<enum name='version'>
|
|
<value>3</value>
|
|
</enum>
|
|
</gic>
|
|
<vmcoreinfo supported='yes'/>
|
|
</features>
|
|
</domainCapabilities>
|
|
"""
|
|
|
|
self.mock_conn.getDomainCapabilities.return_value = (
|
|
xml # pylint: disable=no-member
|
|
)
|
|
caps = virt.domain_capabilities()
|
|
|
|
expected = {
|
|
"emulator": "/usr/bin/qemu-system-aarch64",
|
|
"domain": "kvm",
|
|
"machine": "virt-2.12",
|
|
"arch": "aarch64",
|
|
"max_vcpus": 255,
|
|
"iothreads": True,
|
|
"os": {
|
|
"loader": {
|
|
"type": ["rom", "pflash"],
|
|
"readonly": ["yes", "no"],
|
|
"values": [
|
|
"/usr/share/AAVMF/AAVMF_CODE.fd",
|
|
"/usr/share/AAVMF/AAVMF32_CODE.fd",
|
|
"/usr/share/OVMF/OVMF_CODE.fd",
|
|
],
|
|
}
|
|
},
|
|
"cpu": {
|
|
"host-passthrough": True,
|
|
"host-model": {
|
|
"model": {"name": "sample-cpu", "fallback": "forbid"},
|
|
"vendor": "ACME",
|
|
"features": {"vme": "require", "ss": "require"},
|
|
},
|
|
"custom": {
|
|
"models": {"pxa262": "unknown", "pxa270-a0": "yes", "arm1136": "no"}
|
|
},
|
|
},
|
|
"devices": {
|
|
"disk": {
|
|
"diskDevice": ["disk", "cdrom", "floppy", "lun"],
|
|
"bus": ["fdc", "scsi", "virtio", "usb", "sata"],
|
|
},
|
|
"graphics": {"type": ["sdl", "vnc"]},
|
|
"video": {"modelType": ["vga", "virtio"]},
|
|
"hostdev": {
|
|
"mode": ["subsystem"],
|
|
"startupPolicy": ["default", "mandatory", "requisite", "optional"],
|
|
"subsysType": ["usb", "pci", "scsi"],
|
|
"capsType": [],
|
|
"pciBackend": ["default", "kvm", "vfio"],
|
|
},
|
|
},
|
|
"features": {"gic": {"version": ["3"]}, "vmcoreinfo": {}},
|
|
}
|
|
|
|
self.assertEqual(expected, caps)
|
|
|
|
def test_all_capabilities(self):
|
|
"""
|
|
Test the virt.domain_capabilities default output
|
|
"""
|
|
domainXml = """
|
|
<domainCapabilities>
|
|
<path>/usr/bin/qemu-system-x86_64</path>
|
|
<domain>kvm</domain>
|
|
<machine>virt-2.12</machine>
|
|
<arch>x86_64</arch>
|
|
<vcpu max='255'/>
|
|
<iothreads supported='yes'/>
|
|
</domainCapabilities>
|
|
"""
|
|
hostXml = """
|
|
<capabilities>
|
|
<host>
|
|
<uuid>44454c4c-3400-105a-8033-b3c04f4b344a</uuid>
|
|
<cpu>
|
|
<arch>x86_64</arch>
|
|
<model>Nehalem</model>
|
|
<vendor>Intel</vendor>
|
|
<microcode version='25'/>
|
|
<topology sockets='1' cores='4' threads='2'/>
|
|
</cpu>
|
|
</host>
|
|
<guest>
|
|
<os_type>hvm</os_type>
|
|
<arch name='x86_64'>
|
|
<wordsize>64</wordsize>
|
|
<emulator>/usr/bin/qemu-system-x86_64</emulator>
|
|
<machine maxCpus='255'>pc-i440fx-2.6</machine>
|
|
<machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
|
|
<machine maxCpus='255'>pc-0.12</machine>
|
|
<domain type='qemu'/>
|
|
<domain type='kvm'>
|
|
<emulator>/usr/bin/qemu-kvm</emulator>
|
|
<machine maxCpus='255'>pc-i440fx-2.6</machine>
|
|
<machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
|
|
<machine maxCpus='255'>pc-0.12</machine>
|
|
</domain>
|
|
</arch>
|
|
</guest>
|
|
</capabilities>
|
|
"""
|
|
|
|
# pylint: disable=no-member
|
|
self.mock_conn.getCapabilities.return_value = hostXml
|
|
self.mock_conn.getDomainCapabilities.side_effect = [
|
|
domainXml,
|
|
domainXml.replace("<domain>kvm", "<domain>qemu"),
|
|
]
|
|
# pylint: enable=no-member
|
|
|
|
caps = virt.all_capabilities()
|
|
self.assertEqual(
|
|
"44454c4c-3400-105a-8033-b3c04f4b344a", caps["host"]["host"]["uuid"]
|
|
)
|
|
self.assertEqual(
|
|
{"qemu", "kvm"},
|
|
{domainCaps["domain"] for domainCaps in caps["domains"]},
|
|
)
|
|
|
|
self.assertEqual(
|
|
{call[0] for call in self.mock_conn.getDomainCapabilities.call_args_list},
|
|
{
|
|
("/usr/bin/qemu-system-x86_64", "x86_64", None, "kvm"),
|
|
("/usr/bin/qemu-system-x86_64", "x86_64", None, "qemu"),
|
|
},
|
|
)
|
|
|
|
def test_network_tag(self):
|
|
"""
|
|
Test virt._get_net_xml() with VLAN tag
|
|
"""
|
|
xml_data = virt._gen_net_xml("network", "main", "bridge", "openvswitch", 1001)
|
|
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")
|
|
self.assertEqual(root.find("vlan/tag").attrib["id"], "1001")
|
|
|
|
def test_list_networks(self):
|
|
"""
|
|
Test virt.list_networks()
|
|
"""
|
|
names = ["net1", "default", "net2"]
|
|
net_mocks = [MagicMock(), MagicMock(), MagicMock()]
|
|
for i, value in enumerate(names):
|
|
net_mocks[i].name.return_value = value
|
|
|
|
self.mock_conn.listAllNetworks.return_value = (
|
|
net_mocks # pylint: disable=no-member
|
|
)
|
|
actual = virt.list_networks()
|
|
self.assertEqual(names, actual)
|
|
|
|
def test_network_info(self):
|
|
"""
|
|
Test virt.network_info()
|
|
"""
|
|
self.mock_libvirt.VIR_IP_ADDR_TYPE_IPV4 = 0
|
|
self.mock_libvirt.VIR_IP_ADDR_TYPE_IPV6 = 1
|
|
|
|
net_mock = MagicMock()
|
|
|
|
# pylint: disable=no-member
|
|
net_mock.name.return_value = "foo"
|
|
net_mock.UUIDString.return_value = "some-uuid"
|
|
net_mock.bridgeName.return_value = "br0"
|
|
net_mock.autostart.return_value = True
|
|
net_mock.isActive.return_value = False
|
|
net_mock.isPersistent.return_value = True
|
|
net_mock.DHCPLeases.return_value = [
|
|
{
|
|
"iface": "virbr0",
|
|
"expirytime": 1527757552,
|
|
"type": 0,
|
|
"mac": "52:54:00:01:71:bd",
|
|
"ipaddr": "192.168.122.45",
|
|
"prefix": 24,
|
|
"hostname": "py3-test",
|
|
"clientid": "01:52:54:00:01:71:bd",
|
|
"iaid": None,
|
|
}
|
|
]
|
|
self.mock_conn.listAllNetworks.return_value = [net_mock]
|
|
# pylint: enable=no-member
|
|
|
|
net = virt.network_info("foo")
|
|
self.assertEqual(
|
|
{
|
|
"foo": {
|
|
"uuid": "some-uuid",
|
|
"bridge": "br0",
|
|
"autostart": True,
|
|
"active": False,
|
|
"persistent": True,
|
|
"leases": [
|
|
{
|
|
"iface": "virbr0",
|
|
"expirytime": 1527757552,
|
|
"type": "ipv4",
|
|
"mac": "52:54:00:01:71:bd",
|
|
"ipaddr": "192.168.122.45",
|
|
"prefix": 24,
|
|
"hostname": "py3-test",
|
|
"clientid": "01:52:54:00:01:71:bd",
|
|
"iaid": None,
|
|
}
|
|
],
|
|
}
|
|
},
|
|
net,
|
|
)
|
|
|
|
def test_network_info_all(self):
|
|
"""
|
|
Test virt.network_info()
|
|
"""
|
|
self.mock_libvirt.VIR_IP_ADDR_TYPE_IPV4 = 0
|
|
self.mock_libvirt.VIR_IP_ADDR_TYPE_IPV6 = 1
|
|
|
|
net_mocks = []
|
|
# pylint: disable=no-member
|
|
for i in range(2):
|
|
net_mock = MagicMock()
|
|
|
|
net_mock.name.return_value = "net{}".format(i)
|
|
net_mock.UUIDString.return_value = "some-uuid"
|
|
net_mock.bridgeName.return_value = "br{}".format(i)
|
|
net_mock.autostart.return_value = True
|
|
net_mock.isActive.return_value = False
|
|
net_mock.isPersistent.return_value = True
|
|
net_mock.DHCPLeases.return_value = []
|
|
net_mocks.append(net_mock)
|
|
self.mock_conn.listAllNetworks.return_value = net_mocks
|
|
# pylint: enable=no-member
|
|
|
|
net = virt.network_info()
|
|
self.assertEqual(
|
|
{
|
|
"net0": {
|
|
"uuid": "some-uuid",
|
|
"bridge": "br0",
|
|
"autostart": True,
|
|
"active": False,
|
|
"persistent": True,
|
|
"leases": [],
|
|
},
|
|
"net1": {
|
|
"uuid": "some-uuid",
|
|
"bridge": "br1",
|
|
"autostart": True,
|
|
"active": False,
|
|
"persistent": True,
|
|
"leases": [],
|
|
},
|
|
},
|
|
net,
|
|
)
|
|
|
|
def test_network_info_notfound(self):
|
|
"""
|
|
Test virt.network_info() when the network can't be found
|
|
"""
|
|
# pylint: disable=no-member
|
|
self.mock_conn.listAllNetworks.return_value = []
|
|
# pylint: enable=no-member
|
|
net = virt.network_info("foo")
|
|
self.assertEqual({}, net)
|
|
|
|
def test_network_get_xml(self):
|
|
"""
|
|
Test virt.network_get_xml
|
|
"""
|
|
network_mock = MagicMock()
|
|
network_mock.XMLDesc.return_value = "<net>Raw XML</net>"
|
|
self.mock_conn.networkLookupByName.return_value = network_mock
|
|
|
|
self.assertEqual("<net>Raw XML</net>", virt.network_get_xml("default"))
|
|
|
|
def test_pool(self):
|
|
"""
|
|
Test virt._gen_pool_xml()
|
|
"""
|
|
xml_data = virt._gen_pool_xml("pool", "logical", "/dev/base")
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("name").text, "pool")
|
|
self.assertEqual(root.attrib["type"], "logical")
|
|
self.assertEqual(root.find("target/path").text, "/dev/base")
|
|
|
|
def test_pool_with_source(self):
|
|
"""
|
|
Test virt._gen_pool_xml() with a source device
|
|
"""
|
|
xml_data = virt._gen_pool_xml(
|
|
"pool", "logical", "/dev/base", source_devices=[{"path": "/dev/sda"}]
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("name").text, "pool")
|
|
self.assertEqual(root.attrib["type"], "logical")
|
|
self.assertEqual(root.find("target/path").text, "/dev/base")
|
|
self.assertEqual(root.find("source/device").attrib["path"], "/dev/sda")
|
|
|
|
def test_pool_with_scsi(self):
|
|
"""
|
|
Test virt._gen_pool_xml() with a SCSI source
|
|
"""
|
|
xml_data = virt._gen_pool_xml(
|
|
"pool",
|
|
"scsi",
|
|
"/dev/disk/by-path",
|
|
source_devices=[{"path": "/dev/sda"}],
|
|
source_adapter={
|
|
"type": "scsi_host",
|
|
"parent_address": {
|
|
"unique_id": 5,
|
|
"address": {
|
|
"domain": "0x0000",
|
|
"bus": "0x00",
|
|
"slot": "0x1f",
|
|
"function": "0x2",
|
|
},
|
|
},
|
|
},
|
|
source_name="srcname",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("name").text, "pool")
|
|
self.assertEqual(root.attrib["type"], "scsi")
|
|
self.assertEqual(root.find("target/path").text, "/dev/disk/by-path")
|
|
self.assertEqual(root.find("source/device"), None)
|
|
self.assertEqual(root.find("source/name"), None)
|
|
self.assertEqual(root.find("source/adapter").attrib["type"], "scsi_host")
|
|
self.assertEqual(
|
|
root.find("source/adapter/parentaddr").attrib["unique_id"], "5"
|
|
)
|
|
self.assertEqual(
|
|
root.find("source/adapter/parentaddr/address").attrib["domain"], "0x0000"
|
|
)
|
|
self.assertEqual(
|
|
root.find("source/adapter/parentaddr/address").attrib["bus"], "0x00"
|
|
)
|
|
self.assertEqual(
|
|
root.find("source/adapter/parentaddr/address").attrib["slot"], "0x1f"
|
|
)
|
|
self.assertEqual(
|
|
root.find("source/adapter/parentaddr/address").attrib["function"], "0x2"
|
|
)
|
|
|
|
def test_pool_with_rbd(self):
|
|
"""
|
|
Test virt._gen_pool_xml() with an RBD source
|
|
"""
|
|
xml_data = virt._gen_pool_xml(
|
|
"pool",
|
|
"rbd",
|
|
source_devices=[{"path": "/dev/sda"}],
|
|
source_hosts=["1.2.3.4", "my.ceph.monitor:69"],
|
|
source_auth={
|
|
"type": "ceph",
|
|
"username": "admin",
|
|
"secret": {"type": "uuid", "value": "someuuid"},
|
|
},
|
|
source_name="srcname",
|
|
source_adapter={"type": "scsi_host", "name": "host0"},
|
|
source_dir="/some/dir",
|
|
source_format="fmt",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("name").text, "pool")
|
|
self.assertEqual(root.attrib["type"], "rbd")
|
|
self.assertEqual(root.find("target"), None)
|
|
self.assertEqual(root.find("source/device"), None)
|
|
self.assertEqual(root.find("source/name").text, "srcname")
|
|
self.assertEqual(root.find("source/adapter"), None)
|
|
self.assertEqual(root.find("source/dir"), None)
|
|
self.assertEqual(root.find("source/format"), None)
|
|
self.assertEqual(root.findall("source/host")[0].attrib["name"], "1.2.3.4")
|
|
self.assertTrue("port" not in root.findall("source/host")[0].attrib)
|
|
self.assertEqual(
|
|
root.findall("source/host")[1].attrib["name"], "my.ceph.monitor"
|
|
)
|
|
self.assertEqual(root.findall("source/host")[1].attrib["port"], "69")
|
|
self.assertEqual(root.find("source/auth").attrib["type"], "ceph")
|
|
self.assertEqual(root.find("source/auth").attrib["username"], "admin")
|
|
self.assertEqual(root.find("source/auth/secret").attrib["uuid"], "someuuid")
|
|
|
|
def test_pool_with_netfs(self):
|
|
"""
|
|
Test virt._gen_pool_xml() with a netfs source
|
|
"""
|
|
xml_data = virt._gen_pool_xml(
|
|
"pool",
|
|
"netfs",
|
|
target="/path/to/target",
|
|
permissions={
|
|
"mode": "0770",
|
|
"owner": 1000,
|
|
"group": 100,
|
|
"label": "seclabel",
|
|
},
|
|
source_devices=[{"path": "/dev/sda"}],
|
|
source_hosts=["nfs.host"],
|
|
source_name="srcname",
|
|
source_adapter={"type": "scsi_host", "name": "host0"},
|
|
source_dir="/some/dir",
|
|
source_format="nfs",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("name").text, "pool")
|
|
self.assertEqual(root.attrib["type"], "netfs")
|
|
self.assertEqual(root.find("target/path").text, "/path/to/target")
|
|
self.assertEqual(root.find("target/permissions/mode").text, "0770")
|
|
self.assertEqual(root.find("target/permissions/owner").text, "1000")
|
|
self.assertEqual(root.find("target/permissions/group").text, "100")
|
|
self.assertEqual(root.find("target/permissions/label").text, "seclabel")
|
|
self.assertEqual(root.find("source/device"), None)
|
|
self.assertEqual(root.find("source/name"), None)
|
|
self.assertEqual(root.find("source/adapter"), None)
|
|
self.assertEqual(root.find("source/dir").attrib["path"], "/some/dir")
|
|
self.assertEqual(root.find("source/format").attrib["type"], "nfs")
|
|
self.assertEqual(root.find("source/host").attrib["name"], "nfs.host")
|
|
self.assertEqual(root.find("source/auth"), None)
|
|
|
|
def test_pool_with_iscsi_direct(self):
|
|
"""
|
|
Test virt._gen_pool_xml() with a iscsi-direct source
|
|
"""
|
|
xml_data = virt._gen_pool_xml(
|
|
"pool",
|
|
"iscsi-direct",
|
|
source_hosts=["iscsi.example.com"],
|
|
source_devices=[{"path": "iqn.2013-06.com.example:iscsi-pool"}],
|
|
source_initiator="iqn.2013-06.com.example:iscsi-initiator",
|
|
)
|
|
root = ET.fromstring(xml_data)
|
|
self.assertEqual(root.find("name").text, "pool")
|
|
self.assertEqual(root.attrib["type"], "iscsi-direct")
|
|
self.assertEqual(root.find("target"), None)
|
|
self.assertEqual(
|
|
root.find("source/device").attrib["path"],
|
|
"iqn.2013-06.com.example:iscsi-pool",
|
|
)
|
|
self.assertEqual(
|
|
root.findall("source/host")[0].attrib["name"], "iscsi.example.com"
|
|
)
|
|
self.assertEqual(
|
|
root.find("source/initiator/iqn").attrib["name"],
|
|
"iqn.2013-06.com.example:iscsi-initiator",
|
|
)
|
|
|
|
def test_pool_define(self):
|
|
"""
|
|
Test virt.pool_define()
|
|
"""
|
|
mock_pool = MagicMock()
|
|
mock_secret = MagicMock()
|
|
mock_secret_define = MagicMock(return_value=mock_secret)
|
|
self.mock_conn.secretDefineXML = mock_secret_define
|
|
self.mock_conn.storagePoolCreateXML = MagicMock(return_value=mock_pool)
|
|
self.mock_conn.storagePoolDefineXML = MagicMock(return_value=mock_pool)
|
|
|
|
mocks = [
|
|
mock_pool,
|
|
mock_secret,
|
|
mock_secret_define,
|
|
self.mock_conn.storagePoolCreateXML,
|
|
self.mock_conn.secretDefineXML,
|
|
self.mock_conn.storagePoolDefineXML,
|
|
]
|
|
|
|
# Test case with already defined secret and permanent pool
|
|
self.assertTrue(
|
|
virt.pool_define(
|
|
"default",
|
|
"rbd",
|
|
source_hosts=["one.example.com", "two.example.com"],
|
|
source_name="rbdvol",
|
|
source_auth={
|
|
"type": "ceph",
|
|
"username": "admin",
|
|
"secret": {"type": "uuid", "value": "someuuid"},
|
|
},
|
|
)
|
|
)
|
|
self.mock_conn.storagePoolDefineXML.assert_called_once()
|
|
self.mock_conn.storagePoolCreateXML.assert_not_called()
|
|
mock_pool.create.assert_called_once()
|
|
mock_secret_define.assert_not_called()
|
|
|
|
# Test case with Ceph secret to be defined and transient pool
|
|
for mock in mocks:
|
|
mock.reset_mock()
|
|
self.assertTrue(
|
|
virt.pool_define(
|
|
"default",
|
|
"rbd",
|
|
transient=True,
|
|
source_hosts=["one.example.com", "two.example.com"],
|
|
source_name="rbdvol",
|
|
source_auth={"username": "admin", "password": "c2VjcmV0"},
|
|
)
|
|
)
|
|
self.mock_conn.storagePoolDefineXML.assert_not_called()
|
|
|
|
pool_xml = self.mock_conn.storagePoolCreateXML.call_args[0][0]
|
|
root = ET.fromstring(pool_xml)
|
|
self.assertEqual(root.find("source/auth").attrib["type"], "ceph")
|
|
self.assertEqual(root.find("source/auth").attrib["username"], "admin")
|
|
self.assertEqual(
|
|
root.find("source/auth/secret").attrib["usage"], "pool_default"
|
|
)
|
|
mock_pool.create.assert_not_called()
|
|
mock_secret.setValue.assert_called_once_with(b"secret")
|
|
|
|
secret_xml = mock_secret_define.call_args[0][0]
|
|
root = ET.fromstring(secret_xml)
|
|
self.assertEqual(root.find("usage/name").text, "pool_default")
|
|
self.assertEqual(root.find("usage").attrib["type"], "ceph")
|
|
self.assertEqual(root.attrib["private"], "yes")
|
|
self.assertEqual(
|
|
root.find("description").text, "Passphrase for default pool created by Salt"
|
|
)
|
|
|
|
# Test case with iscsi secret not starting
|
|
for mock in mocks:
|
|
mock.reset_mock()
|
|
self.assertTrue(
|
|
virt.pool_define(
|
|
"default",
|
|
"iscsi",
|
|
target="/dev/disk/by-path",
|
|
source_hosts=["iscsi.example.com"],
|
|
source_devices=[{"path": "iqn.2013-06.com.example:iscsi-pool"}],
|
|
source_auth={"username": "admin", "password": "secret"},
|
|
start=False,
|
|
)
|
|
)
|
|
self.mock_conn.storagePoolCreateXML.assert_not_called()
|
|
|
|
pool_xml = self.mock_conn.storagePoolDefineXML.call_args[0][0]
|
|
root = ET.fromstring(pool_xml)
|
|
self.assertEqual(root.find("source/auth").attrib["type"], "chap")
|
|
self.assertEqual(root.find("source/auth").attrib["username"], "admin")
|
|
self.assertEqual(
|
|
root.find("source/auth/secret").attrib["usage"], "pool_default"
|
|
)
|
|
mock_pool.create.assert_not_called()
|
|
mock_secret.setValue.assert_called_once_with("secret")
|
|
|
|
secret_xml = mock_secret_define.call_args[0][0]
|
|
root = ET.fromstring(secret_xml)
|
|
self.assertEqual(root.find("usage/target").text, "pool_default")
|
|
self.assertEqual(root.find("usage").attrib["type"], "iscsi")
|
|
self.assertEqual(root.attrib["private"], "yes")
|
|
self.assertEqual(
|
|
root.find("description").text, "Passphrase for default pool created by Salt"
|
|
)
|
|
|
|
def test_list_pools(self):
|
|
"""
|
|
Test virt.list_pools()
|
|
"""
|
|
names = ["pool1", "default", "pool2"]
|
|
pool_mocks = [MagicMock(), MagicMock(), MagicMock()]
|
|
for i, value in enumerate(names):
|
|
pool_mocks[i].name.return_value = value
|
|
|
|
self.mock_conn.listAllStoragePools.return_value = (
|
|
pool_mocks # pylint: disable=no-member
|
|
)
|
|
actual = virt.list_pools()
|
|
self.assertEqual(names, actual)
|
|
|
|
def test_pool_info(self):
|
|
"""
|
|
Test virt.pool_info()
|
|
"""
|
|
# pylint: disable=no-member
|
|
pool_mock = MagicMock()
|
|
pool_mock.name.return_value = "foo"
|
|
pool_mock.UUIDString.return_value = "some-uuid"
|
|
pool_mock.info.return_value = [0, 1234, 5678, 123]
|
|
pool_mock.autostart.return_value = True
|
|
pool_mock.isPersistent.return_value = True
|
|
pool_mock.XMLDesc.return_value = """<pool type='dir'>
|
|
<name>default</name>
|
|
<uuid>d92682d0-33cf-4e10-9837-a216c463e158</uuid>
|
|
<capacity unit='bytes'>854374301696</capacity>
|
|
<allocation unit='bytes'>596275986432</allocation>
|
|
<available unit='bytes'>258098315264</available>
|
|
<source>
|
|
</source>
|
|
<target>
|
|
<path>/srv/vms</path>
|
|
<permissions>
|
|
<mode>0755</mode>
|
|
<owner>0</owner>
|
|
<group>0</group>
|
|
</permissions>
|
|
</target>
|
|
</pool>"""
|
|
self.mock_conn.listAllStoragePools.return_value = [pool_mock]
|
|
# pylint: enable=no-member
|
|
|
|
pool = virt.pool_info("foo")
|
|
self.assertEqual(
|
|
{
|
|
"foo": {
|
|
"uuid": "some-uuid",
|
|
"state": "inactive",
|
|
"capacity": 1234,
|
|
"allocation": 5678,
|
|
"free": 123,
|
|
"autostart": True,
|
|
"persistent": True,
|
|
"type": "dir",
|
|
"target_path": "/srv/vms",
|
|
}
|
|
},
|
|
pool,
|
|
)
|
|
|
|
def test_pool_info_notarget(self):
|
|
"""
|
|
Test virt.pool_info()
|
|
"""
|
|
# pylint: disable=no-member
|
|
pool_mock = MagicMock()
|
|
pool_mock.name.return_value = "ceph"
|
|
pool_mock.UUIDString.return_value = "some-uuid"
|
|
pool_mock.info.return_value = [0, 0, 0, 0]
|
|
pool_mock.autostart.return_value = True
|
|
pool_mock.isPersistent.return_value = True
|
|
pool_mock.XMLDesc.return_value = """<pool type='rbd'>
|
|
<name>ceph</name>
|
|
<uuid>some-uuid</uuid>
|
|
<capacity unit='bytes'>0</capacity>
|
|
<allocation unit='bytes'>0</allocation>
|
|
<available unit='bytes'>0</available>
|
|
<source>
|
|
<host name='localhost' port='6789'/>
|
|
<host name='localhost' port='6790'/>
|
|
<name>rbd</name>
|
|
<auth type='ceph' username='admin'>
|
|
<secret uuid='2ec115d7-3a88-3ceb-bc12-0ac909a6fd87'/>
|
|
</auth>
|
|
</source>
|
|
</pool>"""
|
|
self.mock_conn.listAllStoragePools.return_value = [pool_mock]
|
|
# pylint: enable=no-member
|
|
|
|
pool = virt.pool_info("ceph")
|
|
self.assertEqual(
|
|
{
|
|
"ceph": {
|
|
"uuid": "some-uuid",
|
|
"state": "inactive",
|
|
"capacity": 0,
|
|
"allocation": 0,
|
|
"free": 0,
|
|
"autostart": True,
|
|
"persistent": True,
|
|
"type": "rbd",
|
|
"target_path": None,
|
|
}
|
|
},
|
|
pool,
|
|
)
|
|
|
|
def test_pool_info_notfound(self):
|
|
"""
|
|
Test virt.pool_info() when the pool can't be found
|
|
"""
|
|
# pylint: disable=no-member
|
|
self.mock_conn.listAllStoragePools.return_value = []
|
|
# pylint: enable=no-member
|
|
pool = virt.pool_info("foo")
|
|
self.assertEqual({}, pool)
|
|
|
|
def test_pool_info_all(self):
|
|
"""
|
|
Test virt.pool_info()
|
|
"""
|
|
# pylint: disable=no-member
|
|
pool_mocks = []
|
|
for i in range(2):
|
|
pool_mock = MagicMock()
|
|
pool_mock.name.return_value = "pool{}".format(i)
|
|
pool_mock.UUIDString.return_value = "some-uuid-{}".format(i)
|
|
pool_mock.info.return_value = [0, 1234, 5678, 123]
|
|
pool_mock.autostart.return_value = True
|
|
pool_mock.isPersistent.return_value = True
|
|
pool_mock.XMLDesc.return_value = """<pool type='dir'>
|
|
<name>default</name>
|
|
<uuid>d92682d0-33cf-4e10-9837-a216c463e158</uuid>
|
|
<capacity unit='bytes'>854374301696</capacity>
|
|
<allocation unit='bytes'>596275986432</allocation>
|
|
<available unit='bytes'>258098315264</available>
|
|
<source>
|
|
</source>
|
|
<target>
|
|
<path>/srv/vms</path>
|
|
<permissions>
|
|
<mode>0755</mode>
|
|
<owner>0</owner>
|
|
<group>0</group>
|
|
</permissions>
|
|
</target>
|
|
</pool>"""
|
|
pool_mocks.append(pool_mock)
|
|
self.mock_conn.listAllStoragePools.return_value = pool_mocks
|
|
# pylint: enable=no-member
|
|
|
|
pool = virt.pool_info()
|
|
self.assertEqual(
|
|
{
|
|
"pool0": {
|
|
"uuid": "some-uuid-0",
|
|
"state": "inactive",
|
|
"capacity": 1234,
|
|
"allocation": 5678,
|
|
"free": 123,
|
|
"autostart": True,
|
|
"persistent": True,
|
|
"type": "dir",
|
|
"target_path": "/srv/vms",
|
|
},
|
|
"pool1": {
|
|
"uuid": "some-uuid-1",
|
|
"state": "inactive",
|
|
"capacity": 1234,
|
|
"allocation": 5678,
|
|
"free": 123,
|
|
"autostart": True,
|
|
"persistent": True,
|
|
"type": "dir",
|
|
"target_path": "/srv/vms",
|
|
},
|
|
},
|
|
pool,
|
|
)
|
|
|
|
def test_pool_get_xml(self):
|
|
"""
|
|
Test virt.pool_get_xml
|
|
"""
|
|
pool_mock = MagicMock()
|
|
pool_mock.XMLDesc.return_value = "<pool>Raw XML</pool>"
|
|
self.mock_conn.storagePoolLookupByName.return_value = pool_mock
|
|
|
|
self.assertEqual("<pool>Raw XML</pool>", virt.pool_get_xml("default"))
|
|
|
|
def test_pool_list_volumes(self):
|
|
"""
|
|
Test virt.pool_list_volumes
|
|
"""
|
|
names = ["volume1", "volume2"]
|
|
mock_pool = MagicMock()
|
|
# pylint: disable=no-member
|
|
mock_pool.listVolumes.return_value = names
|
|
self.mock_conn.storagePoolLookupByName.return_value = mock_pool
|
|
# pylint: enable=no-member
|
|
self.assertEqual(names, virt.pool_list_volumes("default"))
|
|
|
|
@patch("salt.modules.virt._is_bhyve_hyper", return_value=False)
|
|
@patch("salt.modules.virt._is_kvm_hyper", return_value=True)
|
|
@patch("salt.modules.virt._is_xen_hyper", return_value=False)
|
|
def test_get_hypervisor(self, isxen_mock, iskvm_mock, is_bhyve_mock):
|
|
"""
|
|
test the virt.get_hypervisor() function
|
|
"""
|
|
self.assertEqual("kvm", virt.get_hypervisor())
|
|
|
|
iskvm_mock.return_value = False
|
|
self.assertIsNone(virt.get_hypervisor())
|
|
|
|
is_bhyve_mock.return_value = False
|
|
self.assertIsNone(virt.get_hypervisor())
|
|
|
|
isxen_mock.return_value = True
|
|
self.assertEqual("xen", virt.get_hypervisor())
|
|
|
|
def test_pool_delete(self):
|
|
"""
|
|
Test virt.pool_delete function
|
|
"""
|
|
mock_pool = MagicMock()
|
|
mock_pool.delete = MagicMock(return_value=0)
|
|
self.mock_conn.storagePoolLookupByName = MagicMock(return_value=mock_pool)
|
|
|
|
res = virt.pool_delete("test-pool")
|
|
self.assertTrue(res)
|
|
|
|
self.mock_conn.storagePoolLookupByName.assert_called_once_with("test-pool")
|
|
|
|
# Shouldn't be called with another parameter so far since those are not implemented
|
|
# and thus throwing exceptions.
|
|
mock_pool.delete.assert_called_once_with(
|
|
self.mock_libvirt.VIR_STORAGE_POOL_DELETE_NORMAL
|
|
)
|
|
|
|
def test_pool_undefine_secret(self):
|
|
"""
|
|
Test virt.pool_undefine function where the pool has a secret
|
|
"""
|
|
mock_pool = MagicMock()
|
|
mock_pool.undefine = MagicMock(return_value=0)
|
|
mock_pool.XMLDesc.return_value = """
|
|
<pool type='rbd'>
|
|
<name>test-ses</name>
|
|
<source>
|
|
<host name='myhost'/>
|
|
<name>libvirt-pool</name>
|
|
<auth type='ceph' username='libvirt'>
|
|
<secret usage='pool_test-ses'/>
|
|
</auth>
|
|
</source>
|
|
</pool>
|
|
"""
|
|
self.mock_conn.storagePoolLookupByName = MagicMock(return_value=mock_pool)
|
|
mock_undefine = MagicMock(return_value=0)
|
|
self.mock_conn.secretLookupByUsage.return_value.undefine = mock_undefine
|
|
|
|
res = virt.pool_undefine("test-ses")
|
|
self.assertTrue(res)
|
|
|
|
self.mock_conn.storagePoolLookupByName.assert_called_once_with("test-ses")
|
|
mock_pool.undefine.assert_called_once_with()
|
|
|
|
self.mock_conn.secretLookupByUsage.assert_called_once_with(
|
|
self.mock_libvirt.VIR_SECRET_USAGE_TYPE_CEPH, "pool_test-ses"
|
|
)
|
|
mock_undefine.assert_called_once()
|
|
|
|
def test_full_info(self):
|
|
"""
|
|
Test virt.full_info
|
|
"""
|
|
xml = """<domain type='kvm' id='7'>
|
|
<uuid>28deee33-4859-4f23-891c-ee239cffec94</uuid>
|
|
<name>test-vm</name>
|
|
<on_poweroff>destroy</on_poweroff>
|
|
<on_reboot>restart</on_reboot>
|
|
<on_crash>destroy</on_crash>
|
|
<devices>
|
|
<disk type='file' device='disk'>
|
|
<driver name='qemu' type='qcow2'/>
|
|
<source file='/disks/test.qcow2'/>
|
|
<target dev='vda' bus='virtio'/>
|
|
</disk>
|
|
<disk type='file' device='cdrom'>
|
|
<driver name='qemu' type='raw'/>
|
|
<source file='/disks/test-cdrom.iso'/>
|
|
<target dev='hda' bus='ide'/>
|
|
<readonly/>
|
|
</disk>
|
|
<interface type='bridge'>
|
|
<mac address='ac:de:48:b6:8b:59'/>
|
|
<source bridge='br0'/>
|
|
<model type='virtio'/>
|
|
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
|
|
</interface>
|
|
<graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'>
|
|
<listen type='address' address='0.0.0.0'/>
|
|
</graphics>
|
|
</devices>
|
|
</domain>
|
|
"""
|
|
self.set_mock_vm("test-vm", xml)
|
|
|
|
qemu_infos = """[{
|
|
"virtual-size": 25769803776,
|
|
"filename": "/disks/test.qcow2",
|
|
"cluster-size": 65536,
|
|
"format": "qcow2",
|
|
"actual-size": 217088,
|
|
"format-specific": {
|
|
"type": "qcow2",
|
|
"data": {
|
|
"compat": "1.1",
|
|
"lazy-refcounts": false,
|
|
"refcount-bits": 16,
|
|
"corrupt": false
|
|
}
|
|
},
|
|
"full-backing-filename": "/disks/mybacking.qcow2",
|
|
"backing-filename": "mybacking.qcow2",
|
|
"dirty-flag": false
|
|
},
|
|
{
|
|
"virtual-size": 25769803776,
|
|
"filename": "/disks/mybacking.qcow2",
|
|
"cluster-size": 65536,
|
|
"format": "qcow2",
|
|
"actual-size": 393744384,
|
|
"format-specific": {
|
|
"type": "qcow2",
|
|
"data": {
|
|
"compat": "1.1",
|
|
"lazy-refcounts": false,
|
|
"refcount-bits": 16,
|
|
"corrupt": false
|
|
}
|
|
},
|
|
"dirty-flag": false
|
|
}]"""
|
|
|
|
self.mock_popen.communicate.return_value = [qemu_infos, ""]
|
|
self.mock_popen.returncode = 0
|
|
|
|
self.mock_conn.getInfo = MagicMock(
|
|
return_value=["x86_64", 4096, 8, 2712, 1, 2, 4, 2]
|
|
)
|
|
|
|
actual = virt.full_info()
|
|
|
|
# Check that qemu-img was called with the proper parameters
|
|
qemu_img_call = [
|
|
call
|
|
for call in self.mock_subprocess.Popen.call_args_list
|
|
if "qemu-img" in call[0][0]
|
|
][0]
|
|
self.assertIn("info", qemu_img_call[0][0])
|
|
self.assertIn("-U", qemu_img_call[0][0])
|
|
|
|
# Test the hypervisor infos
|
|
self.assertEqual(2816, actual["freemem"])
|
|
self.assertEqual(6, actual["freecpu"])
|
|
self.assertEqual(4, actual["node_info"]["cpucores"])
|
|
self.assertEqual(2712, actual["node_info"]["cpumhz"])
|
|
self.assertEqual("x86_64", actual["node_info"]["cpumodel"])
|
|
self.assertEqual(8, actual["node_info"]["cpus"])
|
|
self.assertEqual(2, actual["node_info"]["cputhreads"])
|
|
self.assertEqual(1, actual["node_info"]["numanodes"])
|
|
self.assertEqual(4096, actual["node_info"]["phymemory"])
|
|
self.assertEqual(2, actual["node_info"]["sockets"])
|
|
|
|
# Test the vm_info output:
|
|
self.assertEqual(2, actual["vm_info"]["test-vm"]["cpu"])
|
|
self.assertEqual(1234, actual["vm_info"]["test-vm"]["cputime"])
|
|
self.assertEqual(1024 * 1024, actual["vm_info"]["test-vm"]["mem"])
|
|
self.assertEqual(2048 * 1024, actual["vm_info"]["test-vm"]["maxMem"])
|
|
self.assertEqual("shutdown", actual["vm_info"]["test-vm"]["state"])
|
|
self.assertEqual(
|
|
"28deee33-4859-4f23-891c-ee239cffec94", actual["vm_info"]["test-vm"]["uuid"]
|
|
)
|
|
self.assertEqual("destroy", actual["vm_info"]["test-vm"]["on_crash"])
|
|
self.assertEqual("restart", actual["vm_info"]["test-vm"]["on_reboot"])
|
|
self.assertEqual("destroy", actual["vm_info"]["test-vm"]["on_poweroff"])
|
|
|
|
# Test the nics
|
|
nic = actual["vm_info"]["test-vm"]["nics"]["ac:de:48:b6:8b:59"]
|
|
self.assertEqual("bridge", nic["type"])
|
|
self.assertEqual("ac:de:48:b6:8b:59", nic["mac"])
|
|
|
|
# Test the disks
|
|
disks = actual["vm_info"]["test-vm"]["disks"]
|
|
disk = disks.get("vda")
|
|
self.assertEqual("/disks/test.qcow2", disk["file"])
|
|
self.assertEqual("disk", disk["type"])
|
|
self.assertEqual("/disks/mybacking.qcow2", disk["backing file"]["file"])
|
|
cdrom = disks.get("hda")
|
|
self.assertEqual("/disks/test-cdrom.iso", cdrom["file"])
|
|
self.assertEqual("cdrom", cdrom["type"])
|
|
self.assertFalse("backing file" in cdrom.keys())
|
|
|
|
# Test the graphics
|
|
graphics = actual["vm_info"]["test-vm"]["graphics"]
|
|
self.assertEqual("vnc", graphics["type"])
|
|
self.assertEqual("5900", graphics["port"])
|
|
self.assertEqual("0.0.0.0", graphics["listen"])
|
|
|
|
def test_pool_update(self):
|
|
"""
|
|
Test the pool_update function
|
|
"""
|
|
current_xml = """<pool type='dir'>
|
|
<name>default</name>
|
|
<uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid>
|
|
<capacity unit='bytes'>1999421108224</capacity>
|
|
<allocation unit='bytes'>713207042048</allocation>
|
|
<available unit='bytes'>1286214066176</available>
|
|
<source>
|
|
</source>
|
|
<target>
|
|
<path>/path/to/pool</path>
|
|
<permissions>
|
|
<mode>0775</mode>
|
|
<owner>0</owner>
|
|
<group>100</group>
|
|
</permissions>
|
|
</target>
|
|
</pool>"""
|
|
|
|
expected_xml = (
|
|
'<pool type="netfs">'
|
|
"<name>default</name>"
|
|
"<uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid>"
|
|
'<capacity unit="bytes">1999421108224</capacity>'
|
|
'<allocation unit="bytes">713207042048</allocation>'
|
|
'<available unit="bytes">1286214066176</available>'
|
|
"<target>"
|
|
"<path>/mnt/cifs</path>"
|
|
"<permissions>"
|
|
"<mode>0774</mode>"
|
|
"<owner>1234</owner>"
|
|
"<group>123</group>"
|
|
"</permissions>"
|
|
"</target>"
|
|
"<source>"
|
|
'<dir path="samba_share" />'
|
|
'<host name="one.example.com" />'
|
|
'<host name="two.example.com" />'
|
|
'<format type="cifs" />'
|
|
"</source>"
|
|
"</pool>"
|
|
)
|
|
|
|
mocked_pool = MagicMock()
|
|
mocked_pool.XMLDesc = MagicMock(return_value=current_xml)
|
|
self.mock_conn.storagePoolLookupByName = MagicMock(return_value=mocked_pool)
|
|
self.mock_conn.storagePoolDefineXML = MagicMock()
|
|
|
|
self.assertTrue(
|
|
virt.pool_update(
|
|
"default",
|
|
"netfs",
|
|
target="/mnt/cifs",
|
|
permissions={"mode": "0774", "owner": "1234", "group": "123"},
|
|
source_format="cifs",
|
|
source_dir="samba_share",
|
|
source_hosts=["one.example.com", "two.example.com"],
|
|
)
|
|
)
|
|
self.mock_conn.storagePoolDefineXML.assert_called_once_with(expected_xml)
|
|
|
|
def test_pool_update_nochange(self):
|
|
"""
|
|
Test the pool_update function when no change is needed
|
|
"""
|
|
|
|
current_xml = """<pool type='dir'>
|
|
<name>default</name>
|
|
<uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid>
|
|
<capacity unit='bytes'>1999421108224</capacity>
|
|
<allocation unit='bytes'>713207042048</allocation>
|
|
<available unit='bytes'>1286214066176</available>
|
|
<source>
|
|
</source>
|
|
<target>
|
|
<path>/path/to/pool</path>
|
|
<permissions>
|
|
<mode>0775</mode>
|
|
<owner>0</owner>
|
|
<group>100</group>
|
|
</permissions>
|
|
</target>
|
|
</pool>"""
|
|
|
|
mocked_pool = MagicMock()
|
|
mocked_pool.XMLDesc = MagicMock(return_value=current_xml)
|
|
self.mock_conn.storagePoolLookupByName = MagicMock(return_value=mocked_pool)
|
|
self.mock_conn.storagePoolDefineXML = MagicMock()
|
|
|
|
self.assertFalse(
|
|
virt.pool_update(
|
|
"default",
|
|
"dir",
|
|
target="/path/to/pool",
|
|
permissions={"mode": "0775", "owner": "0", "group": "100"},
|
|
test=True,
|
|
)
|
|
)
|
|
self.mock_conn.storagePoolDefineXML.assert_not_called()
|
|
|
|
def test_pool_update_password(self):
|
|
"""
|
|
Test the pool_update function, where the password only is changed
|
|
"""
|
|
current_xml = """<pool type='rbd'>
|
|
<name>default</name>
|
|
<uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid>
|
|
<capacity unit='bytes'>1999421108224</capacity>
|
|
<allocation unit='bytes'>713207042048</allocation>
|
|
<available unit='bytes'>1286214066176</available>
|
|
<source>
|
|
<name>iscsi-images</name>
|
|
<host name='ses4.tf.local'/>
|
|
<host name='ses5.tf.local'/>
|
|
<auth username='libvirt' type='ceph'>
|
|
<secret uuid='14e9a0f1-8fbf-4097-b816-5b094c182212'/>
|
|
</auth>
|
|
</source>
|
|
</pool>"""
|
|
|
|
mock_secret = MagicMock()
|
|
self.mock_conn.secretLookupByUUIDString = MagicMock(return_value=mock_secret)
|
|
|
|
mocked_pool = MagicMock()
|
|
mocked_pool.XMLDesc = MagicMock(return_value=current_xml)
|
|
self.mock_conn.storagePoolLookupByName = MagicMock(return_value=mocked_pool)
|
|
self.mock_conn.storagePoolDefineXML = MagicMock()
|
|
|
|
self.assertFalse(
|
|
virt.pool_update(
|
|
"default",
|
|
"rbd",
|
|
source_name="iscsi-images",
|
|
source_hosts=["ses4.tf.local", "ses5.tf.local"],
|
|
source_auth={"username": "libvirt", "password": "c2VjcmV0"},
|
|
)
|
|
)
|
|
self.mock_conn.storagePoolDefineXML.assert_not_called()
|
|
mock_secret.setValue.assert_called_once_with(b"secret")
|
|
|
|
# Case where the secret can't be found
|
|
self.mock_conn.secretLookupByUUIDString = MagicMock(
|
|
side_effect=self.mock_libvirt.libvirtError("secret not found")
|
|
)
|
|
self.assertFalse(
|
|
virt.pool_update(
|
|
"default",
|
|
"rbd",
|
|
source_name="iscsi-images",
|
|
source_hosts=["ses4.tf.local", "ses5.tf.local"],
|
|
source_auth={"username": "libvirt", "password": "c2VjcmV0"},
|
|
)
|
|
)
|
|
self.mock_conn.storagePoolDefineXML.assert_not_called()
|
|
self.mock_conn.secretDefineXML.assert_called_once()
|
|
mock_secret.setValue.assert_called_once_with(b"secret")
|
|
|
|
def test_pool_update_password_create(self):
|
|
"""
|
|
Test the pool_update function, where the password only is changed
|
|
"""
|
|
current_xml = """<pool type='rbd'>
|
|
<name>default</name>
|
|
<uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid>
|
|
<capacity unit='bytes'>1999421108224</capacity>
|
|
<allocation unit='bytes'>713207042048</allocation>
|
|
<available unit='bytes'>1286214066176</available>
|
|
<source>
|
|
<name>iscsi-images</name>
|
|
<host name='ses4.tf.local'/>
|
|
<host name='ses5.tf.local'/>
|
|
</source>
|
|
</pool>"""
|
|
|
|
expected_xml = (
|
|
'<pool type="rbd">'
|
|
"<name>default</name>"
|
|
"<uuid>20fbe05c-ab40-418a-9afa-136d512f0ede</uuid>"
|
|
'<capacity unit="bytes">1999421108224</capacity>'
|
|
'<allocation unit="bytes">713207042048</allocation>'
|
|
'<available unit="bytes">1286214066176</available>'
|
|
"<source>"
|
|
'<host name="ses4.tf.local" />'
|
|
'<host name="ses5.tf.local" />'
|
|
'<auth type="ceph" username="libvirt">'
|
|
'<secret usage="pool_default" />'
|
|
"</auth>"
|
|
"<name>iscsi-images</name>"
|
|
"</source>"
|
|
"</pool>"
|
|
)
|
|
|
|
mock_secret = MagicMock()
|
|
self.mock_conn.secretDefineXML = MagicMock(return_value=mock_secret)
|
|
|
|
mocked_pool = MagicMock()
|
|
mocked_pool.XMLDesc = MagicMock(return_value=current_xml)
|
|
self.mock_conn.storagePoolLookupByName = MagicMock(return_value=mocked_pool)
|
|
self.mock_conn.storagePoolDefineXML = MagicMock()
|
|
|
|
self.assertTrue(
|
|
virt.pool_update(
|
|
"default",
|
|
"rbd",
|
|
source_name="iscsi-images",
|
|
source_hosts=["ses4.tf.local", "ses5.tf.local"],
|
|
source_auth={"username": "libvirt", "password": "c2VjcmV0"},
|
|
)
|
|
)
|
|
self.mock_conn.storagePoolDefineXML.assert_called_once_with(expected_xml)
|
|
mock_secret.setValue.assert_called_once_with(b"secret")
|
|
|
|
def test_volume_infos(self):
|
|
"""
|
|
Test virt.volume_infos
|
|
"""
|
|
vms_disks = [
|
|
"""
|
|
<disk type='file' device='disk'>
|
|
<driver name='qemu' type='qcow2'/>
|
|
<source file='/path/to/vol0.qcow2'/>
|
|
<target dev='vda' bus='virtio'/>
|
|
</disk>
|
|
""",
|
|
"""
|
|
<disk type='file' device='disk'>
|
|
<driver name='qemu' type='qcow2'/>
|
|
<source file='/path/to/vol3.qcow2'/>
|
|
<target dev='vda' bus='virtio'/>
|
|
</disk>
|
|
""",
|
|
"""
|
|
<disk type='file' device='disk'>
|
|
<driver name='qemu' type='qcow2'/>
|
|
<source file='/path/to/vol2.qcow2'/>
|
|
<target dev='vda' bus='virtio'/>
|
|
</disk>
|
|
""",
|
|
]
|
|
mock_vms = []
|
|
for idx, disk in enumerate(vms_disks):
|
|
vm = MagicMock()
|
|
# pylint: disable=no-member
|
|
vm.name.return_value = "vm{}".format(idx)
|
|
vm.XMLDesc.return_value = """
|
|
<domain type='kvm' id='1'>
|
|
<name>vm{}</name>
|
|
<devices>{}</devices>
|
|
</domain>
|
|
""".format(
|
|
idx, disk
|
|
)
|
|
# pylint: enable=no-member
|
|
mock_vms.append(vm)
|
|
|
|
mock_pool_data = [
|
|
{
|
|
"name": "pool0",
|
|
"state": self.mock_libvirt.VIR_STORAGE_POOL_RUNNING,
|
|
"volumes": [
|
|
{
|
|
"key": "/key/of/vol0",
|
|
"name": "vol0",
|
|
"path": "/path/to/vol0.qcow2",
|
|
"info": [0, 123456789, 123456],
|
|
"backingStore": None,
|
|
}
|
|
],
|
|
},
|
|
{
|
|
"name": "pool1",
|
|
"state": self.mock_libvirt.VIR_STORAGE_POOL_RUNNING,
|
|
"volumes": [
|
|
{
|
|
"key": "/key/of/vol0bad",
|
|
"name": "vol0bad",
|
|
"path": "/path/to/vol0bad.qcow2",
|
|
"info": None,
|
|
"backingStore": None,
|
|
},
|
|
{
|
|
"key": "/key/of/vol1",
|
|
"name": "vol1",
|
|
"path": "/path/to/vol1.qcow2",
|
|
"info": [0, 12345, 1234],
|
|
"backingStore": None,
|
|
},
|
|
{
|
|
"key": "/key/of/vol2",
|
|
"name": "vol2",
|
|
"path": "/path/to/vol2.qcow2",
|
|
"info": [0, 12345, 1234],
|
|
"backingStore": "/path/to/vol0.qcow2",
|
|
},
|
|
],
|
|
},
|
|
]
|
|
mock_pools = []
|
|
for pool_data in mock_pool_data:
|
|
mock_pool = MagicMock()
|
|
mock_pool.name.return_value = pool_data["name"] # pylint: disable=no-member
|
|
mock_pool.info.return_value = [pool_data["state"]]
|
|
mock_volumes = []
|
|
for vol_data in pool_data["volumes"]:
|
|
mock_volume = MagicMock()
|
|
# pylint: disable=no-member
|
|
mock_volume.name.return_value = vol_data["name"]
|
|
mock_volume.key.return_value = vol_data["key"]
|
|
mock_volume.path.return_value = "/path/to/{}.qcow2".format(
|
|
vol_data["name"]
|
|
)
|
|
if vol_data["info"]:
|
|
mock_volume.info.return_value = vol_data["info"]
|
|
backing_store = (
|
|
"""
|
|
<backingStore>
|
|
<format type="qcow2"/>
|
|
<path>{}</path>
|
|
</backingStore>
|
|
""".format(
|
|
vol_data["backingStore"]
|
|
)
|
|
if vol_data["backingStore"]
|
|
else "<backingStore/>"
|
|
)
|
|
mock_volume.XMLDesc.return_value = """
|
|
<volume type='file'>
|
|
<name>{0}</name>
|
|
<target>
|
|
<format type="qcow2"/>
|
|
<path>/path/to/{0}.qcow2</path>
|
|
</target>
|
|
{1}
|
|
</volume>
|
|
""".format(
|
|
vol_data["name"], backing_store
|
|
)
|
|
else:
|
|
mock_volume.info.side_effect = self.mock_libvirt.libvirtError(
|
|
"No such volume"
|
|
)
|
|
mock_volume.XMLDesc.side_effect = self.mock_libvirt.libvirtError(
|
|
"No such volume"
|
|
)
|
|
mock_volumes.append(mock_volume)
|
|
# pylint: enable=no-member
|
|
mock_pool.listAllVolumes.return_value = (
|
|
mock_volumes # pylint: disable=no-member
|
|
)
|
|
mock_pools.append(mock_pool)
|
|
|
|
inactive_pool = MagicMock()
|
|
inactive_pool.name.return_value = "pool2"
|
|
inactive_pool.info.return_value = [self.mock_libvirt.VIR_STORAGE_POOL_INACTIVE]
|
|
inactive_pool.listAllVolumes.side_effect = self.mock_libvirt.libvirtError(
|
|
"pool is inactive"
|
|
)
|
|
mock_pools.append(inactive_pool)
|
|
|
|
self.mock_conn.listAllStoragePools.return_value = (
|
|
mock_pools # pylint: disable=no-member
|
|
)
|
|
|
|
with patch("salt.modules.virt._get_domain", MagicMock(return_value=mock_vms)):
|
|
actual = virt.volume_infos("pool0", "vol0")
|
|
self.assertEqual(1, len(actual.keys()))
|
|
self.assertEqual(1, len(actual["pool0"].keys()))
|
|
self.assertEqual(["vm0", "vm2"], sorted(actual["pool0"]["vol0"]["used_by"]))
|
|
self.assertEqual("/path/to/vol0.qcow2", actual["pool0"]["vol0"]["path"])
|
|
self.assertEqual("file", actual["pool0"]["vol0"]["type"])
|
|
self.assertEqual("/key/of/vol0", actual["pool0"]["vol0"]["key"])
|
|
self.assertEqual(123456789, actual["pool0"]["vol0"]["capacity"])
|
|
self.assertEqual(123456, actual["pool0"]["vol0"]["allocation"])
|
|
|
|
self.assertEqual(
|
|
virt.volume_infos("pool1", None),
|
|
{
|
|
"pool1": {
|
|
"vol1": {
|
|
"type": "file",
|
|
"key": "/key/of/vol1",
|
|
"path": "/path/to/vol1.qcow2",
|
|
"capacity": 12345,
|
|
"allocation": 1234,
|
|
"used_by": [],
|
|
"backing_store": None,
|
|
"format": "qcow2",
|
|
},
|
|
"vol2": {
|
|
"type": "file",
|
|
"key": "/key/of/vol2",
|
|
"path": "/path/to/vol2.qcow2",
|
|
"capacity": 12345,
|
|
"allocation": 1234,
|
|
"used_by": ["vm2"],
|
|
"backing_store": {
|
|
"path": "/path/to/vol0.qcow2",
|
|
"format": "qcow2",
|
|
},
|
|
"format": "qcow2",
|
|
},
|
|
}
|
|
},
|
|
)
|
|
|
|
self.assertEqual(
|
|
virt.volume_infos(None, "vol2"),
|
|
{
|
|
"pool1": {
|
|
"vol2": {
|
|
"type": "file",
|
|
"key": "/key/of/vol2",
|
|
"path": "/path/to/vol2.qcow2",
|
|
"capacity": 12345,
|
|
"allocation": 1234,
|
|
"used_by": ["vm2"],
|
|
"backing_store": {
|
|
"path": "/path/to/vol0.qcow2",
|
|
"format": "qcow2",
|
|
},
|
|
"format": "qcow2",
|
|
}
|
|
}
|
|
},
|
|
)
|
|
|
|
# Single VM test
|
|
with patch(
|
|
"salt.modules.virt._get_domain", MagicMock(return_value=mock_vms[0])
|
|
):
|
|
actual = virt.volume_infos("pool0", "vol0")
|
|
self.assertEqual(1, len(actual.keys()))
|
|
self.assertEqual(1, len(actual["pool0"].keys()))
|
|
self.assertEqual(["vm0"], sorted(actual["pool0"]["vol0"]["used_by"]))
|
|
self.assertEqual("/path/to/vol0.qcow2", actual["pool0"]["vol0"]["path"])
|
|
self.assertEqual("file", actual["pool0"]["vol0"]["type"])
|
|
self.assertEqual("/key/of/vol0", actual["pool0"]["vol0"]["key"])
|
|
self.assertEqual(123456789, actual["pool0"]["vol0"]["capacity"])
|
|
self.assertEqual(123456, actual["pool0"]["vol0"]["allocation"])
|
|
|
|
self.assertEqual(
|
|
virt.volume_infos("pool1", None),
|
|
{
|
|
"pool1": {
|
|
"vol1": {
|
|
"type": "file",
|
|
"key": "/key/of/vol1",
|
|
"path": "/path/to/vol1.qcow2",
|
|
"capacity": 12345,
|
|
"allocation": 1234,
|
|
"used_by": [],
|
|
"backing_store": None,
|
|
"format": "qcow2",
|
|
},
|
|
"vol2": {
|
|
"type": "file",
|
|
"key": "/key/of/vol2",
|
|
"path": "/path/to/vol2.qcow2",
|
|
"capacity": 12345,
|
|
"allocation": 1234,
|
|
"used_by": [],
|
|
"backing_store": {
|
|
"path": "/path/to/vol0.qcow2",
|
|
"format": "qcow2",
|
|
},
|
|
"format": "qcow2",
|
|
},
|
|
}
|
|
},
|
|
)
|
|
|
|
self.assertEqual(
|
|
virt.volume_infos(None, "vol2"),
|
|
{
|
|
"pool1": {
|
|
"vol2": {
|
|
"type": "file",
|
|
"key": "/key/of/vol2",
|
|
"path": "/path/to/vol2.qcow2",
|
|
"capacity": 12345,
|
|
"allocation": 1234,
|
|
"used_by": [],
|
|
"backing_store": {
|
|
"path": "/path/to/vol0.qcow2",
|
|
"format": "qcow2",
|
|
},
|
|
"format": "qcow2",
|
|
}
|
|
}
|
|
},
|
|
)
|
|
|
|
# No VM test
|
|
with patch(
|
|
"salt.modules.virt._get_domain",
|
|
MagicMock(side_effect=CommandExecutionError("no VM")),
|
|
):
|
|
actual = virt.volume_infos("pool0", "vol0")
|
|
self.assertEqual(1, len(actual.keys()))
|
|
self.assertEqual(1, len(actual["pool0"].keys()))
|
|
self.assertEqual([], sorted(actual["pool0"]["vol0"]["used_by"]))
|
|
self.assertEqual("/path/to/vol0.qcow2", actual["pool0"]["vol0"]["path"])
|
|
self.assertEqual("file", actual["pool0"]["vol0"]["type"])
|
|
self.assertEqual("/key/of/vol0", actual["pool0"]["vol0"]["key"])
|
|
self.assertEqual(123456789, actual["pool0"]["vol0"]["capacity"])
|
|
self.assertEqual(123456, actual["pool0"]["vol0"]["allocation"])
|
|
|
|
self.assertEqual(
|
|
virt.volume_infos("pool1", None),
|
|
{
|
|
"pool1": {
|
|
"vol1": {
|
|
"type": "file",
|
|
"key": "/key/of/vol1",
|
|
"path": "/path/to/vol1.qcow2",
|
|
"capacity": 12345,
|
|
"allocation": 1234,
|
|
"used_by": [],
|
|
"backing_store": None,
|
|
"format": "qcow2",
|
|
},
|
|
"vol2": {
|
|
"type": "file",
|
|
"key": "/key/of/vol2",
|
|
"path": "/path/to/vol2.qcow2",
|
|
"capacity": 12345,
|
|
"allocation": 1234,
|
|
"used_by": [],
|
|
"backing_store": {
|
|
"path": "/path/to/vol0.qcow2",
|
|
"format": "qcow2",
|
|
},
|
|
"format": "qcow2",
|
|
},
|
|
}
|
|
},
|
|
)
|
|
|
|
self.assertEqual(
|
|
virt.volume_infos(None, "vol2"),
|
|
{
|
|
"pool1": {
|
|
"vol2": {
|
|
"type": "file",
|
|
"key": "/key/of/vol2",
|
|
"path": "/path/to/vol2.qcow2",
|
|
"capacity": 12345,
|
|
"allocation": 1234,
|
|
"used_by": [],
|
|
"backing_store": {
|
|
"path": "/path/to/vol0.qcow2",
|
|
"format": "qcow2",
|
|
},
|
|
"format": "qcow2",
|
|
}
|
|
}
|
|
},
|
|
)
|
|
|
|
def test_volume_delete(self):
|
|
"""
|
|
Test virt.volume_delete
|
|
"""
|
|
mock_delete = MagicMock(side_effect=[0, 1])
|
|
mock_volume = MagicMock()
|
|
mock_volume.delete = mock_delete # pylint: disable=no-member
|
|
mock_pool = MagicMock()
|
|
# pylint: disable=no-member
|
|
mock_pool.storageVolLookupByName.side_effect = [
|
|
mock_volume,
|
|
mock_volume,
|
|
self.mock_libvirt.libvirtError("Missing volume"),
|
|
mock_volume,
|
|
]
|
|
self.mock_conn.storagePoolLookupByName.side_effect = [
|
|
mock_pool,
|
|
mock_pool,
|
|
mock_pool,
|
|
self.mock_libvirt.libvirtError("Missing pool"),
|
|
]
|
|
|
|
# pylint: enable=no-member
|
|
self.assertTrue(virt.volume_delete("default", "test_volume"))
|
|
self.assertFalse(virt.volume_delete("default", "test_volume"))
|
|
with self.assertRaises(self.mock_libvirt.libvirtError):
|
|
virt.volume_delete("default", "missing")
|
|
virt.volume_delete("missing", "test_volume")
|
|
self.assertEqual(mock_delete.call_count, 2)
|
|
|
|
def test_pool_capabilities(self):
|
|
"""
|
|
Test virt.pool_capabilities where libvirt has the pool-capabilities feature
|
|
"""
|
|
xml_caps = """
|
|
<storagepoolCapabilities>
|
|
<pool type='disk' supported='yes'>
|
|
<poolOptions>
|
|
<defaultFormat type='unknown'/>
|
|
<enum name='sourceFormatType'>
|
|
<value>unknown</value>
|
|
<value>dos</value>
|
|
<value>dvh</value>
|
|
</enum>
|
|
</poolOptions>
|
|
<volOptions>
|
|
<defaultFormat type='none'/>
|
|
<enum name='targetFormatType'>
|
|
<value>none</value>
|
|
<value>linux</value>
|
|
</enum>
|
|
</volOptions>
|
|
</pool>
|
|
<pool type='iscsi' supported='yes'>
|
|
</pool>
|
|
<pool type='rbd' supported='yes'>
|
|
<volOptions>
|
|
<defaultFormat type='raw'/>
|
|
<enum name='targetFormatType'>
|
|
</enum>
|
|
</volOptions>
|
|
</pool>
|
|
<pool type='sheepdog' supported='no'>
|
|
</pool>
|
|
</storagepoolCapabilities>
|
|
"""
|
|
self.mock_conn.getStoragePoolCapabilities = MagicMock(return_value=xml_caps)
|
|
|
|
actual = virt.pool_capabilities()
|
|
self.assertEqual(
|
|
{
|
|
"computed": False,
|
|
"pool_types": [
|
|
{
|
|
"name": "disk",
|
|
"supported": True,
|
|
"options": {
|
|
"pool": {
|
|
"default_format": "unknown",
|
|
"sourceFormatType": ["unknown", "dos", "dvh"],
|
|
},
|
|
"volume": {
|
|
"default_format": "none",
|
|
"targetFormatType": ["none", "linux"],
|
|
},
|
|
},
|
|
},
|
|
{"name": "iscsi", "supported": True},
|
|
{
|
|
"name": "rbd",
|
|
"supported": True,
|
|
"options": {
|
|
"volume": {"default_format": "raw", "targetFormatType": []}
|
|
},
|
|
},
|
|
{"name": "sheepdog", "supported": False},
|
|
],
|
|
},
|
|
actual,
|
|
)
|
|
|
|
@patch("salt.modules.virt.get_hypervisor", return_value="kvm")
|
|
def test_pool_capabilities_computed(self, mock_get_hypervisor):
|
|
"""
|
|
Test virt.pool_capabilities where libvirt doesn't have the pool-capabilities feature
|
|
"""
|
|
self.mock_conn.getLibVersion = MagicMock(return_value=4006000)
|
|
del self.mock_conn.getStoragePoolCapabilities
|
|
|
|
actual = virt.pool_capabilities()
|
|
|
|
self.assertTrue(actual["computed"])
|
|
backends = actual["pool_types"]
|
|
|
|
# libvirt version matching check
|
|
self.assertFalse(
|
|
[backend for backend in backends if backend["name"] == "iscsi-direct"][0][
|
|
"supported"
|
|
]
|
|
)
|
|
self.assertTrue(
|
|
[backend for backend in backends if backend["name"] == "gluster"][0][
|
|
"supported"
|
|
]
|
|
)
|
|
self.assertFalse(
|
|
[backend for backend in backends if backend["name"] == "zfs"][0][
|
|
"supported"
|
|
]
|
|
)
|
|
|
|
# test case matching other hypervisors
|
|
mock_get_hypervisor.return_value = "xen"
|
|
backends = virt.pool_capabilities()["pool_types"]
|
|
self.assertFalse(
|
|
[backend for backend in backends if backend["name"] == "gluster"][0][
|
|
"supported"
|
|
]
|
|
)
|
|
|
|
mock_get_hypervisor.return_value = "bhyve"
|
|
backends = virt.pool_capabilities()["pool_types"]
|
|
self.assertFalse(
|
|
[backend for backend in backends if backend["name"] == "gluster"][0][
|
|
"supported"
|
|
]
|
|
)
|
|
self.assertTrue(
|
|
[backend for backend in backends if backend["name"] == "zfs"][0][
|
|
"supported"
|
|
]
|
|
)
|
|
|
|
# Test options output
|
|
self.assertNotIn(
|
|
"options",
|
|
[backend for backend in backends if backend["name"] == "iscsi"][0],
|
|
)
|
|
self.assertNotIn(
|
|
"pool",
|
|
[backend for backend in backends if backend["name"] == "dir"][0]["options"],
|
|
)
|
|
self.assertNotIn(
|
|
"volume",
|
|
[backend for backend in backends if backend["name"] == "logical"][0][
|
|
"options"
|
|
],
|
|
)
|
|
self.assertEqual(
|
|
{
|
|
"pool": {
|
|
"default_format": "auto",
|
|
"sourceFormatType": ["auto", "nfs", "glusterfs", "cifs"],
|
|
},
|
|
"volume": {
|
|
"default_format": "raw",
|
|
"targetFormatType": [
|
|
"none",
|
|
"raw",
|
|
"dir",
|
|
"bochs",
|
|
"cloop",
|
|
"dmg",
|
|
"iso",
|
|
"vpc",
|
|
"vdi",
|
|
"fat",
|
|
"vhd",
|
|
"ploop",
|
|
"cow",
|
|
"qcow",
|
|
"qcow2",
|
|
"qed",
|
|
"vmdk",
|
|
],
|
|
},
|
|
},
|
|
[backend for backend in backends if backend["name"] == "netfs"][0][
|
|
"options"
|
|
],
|
|
)
|
|
|
|
def test_get_domain(self):
|
|
"""
|
|
Test the virt._get_domain function
|
|
"""
|
|
# Tests with no VM
|
|
self.mock_conn.listDomainsID.return_value = []
|
|
self.mock_conn.listDefinedDomains.return_value = []
|
|
self.assertEqual([], virt._get_domain(self.mock_conn))
|
|
self.assertRaisesRegex(
|
|
CommandExecutionError,
|
|
"No virtual machines found.",
|
|
virt._get_domain,
|
|
self.mock_conn,
|
|
"vm2",
|
|
)
|
|
|
|
# Test with active and inactive VMs
|
|
self.mock_conn.listDomainsID.return_value = [1]
|
|
|
|
def create_mock_vm(idx):
|
|
mock_vm = MagicMock()
|
|
mock_vm.name.return_value = "vm{}".format(idx)
|
|
return mock_vm
|
|
|
|
mock_vms = [create_mock_vm(idx) for idx in range(3)]
|
|
self.mock_conn.lookupByID.return_value = mock_vms[0]
|
|
self.mock_conn.listDefinedDomains.return_value = ["vm1", "vm2"]
|
|
|
|
self.mock_conn.lookupByName.side_effect = mock_vms
|
|
self.assertEqual(mock_vms, virt._get_domain(self.mock_conn))
|
|
|
|
self.mock_conn.lookupByName.side_effect = None
|
|
self.mock_conn.lookupByName.return_value = mock_vms[0]
|
|
self.assertEqual(mock_vms[0], virt._get_domain(self.mock_conn, inactive=False))
|
|
|
|
self.mock_conn.lookupByName.return_value = None
|
|
self.mock_conn.lookupByName.side_effect = [mock_vms[1], mock_vms[2]]
|
|
self.assertEqual(
|
|
[mock_vms[1], mock_vms[2]], virt._get_domain(self.mock_conn, active=False)
|
|
)
|
|
|
|
self.mock_conn.reset_mock()
|
|
self.mock_conn.lookupByName.return_value = None
|
|
self.mock_conn.lookupByName.side_effect = [mock_vms[1], mock_vms[2]]
|
|
self.assertEqual(
|
|
[mock_vms[1], mock_vms[2]], virt._get_domain(self.mock_conn, "vm1", "vm2")
|
|
)
|
|
self.assertRaisesRegex(
|
|
CommandExecutionError,
|
|
'The VM "vm2" is not present',
|
|
virt._get_domain,
|
|
self.mock_conn,
|
|
"vm2",
|
|
inactive=False,
|
|
)
|
|
|
|
def test_volume_define(self):
|
|
"""
|
|
Test virt.volume_define function
|
|
"""
|
|
# Normal test case
|
|
pool_mock = MagicMock()
|
|
pool_mock.XMLDesc.return_value = "<pool type='dir'></pool>"
|
|
self.mock_conn.storagePoolLookupByName.return_value = pool_mock
|
|
|
|
self.assertTrue(
|
|
virt.volume_define(
|
|
"testpool",
|
|
"myvm_system.qcow2",
|
|
8192,
|
|
allocation=4096,
|
|
format="qcow2",
|
|
type="file",
|
|
)
|
|
)
|
|
|
|
expected_xml = (
|
|
"<volume type='file'>\n"
|
|
" <name>myvm_system.qcow2</name>\n"
|
|
" <source>\n"
|
|
" </source>\n"
|
|
" <capacity unit='KiB'>8388608</capacity>\n"
|
|
" <allocation unit='KiB'>4194304</allocation>\n"
|
|
" <target>\n"
|
|
" <format type='qcow2'/>\n"
|
|
" </target>\n"
|
|
"</volume>"
|
|
)
|
|
|
|
pool_mock.createXML.assert_called_once_with(expected_xml, 0)
|
|
|
|
# backing store test case
|
|
pool_mock.reset_mock()
|
|
self.assertTrue(
|
|
virt.volume_define(
|
|
"testpool",
|
|
"myvm_system.qcow2",
|
|
8192,
|
|
allocation=4096,
|
|
format="qcow2",
|
|
type="file",
|
|
backing_store={"path": "/path/to/base.raw", "format": "raw"},
|
|
)
|
|
)
|
|
|
|
expected_xml = (
|
|
"<volume type='file'>\n"
|
|
" <name>myvm_system.qcow2</name>\n"
|
|
" <source>\n"
|
|
" </source>\n"
|
|
" <capacity unit='KiB'>8388608</capacity>\n"
|
|
" <allocation unit='KiB'>4194304</allocation>\n"
|
|
" <target>\n"
|
|
" <format type='qcow2'/>\n"
|
|
" </target>\n"
|
|
" <backingStore>\n"
|
|
" <path>/path/to/base.raw</path>\n"
|
|
" <format type='raw'/>\n"
|
|
" </backingStore>\n"
|
|
"</volume>"
|
|
)
|
|
|
|
pool_mock.createXML.assert_called_once_with(expected_xml, 0)
|
|
|
|
# logical pool test case
|
|
pool_mock.reset_mock()
|
|
pool_mock.XMLDesc.return_value = "<pool type='logical'></pool>"
|
|
self.mock_conn.storagePoolLookupByName.return_value = pool_mock
|
|
|
|
self.assertTrue(
|
|
virt.volume_define(
|
|
"testVG",
|
|
"myvm_system",
|
|
8192,
|
|
backing_store={"path": "/dev/testVG/base"},
|
|
)
|
|
)
|
|
|
|
expected_xml = (
|
|
"<volume>\n"
|
|
" <name>myvm_system</name>\n"
|
|
" <source>\n"
|
|
" </source>\n"
|
|
" <capacity unit='KiB'>8388608</capacity>\n"
|
|
" <allocation unit='KiB'>8388608</allocation>\n"
|
|
" <target>\n"
|
|
" </target>\n"
|
|
" <backingStore>\n"
|
|
" <path>/dev/testVG/base</path>\n"
|
|
" </backingStore>\n"
|
|
"</volume>"
|
|
)
|
|
|
|
pool_mock.createXML.assert_called_once_with(expected_xml, 0)
|
|
|
|
def test_volume_upload(self):
|
|
"""
|
|
Test virt.volume_upload function
|
|
"""
|
|
pool_mock = MagicMock()
|
|
vol_mock = MagicMock()
|
|
pool_mock.storageVolLookupByName.return_value = vol_mock
|
|
self.mock_conn.storagePoolLookupByName.return_value = pool_mock
|
|
stream_mock = MagicMock()
|
|
self.mock_conn.newStream.return_value = stream_mock
|
|
|
|
open_mock = MagicMock()
|
|
close_mock = MagicMock()
|
|
with patch.dict(
|
|
os.__dict__, {"open": open_mock, "close": close_mock}
|
|
): # pylint: disable=no-member
|
|
# Normal case
|
|
self.assertTrue(virt.volume_upload("pool0", "vol1.qcow2", "/path/to/file"))
|
|
stream_mock.sendAll.assert_called_once()
|
|
stream_mock.finish.assert_called_once()
|
|
self.mock_conn.close.assert_called_once()
|
|
vol_mock.upload.assert_called_once_with(stream_mock, 0, 0, 0)
|
|
|
|
# Sparse upload case
|
|
stream_mock.sendAll.reset_mock()
|
|
vol_mock.upload.reset_mock()
|
|
self.assertTrue(
|
|
virt.volume_upload(
|
|
"pool0",
|
|
"vol1.qcow2",
|
|
"/path/to/file",
|
|
offset=123,
|
|
length=456,
|
|
sparse=True,
|
|
)
|
|
)
|
|
stream_mock.sendAll.assert_not_called()
|
|
stream_mock.sparseSendAll.assert_called_once()
|
|
vol_mock.upload.assert_called_once_with(
|
|
stream_mock,
|
|
123,
|
|
456,
|
|
self.mock_libvirt.VIR_STORAGE_VOL_UPLOAD_SPARSE_STREAM,
|
|
)
|
|
|
|
# Upload unsupported case
|
|
vol_mock.upload.side_effect = self.mock_libvirt.libvirtError("Unsupported")
|
|
self.assertRaisesRegex(
|
|
CommandExecutionError,
|
|
"Unsupported",
|
|
virt.volume_upload,
|
|
"pool0",
|
|
"vol1.qcow2",
|
|
"/path/to/file",
|
|
)
|