virt: add VM memory tunning support

This commit is contained in:
firefly 2020-09-08 17:21:37 +09:00 committed by Daniel Wozniak
parent 6554e5ac3d
commit 667880b954
5 changed files with 594 additions and 24 deletions

1
changelog/57639.added Normal file
View file

@ -0,0 +1 @@
Memory Tuning Support which allows much greater control of memory allocation

View file

@ -71,12 +71,57 @@ The calls not using the libvirt connection setup are:
- `libvirt URI format <http://libvirt.org/uri.html#URI_config>`_
- `libvirt authentication configuration <http://libvirt.org/auth.html#Auth_client_config>`_
Units
==========
.. _virt-units:
.. rubric:: Units specification
.. versionadded:: Magnesium
The string should contain a number optionally followed
by a unit. The number may have a decimal fraction. If
the unit is not given then MiB are set by default.
Units can optionally be given in IEC style (such as MiB),
although the standard single letter style (such as M) is
more convenient.
Valid units include:
========== ===== ========== ========== ======
Standard IEC Standard IEC
Unit Unit Name Name Factor
========== ===== ========== ========== ======
B Bytes 1
K KiB Kilobytes Kibibytes 2**10
M MiB Megabytes Mebibytes 2**20
G GiB Gigabytes Gibibytes 2**30
T TiB Terabytes Tebibytes 2**40
P PiB Petabytes Pebibytes 2**50
E EiB Exabytes Exbibytes 2**60
Z ZiB Zettabytes Zebibytes 2**70
Y YiB Yottabytes Yobibytes 2**80
========== ===== ========== ========== ======
Additional decimal based units:
====== =======
Unit Factor
====== =======
KB 10**3
MB 10**6
GB 10**9
TB 10**12
PB 10**15
EB 10**18
ZB 10**21
YB 10**24
====== =======
"""
# Special Thanks to Michael Dehann, many of the concepts, and a few structures
# of his in the virt func module have been used
import base64
import collections
import copy
import datetime
import logging
@ -841,6 +886,39 @@ def _disk_from_pool(conn, pool, pool_xml, volume_name):
return disk_context
def _handle_unit(s, def_unit="m"):
"""
Handle the unit conversion, return the value in bytes
"""
m = re.match(r"(?P<value>[0-9.]*)\s*(?P<unit>.*)$", str(s).strip())
value = m.group("value")
# default unit
unit = m.group("unit").lower() or def_unit
try:
value = int(value)
except ValueError:
try:
value = float(value)
except ValueError:
raise SaltInvocationError("invalid number")
# flag for base ten
dec = False
if re.match(r"[kmgtpezy]b$", unit):
dec = True
elif not re.match(r"(b|[kmgtpezy](ib)?)$", unit):
raise SaltInvocationError("invalid units")
p = "bkmgtpezy".index(unit[0])
value *= 10 ** (p * 3) if dec else 2 ** (p * 10)
return int(value)
def nesthash():
"""
create default dict that allows arbitrary level of nesting
"""
return collections.defaultdict(nesthash)
def _gen_xml(
conn,
name,
@ -859,13 +937,25 @@ def _gen_xml(
"""
Generate the XML string to define a libvirt VM
"""
mem = int(mem) * 1024 # MB
context = {
"hypervisor": hypervisor,
"name": name,
"cpu": str(cpu),
"mem": str(mem),
}
context["mem"] = nesthash()
if isinstance(mem, int):
mem = int(mem) * 1024 # MB
context["mem"]["boot"] = str(mem)
context["mem"]["current"] = str(mem)
elif isinstance(mem, dict):
for tag, val in mem.items():
if val:
if tag == "slots":
context["mem"]["slots"] = "{}='{}'".format(tag, val)
else:
context["mem"][tag] = str(int(_handle_unit(val) / 1024))
if hypervisor in ["qemu", "kvm"]:
context["controller_model"] = False
elif hypervisor == "vmware":
@ -985,7 +1075,6 @@ def _gen_xml(
except jinja2.exceptions.TemplateNotFound:
log.error("Could not load template %s", fn_)
return ""
return template.render(**context)
@ -1793,7 +1882,28 @@ def init(
:param name: name of the virtual machine to create
:param cpu: Number of virtual CPUs to assign to the virtual machine
:param mem: Amount of memory to allocate to the virtual machine in MiB.
:param mem: Amount of memory to allocate to the virtual machine in MiB. Since Magnesium, a dictionary can be used to
contain detailed configuration which support memory allocation or tuning. Supported parameters are ``boot``,
``current``, ``max``, ``slots``, ``hard_limit``, ``soft_limit``, ``swap_hard_limit`` and ``min_guarantee``. The
structure of the dictionary is documented in :ref:`init-mem-def`. Both decimal and binary base are supported.
Detail unit specification is documented in :ref:`virt-units`. Please note that the value for ``slots`` must be
an integer.
.. code-block:: python
{
'boot': 1g,
'current': 1g,
'max': 1g,
'slots': 10,
'hard_limit': '1024'
'soft_limit': '512m'
'swap_hard_limit': '1g'
'min_guarantee': '512mib'
}
.. versionchanged:: Magnesium
:param nic: NIC profile to use (Default: ``'default'``).
The profile interfaces can be customized / extended with the interfaces parameter.
If set to ``None``, no profile will be used.
@ -1906,6 +2016,36 @@ def init(
.. versionadded:: sodium
.. _init-mem-def:
.. rubric:: Memory parameter definition
Memory parameter can contain the following properties:
boot
The maximum allocation of memory for the guest at boot time
current
The actual allocation of memory for the guest
max
The run time maximum memory allocation of the guest
slots
specifies the number of slots available for adding memory to the guest
hard_limit
the maximum memory the guest can use
soft_limit
memory limit to enforce during memory contention
swap_hard_limit
the maximum memory plus swap the guest can use
min_guarantee
the guaranteed minimum memory allocation for the guest
.. _init-nic-def:
.. rubric:: Network Interfaces Definitions
@ -2436,7 +2576,24 @@ def update(
:param name: Name of the domain to update
:param cpu: Number of virtual CPUs to assign to the virtual machine
:param mem: Amount of memory to allocate to the virtual machine in MiB.
:param mem: Amount of memory to allocate to the virtual machine in MiB. Since Magnesium, a dictionary can be used to
contain detailed configuration which support memory allocation or tuning. Supported parameters are ``boot``,
``current``, ``max``, ``slots``, ``hard_limit``, ``soft_limit``, ``swap_hard_limit`` and ``min_guarantee``. The
structure of the dictionary is documented in :ref:`init-mem-def`. Both decimal and binary base are supported.
Detail unit specification is documented in :ref:`virt-units`. Please note that the value for ``slots`` must be
an integer.
To remove any parameters, pass a None object, for instance: 'soft_limit': ``None``. Please note that ``None``
is mapped to ``null`` in sls file, pass ``null`` in sls file instead.
.. code-block:: yaml
- mem:
hard_limit: null
soft_limit: null
.. versionchanged:: Magnesium
:param disk_profile: disk profile to use
:param disks:
Disk definitions as documented in the :func:`init` function.
@ -2576,9 +2733,16 @@ def update(
def _set_nvram(node, value):
node.set("template", value)
def _set_with_mib_unit(node, value):
def _set_with_byte_unit(node, value):
node.text = str(value)
node.set("unit", "MiB")
node.set("unit", "bytes")
def _get_with_unit(node):
unit = node.get("unit", "KiB")
# _handle_unit treats bytes as invalid unit for the purpose of consistency
unit = unit if unit != "bytes" else "b"
value = node.get("memory") or node.text
return _handle_unit("{}{}".format(value, unit)) if value else None
# Update the kernel boot parameters
params_mapping = [
@ -2591,14 +2755,72 @@ def update(
{
"path": "mem",
"xpath": "memory",
"get": lambda n: int(n.text) / 1024,
"set": _set_with_mib_unit,
"convert": _handle_unit,
"get": _get_with_unit,
"set": _set_with_byte_unit,
},
{
"path": "mem",
"xpath": "currentMemory",
"get": lambda n: int(n.text) / 1024,
"set": _set_with_mib_unit,
"convert": _handle_unit,
"get": _get_with_unit,
"set": _set_with_byte_unit,
},
{
"path": "mem:max",
"convert": _handle_unit,
"xpath": "maxMemory",
"get": _get_with_unit,
"set": _set_with_byte_unit,
},
{
"path": "mem:boot",
"convert": _handle_unit,
"xpath": "memory",
"get": _get_with_unit,
"set": _set_with_byte_unit,
},
{
"path": "mem:current",
"convert": _handle_unit,
"xpath": "currentMemory",
"get": _get_with_unit,
"set": _set_with_byte_unit,
},
{
"path": "mem:slots",
"xpath": "maxMemory",
"get": lambda n: n.get("slots"),
"set": lambda n, v: n.set("slots", str(v)),
"del": salt.utils.xmlutil.del_attribute("slots", ["unit"]),
},
{
"path": "mem:hard_limit",
"convert": _handle_unit,
"xpath": "memtune/hard_limit",
"get": _get_with_unit,
"set": _set_with_byte_unit,
},
{
"path": "mem:soft_limit",
"convert": _handle_unit,
"xpath": "memtune/soft_limit",
"get": _get_with_unit,
"set": _set_with_byte_unit,
},
{
"path": "mem:swap_hard_limit",
"convert": _handle_unit,
"xpath": "memtune/swap_hard_limit",
"get": _get_with_unit,
"set": _set_with_byte_unit,
},
{
"path": "mem:min_guarantee",
"convert": _handle_unit,
"xpath": "memtune/min_guarantee",
"get": _get_with_unit,
"set": _set_with_byte_unit,
},
{
"path": "boot_dev:{dev}",
@ -2684,11 +2906,15 @@ def update(
}
)
if mem:
if isinstance(mem, dict):
mem = str(_handle_unit(mem.get("current", 0)))
elif isinstance(mem, int):
mem = str(mem * 1024)
commands.append(
{
"device": "mem",
"cmd": "setMemoryFlags",
"args": [mem * 1024, libvirt.VIR_DOMAIN_AFFECT_LIVE],
"args": [mem, libvirt.VIR_DOMAIN_AFFECT_LIVE],
}
)

View file

@ -296,7 +296,28 @@ def defined(
:param name: name of the virtual machine to run
:param cpu: number of CPUs for the virtual machine to create
:param mem: amount of memory in MiB for the new virtual machine
:param mem: Amount of memory to allocate to the virtual machine in MiB. Since Magnesium, a dictionary can be used to
contain detailed configuration which support memory allocation or tuning. Supported parameters are ``boot``,
``current``, ``max``, ``slots``, ``hard_limit``, ``soft_limit``, ``swap_hard_limit`` and ``min_guarantee``. The
structure of the dictionary is documented in :ref:`init-mem-def`. Both decimal and binary base are supported.
Detail unit specification is documented in :ref:`virt-units`. Please note that the value for ``slots`` must be
an integer.
.. code-block:: python
{
'boot': 1g,
'current': 1g,
'max': 1g,
'slots': 10,
'hard_limit': '1024'
'soft_limit': '512m'
'swap_hard_limit': '1g'
'min_guarantee': '512mib'
}
.. versionchanged:: Magnesium
:param vm_type: force virtual machine type for the new VM. The default value is taken from
the host capabilities. This could be useful for example to use ``'qemu'`` type instead
of the ``'kvm'`` one.
@ -496,7 +517,23 @@ def running(
:param name: name of the virtual machine to run
:param cpu: number of CPUs for the virtual machine to create
:param mem: amount of memory in MiB for the new virtual machine
:param mem: Amount of memory to allocate to the virtual machine in MiB. Since Magnesium, a dictionary can be used to
contain detailed configuration which support memory allocation or tuning. Supported parameters are ``boot``,
``current``, ``max``, ``slots``, ``hard_limit``, ``soft_limit``, ``swap_hard_limit`` and ``min_guarantee``. The
structure of the dictionary is documented in :ref:`init-mem-def`. Both decimal and binary base are supported.
Detail unit specification is documented in :ref:`virt-units`. Please note that the value for ``slots`` must be
an integer.
To remove any parameters, pass a None object, for instance: 'soft_limit': ``None``. Please note that ``None``
is mapped to ``null`` in sls file, pass ``null`` in sls file instead.
.. code-block:: yaml
- mem:
hard_limit: null
soft_limit: null
.. versionchanged:: Magnesium
:param vm_type: force virtual machine type for the new VM. The default value is taken from
the host capabilities. This could be useful for example to use ``'qemu'`` type instead
of the ``'kvm'`` one.

View file

@ -2,9 +2,32 @@
<domain type='{{ hypervisor }}'>
<name>{{ name }}</name>
<vcpu>{{ cpu }}</vcpu>
<memory unit='KiB'>{{ mem }}</memory>
<currentMemory unit='KiB'>{{ mem }}</currentMemory>
<os {{boot.os_attrib}}>
{%- if mem.max %}
<maxMemory {{ mem.slots }} unit='KiB'> {{ mem.max }}</maxMemory>
{%- endif %}
{%- if mem.boot %}
<memory unit='KiB'>{{ mem.boot }}</memory>
{%- endif %}
{%- if mem.current %}
<currentMemory unit='KiB'>{{ mem.current }}</currentMemory>
{%- endif %}
{%- if mem %}
<memtune>
{%- if 'hard_limit' in mem and mem.hard_limit %}
<hard_limit unit="KiB">{{ mem.hard_limit }}</hard_limit>
{%- endif %}
{%- if 'soft_limit' in mem and mem.soft_limit %}
<soft_limit unit="KiB">{{ mem.soft_limit }}</soft_limit>
{%- endif %}
{%- if 'swap_hard_limit' in mem and mem.swap_hard_limit %}
<swap_hard_limit unit="KiB">{{ mem.swap_hard_limit }}</swap_hard_limit>
{%- endif %}
{%- if 'min_guarantee' in mem and mem.min_guarantee %}
<min_guarantee unit="KiB">{{ mem.min_guarantee }}</min_guarantee>
{%- endif %}
</memtune>
{%- endif %}
<os {{ boot.os_attrib }}>
<type arch='{{ arch }}'>{{ os_type }}</type>
{% if boot %}
{% if 'kernel' in boot %}

View file

@ -2016,6 +2016,51 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
with self.assertRaises(SaltInvocationError):
virt.update("my_vm", boot={"efi": "Not a boolean value"})
# Update memtune parameter case
memtune = {
"soft_limit": "0.5g",
"hard_limit": "1024",
"swap_hard_limit": "2048m",
"min_guarantee": "1 g",
}
self.assertEqual(
{
"definition": True,
"disk": {"attached": [], "detached": [], "updated": []},
"interface": {"attached": [], "detached": []},
"mem": False,
},
virt.update("my_vm", mem=memtune),
)
setxml = ET.fromstring(define_mock.call_args[0][0])
self.assertEqual(
setxml.find("memtune").find("soft_limit").text, str(int(0.5 * 1024 ** 3))
)
self.assertEqual(setxml.find("memtune").find("soft_limit").get("unit"), "bytes")
self.assertEqual(
setxml.find("memtune").find("hard_limit").text, str(1024 * 1024 ** 2)
)
self.assertEqual(
setxml.find("memtune").find("swap_hard_limit").text, str(2048 * 1024 ** 2)
)
self.assertEqual(
setxml.find("memtune").find("min_guarantee").text, str(1 * 1024 ** 3)
)
invalid_unit = {"soft_limit": "2HB"}
with self.assertRaises(SaltInvocationError):
virt.update("my_vm", mem=invalid_unit)
invalid_number = {
"soft_limit": "3.4.MB",
}
with self.assertRaises(SaltInvocationError):
virt.update("my_vm", mem=invalid_number)
# Update memory case
setmem_mock = MagicMock(return_value=0)
domain_mock.setMemoryFlags = setmem_mock
@ -2030,9 +2075,43 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
virt.update("my_vm", mem=2048),
)
setxml = ET.fromstring(define_mock.call_args[0][0])
self.assertEqual(setxml.find("memory").text, "2048")
self.assertEqual(setxml.find("memory").get("unit"), "MiB")
self.assertEqual(setmem_mock.call_args[0][0], 2048 * 1024)
self.assertEqual(setxml.find("memory").text, str(2048 * 1024 ** 2))
self.assertEqual(setxml.find("memory").get("unit"), "bytes")
self.assertEqual(setmem_mock.call_args[0][0], str(2048 * 1024))
mem_dict = {"boot": "0.5g", "current": "2g", "max": "1g", "slots": 12}
self.assertEqual(
{
"definition": True,
"mem": True,
"disk": {"attached": [], "detached": [], "updated": []},
"interface": {"attached": [], "detached": []},
},
virt.update("my_vm", mem=mem_dict),
)
setxml = ET.fromstring(define_mock.call_args[0][0])
self.assertEqual(setxml.find("memory").get("unit"), "bytes")
self.assertEqual(setxml.find("memory").text, str(int(0.5 * 1024 ** 3)))
self.assertEqual(setxml.find("maxMemory").text, str(1 * 1024 ** 3))
self.assertEqual(setxml.find("currentMemory").text, str(2 * 1024 ** 3))
max_slot_reverse = {
"slots": "10",
"max": "3096m",
}
self.assertEqual(
{
"definition": True,
"disk": {"attached": [], "detached": [], "updated": []},
"interface": {"attached": [], "detached": []},
"mem": True,
},
virt.update("my_vm", 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").attrib.get("slots"), "10")
# Update disks case
devattach_mock = MagicMock(return_value=0)
@ -2548,7 +2627,6 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
"""
Test virt.update() with existing boot parameters.
"""
root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images")
xml_boot = """
<domain type='kvm' id='8'>
<name>vm_with_boot_param</name>
@ -2606,9 +2684,7 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
</video>
</devices>
</domain>
""".format(
root_dir, os.sep
)
"""
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)
@ -2709,6 +2785,213 @@ class VirtTestCase(TestCase, LoaderModuleMockMixin):
self.assertEqual(setxml.find("os").find("loader"), None)
self.assertEqual(setxml.find("os").find("nvram"), 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",
}
self.assertEqual(
{
"definition": True,
"disk": {"attached": [], "detached": [], "updated": []},
"interface": {"attached": [], "detached": []},
"mem": False,
},
virt.update("vm_with_memtune_param", mem=memtune_new_val),
)
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": []},
"mem": False,
},
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",
}
self.assertEqual(
{
"definition": True,
"disk": {"attached": [], "detached": [], "updated": []},
"interface": {"attached": [], "detached": []},
"mem": False,
},
virt.update("vm_with_memtune_param", mem=max_swap_none),
)
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": []},
"mem": False,
},
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": []},
"mem": False,
},
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_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.