"Fossies" - the Fresh Open Source Software Archive 
Member "salt-3002.2/tests/unit/modules/test_virt.py" (18 Nov 2020, 220199 Bytes) of package /linux/misc/salt-3002.2.tar.gz:
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style:
standard) with prefixed line numbers.
Alternatively you can here
view or
download the uninterpreted source code file.
See also the latest
Fossies "Diffs" side-by-side code changes report for "test_virt.py":
3002.1_vs_3002.2.
1 """
2 virt execution module unit tests
3 """
4
5 # pylint: disable=3rd-party-module-not-gated
6
7
8 import datetime
9 import os
10 import shutil
11 import tempfile
12
13 import salt.config
14 import salt.modules.config as config
15 import salt.modules.virt as virt
16 import salt.syspaths
17 import salt.utils.yaml
18 from salt._compat import ElementTree as ET
19 from salt.exceptions import CommandExecutionError, SaltInvocationError
20
21 # pylint: disable=import-error
22 from salt.ext.six.moves import range # pylint: disable=redefined-builtin
23 from tests.support.helpers import dedent
24 from tests.support.mixins import LoaderModuleMockMixin
25 from tests.support.mock import MagicMock, patch
26 from tests.support.unit import TestCase
27
28 # pylint: disable=invalid-name,protected-access,attribute-defined-outside-init,too-many-public-methods,unused-argument
29
30
31 class LibvirtMock(MagicMock): # pylint: disable=too-many-ancestors
32 """
33 Libvirt library mock
34 """
35
36 class virDomain(MagicMock):
37 """
38 virDomain mock
39 """
40
41 class libvirtError(Exception):
42 """
43 libvirtError mock
44 """
45
46 def __init__(self, msg):
47 super().__init__(msg)
48 self.msg = msg
49
50 def get_error_message(self):
51 return self.msg
52
53
54 class VirtTestCase(TestCase, LoaderModuleMockMixin):
55 """
56 Test cases for salt.module.virt
57 """
58
59 def setup_loader_modules(self):
60 self.mock_libvirt = LibvirtMock()
61 self.mock_conn = MagicMock()
62 self.mock_conn.getStoragePoolCapabilities.return_value = (
63 "<storagepoolCapabilities/>"
64 )
65 self.mock_libvirt.openAuth.return_value = self.mock_conn
66 self.mock_popen = MagicMock()
67 self.addCleanup(delattr, self, "mock_libvirt")
68 self.addCleanup(delattr, self, "mock_conn")
69 self.addCleanup(delattr, self, "mock_popen")
70 self.mock_subprocess = MagicMock()
71 self.mock_subprocess.return_value = (
72 self.mock_subprocess
73 ) # pylint: disable=no-member
74 self.mock_subprocess.Popen.return_value = (
75 self.mock_popen
76 ) # pylint: disable=no-member
77 loader_globals = {
78 "__salt__": {"config.get": config.get, "config.option": config.option},
79 "libvirt": self.mock_libvirt,
80 "subprocess": self.mock_subprocess,
81 }
82 return {virt: loader_globals, config: loader_globals}
83
84 def set_mock_vm(self, name, xml):
85 """
86 Define VM to use in tests
87 """
88 self.mock_conn.listDefinedDomains.return_value = [
89 name
90 ] # pylint: disable=no-member
91 mock_domain = self.mock_libvirt.virDomain()
92 self.mock_conn.lookupByName.return_value = (
93 mock_domain # pylint: disable=no-member
94 )
95 mock_domain.XMLDesc.return_value = xml # pylint: disable=no-member
96
97 # Return state as shutdown
98 mock_domain.info.return_value = [
99 4,
100 2048 * 1024,
101 1024 * 1024,
102 2,
103 1234,
104 ] # pylint: disable=no-member
105 mock_domain.ID.return_value = 1
106 mock_domain.name.return_value = name
107 return mock_domain
108
109 def test_disk_profile_merge(self):
110 """
111 Test virt._disk_profile() when merging with user-defined disks
112 """
113 root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images")
114 userdisks = [
115 {"name": "system", "image": "/path/to/image"},
116 {"name": "data", "size": 16384, "format": "raw"},
117 ]
118
119 disks = virt._disk_profile(self.mock_conn, "default", "kvm", userdisks, "myvm")
120 self.assertEqual(
121 [
122 {
123 "name": "system",
124 "device": "disk",
125 "size": 8192,
126 "format": "qcow2",
127 "model": "virtio",
128 "filename": "myvm_system.qcow2",
129 "image": "/path/to/image",
130 "source_file": "{}{}myvm_system.qcow2".format(root_dir, os.sep),
131 },
132 {
133 "name": "data",
134 "device": "disk",
135 "size": 16384,
136 "format": "raw",
137 "model": "virtio",
138 "filename": "myvm_data.raw",
139 "source_file": "{}{}myvm_data.raw".format(root_dir, os.sep),
140 },
141 ],
142 disks,
143 )
144
145 def test_boot_default_dev(self):
146 """
147 Test virt._gen_xml() default boot device
148 """
149 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
150 nicp = virt._nic_profile("default", "kvm")
151 xml_data = virt._gen_xml(
152 self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64"
153 )
154 root = ET.fromstring(xml_data)
155 self.assertEqual(root.find("os/boot").attrib["dev"], "hd")
156 self.assertEqual(root.find("os/type").attrib["arch"], "x86_64")
157 self.assertEqual(root.find("os/type").text, "hvm")
158
159 def test_boot_custom_dev(self):
160 """
161 Test virt._gen_xml() custom boot device
162 """
163 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
164 nicp = virt._nic_profile("default", "kvm")
165 xml_data = virt._gen_xml(
166 self.mock_conn,
167 "hello",
168 1,
169 512,
170 diskp,
171 nicp,
172 "kvm",
173 "hvm",
174 "x86_64",
175 boot_dev="cdrom",
176 )
177 root = ET.fromstring(xml_data)
178 self.assertEqual(root.find("os/boot").attrib["dev"], "cdrom")
179
180 def test_boot_multiple_devs(self):
181 """
182 Test virt._gen_xml() multiple boot devices
183 """
184 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
185 nicp = virt._nic_profile("default", "kvm")
186 xml_data = virt._gen_xml(
187 self.mock_conn,
188 "hello",
189 1,
190 512,
191 diskp,
192 nicp,
193 "kvm",
194 "hvm",
195 "x86_64",
196 boot_dev="cdrom network",
197 )
198 root = ET.fromstring(xml_data)
199 devs = root.findall(".//boot")
200 self.assertTrue(len(devs) == 2)
201
202 def test_gen_xml_no_nic(self):
203 """
204 Test virt._gen_xml() serial console
205 """
206 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
207 nicp = virt._nic_profile("default", "kvm")
208 xml_data = virt._gen_xml(
209 self.mock_conn,
210 "hello",
211 1,
212 512,
213 diskp,
214 nicp,
215 "kvm",
216 "hvm",
217 "x86_64",
218 serial_type="pty",
219 console=True,
220 )
221 root = ET.fromstring(xml_data)
222 self.assertEqual(root.find("devices/serial").attrib["type"], "pty")
223 self.assertEqual(root.find("devices/console").attrib["type"], "pty")
224
225 def test_gen_xml_for_serial_console(self):
226 """
227 Test virt._gen_xml() serial console
228 """
229 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
230 nicp = virt._nic_profile("default", "kvm")
231 xml_data = virt._gen_xml(
232 self.mock_conn,
233 "hello",
234 1,
235 512,
236 diskp,
237 nicp,
238 "kvm",
239 "hvm",
240 "x86_64",
241 serial_type="pty",
242 console=True,
243 )
244 root = ET.fromstring(xml_data)
245 self.assertEqual(root.find("devices/serial").attrib["type"], "pty")
246 self.assertEqual(root.find("devices/console").attrib["type"], "pty")
247
248 def test_gen_xml_for_telnet_console(self):
249 """
250 Test virt._gen_xml() telnet console
251 """
252 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
253 nicp = virt._nic_profile("default", "kvm")
254 xml_data = virt._gen_xml(
255 self.mock_conn,
256 "hello",
257 1,
258 512,
259 diskp,
260 nicp,
261 "kvm",
262 "hvm",
263 "x86_64",
264 serial_type="tcp",
265 console=True,
266 telnet_port=22223,
267 )
268 root = ET.fromstring(xml_data)
269 self.assertEqual(root.find("devices/serial").attrib["type"], "tcp")
270 self.assertEqual(root.find("devices/console").attrib["type"], "tcp")
271 self.assertEqual(root.find("devices/console/source").attrib["service"], "22223")
272
273 def test_gen_xml_for_telnet_console_unspecified_port(self):
274 """
275 Test virt._gen_xml() telnet console without any specified port
276 """
277 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
278 nicp = virt._nic_profile("default", "kvm")
279 xml_data = virt._gen_xml(
280 self.mock_conn,
281 "hello",
282 1,
283 512,
284 diskp,
285 nicp,
286 "kvm",
287 "hvm",
288 "x86_64",
289 serial_type="tcp",
290 console=True,
291 )
292 root = ET.fromstring(xml_data)
293 self.assertEqual(root.find("devices/serial").attrib["type"], "tcp")
294 self.assertEqual(root.find("devices/console").attrib["type"], "tcp")
295 self.assertIsInstance(
296 int(root.find("devices/console/source").attrib["service"]), int
297 )
298
299 def test_gen_xml_for_serial_no_console(self):
300 """
301 Test virt._gen_xml() with no serial console
302 """
303 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
304 nicp = virt._nic_profile("default", "kvm")
305 xml_data = virt._gen_xml(
306 self.mock_conn,
307 "hello",
308 1,
309 512,
310 diskp,
311 nicp,
312 "kvm",
313 "hvm",
314 "x86_64",
315 serial_type="pty",
316 console=False,
317 )
318 root = ET.fromstring(xml_data)
319 self.assertEqual(root.find("devices/serial").attrib["type"], "pty")
320 self.assertEqual(root.find("devices/console"), None)
321
322 def test_gen_xml_for_telnet_no_console(self):
323 """
324 Test virt._gen_xml() with no telnet console
325 """
326 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
327 nicp = virt._nic_profile("default", "kvm")
328 xml_data = virt._gen_xml(
329 self.mock_conn,
330 "hello",
331 1,
332 512,
333 diskp,
334 nicp,
335 "kvm",
336 "hvm",
337 "x86_64",
338 serial_type="tcp",
339 console=False,
340 )
341 root = ET.fromstring(xml_data)
342 self.assertEqual(root.find("devices/serial").attrib["type"], "tcp")
343 self.assertEqual(root.find("devices/console"), None)
344
345 def test_gen_xml_nographics_default(self):
346 """
347 Test virt._gen_xml() with default no graphics device
348 """
349 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
350 nicp = virt._nic_profile("default", "kvm")
351 xml_data = virt._gen_xml(
352 self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64"
353 )
354 root = ET.fromstring(xml_data)
355 self.assertIsNone(root.find("devices/graphics"))
356
357 def test_gen_xml_noloader_default(self):
358 """
359 Test virt._gen_xml() with default no loader
360 """
361 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
362 nicp = virt._nic_profile("default", "kvm")
363 xml_data = virt._gen_xml(
364 self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64"
365 )
366 root = ET.fromstring(xml_data)
367 self.assertIsNone(root.find("os/loader"))
368
369 def test_gen_xml_vnc_default(self):
370 """
371 Test virt._gen_xml() with default vnc graphics device
372 """
373 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
374 nicp = virt._nic_profile("default", "kvm")
375 xml_data = virt._gen_xml(
376 self.mock_conn,
377 "hello",
378 1,
379 512,
380 diskp,
381 nicp,
382 "kvm",
383 "hvm",
384 "x86_64",
385 graphics={
386 "type": "vnc",
387 "port": 1234,
388 "tlsPort": 5678,
389 "listen": {"type": "address", "address": "myhost"},
390 },
391 )
392 root = ET.fromstring(xml_data)
393 self.assertEqual(root.find("devices/graphics").attrib["type"], "vnc")
394 self.assertEqual(root.find("devices/graphics").attrib["autoport"], "no")
395 self.assertEqual(root.find("devices/graphics").attrib["port"], "1234")
396 self.assertFalse("tlsPort" in root.find("devices/graphics").attrib)
397 self.assertEqual(root.find("devices/graphics").attrib["listen"], "myhost")
398 self.assertEqual(root.find("devices/graphics/listen").attrib["type"], "address")
399 self.assertEqual(
400 root.find("devices/graphics/listen").attrib["address"], "myhost"
401 )
402
403 def test_gen_xml_spice_default(self):
404 """
405 Test virt._gen_xml() with default spice graphics device
406 """
407 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
408 nicp = virt._nic_profile("default", "kvm")
409 xml_data = virt._gen_xml(
410 self.mock_conn,
411 "hello",
412 1,
413 512,
414 diskp,
415 nicp,
416 "kvm",
417 "hvm",
418 "x86_64",
419 graphics={"type": "spice"},
420 )
421 root = ET.fromstring(xml_data)
422 self.assertEqual(root.find("devices/graphics").attrib["type"], "spice")
423 self.assertEqual(root.find("devices/graphics").attrib["autoport"], "yes")
424 self.assertEqual(root.find("devices/graphics").attrib["listen"], "0.0.0.0")
425 self.assertEqual(root.find("devices/graphics/listen").attrib["type"], "address")
426 self.assertEqual(
427 root.find("devices/graphics/listen").attrib["address"], "0.0.0.0"
428 )
429
430 def test_gen_xml_spice(self):
431 """
432 Test virt._gen_xml() with spice graphics device
433 """
434 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
435 nicp = virt._nic_profile("default", "kvm")
436 xml_data = virt._gen_xml(
437 self.mock_conn,
438 "hello",
439 1,
440 512,
441 diskp,
442 nicp,
443 "kvm",
444 "hvm",
445 "x86_64",
446 graphics={
447 "type": "spice",
448 "port": 1234,
449 "tls_port": 5678,
450 "listen": {"type": "none"},
451 },
452 )
453 root = ET.fromstring(xml_data)
454 self.assertEqual(root.find("devices/graphics").attrib["type"], "spice")
455 self.assertEqual(root.find("devices/graphics").attrib["autoport"], "no")
456 self.assertEqual(root.find("devices/graphics").attrib["port"], "1234")
457 self.assertEqual(root.find("devices/graphics").attrib["tlsPort"], "5678")
458 self.assertFalse("listen" in root.find("devices/graphics").attrib)
459 self.assertEqual(root.find("devices/graphics/listen").attrib["type"], "none")
460 self.assertFalse("address" in root.find("devices/graphics/listen").attrib)
461
462 def test_default_disk_profile_hypervisor_esxi(self):
463 """
464 Test virt._disk_profile() default ESXi profile
465 """
466 mock = MagicMock(return_value={})
467 with patch.dict(
468 virt.__salt__, {"config.get": mock} # pylint: disable=no-member
469 ):
470 ret = virt._disk_profile(
471 self.mock_conn, "nonexistent", "vmware", None, "test-vm"
472 )
473 self.assertTrue(len(ret) == 1)
474 found = [disk for disk in ret if disk["name"] == "system"]
475 self.assertTrue(bool(found))
476 system = found[0]
477 self.assertEqual(system["format"], "vmdk")
478 self.assertEqual(system["model"], "scsi")
479 self.assertTrue(int(system["size"]) >= 1)
480
481 def test_default_disk_profile_hypervisor_kvm(self):
482 """
483 Test virt._disk_profile() default KVM profile
484 """
485 mock = MagicMock(side_effect=[{}, "/images/dir"])
486 with patch.dict(
487 virt.__salt__, {"config.get": mock} # pylint: disable=no-member
488 ):
489 ret = virt._disk_profile(
490 self.mock_conn, "nonexistent", "kvm", None, "test-vm"
491 )
492 self.assertTrue(len(ret) == 1)
493 found = [disk for disk in ret if disk["name"] == "system"]
494 self.assertTrue(bool(found))
495 system = found[0]
496 self.assertEqual(system["format"], "qcow2")
497 self.assertEqual(system["model"], "virtio")
498 self.assertTrue(int(system["size"]) >= 1)
499
500 def test_default_disk_profile_hypervisor_xen(self):
501 """
502 Test virt._disk_profile() default XEN profile
503 """
504 mock = MagicMock(side_effect=[{}, "/images/dir"])
505 with patch.dict(
506 virt.__salt__, {"config.get": mock} # pylint: disable=no-member
507 ):
508 ret = virt._disk_profile(
509 self.mock_conn, "nonexistent", "xen", None, "test-vm"
510 )
511 self.assertTrue(len(ret) == 1)
512 found = [disk for disk in ret if disk["name"] == "system"]
513 self.assertTrue(bool(found))
514 system = found[0]
515 self.assertEqual(system["format"], "qcow2")
516 self.assertEqual(system["model"], "xen")
517 self.assertTrue(int(system["size"]) >= 1)
518
519 def test_default_nic_profile_hypervisor_esxi(self):
520 """
521 Test virt._nic_profile() default ESXi profile
522 """
523 mock = MagicMock(return_value={})
524 with patch.dict(
525 virt.__salt__, {"config.get": mock} # pylint: disable=no-member
526 ):
527 ret = virt._nic_profile("nonexistent", "vmware")
528 self.assertTrue(len(ret) == 1)
529 eth0 = ret[0]
530 self.assertEqual(eth0["name"], "eth0")
531 self.assertEqual(eth0["type"], "bridge")
532 self.assertEqual(eth0["source"], "DEFAULT")
533 self.assertEqual(eth0["model"], "e1000")
534
535 def test_default_nic_profile_hypervisor_kvm(self):
536 """
537 Test virt._nic_profile() default KVM profile
538 """
539 mock = MagicMock(return_value={})
540 with patch.dict(
541 virt.__salt__, {"config.get": mock} # pylint: disable=no-member
542 ):
543 ret = virt._nic_profile("nonexistent", "kvm")
544 self.assertTrue(len(ret) == 1)
545 eth0 = ret[0]
546 self.assertEqual(eth0["name"], "eth0")
547 self.assertEqual(eth0["type"], "bridge")
548 self.assertEqual(eth0["source"], "br0")
549 self.assertEqual(eth0["model"], "virtio")
550
551 def test_default_nic_profile_hypervisor_xen(self):
552 """
553 Test virt._nic_profile() default XEN profile
554 """
555 mock = MagicMock(return_value={})
556 with patch.dict(
557 virt.__salt__, {"config.get": mock} # pylint: disable=no-member
558 ):
559 ret = virt._nic_profile("nonexistent", "xen")
560 self.assertTrue(len(ret) == 1)
561 eth0 = ret[0]
562 self.assertEqual(eth0["name"], "eth0")
563 self.assertEqual(eth0["type"], "bridge")
564 self.assertEqual(eth0["source"], "br0")
565 self.assertFalse(eth0["model"])
566
567 def test_gen_vol_xml_esx(self):
568 """
569 Test virt._get_vol_xml() for the ESX case
570 """
571 xml_data = virt._gen_vol_xml("vmname/system.vmdk", 8192, format="vmdk")
572 root = ET.fromstring(xml_data)
573 self.assertIsNone(root.get("type"))
574 self.assertEqual(root.find("name").text, "vmname/system.vmdk")
575 self.assertEqual(root.find("capacity").attrib["unit"], "KiB")
576 self.assertEqual(root.find("capacity").text, str(8192 * 1024))
577 self.assertEqual(root.find("allocation").text, str(0))
578 self.assertEqual(root.find("target/format").get("type"), "vmdk")
579 self.assertIsNone(root.find("target/permissions"))
580 self.assertIsNone(root.find("target/nocow"))
581 self.assertIsNone(root.find("backingStore"))
582
583 def test_gen_vol_xml_file(self):
584 """
585 Test virt._get_vol_xml() for a file volume
586 """
587 xml_data = virt._gen_vol_xml(
588 "myvm_system.qcow2",
589 8192,
590 format="qcow2",
591 allocation=4096,
592 type="file",
593 permissions={
594 "mode": "0775",
595 "owner": "123",
596 "group": "456",
597 "label": "sec_label",
598 },
599 backing_store={"path": "/backing/image", "format": "raw"},
600 nocow=True,
601 )
602 root = ET.fromstring(xml_data)
603 self.assertEqual(root.get("type"), "file")
604 self.assertEqual(root.find("name").text, "myvm_system.qcow2")
605 self.assertIsNone(root.find("key"))
606 self.assertIsNone(root.find("target/path"))
607 self.assertEqual(root.find("target/format").get("type"), "qcow2")
608 self.assertEqual(root.find("capacity").attrib["unit"], "KiB")
609 self.assertEqual(root.find("capacity").text, str(8192 * 1024))
610 self.assertEqual(root.find("capacity").attrib["unit"], "KiB")
611 self.assertEqual(root.find("allocation").text, str(4096 * 1024))
612 self.assertEqual(root.find("target/permissions/mode").text, "0775")
613 self.assertEqual(root.find("target/permissions/owner").text, "123")
614 self.assertEqual(root.find("target/permissions/group").text, "456")
615 self.assertEqual(root.find("target/permissions/label").text, "sec_label")
616 self.assertIsNotNone(root.find("target/nocow"))
617 self.assertEqual(root.find("backingStore/path").text, "/backing/image")
618 self.assertEqual(root.find("backingStore/format").get("type"), "raw")
619
620 def test_gen_xml_for_kvm_default_profile(self):
621 """
622 Test virt._gen_xml(), KVM default profile case
623 """
624 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
625 nicp = virt._nic_profile("default", "kvm")
626 xml_data = virt._gen_xml(
627 self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64",
628 )
629 root = ET.fromstring(xml_data)
630 self.assertEqual(root.attrib["type"], "kvm")
631 self.assertEqual(root.find("vcpu").text, "1")
632 self.assertEqual(root.find("memory").text, str(512 * 1024))
633 self.assertEqual(root.find("memory").attrib["unit"], "KiB")
634
635 disks = root.findall(".//disk")
636 self.assertEqual(len(disks), 1)
637 disk = disks[0]
638 root_dir = salt.config.DEFAULT_MINION_OPTS.get("root_dir")
639 self.assertTrue(disk.find("source").attrib["file"].startswith(root_dir))
640 self.assertTrue("hello_system" in disk.find("source").attrib["file"])
641 self.assertEqual(disk.find("target").attrib["dev"], "vda")
642 self.assertEqual(disk.find("target").attrib["bus"], "virtio")
643 self.assertEqual(disk.find("driver").attrib["name"], "qemu")
644 self.assertEqual(disk.find("driver").attrib["type"], "qcow2")
645
646 interfaces = root.findall(".//interface")
647 self.assertEqual(len(interfaces), 1)
648 iface = interfaces[0]
649 self.assertEqual(iface.attrib["type"], "bridge")
650 self.assertEqual(iface.find("source").attrib["bridge"], "br0")
651 self.assertEqual(iface.find("model").attrib["type"], "virtio")
652
653 def test_gen_xml_for_esxi_default_profile(self):
654 """
655 Test virt._gen_xml(), ESXi/vmware default profile case
656 """
657 diskp = virt._disk_profile(self.mock_conn, "default", "vmware", [], "hello")
658 nicp = virt._nic_profile("default", "vmware")
659 xml_data = virt._gen_xml(
660 self.mock_conn, "hello", 1, 512, diskp, nicp, "vmware", "hvm", "x86_64",
661 )
662 root = ET.fromstring(xml_data)
663 self.assertEqual(root.attrib["type"], "vmware")
664 self.assertEqual(root.find("vcpu").text, "1")
665 self.assertEqual(root.find("memory").text, str(512 * 1024))
666 self.assertEqual(root.find("memory").attrib["unit"], "KiB")
667
668 disks = root.findall(".//disk")
669 self.assertEqual(len(disks), 1)
670 disk = disks[0]
671 self.assertTrue("[0]" in disk.find("source").attrib["file"])
672 self.assertTrue("hello_system" in disk.find("source").attrib["file"])
673 self.assertEqual(disk.find("target").attrib["dev"], "sda")
674 self.assertEqual(disk.find("target").attrib["bus"], "scsi")
675 self.assertEqual(disk.find("address").attrib["unit"], "0")
676
677 interfaces = root.findall(".//interface")
678 self.assertEqual(len(interfaces), 1)
679 iface = interfaces[0]
680 self.assertEqual(iface.attrib["type"], "bridge")
681 self.assertEqual(iface.find("source").attrib["bridge"], "DEFAULT")
682 self.assertEqual(iface.find("model").attrib["type"], "e1000")
683
684 def test_gen_xml_for_xen_default_profile(self):
685 """
686 Test virt._gen_xml(), XEN PV default profile case
687 """
688 diskp = virt._disk_profile(self.mock_conn, "default", "xen", [], "hello")
689 nicp = virt._nic_profile("default", "xen")
690 with patch.dict(
691 virt.__grains__, {"os_family": "Suse"} # pylint: disable=no-member
692 ):
693 xml_data = virt._gen_xml(
694 self.mock_conn,
695 "hello",
696 1,
697 512,
698 diskp,
699 nicp,
700 "xen",
701 "xen",
702 "x86_64",
703 boot=None,
704 )
705 root = ET.fromstring(xml_data)
706 self.assertEqual(root.attrib["type"], "xen")
707 self.assertEqual(root.find("vcpu").text, "1")
708 self.assertEqual(root.find("memory").text, str(512 * 1024))
709 self.assertEqual(root.find("memory").attrib["unit"], "KiB")
710 self.assertEqual(
711 root.find(".//kernel").text, "/usr/lib/grub2/x86_64-xen/grub.xen"
712 )
713
714 disks = root.findall(".//disk")
715 self.assertEqual(len(disks), 1)
716 disk = disks[0]
717 root_dir = salt.config.DEFAULT_MINION_OPTS.get("root_dir")
718 self.assertTrue(disk.find("source").attrib["file"].startswith(root_dir))
719 self.assertTrue("hello_system" in disk.find("source").attrib["file"])
720 self.assertEqual(disk.find("target").attrib["dev"], "xvda")
721 self.assertEqual(disk.find("target").attrib["bus"], "xen")
722 self.assertEqual(disk.find("driver").attrib["name"], "qemu")
723 self.assertEqual(disk.find("driver").attrib["type"], "qcow2")
724
725 interfaces = root.findall(".//interface")
726 self.assertEqual(len(interfaces), 1)
727 iface = interfaces[0]
728 self.assertEqual(iface.attrib["type"], "bridge")
729 self.assertEqual(iface.find("source").attrib["bridge"], "br0")
730 self.assertIsNone(iface.find("model"))
731
732 def test_gen_xml_for_esxi_custom_profile(self):
733 """
734 Test virt._gen_xml(), ESXi/vmware custom profile case
735 """
736 disks = {
737 "noeffect": [
738 {"first": {"size": 8192, "pool": "datastore1"}},
739 {"second": {"size": 4096, "pool": "datastore2"}},
740 ]
741 }
742 nics = {
743 "noeffect": [
744 {"name": "eth1", "source": "ONENET"},
745 {"name": "eth2", "source": "TWONET"},
746 ]
747 }
748 with patch.dict(
749 virt.__salt__, # pylint: disable=no-member
750 {"config.get": MagicMock(side_effect=[disks, nics])},
751 ):
752 diskp = virt._disk_profile(
753 self.mock_conn, "noeffect", "vmware", [], "hello"
754 )
755 nicp = virt._nic_profile("noeffect", "vmware")
756 xml_data = virt._gen_xml(
757 self.mock_conn, "hello", 1, 512, diskp, nicp, "vmware", "hvm", "x86_64",
758 )
759 root = ET.fromstring(xml_data)
760 self.assertEqual(root.attrib["type"], "vmware")
761 self.assertEqual(root.find("vcpu").text, "1")
762 self.assertEqual(root.find("memory").text, str(512 * 1024))
763 self.assertEqual(root.find("memory").attrib["unit"], "KiB")
764 self.assertTrue(len(root.findall(".//disk")) == 2)
765 self.assertTrue(len(root.findall(".//interface")) == 2)
766
767 def test_gen_xml_for_kvm_custom_profile(self):
768 """
769 Test virt._gen_xml(), KVM custom profile case
770 """
771 disks = {
772 "noeffect": [
773 {"first": {"size": 8192, "pool": "/var/lib/images"}},
774 {"second": {"size": 4096, "pool": "/var/lib/images"}},
775 ]
776 }
777 nics = {
778 "noeffect": [
779 {"name": "eth1", "source": "b2"},
780 {"name": "eth2", "source": "b2"},
781 ]
782 }
783 with patch.dict(
784 virt.__salt__, # pylint: disable=no-member
785 {"config.get": MagicMock(side_effect=[disks, nics])},
786 ):
787 diskp = virt._disk_profile(self.mock_conn, "noeffect", "kvm", [], "hello")
788 nicp = virt._nic_profile("noeffect", "kvm")
789 xml_data = virt._gen_xml(
790 self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64",
791 )
792 root = ET.fromstring(xml_data)
793 self.assertEqual(root.attrib["type"], "kvm")
794 self.assertEqual(root.find("vcpu").text, "1")
795 self.assertEqual(root.find("memory").text, str(512 * 1024))
796 self.assertEqual(root.find("memory").attrib["unit"], "KiB")
797 disks = root.findall(".//disk")
798 self.assertTrue(len(disks) == 2)
799 self.assertEqual(disks[0].find("target").get("dev"), "vda")
800 self.assertEqual(disks[1].find("target").get("dev"), "vdb")
801 self.assertTrue(len(root.findall(".//interface")) == 2)
802
803 def test_disk_profile_kvm_disk_pool(self):
804 """
805 Test virt._disk_profile(), KVM case with pools defined.
806 """
807 disks = {
808 "noeffect": [
809 {"first": {"size": 8192, "pool": "mypool"}},
810 {"second": {"size": 4096}},
811 ]
812 }
813
814 # pylint: disable=no-member
815 with patch.dict(
816 virt.__salt__,
817 {
818 "config.get": MagicMock(
819 side_effect=[
820 disks,
821 os.path.join(salt.syspaths.ROOT_DIR, "default", "path"),
822 ]
823 )
824 },
825 ):
826
827 diskp = virt._disk_profile(self.mock_conn, "noeffect", "kvm", [], "hello")
828
829 pools_path = (
830 os.path.join(salt.syspaths.ROOT_DIR, "pools", "mypool") + os.sep
831 )
832 default_path = (
833 os.path.join(salt.syspaths.ROOT_DIR, "default", "path") + os.sep
834 )
835
836 self.assertEqual(len(diskp), 2)
837 self.assertTrue(diskp[1]["source_file"].startswith(default_path))
838 # pylint: enable=no-member
839
840 def test_disk_profile_kvm_disk_external_image(self):
841 """
842 Test virt._gen_xml(), KVM case with an external image.
843 """
844 with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=True)}):
845 diskp = virt._disk_profile(
846 self.mock_conn,
847 None,
848 "kvm",
849 [{"name": "mydisk", "source_file": "/path/to/my/image.qcow2"}],
850 "hello",
851 )
852
853 self.assertEqual(len(diskp), 1)
854 self.assertEqual(diskp[0]["source_file"], ("/path/to/my/image.qcow2"))
855
856 def test_disk_profile_cdrom_default(self):
857 """
858 Test virt._gen_xml(), KVM case with cdrom.
859 """
860 with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=True)}):
861 diskp = virt._disk_profile(
862 self.mock_conn,
863 None,
864 "kvm",
865 [
866 {
867 "name": "mydisk",
868 "device": "cdrom",
869 "source_file": "/path/to/my.iso",
870 }
871 ],
872 "hello",
873 )
874
875 self.assertEqual(len(diskp), 1)
876 self.assertEqual(diskp[0]["model"], "ide")
877 self.assertEqual(diskp[0]["format"], "raw")
878
879 def test_disk_profile_pool_disk_type(self):
880 """
881 Test virt._disk_profile(), with a disk pool of disk type
882 """
883 self.mock_conn.listStoragePools.return_value = ["test-vdb"]
884 self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = """
885 <pool type="disk">
886 <name>test-vdb</name>
887 <source>
888 <device path='/dev/vdb'/>
889 </source>
890 <target>
891 <path>/dev</path>
892 </target>
893 </pool>
894 """
895
896 # No existing disk case
897 self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = (
898 []
899 )
900 diskp = virt._disk_profile(
901 self.mock_conn,
902 None,
903 "kvm",
904 [{"name": "mydisk", "pool": "test-vdb"}],
905 "hello",
906 )
907 self.assertEqual(diskp[0]["filename"], ("vdb1"))
908
909 # Append to the end case
910 self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = [
911 "vdb1",
912 "vdb2",
913 ]
914 diskp = virt._disk_profile(
915 self.mock_conn,
916 None,
917 "kvm",
918 [{"name": "mydisk", "pool": "test-vdb"}],
919 "hello",
920 )
921 self.assertEqual(diskp[0]["filename"], ("vdb3"))
922
923 # Hole in the middle case
924 self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = [
925 "vdb1",
926 "vdb3",
927 ]
928 diskp = virt._disk_profile(
929 self.mock_conn,
930 None,
931 "kvm",
932 [{"name": "mydisk", "pool": "test-vdb"}],
933 "hello",
934 )
935 self.assertEqual(diskp[0]["filename"], ("vdb2"))
936
937 # Reuse existing volume case
938 diskp = virt._disk_profile(
939 self.mock_conn,
940 None,
941 "kvm",
942 [{"name": "mydisk", "pool": "test-vdb", "source_file": "vdb1"}],
943 "hello",
944 )
945 self.assertEqual(diskp[0]["filename"], ("vdb1"))
946
947 def test_gen_xml_volume(self):
948 """
949 Test virt._gen_xml(), generating a disk of volume type
950 """
951 self.mock_conn.listStoragePools.return_value = ["default"]
952 self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = (
953 "<pool type='dir'/>"
954 )
955 self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = [
956 "myvolume"
957 ]
958 diskp = virt._disk_profile(
959 self.mock_conn,
960 None,
961 "kvm",
962 [
963 {"name": "system", "pool": "default"},
964 {"name": "data", "pool": "default", "source_file": "myvolume"},
965 ],
966 "hello",
967 )
968 self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = (
969 "<pool type='dir'/>"
970 )
971 nicp = virt._nic_profile(None, "kvm")
972 xml_data = virt._gen_xml(
973 self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64",
974 )
975 root = ET.fromstring(xml_data)
976 disk = root.findall(".//disk")[0]
977 self.assertEqual(disk.attrib["device"], "disk")
978 self.assertEqual(disk.attrib["type"], "volume")
979 source = disk.find("source")
980 self.assertEqual("default", source.attrib["pool"])
981 self.assertEqual("hello_system", source.attrib["volume"])
982 self.assertEqual("myvolume", root.find(".//disk[2]/source").get("volume"))
983
984 # RBD volume usage auth test case
985 self.mock_conn.listStoragePools.return_value = ["test-rbd"]
986 self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = """
987 <pool type='rbd'>
988 <name>test-rbd</name>
989 <uuid>ede33e0a-9df0-479f-8afd-55085a01b244</uuid>
990 <capacity unit='bytes'>526133493760</capacity>
991 <allocation unit='bytes'>589928</allocation>
992 <available unit='bytes'>515081306112</available>
993 <source>
994 <host name='ses2.tf.local'/>
995 <host name='ses3.tf.local' port='1234'/>
996 <name>libvirt-pool</name>
997 <auth type='ceph' username='libvirt'>
998 <secret usage='pool_test-rbd'/>
999 </auth>
1000 </source>
1001 </pool>
1002 """
1003 self.mock_conn.getStoragePoolCapabilities.return_value = """
1004 <storagepoolCapabilities>
1005 <pool type='rbd' supported='yes'>
1006 <volOptions>
1007 <defaultFormat type='raw'/>
1008 <enum name='targetFormatType'>
1009 </enum>
1010 </volOptions>
1011 </pool>
1012 </storagepoolCapabilities>
1013 """
1014 diskp = virt._disk_profile(
1015 self.mock_conn,
1016 None,
1017 "kvm",
1018 [{"name": "system", "pool": "test-rbd"}],
1019 "test-vm",
1020 )
1021 xml_data = virt._gen_xml(
1022 self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64",
1023 )
1024 root = ET.fromstring(xml_data)
1025 disk = root.findall(".//disk")[0]
1026 self.assertDictEqual(
1027 {
1028 "type": "network",
1029 "device": "disk",
1030 "source": {
1031 "protocol": "rbd",
1032 "name": "libvirt-pool/test-vm_system",
1033 "host": [
1034 {"name": "ses2.tf.local"},
1035 {"name": "ses3.tf.local", "port": "1234"},
1036 ],
1037 "auth": {
1038 "username": "libvirt",
1039 "secret": {"type": "ceph", "usage": "pool_test-rbd"},
1040 },
1041 },
1042 "target": {"dev": "vda", "bus": "virtio"},
1043 "driver": {
1044 "name": "qemu",
1045 "type": "raw",
1046 "cache": "none",
1047 "io": "native",
1048 },
1049 },
1050 salt.utils.xmlutil.to_dict(disk, True),
1051 )
1052
1053 # RBD volume UUID auth test case
1054 self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = """
1055 <pool type='rbd'>
1056 <name>test-rbd</name>
1057 <uuid>ede33e0a-9df0-479f-8afd-55085a01b244</uuid>
1058 <capacity unit='bytes'>526133493760</capacity>
1059 <allocation unit='bytes'>589928</allocation>
1060 <available unit='bytes'>515081306112</available>
1061 <source>
1062 <host name='ses2.tf.local'/>
1063 <host name='ses3.tf.local' port='1234'/>
1064 <name>libvirt-pool</name>
1065 <auth type='ceph' username='libvirt'>
1066 <secret uuid='some-uuid'/>
1067 </auth>
1068 </source>
1069 </pool>
1070 """
1071 self.mock_conn.secretLookupByUUIDString.return_value.usageID.return_value = (
1072 "pool_test-rbd"
1073 )
1074 diskp = virt._disk_profile(
1075 self.mock_conn,
1076 None,
1077 "kvm",
1078 [{"name": "system", "pool": "test-rbd"}],
1079 "test-vm",
1080 )
1081 xml_data = virt._gen_xml(
1082 self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64",
1083 )
1084 root = ET.fromstring(xml_data)
1085 self.assertDictEqual(
1086 {
1087 "username": "libvirt",
1088 "secret": {"type": "ceph", "usage": "pool_test-rbd"},
1089 },
1090 salt.utils.xmlutil.to_dict(root.find(".//disk/source/auth"), True),
1091 )
1092 self.mock_conn.secretLookupByUUIDString.assert_called_once_with("some-uuid")
1093
1094 # Disk volume test case
1095 self.mock_conn.getStoragePoolCapabilities.return_value = """
1096 <storagepoolCapabilities>
1097 <pool type='disk' supported='yes'>
1098 <volOptions>
1099 <defaultFormat type='none'/>
1100 <enum name='targetFormatType'>
1101 <value>none</value>
1102 <value>linux</value>
1103 <value>fat16</value>
1104 </enum>
1105 </volOptions>
1106 </pool>
1107 </storagepoolCapabilities>
1108 """
1109 self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = """
1110 <pool type='disk'>
1111 <name>test-vdb</name>
1112 <source>
1113 <device path='/dev/vdb'/>
1114 <format type='gpt'/>
1115 </source>
1116 </pool>
1117 """
1118 self.mock_conn.listStoragePools.return_value = ["test-vdb"]
1119 self.mock_conn.storagePoolLookupByName.return_value.listVolumes.return_value = [
1120 "vdb1",
1121 ]
1122 diskp = virt._disk_profile(
1123 self.mock_conn,
1124 None,
1125 "kvm",
1126 [{"name": "system", "pool": "test-vdb"}],
1127 "test-vm",
1128 )
1129 xml_data = virt._gen_xml(
1130 self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64",
1131 )
1132 root = ET.fromstring(xml_data)
1133 disk = root.findall(".//disk")[0]
1134 self.assertEqual(disk.attrib["type"], "volume")
1135 source = disk.find("source")
1136 self.assertEqual("test-vdb", source.attrib["pool"])
1137 self.assertEqual("vdb2", source.attrib["volume"])
1138 self.assertEqual("raw", disk.find("driver").get("type"))
1139
1140 def test_get_xml_volume_xen_dir(self):
1141 """
1142 Test virt._gen_xml generating disks for a Xen hypervisor
1143 """
1144 self.mock_conn.listStoragePools.return_value = ["default"]
1145 pool_mock = MagicMock()
1146 pool_mock.XMLDesc.return_value = (
1147 "<pool type='dir'><target><path>/path/to/images</path></target></pool>"
1148 )
1149 volume_xml = "<volume><target><path>/path/to/images/hello_system</path></target></volume>"
1150 pool_mock.storageVolLookupByName.return_value.XMLDesc.return_value = volume_xml
1151 self.mock_conn.storagePoolLookupByName.return_value = pool_mock
1152 diskp = virt._disk_profile(
1153 self.mock_conn,
1154 None,
1155 "xen",
1156 [{"name": "system", "pool": "default"}],
1157 "hello",
1158 )
1159 xml_data = virt._gen_xml(
1160 self.mock_conn, "hello", 1, 512, diskp, [], "xen", "hvm", "x86_64",
1161 )
1162 root = ET.fromstring(xml_data)
1163 disk = root.findall(".//disk")[0]
1164 self.assertEqual(disk.attrib["type"], "file")
1165 self.assertEqual(
1166 "/path/to/images/hello_system", disk.find("source").attrib["file"]
1167 )
1168
1169 def test_get_xml_volume_xen_block(self):
1170 """
1171 Test virt._gen_xml generating disks for a Xen hypervisor
1172 """
1173 self.mock_conn.listStoragePools.return_value = ["default"]
1174 pool_mock = MagicMock()
1175 pool_mock.listVolumes.return_value = ["vol01"]
1176 volume_xml = "<volume><target><path>/dev/to/vol01</path></target></volume>"
1177 pool_mock.storageVolLookupByName.return_value.XMLDesc.return_value = volume_xml
1178 self.mock_conn.storagePoolLookupByName.return_value = pool_mock
1179
1180 for pool_type in ["logical", "disk", "iscsi", "scsi"]:
1181 pool_mock.XMLDesc.return_value = "<pool type='{}'><source><device path='/dev/sda'/></source></pool>".format(
1182 pool_type
1183 )
1184 diskp = virt._disk_profile(
1185 self.mock_conn,
1186 None,
1187 "xen",
1188 [{"name": "system", "pool": "default", "source_file": "vol01"}],
1189 "hello",
1190 )
1191 xml_data = virt._gen_xml(
1192 self.mock_conn, "hello", 1, 512, diskp, [], "xen", "hvm", "x86_64",
1193 )
1194 root = ET.fromstring(xml_data)
1195 disk = root.findall(".//disk")[0]
1196 self.assertEqual(disk.attrib["type"], "block")
1197 self.assertEqual("/dev/to/vol01", disk.find("source").attrib["dev"])
1198
1199 def test_gen_xml_cdrom(self):
1200 """
1201 Test virt._gen_xml(), generating a cdrom device (different disk type, no source)
1202 """
1203 self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = (
1204 "<pool type='dir'/>"
1205 )
1206 diskp = virt._disk_profile(
1207 self.mock_conn,
1208 None,
1209 "kvm",
1210 [
1211 {"name": "system", "pool": "default"},
1212 {
1213 "name": "tested",
1214 "device": "cdrom",
1215 "source_file": None,
1216 "model": "ide",
1217 },
1218 {
1219 "name": "remote",
1220 "device": "cdrom",
1221 "source_file": "http://myhost:8080/url/to/image?query=foo&filter=bar",
1222 "model": "ide",
1223 },
1224 ],
1225 "hello",
1226 )
1227 nicp = virt._nic_profile(None, "kvm")
1228 xml_data = virt._gen_xml(
1229 self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64",
1230 )
1231 root = ET.fromstring(xml_data)
1232 disk = root.findall(".//disk")[1]
1233 self.assertEqual(disk.get("type"), "file")
1234 self.assertEqual(disk.attrib["device"], "cdrom")
1235 self.assertIsNone(disk.find("source"))
1236 self.assertEqual(disk.find("target").get("dev"), "hda")
1237
1238 disk = root.findall(".//disk")[2]
1239 self.assertEqual(disk.get("type"), "network")
1240 self.assertEqual(disk.attrib["device"], "cdrom")
1241 self.assertEqual(
1242 {
1243 "protocol": "http",
1244 "name": "/url/to/image",
1245 "query": "query=foo&filter=bar",
1246 "host": {"name": "myhost", "port": "8080"},
1247 },
1248 salt.utils.xmlutil.to_dict(disk.find("source"), True),
1249 )
1250
1251 def test_controller_for_esxi(self):
1252 """
1253 Test virt._gen_xml() generated device controller for ESXi/vmware
1254 """
1255 diskp = virt._disk_profile(self.mock_conn, "default", "vmware", [], "hello")
1256 nicp = virt._nic_profile("default", "vmware")
1257 xml_data = virt._gen_xml(
1258 self.mock_conn, "hello", 1, 512, diskp, nicp, "vmware", "hvm", "x86_64",
1259 )
1260 root = ET.fromstring(xml_data)
1261 controllers = root.findall(".//devices/controller")
1262 self.assertTrue(len(controllers) == 1)
1263 controller = controllers[0]
1264 self.assertEqual(controller.attrib["model"], "lsilogic")
1265
1266 def test_controller_for_kvm(self):
1267 """
1268 Test virt._gen_xml() generated device controller for KVM
1269 """
1270 diskp = virt._disk_profile(self.mock_conn, "default", "kvm", [], "hello")
1271 nicp = virt._nic_profile("default", "kvm")
1272 xml_data = virt._gen_xml(
1273 self.mock_conn, "hello", 1, 512, diskp, nicp, "kvm", "hvm", "x86_64",
1274 )
1275 root = ET.fromstring(xml_data)
1276 controllers = root.findall(".//devices/controller")
1277 # There should be no controller
1278 self.assertTrue(len(controllers) == 0)
1279
1280 def test_diff_disks(self):
1281 """
1282 Test virt._diff_disks()
1283 """
1284 old_disks = ET.fromstring(
1285 """
1286 <devices>
1287 <disk type='file' device='disk'>
1288 <source file='/path/to/img0.qcow2'/>
1289 <target dev='vda' bus='virtio'/>
1290 </disk>
1291 <disk type='file' device='disk'>
1292 <source file='/path/to/img1.qcow2'/>
1293 <target dev='vdb' bus='virtio'/>
1294 </disk>
1295 <disk type='file' device='disk'>
1296 <source file='/path/to/img2.qcow2'/>
1297 <target dev='hda' bus='ide'/>
1298 </disk>
1299 <disk type='file' device='disk'>
1300 <source file='/path/to/img4.qcow2'/>
1301 <target dev='hdb' bus='ide'/>
1302 </disk>
1303 <disk type='file' device='cdrom'>
1304 <target dev='hdc' bus='ide'/>
1305 </disk>
1306 </devices>
1307 """
1308 ).findall("disk")
1309
1310 new_disks = ET.fromstring(
1311 """
1312 <devices>
1313 <disk type='file' device='disk'>
1314 <source file='/path/to/img3.qcow2'/>
1315 <target dev='vda' bus='virtio'/>
1316 </disk>
1317 <disk type='file' device='disk' cache='default'>
1318 <source file='/path/to/img0.qcow2'/>
1319 <target dev='vda' bus='virtio'/>
1320 </disk>
1321 <disk type='file' device='disk'>
1322 <source file='/path/to/img4.qcow2'/>
1323 <target dev='vda' bus='virtio'/>
1324 </disk>
1325 <disk type='file' device='cdrom'>
1326 <target dev='hda' bus='ide'/>
1327 </disk>
1328 </devices>
1329 """
1330 ).findall("disk")
1331 ret = virt._diff_disk_lists(old_disks, new_disks)
1332 self.assertEqual(
1333 [
1334 disk.find("source").get("file")
1335 if disk.find("source") is not None
1336 else None
1337 for disk in ret["unchanged"]
1338 ],
1339 [],
1340 )
1341 self.assertEqual(
1342 [
1343 disk.find("source").get("file")
1344 if disk.find("source") is not None
1345 else None
1346 for disk in ret["new"]
1347 ],
1348 ["/path/to/img3.qcow2", "/path/to/img0.qcow2", "/path/to/img4.qcow2", None],
1349 )
1350 self.assertEqual(
1351 [disk.find("target").get("dev") for disk in ret["sorted"]],
1352 ["vda", "vdb", "vdc", "hda"],
1353 )
1354 self.assertEqual(
1355 [
1356 disk.find("source").get("file")
1357 if disk.find("source") is not None
1358 else None
1359 for disk in ret["sorted"]
1360 ],
1361 ["/path/to/img3.qcow2", "/path/to/img0.qcow2", "/path/to/img4.qcow2", None],
1362 )
1363 self.assertEqual(ret["new"][1].find("target").get("bus"), "virtio")
1364 self.assertEqual(
1365 [
1366 disk.find("source").get("file")
1367 if disk.find("source") is not None
1368 else None
1369 for disk in ret["deleted"]
1370 ],
1371 [
1372 "/path/to/img0.qcow2",
1373 "/path/to/img1.qcow2",
1374 "/path/to/img2.qcow2",
1375 "/path/to/img4.qcow2",
1376 None,
1377 ],
1378 )
1379
1380 def test_diff_nics(self):
1381 """
1382 Test virt._diff_nics()
1383 """
1384 old_nics = ET.fromstring(
1385 """
1386 <devices>
1387 <interface type='network'>
1388 <mac address='52:54:00:39:02:b1'/>
1389 <source network='default'/>
1390 <model type='virtio'/>
1391 <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
1392 </interface>
1393 <interface type='network'>
1394 <mac address='52:54:00:39:02:b2'/>
1395 <source network='admin'/>
1396 <model type='virtio'/>
1397 <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
1398 </interface>
1399 <interface type='network'>
1400 <mac address='52:54:00:39:02:b3'/>
1401 <source network='admin'/>
1402 <model type='virtio'/>
1403 <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
1404 </interface>
1405 </devices>
1406 """
1407 ).findall("interface")
1408
1409 new_nics = ET.fromstring(
1410 """
1411 <devices>
1412 <interface type='network'>
1413 <mac address='52:54:00:39:02:b1'/>
1414 <source network='default'/>
1415 <model type='virtio'/>
1416 </interface>
1417 <interface type='network'>
1418 <mac address='52:54:00:39:02:b2'/>
1419 <source network='default'/>
1420 <model type='virtio'/>
1421 </interface>
1422 <interface type='network'>
1423 <mac address='52:54:00:39:02:b4'/>
1424 <source network='admin'/>
1425 <model type='virtio'/>
1426 </interface>
1427 </devices>
1428 """
1429 ).findall("interface")
1430 ret = virt._diff_interface_lists(old_nics, new_nics)
1431 self.assertEqual(
1432 [nic.find("mac").get("address") for nic in ret["unchanged"]],
1433 ["52:54:00:39:02:b1"],
1434 )
1435 self.assertEqual(
1436 [nic.find("mac").get("address") for nic in ret["new"]],
1437 ["52:54:00:39:02:b2", "52:54:00:39:02:b4"],
1438 )
1439 self.assertEqual(
1440 [nic.find("mac").get("address") for nic in ret["deleted"]],
1441 ["52:54:00:39:02:b2", "52:54:00:39:02:b3"],
1442 )
1443
1444 def test_init(self):
1445 """
1446 Test init() function
1447 """
1448 xml = """
1449 <capabilities>
1450 <host>
1451 <uuid>44454c4c-3400-105a-8033-b3c04f4b344a</uuid>
1452 <cpu>
1453 <arch>x86_64</arch>
1454 <model>Nehalem</model>
1455 <vendor>Intel</vendor>
1456 <microcode version='25'/>
1457 <topology sockets='1' cores='4' threads='2'/>
1458 <feature name='vme'/>
1459 <feature name='ds'/>
1460 <feature name='acpi'/>
1461 <pages unit='KiB' size='4'/>
1462 <pages unit='KiB' size='2048'/>
1463 </cpu>
1464 <power_management>
1465 <suspend_mem/>
1466 <suspend_disk/>
1467 <suspend_hybrid/>
1468 </power_management>
1469 <migration_features>
1470 <live/>
1471 <uri_transports>
1472 <uri_transport>tcp</uri_transport>
1473 <uri_transport>rdma</uri_transport>
1474 </uri_transports>
1475 </migration_features>
1476 <topology>
1477 <cells num='1'>
1478 <cell id='0'>
1479 <memory unit='KiB'>12367120</memory>
1480 <pages unit='KiB' size='4'>3091780</pages>
1481 <pages unit='KiB' size='2048'>0</pages>
1482 <distances>
1483 <sibling id='0' value='10'/>
1484 </distances>
1485 <cpus num='8'>
1486 <cpu id='0' socket_id='0' core_id='0' siblings='0,4'/>
1487 <cpu id='1' socket_id='0' core_id='1' siblings='1,5'/>
1488 <cpu id='2' socket_id='0' core_id='2' siblings='2,6'/>
1489 <cpu id='3' socket_id='0' core_id='3' siblings='3,7'/>
1490 <cpu id='4' socket_id='0' core_id='0' siblings='0,4'/>
1491 <cpu id='5' socket_id='0' core_id='1' siblings='1,5'/>
1492 <cpu id='6' socket_id='0' core_id='2' siblings='2,6'/>
1493 <cpu id='7' socket_id='0' core_id='3' siblings='3,7'/>
1494 </cpus>
1495 </cell>
1496 </cells>
1497 </topology>
1498 <cache>
1499 <bank id='0' level='3' type='both' size='8' unit='MiB' cpus='0-7'/>
1500 </cache>
1501 <secmodel>
1502 <model>apparmor</model>
1503 <doi>0</doi>
1504 </secmodel>
1505 <secmodel>
1506 <model>dac</model>
1507 <doi>0</doi>
1508 <baselabel type='kvm'>+487:+486</baselabel>
1509 <baselabel type='qemu'>+487:+486</baselabel>
1510 </secmodel>
1511 </host>
1512
1513 <guest>
1514 <os_type>hvm</os_type>
1515 <arch name='i686'>
1516 <wordsize>32</wordsize>
1517 <emulator>/usr/bin/qemu-system-i386</emulator>
1518 <machine maxCpus='255'>pc-i440fx-2.6</machine>
1519 <machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
1520 <machine maxCpus='255'>pc-0.12</machine>
1521 <domain type='qemu'/>
1522 <domain type='kvm'>
1523 <emulator>/usr/bin/qemu-kvm</emulator>
1524 <machine maxCpus='255'>pc-i440fx-2.6</machine>
1525 <machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
1526 <machine maxCpus='255'>pc-0.12</machine>
1527 </domain>
1528 </arch>
1529 <features>
1530 <cpuselection/>
1531 <deviceboot/>
1532 <disksnapshot default='on' toggle='no'/>
1533 <acpi default='on' toggle='yes'/>
1534 <apic default='on' toggle='no'/>
1535 <pae/>
1536 <nonpae/>
1537 </features>
1538 </guest>
1539
1540 <guest>
1541 <os_type>hvm</os_type>
1542 <arch name='x86_64'>
1543 <wordsize>64</wordsize>
1544 <emulator>/usr/bin/qemu-system-x86_64</emulator>
1545 <machine maxCpus='255'>pc-i440fx-2.6</machine>
1546 <machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
1547 <machine maxCpus='255'>pc-0.12</machine>
1548 <domain type='qemu'/>
1549 <domain type='kvm'>
1550 <emulator>/usr/bin/qemu-kvm</emulator>
1551 <machine maxCpus='255'>pc-i440fx-2.6</machine>
1552 <machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
1553 <machine maxCpus='255'>pc-0.12</machine>
1554 </domain>
1555 </arch>
1556 <features>
1557 <cpuselection/>
1558 <deviceboot/>
1559 <disksnapshot default='on' toggle='no'/>
1560 <acpi default='on' toggle='yes'/>
1561 <apic default='on' toggle='no'/>
1562 </features>
1563 </guest>
1564
1565 </capabilities>
1566 """
1567 self.mock_conn.getCapabilities.return_value = xml # pylint: disable=no-member
1568
1569 root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images")
1570
1571 defineMock = MagicMock(return_value=1)
1572 self.mock_conn.defineXML = defineMock
1573 mock_chmod = MagicMock()
1574 mock_run = MagicMock()
1575 with patch.dict(
1576 os.__dict__, {"chmod": mock_chmod, "makedirs": MagicMock()}
1577 ): # pylint: disable=no-member
1578 with patch.dict(
1579 virt.__salt__, {"cmd.run": mock_run}
1580 ): # pylint: disable=no-member
1581
1582 # Ensure the init() function allows creating VM without NIC and disk
1583 virt.init(
1584 "test vm", 2, 1234, nic=None, disk=None, seed=False, start=False
1585 )
1586 definition = defineMock.call_args_list[0][0][0]
1587 self.assertFalse("<interface" in definition)
1588 self.assertFalse("<disk" in definition)
1589
1590 # Ensure the init() function allows creating VM without NIC and
1591 # disk but with boot parameters.
1592
1593 defineMock.reset_mock()
1594 mock_run.reset_mock()
1595 boot = {
1596 "kernel": "/root/f8-i386-vmlinuz",
1597 "initrd": "/root/f8-i386-initrd",
1598 "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/",
1599 }
1600 retval = virt.init(
1601 "test vm boot params",
1602 2,
1603 1234,
1604 nic=None,
1605 disk=None,
1606 seed=False,
1607 start=False,
1608 boot=boot,
1609 )
1610 definition = defineMock.call_args_list[0][0][0]
1611 self.assertEqual("<kernel" in definition, True)
1612 self.assertEqual("<initrd" in definition, True)
1613 self.assertEqual("<cmdline" in definition, True)
1614 self.assertEqual(retval, True)
1615
1616 # Verify that remote paths are downloaded and the xml has been
1617 # modified
1618 mock_response = MagicMock()
1619 mock_response.read = MagicMock(return_value="filecontent")
1620 cache_dir = tempfile.mkdtemp()
1621
1622 with patch.dict(virt.__dict__, {"CACHE_DIR": cache_dir}):
1623 with patch(
1624 "salt.ext.six.moves.urllib.request.urlopen",
1625 MagicMock(return_value=mock_response),
1626 ):
1627 with patch(
1628 "salt.utils.files.fopen", return_value=mock_response
1629 ):
1630
1631 defineMock.reset_mock()
1632 mock_run.reset_mock()
1633 boot = {
1634 "kernel": "https://www.example.com/download/vmlinuz",
1635 "initrd": "",
1636 "cmdline": "console=ttyS0 "
1637 "ks=http://example.com/f8-i386/os/",
1638 }
1639
1640 retval = virt.init(
1641 "test remote vm boot params",
1642 2,
1643 1234,
1644 nic=None,
1645 disk=None,
1646 seed=False,
1647 start=False,
1648 boot=boot,
1649 )
1650 definition = defineMock.call_args_list[0][0][0]
1651 self.assertEqual(cache_dir in definition, True)
1652
1653 shutil.rmtree(cache_dir)
1654
1655 # Test case creating disks
1656 defineMock.reset_mock()
1657 mock_run.reset_mock()
1658 pool_mock = MagicMock()
1659 pool_mock.XMLDesc.return_value = '<pool type="dir"/>'
1660 self.mock_conn.storagePoolLookupByName.return_value = pool_mock
1661 virt.init(
1662 "test vm",
1663 2,
1664 1234,
1665 nic=None,
1666 disk=None,
1667 disks=[
1668 {"name": "system", "size": 10240},
1669 {
1670 "name": "cddrive",
1671 "device": "cdrom",
1672 "source_file": None,
1673 "model": "ide",
1674 },
1675 ],
1676 seed=False,
1677 start=False,
1678 )
1679 definition = ET.fromstring(defineMock.call_args_list[0][0][0])
1680 expected_disk_path = os.path.join(root_dir, "test vm_system.qcow2")
1681 self.assertEqual(
1682 expected_disk_path,
1683 definition.find("./devices/disk[1]/source").get("file"),
1684 )
1685 self.assertIsNone(definition.find("./devices/disk[2]/source"))
1686 self.assertEqual(
1687 mock_run.call_args[0][0],
1688 'qemu-img create -f qcow2 "{}" 10240M'.format(expected_disk_path),
1689 )
1690 self.assertEqual(mock_chmod.call_args[0][0], expected_disk_path)
1691
1692 # Test case creating disks volumes
1693 defineMock.reset_mock()
1694 mock_run.reset_mock()
1695 vol_mock = MagicMock()
1696 pool_mock.storageVolLookupByName.return_value = vol_mock
1697 pool_mock.listVolumes.return_value = ["test vm_data"]
1698 stream_mock = MagicMock()
1699 self.mock_conn.newStream.return_value = stream_mock
1700 self.mock_conn.listStoragePools.return_value = ["default", "test"]
1701 with patch.dict(
1702 os.__dict__, {"open": MagicMock(), "close": MagicMock()}
1703 ):
1704 cache_mock = MagicMock()
1705 with patch.dict(virt.__salt__, {"cp.cache_file": cache_mock}):
1706 virt.init(
1707 "test vm",
1708 2,
1709 1234,
1710 nic=None,
1711 disk=None,
1712 disks=[
1713 {
1714 "name": "system",
1715 "size": 10240,
1716 "image": "/path/to/image",
1717 "pool": "test",
1718 },
1719 {"name": "data", "size": 10240, "pool": "default"},
1720 {
1721 "name": "test",
1722 "size": 1024,
1723 "pool": "default",
1724 "format": "qcow2",
1725 "backing_store_path": "/backing/path",
1726 "backing_store_format": "raw",
1727 },
1728 ],
1729 seed=False,
1730 start=False,
1731 )
1732 definition = ET.fromstring(defineMock.call_args_list[0][0][0])
1733 self.assertTrue(
1734 all(
1735 [
1736 disk.get("type") == "volume"
1737 for disk in definition.findall("./devices/disk")
1738 ]
1739 )
1740 )
1741 self.assertEqual(
1742 ["test", "default", "default"],
1743 [
1744 src.get("pool")
1745 for src in definition.findall("./devices/disk/source")
1746 ],
1747 )
1748 self.assertEqual(
1749 ["test vm_system", "test vm_data", "test vm_test"],
1750 [
1751 src.get("volume")
1752 for src in definition.findall("./devices/disk/source")
1753 ],
1754 )
1755
1756 create_calls = pool_mock.createXML.call_args_list
1757 vol_names = [
1758 ET.fromstring(call[0][0]).find("name").text
1759 for call in create_calls
1760 ]
1761 self.assertEqual(
1762 ["test vm_system", "test vm_test"], vol_names,
1763 )
1764
1765 stream_mock.sendAll.assert_called_once()
1766 stream_mock.finish.assert_called_once()
1767 vol_mock.upload.assert_called_once_with(stream_mock, 0, 0, 0)
1768
1769 def test_update(self):
1770 """
1771 Test virt.update()
1772 """
1773 root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images")
1774 xml = """
1775 <domain type='kvm' id='7'>
1776 <name>my_vm</name>
1777 <memory unit='KiB'>1048576</memory>
1778 <currentMemory unit='KiB'>1048576</currentMemory>
1779 <vcpu placement='auto'>1</vcpu>
1780 <os>
1781 <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
1782 <boot dev="hd"/>
1783 </os>
1784 <devices>
1785 <disk type='file' device='disk'>
1786 <driver name='qemu' type='qcow2'/>
1787 <source file='{}{}my_vm_system.qcow2'/>
1788 <backingStore/>
1789 <target dev='vda' bus='virtio'/>
1790 <alias name='virtio-disk0'/>
1791 <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
1792 </disk>
1793 <disk type='volume' device='disk'>
1794 <driver name='qemu' type='qcow2'/>
1795 <source pool='default' volume='my_vm_data'/>
1796 <backingStore/>
1797 <target dev='vdb' bus='virtio'/>
1798 <alias name='virtio-disk1'/>
1799 <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x1'/>
1800 </disk>
1801 <disk type="network" device="disk">
1802 <driver name='raw' type='qcow2'/>
1803 <source protocol='rbd' name='libvirt-pool/my_vm_data2'>
1804 <host name='ses2.tf.local'/>
1805 <host name='ses3.tf.local' port='1234'/>
1806 <auth username='libvirt'>
1807 <secret type='ceph' usage='pool_test-rbd'/>
1808 </auth>
1809 </source>
1810 <target dev='vdc' bus='virtio'/>
1811 <alias name='virtio-disk2'/>
1812 <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x2'/>
1813 </disk>
1814 <interface type='network'>
1815 <mac address='52:54:00:39:02:b1'/>
1816 <source network='default' bridge='virbr0'/>
1817 <target dev='vnet0'/>
1818 <model type='virtio'/>
1819 <alias name='net0'/>
1820 <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
1821 </interface>
1822 <interface type='network'>
1823 <mac address='52:54:00:39:02:b2'/>
1824 <source network='oldnet' bridge='virbr1'/>
1825 <target dev='vnet1'/>
1826 <model type='virtio'/>
1827 <alias name='net1'/>
1828 <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x1'/>
1829 </interface>
1830 <graphics type='spice' listen='127.0.0.1' autoport='yes'>
1831 <listen type='address' address='127.0.0.1'/>
1832 </graphics>
1833 <video>
1834 <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/>
1835 <alias name='video0'/>
1836 <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
1837 </video>
1838 </devices>
1839 </domain>
1840 """.format(
1841 root_dir, os.sep
1842 )
1843 domain_mock = self.set_mock_vm("my_vm", xml)
1844 domain_mock.OSType = MagicMock(return_value="hvm")
1845 define_mock = MagicMock(return_value=True)
1846 self.mock_conn.defineXML = define_mock
1847
1848 # No parameter passed case
1849 self.assertEqual(
1850 {
1851 "definition": False,
1852 "disk": {"attached": [], "detached": [], "updated": []},
1853 "interface": {"attached": [], "detached": []},
1854 },
1855 virt.update("my_vm"),
1856 )
1857
1858 # mem + cpu case
1859 define_mock.reset_mock()
1860 domain_mock.setMemoryFlags.return_value = 0
1861 domain_mock.setVcpusFlags.return_value = 0
1862 self.assertEqual(
1863 {
1864 "definition": True,
1865 "disk": {"attached": [], "detached": [], "updated": []},
1866 "interface": {"attached": [], "detached": []},
1867 "mem": True,
1868 "cpu": True,
1869 },
1870 virt.update("my_vm", mem=2048, cpu=2),
1871 )
1872 setxml = ET.fromstring(define_mock.call_args[0][0])
1873 self.assertEqual("2", setxml.find("vcpu").text)
1874 self.assertEqual("2147483648", setxml.find("memory").text)
1875 self.assertEqual(2048 * 1024, domain_mock.setMemoryFlags.call_args[0][0])
1876
1877 # Same parameters passed than in default virt.defined state case
1878 self.assertEqual(
1879 {
1880 "definition": False,
1881 "disk": {"attached": [], "detached": [], "updated": []},
1882 "interface": {"attached": [], "detached": []},
1883 },
1884 virt.update(
1885 "my_vm",
1886 cpu=None,
1887 mem=None,
1888 disk_profile=None,
1889 disks=None,
1890 nic_profile=None,
1891 interfaces=None,
1892 graphics=None,
1893 live=True,
1894 connection=None,
1895 username=None,
1896 password=None,
1897 boot=None,
1898 ),
1899 )
1900
1901 # Update vcpus case
1902 setvcpus_mock = MagicMock(return_value=0)
1903 domain_mock.setVcpusFlags = setvcpus_mock
1904 self.assertEqual(
1905 {
1906 "definition": True,
1907 "cpu": True,
1908 "disk": {"attached": [], "detached": [], "updated": []},
1909 "interface": {"attached": [], "detached": []},
1910 },
1911 virt.update("my_vm", cpu=2),
1912 )
1913 setxml = ET.fromstring(define_mock.call_args[0][0])
1914 self.assertEqual(setxml.find("vcpu").text, "2")
1915 self.assertEqual(setvcpus_mock.call_args[0][0], 2)
1916
1917 boot = {
1918 "kernel": "/root/f8-i386-vmlinuz",
1919 "initrd": "/root/f8-i386-initrd",
1920 "cmdline": "console=ttyS0 ks=http://example.com/f8-i386/os/",
1921 }
1922
1923 # Update boot devices case
1924 define_mock.reset_mock()
1925 self.assertEqual(
1926 {
1927 "definition": True,
1928 "disk": {"attached": [], "detached": [], "updated": []},
1929 "interface": {"attached": [], "detached": []},
1930 },
1931 virt.update("my_vm", boot_dev="cdrom network hd"),
1932 )
1933 setxml = ET.fromstring(define_mock.call_args[0][0])
1934 self.assertEqual(
1935 ["cdrom", "network", "hd"],
1936 [node.get("dev") for node in setxml.findall("os/boot")],
1937 )
1938
1939 # Update unchanged boot devices case
1940 define_mock.reset_mock()
1941 self.assertEqual(
1942 {
1943 "definition": False,
1944 "disk": {"attached": [], "detached": [], "updated": []},
1945 "interface": {"attached": [], "detached": []},
1946 },
1947 virt.update("my_vm", boot_dev="hd"),
1948 )
1949 define_mock.assert_not_called()
1950
1951 # Update with boot parameter case
1952 define_mock.reset_mock()
1953 self.assertEqual(
1954 {
1955 "definition": True,
1956 "disk": {"attached": [], "detached": [], "updated": []},
1957 "interface": {"attached": [], "detached": []},
1958 },
1959 virt.update("my_vm", boot=boot),
1960 )
1961 setxml = ET.fromstring(define_mock.call_args[0][0])
1962 self.assertEqual(setxml.find("os").find("kernel").text, "/root/f8-i386-vmlinuz")
1963 self.assertEqual(setxml.find("os").find("initrd").text, "/root/f8-i386-initrd")
1964 self.assertEqual(
1965 setxml.find("os").find("cmdline").text,
1966 "console=ttyS0 ks=http://example.com/f8-i386/os/",
1967 )
1968 setxml = ET.fromstring(define_mock.call_args[0][0])
1969 self.assertEqual(setxml.find("os").find("kernel").text, "/root/f8-i386-vmlinuz")
1970 self.assertEqual(setxml.find("os").find("initrd").text, "/root/f8-i386-initrd")
1971 self.assertEqual(
1972 setxml.find("os").find("cmdline").text,
1973 "console=ttyS0 ks=http://example.com/f8-i386/os/",
1974 )
1975
1976 boot_uefi = {
1977 "loader": "/usr/share/OVMF/OVMF_CODE.fd",
1978 "nvram": "/usr/share/OVMF/OVMF_VARS.ms.fd",
1979 }
1980
1981 self.assertEqual(
1982 {
1983 "definition": True,
1984 "disk": {"attached": [], "detached": [], "updated": []},
1985 "interface": {"attached": [], "detached": []},
1986 },
1987 virt.update("my_vm", boot=boot_uefi),
1988 )
1989 setxml = ET.fromstring(define_mock.call_args[0][0])
1990 self.assertEqual(
1991 setxml.find("os").find("loader").text, "/usr/share/OVMF/OVMF_CODE.fd"
1992 )
1993 self.assertEqual(setxml.find("os").find("loader").attrib.get("readonly"), "yes")
1994 self.assertEqual(setxml.find("os").find("loader").attrib["type"], "pflash")
1995 self.assertEqual(
1996 setxml.find("os").find("nvram").attrib["template"],
1997 "/usr/share/OVMF/OVMF_VARS.ms.fd",
1998 )
1999
2000 self.assertEqual(
2001 {
2002 "definition": True,
2003 "disk": {"attached": [], "detached": [], "updated": []},
2004 "interface": {"attached": [], "detached": []},
2005 },
2006 virt.update("my_vm", boot={"efi": True}),
2007 )
2008 setxml = ET.fromstring(define_mock.call_args[0][0])
2009 self.assertEqual(setxml.find("os").attrib.get("firmware"), "efi")
2010
2011 invalid_boot = {
2012 "loader": "/usr/share/OVMF/OVMF_CODE.fd",
2013 "initrd": "/root/f8-i386-initrd",
2014 }
2015
2016 with self.assertRaises(SaltInvocationError):
2017 virt.update("my_vm", boot=invalid_boot)
2018
2019 with self.assertRaises(SaltInvocationError):
2020 virt.update("my_vm", boot={"efi": "Not a boolean value"})
2021
2022 # Update memtune parameter case
2023 memtune = {
2024 "soft_limit": "0.5g",
2025 "hard_limit": "1024",
2026 "swap_hard_limit": "2048m",
2027 "min_guarantee": "1 g",
2028 }
2029
2030 self.assertEqual(
2031 {
2032 "definition": True,
2033 "disk": {"attached": [], "detached": [], "updated": []},
2034 "interface": {"attached": [], "detached": []},
2035 },
2036 virt.update("my_vm", mem=memtune),
2037 )
2038
2039 setxml = ET.fromstring(define_mock.call_args[0][0])
2040 self.assertEqual(
2041 setxml.find("memtune").find("soft_limit").text, str(int(0.5 * 1024 ** 3))
2042 )
2043 self.assertEqual(setxml.find("memtune").find("soft_limit").get("unit"), "bytes")
2044 self.assertEqual(
2045 setxml.find("memtune").find("hard_limit").text, str(1024 * 1024 ** 2)
2046 )
2047 self.assertEqual(
2048 setxml.find("memtune").find("swap_hard_limit").text, str(2048 * 1024 ** 2)
2049 )
2050 self.assertEqual(
2051 setxml.find("memtune").find("min_guarantee").text, str(1 * 1024 ** 3)
2052 )
2053
2054 invalid_unit = {"soft_limit": "2HB"}
2055
2056 with self.assertRaises(SaltInvocationError):
2057 virt.update("my_vm", mem=invalid_unit)
2058
2059 invalid_number = {
2060 "soft_limit": "3.4.MB",
2061 }
2062
2063 with self.assertRaises(SaltInvocationError):
2064 virt.update("my_vm", mem=invalid_number)
2065
2066 # Update memory case
2067 setmem_mock = MagicMock(return_value=0)
2068 domain_mock.setMemoryFlags = setmem_mock
2069
2070 self.assertEqual(
2071 {
2072 "definition": True,
2073 "mem": True,
2074 "disk": {"attached": [], "detached": [], "updated": []},
2075 "interface": {"attached": [], "detached": []},
2076 },
2077 virt.update("my_vm", mem=2048),
2078 )
2079 setxml = ET.fromstring(define_mock.call_args[0][0])
2080 self.assertEqual(setxml.find("memory").text, str(2048 * 1024 ** 2))
2081 self.assertEqual(setxml.find("memory").get("unit"), "bytes")
2082 self.assertEqual(setmem_mock.call_args[0][0], 2048 * 1024)
2083
2084 mem_dict = {"boot": "0.5g", "current": "2g", "max": "1g", "slots": 12}
2085 self.assertEqual(
2086 {
2087 "definition": True,
2088 "mem": True,
2089 "disk": {"attached": [], "detached": [], "updated": []},
2090 "interface": {"attached": [], "detached": []},
2091 },
2092 virt.update("my_vm", mem=mem_dict),
2093 )
2094
2095 setxml = ET.fromstring(define_mock.call_args[0][0])
2096 self.assertEqual(setxml.find("memory").get("unit"), "bytes")
2097 self.assertEqual(setxml.find("memory").text, str(int(0.5 * 1024 ** 3)))
2098 self.assertEqual(setxml.find("maxMemory").text, str(1 * 1024 ** 3))
2099 self.assertEqual(setxml.find("currentMemory").text, str(2 * 1024 ** 3))
2100
2101 max_slot_reverse = {
2102 "slots": "10",
2103 "max": "3096m",
2104 }
2105 self.assertEqual(
2106 {
2107 "definition": True,
2108 "disk": {"attached": [], "detached": [], "updated": []},
2109 "interface": {"attached": [], "detached": []},
2110 },
2111 virt.update("my_vm", mem=max_slot_reverse),
2112 )
2113 setxml = ET.fromstring(define_mock.call_args[0][0])
2114 self.assertEqual(setxml.find("maxMemory").text, str(3096 * 1024 ** 2))
2115 self.assertEqual(setxml.find("maxMemory").attrib.get("slots"), "10")
2116
2117 # Update disks case
2118 devattach_mock = MagicMock(return_value=0)
2119 devdetach_mock = MagicMock(return_value=0)
2120 domain_mock.attachDevice = devattach_mock
2121 domain_mock.detachDevice = devdetach_mock
2122 mock_chmod = MagicMock()
2123 mock_run = MagicMock()
2124 with patch.dict(
2125 os.__dict__, {"chmod": mock_chmod, "makedirs": MagicMock()}
2126 ): # pylint: disable=no-member
2127 with patch.dict(
2128 virt.__salt__, {"cmd.run": mock_run}
2129 ): # pylint: disable=no-member
2130 ret = virt.update(
2131 "my_vm",
2132 disk_profile="default",
2133 disks=[
2134 {
2135 "name": "cddrive",
2136 "device": "cdrom",
2137 "source_file": None,
2138 "model": "ide",
2139 },
2140 {"name": "added", "size": 2048},
2141 ],
2142 )
2143 added_disk_path = os.path.join(
2144 virt.__salt__["config.get"]("virt:images"), "my_vm_added.qcow2"
2145 ) # pylint: disable=no-member
2146 self.assertEqual(
2147 mock_run.call_args[0][0],
2148 'qemu-img create -f qcow2 "{}" 2048M'.format(added_disk_path),
2149 )
2150 self.assertEqual(mock_chmod.call_args[0][0], added_disk_path)
2151 self.assertListEqual(
2152 [None, os.path.join(root_dir, "my_vm_added.qcow2")],
2153 [
2154 ET.fromstring(disk).find("source").get("file")
2155 if str(disk).find("<source") > -1
2156 else None
2157 for disk in ret["disk"]["attached"]
2158 ],
2159 )
2160
2161 self.assertListEqual(
2162 ["my_vm_data", "libvirt-pool/my_vm_data2"],
2163 [
2164 ET.fromstring(disk).find("source").get("volume")
2165 or ET.fromstring(disk).find("source").get("name")
2166 for disk in ret["disk"]["detached"]
2167 ],
2168 )
2169 self.assertEqual(devattach_mock.call_count, 2)
2170 self.assertEqual(devdetach_mock.call_count, 2)
2171
2172 # Update nics case
2173 yaml_config = """
2174 virt:
2175 nic:
2176 myprofile:
2177 - network: default
2178 name: eth0
2179 """
2180 mock_config = salt.utils.yaml.safe_load(yaml_config)
2181 devattach_mock.reset_mock()
2182 devdetach_mock.reset_mock()
2183 with patch.dict(
2184 salt.modules.config.__opts__, mock_config # pylint: disable=no-member
2185 ):
2186 ret = virt.update(
2187 "my_vm",
2188 nic_profile="myprofile",
2189 interfaces=[
2190 {
2191 "name": "eth0",
2192 "type": "network",
2193 "source": "default",
2194 "mac": "52:54:00:39:02:b1",
2195 },
2196 {"name": "eth1", "type": "network", "source": "newnet"},
2197 ],
2198 )
2199 self.assertEqual(
2200 ["newnet"],
2201 [
2202 ET.fromstring(nic).find("source").get("network")
2203 for nic in ret["interface"]["attached"]
2204 ],
2205 )
2206 self.assertEqual(
2207 ["oldnet"],
2208 [
2209 ET.fromstring(nic).find("source").get("network")
2210 for nic in ret["interface"]["detached"]
2211 ],
2212 )
2213 devattach_mock.assert_called_once()
2214 devdetach_mock.assert_called_once()
2215
2216 # Remove nics case
2217 devattach_mock.reset_mock()
2218 devdetach_mock.reset_mock()
2219 ret = virt.update("my_vm", nic_profile=None, interfaces=[])
2220 self.assertEqual([], ret["interface"]["attached"])
2221 self.assertEqual(2, len(ret["interface"]["detached"]))
2222 devattach_mock.assert_not_called()
2223 devdetach_mock.assert_called()
2224
2225 # Remove disks case (yeah, it surely is silly)
2226 devattach_mock.reset_mock()
2227 devdetach_mock.reset_mock()
2228 ret = virt.update("my_vm", disk_profile=None, disks=[])
2229 self.assertEqual([], ret["disk"]["attached"])
2230 self.assertEqual(3, len(ret["disk"]["detached"]))
2231 devattach_mock.assert_not_called()
2232 devdetach_mock.assert_called()
2233
2234 # Graphics change test case
2235 self.assertEqual(
2236 {
2237 "definition": True,
2238 "disk": {"attached": [], "detached": [], "updated": []},
2239 "interface": {"attached": [], "detached": []},
2240 },
2241 virt.update("my_vm", graphics={"type": "vnc"}),
2242 )
2243 setxml = ET.fromstring(define_mock.call_args[0][0])
2244 self.assertEqual("vnc", setxml.find("devices/graphics").get("type"))
2245
2246 # Update with no diff case
2247 pool_mock = MagicMock()
2248 default_pool_desc = "<pool type='dir'></pool>"
2249 rbd_pool_desc = """
2250 <pool type='rbd'>
2251 <name>test-rbd</name>
2252 <source>
2253 <host name='ses2.tf.local'/>
2254 <host name='ses3.tf.local' port='1234'/>
2255 <name>libvirt-pool</name>
2256 <auth type='ceph' username='libvirt'>
2257 <secret usage='pool_test-rbd'/>
2258 </auth>
2259 </source>
2260 </pool>
2261 """
2262 pool_mock.XMLDesc.side_effect = [
2263 default_pool_desc,
2264 rbd_pool_desc,
2265 default_pool_desc,
2266 rbd_pool_desc,
2267 ]
2268 self.mock_conn.storagePoolLookupByName.return_value = pool_mock
2269 self.mock_conn.listStoragePools.return_value = ["test-rbd", "default"]
2270 self.assertEqual(
2271 {
2272 "definition": False,
2273 "disk": {"attached": [], "detached": [], "updated": []},
2274 "interface": {"attached": [], "detached": []},
2275 },
2276 virt.update(
2277 "my_vm",
2278 cpu=1,
2279 mem=1024,
2280 disk_profile="default",
2281 disks=[
2282 {"name": "data", "size": 2048, "pool": "default"},
2283 {
2284 "name": "data2",
2285 "size": 4096,
2286 "pool": "test-rbd",
2287 "format": "raw",
2288 },
2289 ],
2290 nic_profile="myprofile",
2291 interfaces=[
2292 {
2293 "name": "eth0",
2294 "type": "network",
2295 "source": "default",
2296 "mac": "52:54:00:39:02:b1",
2297 },
2298 {"name": "eth1", "type": "network", "source": "oldnet"},
2299 ],
2300 graphics={
2301 "type": "spice",
2302 "listen": {"type": "address", "address": "127.0.0.1"},
2303 },
2304 ),
2305 )
2306
2307 # Failed XML description update case
2308 self.mock_conn.defineXML.side_effect = self.mock_libvirt.libvirtError(
2309 "Test error"
2310 )
2311 setmem_mock.reset_mock()
2312 with self.assertRaises(self.mock_libvirt.libvirtError):
2313 virt.update("my_vm", mem=2048)
2314
2315 # Failed single update failure case
2316 self.mock_conn.defineXML = MagicMock(return_value=True)
2317 setmem_mock.side_effect = self.mock_libvirt.libvirtError(
2318 "Failed to live change memory"
2319 )
2320 self.assertEqual(
2321 {
2322 "definition": True,
2323 "errors": ["Failed to live change memory"],
2324 "disk": {"attached": [], "detached": [], "updated": []},
2325 "interface": {"attached": [], "detached": []},
2326 },
2327 virt.update("my_vm", mem=2048),
2328 )
2329
2330 # Failed multiple updates failure case
2331 self.assertEqual(
2332 {
2333 "definition": True,
2334 "errors": ["Failed to live change memory"],
2335 "cpu": True,
2336 "disk": {"attached": [], "detached": [], "updated": []},
2337 "interface": {"attached": [], "detached": []},
2338 },
2339 virt.update("my_vm", cpu=4, mem=2048),
2340 )
2341
2342 def test_update_backing_store(self):
2343 """
2344 Test updating a disk with a backing store
2345 """
2346 xml = """
2347 <domain type='kvm' id='7'>
2348 <name>my_vm</name>
2349 <memory unit='KiB'>1048576</memory>
2350 <currentMemory unit='KiB'>1048576</currentMemory>
2351 <vcpu placement='auto'>1</vcpu>
2352 <os>
2353 <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
2354 </os>
2355 <devices>
2356 <disk type='volume' device='disk'>
2357 <driver name='qemu' type='qcow2' cache='none' io='native'/>
2358 <source pool='default' volume='my_vm_system' index='1'/>
2359 <backingStore type='file' index='2'>
2360 <format type='qcow2'/>
2361 <source file='/path/to/base.qcow2'/>
2362 <backingStore/>
2363 </backingStore>
2364 <target dev='vda' bus='virtio'/>
2365 <alias name='virtio-disk0'/>
2366 <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
2367 </disk>
2368 </devices>
2369 </domain>
2370 """
2371 domain_mock = self.set_mock_vm("my_vm", xml)
2372 domain_mock.OSType.return_value = "hvm"
2373 self.mock_conn.defineXML.return_value = True
2374 updatedev_mock = MagicMock(return_value=0)
2375 domain_mock.updateDeviceFlags = updatedev_mock
2376 self.mock_conn.listStoragePools.return_value = ["default"]
2377 self.mock_conn.storagePoolLookupByName.return_value.XMLDesc.return_value = (
2378 "<pool type='dir'/>"
2379 )
2380
2381 ret = virt.update(
2382 "my_vm",
2383 disks=[
2384 {
2385 "name": "system",
2386 "pool": "default",
2387 "backing_store_path": "/path/to/base.qcow2",
2388 "backing_store_format": "qcow2",
2389 },
2390 ],
2391 )
2392 self.assertFalse(ret["definition"])
2393 self.assertFalse(ret["disk"]["attached"])
2394 self.assertFalse(ret["disk"]["detached"])
2395
2396 def test_update_removables(self):
2397 """
2398 Test attaching, detaching, changing removable devices
2399 """
2400 xml = """
2401 <domain type='kvm' id='7'>
2402 <name>my_vm</name>
2403 <memory unit='KiB'>1048576</memory>
2404 <currentMemory unit='KiB'>1048576</currentMemory>
2405 <vcpu placement='auto'>1</vcpu>
2406 <os>
2407 <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
2408 </os>
2409 <devices>
2410 <disk type='network' device='cdrom'>
2411 <driver name='qemu' type='raw' cache='none' io='native'/>
2412 <source protocol='https' name='/dvd-image-1.iso'>
2413 <host name='test-srv.local' port='80'/>
2414 </source>
2415 <backingStore/>
2416 <target dev='hda' bus='ide'/>
2417 <readonly/>
2418 <alias name='ide0-0-0'/>
2419 <address type='drive' controller='0' bus='0' target='0' unit='0'/>
2420 </disk>
2421 <disk type='file' device='cdrom'>
2422 <driver name='qemu' type='raw' cache='none' io='native'/>
2423 <target dev='hdb' bus='ide'/>
2424 <readonly/>
2425 <alias name='ide0-0-1'/>
2426 <address type='drive' controller='0' bus='0' target='0' unit='1'/>
2427 </disk>
2428 <disk type='file' device='cdrom'>
2429 <driver name='qemu' type='raw' cache='none' io='native'/>
2430 <source file='/srv/dvd-image-2.iso'/>
2431 <backingStore/>
2432 <target dev='hdc' bus='ide'/>
2433 <readonly/>
2434 <alias name='ide0-0-2'/>
2435 <address type='drive' controller='0' bus='0' target='0' unit='2'/>
2436 </disk>
2437 <disk type='file' device='cdrom'>
2438 <driver name='qemu' type='raw' cache='none' io='native'/>
2439 <source file='/srv/dvd-image-3.iso'/>
2440 <backingStore/>
2441 <target dev='hdd' bus='ide'/>
2442 <readonly/>
2443 <alias name='ide0-0-3'/>
2444 <address type='drive' controller='0' bus='0' target='0' unit='3'/>
2445 </disk>
2446 <disk type='network' device='cdrom'>
2447 <driver name='qemu' type='raw' cache='none' io='native'/>
2448 <source protocol='https' name='/dvd-image-6.iso'>
2449 <host name='test-srv.local' port='80'/>
2450 </source>
2451 <backingStore/>
2452 <target dev='hde' bus='ide'/>
2453 <readonly/>
2454 </disk>
2455 </devices>
2456 </domain>
2457 """
2458 domain_mock = self.set_mock_vm("my_vm", xml)
2459 domain_mock.OSType.return_value = "hvm"
2460 self.mock_conn.defineXML.return_value = True
2461 updatedev_mock = MagicMock(return_value=0)
2462 domain_mock.updateDeviceFlags = updatedev_mock
2463
2464 ret = virt.update(
2465 "my_vm",
2466 disks=[
2467 {
2468 "name": "dvd1",
2469 "device": "cdrom",
2470 "source_file": None,
2471 "model": "ide",
2472 },
2473 {
2474 "name": "dvd2",
2475 "device": "cdrom",
2476 "source_file": "/srv/dvd-image-4.iso",
2477 "model": "ide",
2478 },
2479 {
2480 "name": "dvd3",
2481 "device": "cdrom",
2482 "source_file": "/srv/dvd-image-2.iso",
2483 "model": "ide",
2484 },
2485 {
2486 "name": "dvd4",
2487 "device": "cdrom",
2488 "source_file": "/srv/dvd-image-5.iso",
2489 "model": "ide",
2490 },
2491 {
2492 "name": "dvd5",
2493 "device": "cdrom",
2494 "source_file": "/srv/dvd-image-6.iso",
2495 "model": "ide",
2496 },
2497 ],
2498 )
2499
2500 self.assertTrue(ret["definition"])
2501 self.assertFalse(ret["disk"]["attached"])
2502 self.assertFalse(ret["disk"]["detached"])
2503 self.assertEqual(
2504 [
2505 {
2506 "type": "file",
2507 "device": "cdrom",
2508 "driver": {
2509 "name": "qemu",
2510 "type": "raw",
2511 "cache": "none",
2512 "io": "native",
2513 },
2514 "backingStore": None,
2515 "target": {"dev": "hda", "bus": "ide"},
2516 "readonly": None,
2517 "alias": {"name": "ide0-0-0"},
2518 "address": {
2519 "type": "drive",
2520 "controller": "0",
2521 "bus": "0",
2522 "target": "0",
2523 "unit": "0",
2524 },
2525 },
2526 {
2527 "type": "file",
2528 "device": "cdrom",
2529 "driver": {
2530 "name": "qemu",
2531 "type": "raw",
2532 "cache": "none",
2533 "io": "native",
2534 },
2535 "target": {"dev": "hdb", "bus": "ide"},
2536 "readonly": None,
2537 "alias": {"name": "ide0-0-1"},
2538 "address": {
2539 "type": "drive",
2540 "controller": "0",
2541 "bus": "0",
2542 "target": "0",
2543 "unit": "1",
2544 },
2545 "source": {"file": "/srv/dvd-image-4.iso"},
2546 },
2547 {
2548 "type": "file",
2549 "device": "cdrom",
2550 "driver": {
2551 "name": "qemu",
2552 "type": "raw",
2553 "cache": "none",
2554 "io": "native",
2555 },
2556 "backingStore": None,
2557 "target": {"dev": "hdd", "bus": "ide"},
2558 "readonly": None,
2559 "alias": {"name": "ide0-0-3"},
2560 "address": {
2561 "type": "drive",
2562 "controller": "0",
2563 "bus": "0",
2564 "target": "0",
2565 "unit": "3",
2566 },
2567 "source": {"file": "/srv/dvd-image-5.iso"},
2568 },
2569 {
2570 "type": "file",
2571 "device": "cdrom",
2572 "driver": {
2573 "name": "qemu",
2574 "type": "raw",
2575 "cache": "none",
2576 "io": "native",
2577 },
2578 "backingStore": None,
2579 "target": {"dev": "hde", "bus": "ide"},
2580 "readonly": None,
2581 "source": {"file": "/srv/dvd-image-6.iso"},
2582 },
2583 ],
2584 [
2585 salt.utils.xmlutil.to_dict(ET.fromstring(disk), True)
2586 for disk in ret["disk"]["updated"]
2587 ],
2588 )
2589
2590 def test_update_xen_boot_params(self):
2591 """
2592 Test virt.update() a Xen definition no boot parameter.
2593 """
2594 root_dir = os.path.join(salt.syspaths.ROOT_DIR, "srv", "salt-images")
2595 xml_boot = """
2596 <domain type='xen' id='8'>
2597 <name>vm</name>
2598 <memory unit='KiB'>1048576</memory>
2599 <currentMemory unit='KiB'>1048576</currentMemory>
2600 <vcpu placement='auto'>1</vcpu>
2601 <os>
2602 <type arch='x86_64' machine='xenfv'>hvm</type>
2603 <loader type='rom'>/usr/lib/xen/boot/hvmloader</loader>
2604 </os>
2605 </domain>
2606 """
2607 domain_mock_boot = self.set_mock_vm("vm", xml_boot)
2608 domain_mock_boot.OSType = MagicMock(return_value="hvm")
2609 define_mock_boot = MagicMock(return_value=True)
2610 define_mock_boot.setVcpusFlags = MagicMock(return_value=0)
2611 self.mock_conn.defineXML = define_mock_boot
2612 self.assertEqual(
2613 {
2614 "cpu": False,
2615 "definition": True,
2616 "disk": {"attached": [], "detached": [], "updated": []},
2617 "interface": {"attached": [], "detached": []},
2618 },
2619 virt.update("vm", cpu=2),
2620 )
2621 setxml = ET.fromstring(define_mock_boot.call_args[0][0])
2622 self.assertEqual(setxml.find("os").find("loader").attrib.get("type"), "rom")
2623 self.assertEqual(
2624 setxml.find("os").find("loader").text, "/usr/lib/xen/boot/hvmloader"
2625 )
2626
2627 def test_update_existing_boot_params(self):
2628 """
2629 Test virt.update() with existing boot parameters.
2630 """
2631 xml_boot = """
2632 <domain type='kvm' id='8'>
2633 <name>vm_with_boot_param</name>
2634 <memory unit='KiB'>1048576</memory>
2635 <currentMemory unit='KiB'>1048576</currentMemory>
2636 <vcpu placement='auto'>1</vcpu>
2637 <os>
2638 <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
2639 <kernel>/boot/oldkernel</kernel>
2640 <initrd>/boot/initrdold.img</initrd>
2641 <cmdline>console=ttyS0 ks=http://example.com/old/os/</cmdline>
2642 <loader>/usr/share/old/OVMF_CODE.fd</loader>
2643 <nvram>/usr/share/old/OVMF_VARS.ms.fd</nvram>
2644 </os>
2645 <devices>
2646 <disk type='file' device='disk'>
2647 <driver name='qemu' type='qcow2'/>
2648 <source file='{0}{1}vm_with_boot_param_system.qcow2'/>
2649 <backingStore/>
2650 <target dev='vda' bus='virtio'/>
2651 <alias name='virtio-disk0'/>
2652 <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
2653 </disk>
2654 <disk type='file' device='disk'>
2655 <driver name='qemu' type='qcow2'/>
2656 <source file='{0}{1}vm_with_boot_param_data.qcow2'/>
2657 <backingStore/>
2658 <target dev='vdb' bus='virtio'/>
2659 <alias name='virtio-disk1'/>
2660 <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x1'/>
2661 </disk>
2662 <interface type='network'>
2663 <mac address='52:54:00:39:02:b1'/>
2664 <source network='default' bridge='virbr0'/>
2665 <target dev='vnet0'/>
2666 <model type='virtio'/>
2667 <alias name='net0'/>
2668 <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
2669 </interface>
2670 <interface type='network'>
2671 <mac address='52:54:00:39:02:b2'/>
2672 <source network='oldnet' bridge='virbr1'/>
2673 <target dev='vnet1'/>
2674 <model type='virtio'/>
2675 <alias name='net1'/>
2676 <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x1'/>
2677 </interface>
2678 <graphics type='spice' port='5900' autoport='yes' listen='127.0.0.1'>
2679 <listen type='address' address='127.0.0.1'/>
2680 </graphics>
2681 <video>
2682 <model type='qxl' ram='65536' vram='65536' vgamem='16384' heads='1' primary='yes'/>
2683 <alias name='video0'/>
2684 <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
2685 </video>
2686 </devices>
2687 </domain>
2688 """
2689 domain_mock_boot = self.set_mock_vm("vm_with_boot_param", xml_boot)
2690 domain_mock_boot.OSType = MagicMock(return_value="hvm")
2691 define_mock_boot = MagicMock(return_value=True)
2692 self.mock_conn.defineXML = define_mock_boot
2693 boot_new = {
2694 "kernel": "/root/new-vmlinuz",
2695 "initrd": "/root/new-initrd",
2696 "cmdline": "console=ttyS0 ks=http://example.com/new/os/",
2697 }
2698
2699 uefi_boot_new = {
2700 "loader": "/usr/share/new/OVMF_CODE.fd",
2701 "nvram": "/usr/share/new/OVMF_VARS.ms.fd",
2702 }
2703
2704 self.assertEqual(
2705 {
2706 "definition": True,
2707 "disk": {"attached": [], "detached": [], "updated": []},
2708 "interface": {"attached": [], "detached": []},
2709 },
2710 virt.update("vm_with_boot_param", boot=boot_new),
2711 )
2712 setxml_boot = ET.fromstring(define_mock_boot.call_args[0][0])
2713 self.assertEqual(
2714 setxml_boot.find("os").find("kernel").text, "/root/new-vmlinuz"
2715 )
2716 self.assertEqual(setxml_boot.find("os").find("initrd").text, "/root/new-initrd")
2717 self.assertEqual(
2718 setxml_boot.find("os").find("cmdline").text,
2719 "console=ttyS0 ks=http://example.com/new/os/",
2720 )
2721
2722 self.assertEqual(
2723 {
2724 "definition": True,
2725 "disk": {"attached": [], "detached": [], "updated": []},
2726 "interface": {"attached": [], "detached": []},
2727 },
2728 virt.update("vm_with_boot_param", boot=uefi_boot_new),
2729 )
2730
2731 setxml = ET.fromstring(define_mock_boot.call_args[0][0])
2732 self.assertEqual(
2733 setxml.find("os").find("loader").text, "/usr/share/new/OVMF_CODE.fd"
2734 )
2735 self.assertEqual(setxml.find("os").find("loader").attrib.get("readonly"), "yes")
2736 self.assertEqual(setxml.find("os").find("loader").attrib["type"], "pflash")
2737 self.assertEqual(
2738 setxml.find("os").find("nvram").attrib["template"],
2739 "/usr/share/new/OVMF_VARS.ms.fd",
2740 )
2741
2742 kernel_none = {
2743 "kernel": None,
2744 "initrd": None,
2745 "cmdline": None,
2746 }
2747
2748 uefi_none = {"loader": None, "nvram": None}
2749
2750 self.assertEqual(
2751 {
2752 "definition": True,
2753 "disk": {"attached": [], "detached": [], "updated": []},
2754 "interface": {"attached": [], "detached": []},
2755 },
2756 virt.update("vm_with_boot_param", boot=kernel_none),
2757 )
2758
2759 setxml = ET.fromstring(define_mock_boot.call_args[0][0])
2760 self.assertEqual(setxml.find("os").find("kernel"), None)
2761 self.assertEqual(setxml.find("os").find("initrd"), None)
2762 self.assertEqual(setxml.find("os").find("cmdline"), None)
2763
2764 self.assertEqual(
2765 {
2766 "definition": True,
2767 "disk": {"attached": [], "detached": [], "updated": []},
2768 "interface": {"attached": [], "detached": []},
2769 },
2770 virt.update("vm_with_boot_param", boot={"efi": False}),
2771 )
2772 setxml = ET.fromstring(define_mock_boot.call_args[0][0])
2773 self.assertEqual(setxml.find("os").find("nvram"), None)
2774 self.assertEqual(setxml.find("os").find("loader"), None)
2775
2776 self.assertEqual(
2777 {
2778 "definition": True,
2779 "disk": {"attached": [], "detached": [], "updated": []},
2780 "interface": {"attached": [], "detached": []},
2781 },
2782 virt.update("vm_with_boot_param", boot=uefi_none),
2783 )
2784
2785 setxml = ET.fromstring(define_mock_boot.call_args[0][0])
2786 self.assertEqual(setxml.find("os").find("loader"), None)
2787 self.assertEqual(setxml.find("os").find("nvram"), None)
2788
2789 def test_update_memtune_params(self):
2790 """
2791 Test virt.update() with memory tuning parameters.
2792 """
2793 xml_with_memtune_params = """
2794 <domain type='kvm' id='8'>
2795 <name>vm_with_boot_param</name>
2796 <memory unit='KiB'>1048576</memory>
2797 <currentMemory unit='KiB'>1048576</currentMemory>
2798 <maxMemory slots="12" unit="bytes">1048576</maxMemory>
2799 <vcpu placement='auto'>1</vcpu>
2800 <memtune>
2801 <hard_limit unit="KiB">1048576</hard_limit>
2802 <soft_limit unit="KiB">2097152</soft_limit>
2803 <swap_hard_limit unit="KiB">2621440</swap_hard_limit>
2804 <min_guarantee unit='KiB'>671088</min_guarantee>
2805 </memtune>
2806 <os>
2807 <type arch='x86_64' machine='pc-i440fx-2.6'>hvm</type>
2808 </os>
2809 </domain>
2810 """
2811 domain_mock = self.set_mock_vm("vm_with_memtune_param", xml_with_memtune_params)
2812 domain_mock.OSType = MagicMock(return_value="hvm")
2813 define_mock = MagicMock(return_value=True)
2814 self.mock_conn.defineXML = define_mock
2815
2816 memtune_new_val = {
2817 "boot": "0.7g",
2818 "current": "2.5g",
2819 "max": "3096m",
2820 "slots": "10",
2821 "soft_limit": "2048m",
2822 "hard_limit": "1024",
2823 "swap_hard_limit": "2.5g",
2824 "min_guarantee": "1 g",
2825 }
2826
2827 domain_mock.setMemoryFlags.return_value = 0
2828 self.assertEqual(
2829 {
2830 "definition": True,
2831 "disk": {"attached": [], "detached": [], "updated": []},
2832 "interface": {"attached": [], "detached": []},
2833 "mem": True,
2834 },
2835 virt.update("vm_with_memtune_param", mem=memtune_new_val),
2836 )
2837 self.assertEqual(
2838 domain_mock.setMemoryFlags.call_args[0][0], int(2.5 * 1024 ** 2)
2839 )
2840
2841 setxml = ET.fromstring(define_mock.call_args[0][0])
2842 self.assertEqual(
2843 setxml.find("memtune").find("soft_limit").text, str(2048 * 1024)
2844 )
2845 self.assertEqual(
2846 setxml.find("memtune").find("hard_limit").text, str(1024 * 1024)
2847 )
2848 self.assertEqual(
2849 setxml.find("memtune").find("swap_hard_limit").text,
2850 str(int(2.5 * 1024 ** 2)),
2851 )
2852 self.assertEqual(
2853 setxml.find("memtune").find("swap_hard_limit").get("unit"), "KiB",
2854 )
2855 self.assertEqual(
2856 setxml.find("memtune").find("min_guarantee").text, str(1 * 1024 ** 3)
2857 )
2858 self.assertEqual(
2859 setxml.find("memtune").find("min_guarantee").attrib.get("unit"), "bytes"
2860 )
2861 self.assertEqual(setxml.find("maxMemory").text, str(3096 * 1024 ** 2))
2862 self.assertEqual(setxml.find("maxMemory").attrib.get("slots"), "10")
2863 self.assertEqual(setxml.find("currentMemory").text, str(int(2.5 * 1024 ** 3)))
2864 self.assertEqual(setxml.find("memory").text, str(int(0.7 * 1024 ** 3)))
2865
2866 max_slot_reverse = {
2867 "slots": "10",
2868 "max": "3096m",
2869 }
2870 self.assertEqual(
2871 {
2872 "definition": True,
2873 "disk": {"attached": [], "detached": [], "updated": []},
2874 "interface": {"attached": [], "detached": []},
2875 },
2876 virt.update("vm_with_memtune_param", mem=max_slot_reverse),
2877 )
2878 setxml = ET.fromstring(define_mock.call_args[0][0])
2879 self.assertEqual(setxml.find("maxMemory").text, str(3096 * 1024 ** 2))
2880 self.assertEqual(setxml.find("maxMemory").get("unit"), "bytes")
2881 self.assertEqual(setxml.find("maxMemory").attrib.get("slots"), "10")
2882
2883 max_swap_none = {
2884 "boot": "0.7g",
2885 "current": "2.5g",
2886 "max": None,
2887 "slots": "10",
2888 "soft_limit": "2048m",
2889 "hard_limit": "1024",
2890 "swap_hard_limit": None,
2891 "min_guarantee": "1 g",
2892 }
2893
2894 domain_mock.setMemoryFlags.reset_mock()
2895 self.assertEqual(
2896 {
2897 "definition": True,
2898 "disk": {"attached": [], "detached": [], "updated": []},
2899 "interface": {"attached": [], "detached": []},
2900 "mem": True,
2901 },
2902 virt.update("vm_with_memtune_param", mem=max_swap_none),
2903 )
2904 self.assertEqual(
2905 domain_mock.setMemoryFlags.call_args[0][0], int(2.5 * 1024 ** 2)
2906 )
2907
2908 setxml = ET.fromstring(define_mock.call_args[0][0])
2909 self.assertEqual(
2910 setxml.find("memtune").find("soft_limit").text, str(2048 * 1024)
2911 )
2912 self.assertEqual(
2913 setxml.find("memtune").find("hard_limit").text, str(1024 * 1024)
2914 )
2915 self.assertEqual(setxml.find("memtune").find("swap_hard_limit"), None)
2916 self.assertEqual(
2917 setxml.find("memtune").find("min_guarantee").text, str(1 * 1024 ** 3)
2918 )
2919 self.assertEqual(
2920 setxml.find("memtune").find("min_guarantee").attrib.get("unit"), "bytes"
2921 )
2922 self.assertEqual(setxml.find("maxMemory").text, None)
2923 self.assertEqual(setxml.find("currentMemory").text, str(int(2.5 * 1024 ** 3)))
2924 self.assertEqual(setxml.find("memory").text, str(int(0.7 * 1024 ** 3)))
2925
2926 memtune_none = {
2927 "soft_limit": None,
2928 "hard_limit": None,
2929 "swap_hard_limit": None,
2930 "min_guarantee": None,
2931 }
2932
2933 self.assertEqual(
2934 {
2935 "definition": True,
2936 "disk": {"attached": [], "detached": [], "updated": []},
2937 "interface": {"attached": [], "detached": []},
2938 },
2939 virt.update("vm_with_memtune_param", mem=memtune_none),
2940 )
2941
2942 setxml = ET.fromstring(define_mock.call_args[0][0])
2943 self.assertEqual(setxml.find("memtune").find("soft_limit"), None)
2944 self.assertEqual(setxml.find("memtune").find("hard_limit"), None)
2945 self.assertEqual(setxml.find("memtune").find("swap_hard_limit"), None)
2946 self.assertEqual(setxml.find("memtune").find("min_guarantee"), None)
2947
2948 max_none = {
2949 "max": None,
2950 }
2951
2952 self.assertEqual(
2953 {
2954 "definition": True,
2955 "disk": {"attached": [], "detached": [], "updated": []},
2956 "interface": {"attached": [], "detached": []},
2957 },
2958 virt.update("vm_with_memtune_param", mem=max_none),
2959 )
2960
2961 setxml = ET.fromstring(define_mock.call_args[0][0])
2962 self.assertEqual(setxml.find("maxMemory"), None)
2963 self.assertEqual(setxml.find("currentMemory").text, str(int(1 * 1024 ** 2)))
2964 self.assertEqual(setxml.find("memory").text, str(int(1 * 1024 ** 2)))
2965
2966 def test_handle_unit(self):
2967 """
2968 Test regex function for handling units
2969 """
2970 valid_case = [
2971 ("2", 2097152),
2972 ("42", 44040192),
2973 ("5b", 5),
2974 ("2.3Kib", 2355),
2975 ("5.8Kb", 5800),
2976 ("16MiB", 16777216),
2977 ("20 GB", 20000000000),
2978 ("16KB", 16000),
2979 (".5k", 512),
2980 ("2.k", 2048),
2981 ]
2982
2983 for key, val in valid_case:
2984 self.assertEqual(virt._handle_unit(key), val)
2985
2986 invalid_case = [
2987 ("9ib", "invalid units"),
2988 ("8byte", "invalid units"),
2989 ("512bytes", "invalid units"),
2990 ("4 Kbytes", "invalid units"),
2991 ("3.4.MB", "invalid number"),
2992 ("", "invalid number"),
2993 ("bytes", "invalid number"),
2994 ("2HB", "invalid units"),
2995 ]
2996
2997 for key, val in invalid_case:
2998 with self.assertRaises(SaltInvocationError):
2999 virt._handle_unit(key)
3000
3001 def test_mixed_dict_and_list_as_profile_objects(self):
3002 """
3003 Test virt._nic_profile with mixed dictionaries and lists as input.
3004 """
3005 yaml_config = """
3006 virt:
3007 nic:
3008 new-listonly-profile:
3009 - bridge: br0
3010 name: eth0
3011 - model: virtio
3012 name: eth1
3013 source: test_network
3014 type: network
3015 new-list-with-legacy-names:
3016 - eth0:
3017 bridge: br0
3018 - eth1:
3019 bridge: br1
3020 model: virtio
3021 non-default-legacy-profile:
3022 eth0:
3023 bridge: br0
3024 eth1:
3025 bridge: br1
3026 model: virtio
3027 """
3028 mock_config = salt.utils.yaml.safe_load(yaml_config)
3029 with patch.dict(
3030 salt.modules.config.__opts__, mock_config # pylint: disable=no-member
3031 ):
3032
3033 for name in mock_config["virt"]["nic"].keys():
3034 profile = salt.modules.virt._nic_profile(name, "kvm")
3035 self.assertEqual(len(profile), 2)
3036
3037 interface_attrs = profile[0]
3038 self.assertIn("source", interface_attrs)
3039 self.assertIn("type", interface_attrs)
3040 self.assertIn("name", interface_attrs)
3041 self.assertIn("model", interface_attrs)
3042 self.assertEqual(interface_attrs["model"], "virtio")
3043
3044 def test_get_xml(self):
3045 """
3046 Test virt.get_xml()
3047 """
3048 xml = """<domain type='kvm' id='7'>
3049 <name>test-vm</name>
3050 <devices>
3051 <graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'>
3052 <listen type='address' address='0.0.0.0'/>
3053 </graphics>
3054 </devices>
3055 </domain>
3056 """
3057 domain = self.set_mock_vm("test-vm", xml)
3058 self.assertEqual(xml, virt.get_xml("test-vm"))
3059 self.assertEqual(xml, virt.get_xml(domain))
3060
3061 def test_get_loader(self):
3062 """
3063 Test virt.get_loader()
3064 """
3065 xml = """<domain type='kvm' id='7'>
3066 <name>test-vm</name>
3067 <os>
3068 <loader readonly='yes' type='pflash'>/foo/bar</loader>
3069 </os>
3070 </domain>
3071 """
3072 self.set_mock_vm("test-vm", xml)
3073
3074 loader = virt.get_loader("test-vm")
3075 self.assertEqual("/foo/bar", loader["path"])
3076 self.assertEqual("yes", loader["readonly"])
3077
3078 def test_cpu_baseline(self):
3079 """
3080 Test virt.cpu_baseline()
3081 """
3082 capabilities_xml = dedent(
3083 """<capabilities>
3084 <host>
3085 <uuid>44454c4c-3400-105a-8033-b3c04f4b344a</uuid>
3086 <cpu>
3087 <arch>x86_64</arch>
3088 <vendor>Intel</vendor>
3089 </cpu>
3090 </host>
3091 </capabilities>"""
3092 )
3093
3094 baseline_cpu_xml = b"""<cpu match="exact" mode="custom">
3095 <vendor>Intel</vendor>
3096 </cpu>"""
3097
3098 self.mock_conn.getCapabilities.return_value = capabilities_xml
3099 self.mock_conn.baselineCPU.return_value = baseline_cpu_xml
3100 self.assertMultiLineEqual(str(baseline_cpu_xml), str(virt.cpu_baseline()))
3101
3102 def test_parse_qemu_img_info(self):
3103 """
3104 Make sure that qemu-img info output is properly parsed
3105 """
3106 qemu_infos = """[{
3107 "snapshots": [
3108 {
3109 "vm-clock-nsec": 0,
3110 "name": "first-snap",
3111 "date-sec": 1528877587,
3112 "date-nsec": 380589000,
3113 "vm-clock-sec": 0,
3114 "id": "1",
3115 "vm-state-size": 1234
3116 },
3117 {
3118 "vm-clock-nsec": 0,
3119 "name": "second snap",
3120 "date-sec": 1528877592,
3121 "date-nsec": 933509000,
3122 "vm-clock-sec": 0,
3123 "id": "2",
3124 "vm-state-size": 4567
3125 }
3126 ],
3127 "virtual-size": 25769803776,
3128 "filename": "/disks/test.qcow2",
3129 "cluster-size": 65536,
3130 "format": "qcow2",
3131 "actual-size": 217088,
3132 "format-specific": {
3133 "type": "qcow2",
3134 "data": {
3135 "compat": "1.1",
3136 "lazy-refcounts": false,
3137 "refcount-bits": 16,
3138 "corrupt": false
3139 }
3140 },
3141 "full-backing-filename": "/disks/mybacking.qcow2",
3142 "backing-filename": "mybacking.qcow2",
3143 "dirty-flag": false
3144 },
3145 {
3146 "virtual-size": 25769803776,
3147 "filename": "/disks/mybacking.qcow2",
3148 "cluster-size": 65536,
3149 "format": "qcow2",
3150 "actual-size": 393744384,
3151 "format-specific": {
3152 "type": "qcow2",
3153 "data": {
3154 "compat": "1.1",
3155 "lazy-refcounts": false,
3156 "refcount-bits": 16,
3157 "corrupt": false
3158 }
3159 },
3160 "full-backing-filename": "/disks/root.qcow2",
3161 "backing-filename": "root.qcow2",
3162 "dirty-flag": false
3163 },
3164 {
3165 "virtual-size": 25769803776,
3166 "filename": "/disks/root.qcow2",
3167 "cluster-size": 65536,
3168 "format": "qcow2",
3169 "actual-size": 196872192,
3170 "format-specific": {
3171 "type": "qcow2",
3172 "data": {
3173 "compat": "1.1",
3174 "lazy-refcounts": false,
3175 "refcount-bits": 16,
3176 "corrupt": false
3177 }
3178 },
3179 "dirty-flag": false
3180 }]"""
3181
3182 self.assertEqual(
3183 {
3184 "file": "/disks/test.qcow2",
3185 "file format": "qcow2",
3186 "backing file": {
3187 "file": "/disks/mybacking.qcow2",
3188 "file format": "qcow2",
3189 "disk size": 393744384,
3190 "virtual size": 25769803776,
3191 "cluster size": 65536,
3192 "backing file": {
3193 "file": "/disks/root.qcow2",
3194 "file format": "qcow2",
3195 "disk size": 196872192,
3196 "virtual size": 25769803776,
3197 "cluster size": 65536,
3198 },
3199 },
3200 "disk size": 217088,
3201 "virtual size": 25769803776,
3202 "cluster size": 65536,
3203 "snapshots": [
3204 {
3205 "id": "1",
3206 "tag": "first-snap",
3207 "vmsize": 1234,
3208 "date": datetime.datetime.fromtimestamp(
3209 float("{}.{}".format(1528877587, 380589000))
3210 ).isoformat(),
3211 "vmclock": "00:00:00",
3212 },
3213 {
3214 "id": "2",
3215 "tag": "second snap",
3216 "vmsize": 4567,
3217 "date": datetime.datetime.fromtimestamp(
3218 float("{}.{}".format(1528877592, 933509000))
3219 ).isoformat(),
3220 "vmclock": "00:00:00",
3221 },
3222 ],
3223 },
3224 virt._parse_qemu_img_info(qemu_infos),
3225 )
3226
3227 @patch("salt.modules.virt.stop", return_value=True)
3228 @patch("salt.modules.virt.undefine")
3229 @patch("os.remove")
3230 def test_purge_default(self, mock_remove, mock_undefine, mock_stop):
3231 """
3232 Test virt.purge() with default parameters
3233 """
3234 xml = """<domain type='kvm' id='7'>
3235 <name>test-vm</name>
3236 <devices>
3237 <disk type='file' device='disk'>
3238 <driver name='qemu' type='qcow2'/>
3239 <source file='/disks/test.qcow2'/>
3240 <target dev='vda' bus='virtio'/>
3241 </disk>
3242 <disk type='file' device='cdrom'>
3243 <driver name='qemu' type='raw'/>
3244 <source file='/disks/test-cdrom.iso'/>
3245 <target dev='hda' bus='ide'/>
3246 <readonly/>
3247 </disk>
3248 </devices>
3249 </domain>
3250 """
3251 self.set_mock_vm("test-vm", xml)
3252
3253 qemu_infos = """[{
3254 "virtual-size": 25769803776,
3255 "filename": "/disks/test.qcow2",
3256 "cluster-size": 65536,
3257 "format": "qcow2",
3258 "actual-size": 217088,
3259 "format-specific": {
3260 "type": "qcow2",
3261 "data": {
3262 "compat": "1.1",
3263 "lazy-refcounts": false,
3264 "refcount-bits": 16,
3265 "corrupt": false
3266 }
3267 },
3268 "dirty-flag": false
3269 }]"""
3270
3271 self.mock_popen.communicate.return_value = [
3272 qemu_infos
3273 ] # pylint: disable=no-member
3274
3275 with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=True)}):
3276 res = virt.purge("test-vm")
3277 self.assertTrue(res)
3278 mock_remove.assert_called_once()
3279 mock_remove.assert_any_call("/disks/test.qcow2")
3280
3281 @patch("salt.modules.virt.stop", return_value=True)
3282 @patch("salt.modules.virt.undefine")
3283 def test_purge_volumes(self, mock_undefine, mock_stop):
3284 """
3285 Test virt.purge() with volume disks
3286 """
3287 xml = """<domain type='kvm' id='7'>
3288 <name>test-vm</name>
3289 <devices>
3290 <disk type='volume' device='disk'>
3291 <driver name='qemu' type='qcow2' cache='none' io='native'/>
3292 <source pool='default' volume='vm05_system'/>
3293 <backingStore type='file' index='1'>
3294 <format type='qcow2'/>
3295 <source file='/var/lib/libvirt/images/vm04_system.qcow2'/>
3296 <backingStore type='file' index='2'>
3297 <format type='qcow2'/>
3298 <source file='/var/testsuite-data/disk-image-template.qcow2'/>
3299 <backingStore/>
3300 </backingStore>
3301 </backingStore>
3302 <target dev='vda' bus='virtio'/>
3303 <alias name='virtio-disk0'/>
3304 <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
3305 </disk>
3306 </devices>
3307 </domain>
3308 """
3309 self.set_mock_vm("test-vm", xml)
3310
3311 pool_mock = MagicMock()
3312 pool_mock.storageVolLookupByName.return_value.info.return_value = [
3313 0,
3314 1234567,
3315 12345,
3316 ]
3317 pool_mock.storageVolLookupByName.return_value.XMLDesc.return_value = [
3318 """
3319 <volume type='file'>
3320 <name>vm05_system</name>
3321 <target>
3322 <path>/var/lib/libvirt/images/vm05_system</path>
3323 <format type='qcow2'/>
3324 </target>
3325 <backingStore>
3326 <path>/var/lib/libvirt/images/vm04_system.qcow2</path>
3327 <format type='qcow2'/>
3328 </backingStore>
3329 </volume>
3330 """,
3331 ]
3332 pool_mock.listVolumes.return_value = ["vm05_system", "vm04_system.qcow2"]
3333 self.mock_conn.storagePoolLookupByName.return_value = pool_mock
3334 self.mock_conn.listStoragePools.return_value = ["default"]
3335
3336 with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=False)}):
3337 res = virt.purge("test-vm")
3338 self.assertTrue(res)
3339 pool_mock.storageVolLookupByName.return_value.delete.assert_called_once()
3340
3341 @patch("salt.modules.virt.stop", return_value=True)
3342 @patch("salt.modules.virt.undefine")
3343 def test_purge_rbd(self, mock_undefine, mock_stop):
3344 """
3345 Test virt.purge() with RBD disks
3346 """
3347 xml = """<domain type='kvm' id='7'>
3348 <name>test-vm</name>
3349 <devices>
3350 <disk type="network" device="disk">
3351 <driver name='raw' type='qcow2'/>
3352 <source protocol='rbd' name='libvirt-pool/my_vm_data2'>
3353 <host name='ses2.tf.local'/>
3354 <host name='ses3.tf.local' port='1234'/>
3355 <auth username='libvirt'>
3356 <secret type='ceph' usage='pool_test-rbd'/>
3357 </auth>
3358 </source>
3359 <target dev='vdc' bus='virtio'/>
3360 <alias name='virtio-disk2'/>
3361 <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x2'/>
3362 </disk>
3363 </devices>
3364 </domain>
3365 """
3366 self.set_mock_vm("test-vm", xml)
3367
3368 pool_mock = MagicMock()
3369 pool_mock.storageVolLookupByName.return_value.info.return_value = [
3370 0,
3371 1234567,
3372 12345,
3373 ]
3374 pool_mock.XMLDesc.return_value = """
3375 <pool type='rbd'>
3376 <name>test-ses</name>
3377 <source>
3378 <host name='ses2.tf.local'/>
3379 <name>libvirt-pool</name>
3380 <auth type='ceph' username='libvirt'>
3381 <secret usage='pool_test-ses'/>
3382 </auth>
3383 </source>
3384 </pool>
3385 """
3386 pool_mock.name.return_value = "test-ses"
3387 pool_mock.storageVolLookupByName.return_value.XMLDesc.return_value = [
3388 """
3389 <volume type='network'>
3390 <name>my_vm_data2</name>
3391 <source>
3392 </source>
3393 <capacity unit='bytes'>536870912</capacity>
3394 <allocation unit='bytes'>0</allocation>
3395 <target>
3396 <path>libvirt-pool/my_vm_data2</path>
3397 <format type='raw'/>
3398 </target>
3399 </volume>
3400 """,
3401 ]
3402 pool_mock.listVolumes.return_value = ["my_vm_data2"]
3403 self.mock_conn.listAllStoragePools.return_value = [pool_mock]
3404 self.mock_conn.listStoragePools.return_value = ["test-ses"]
3405 self.mock_conn.storagePoolLookupByName.return_value = pool_mock
3406
3407 with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=False)}):
3408 res = virt.purge("test-vm")
3409 self.assertTrue(res)
3410 pool_mock.storageVolLookupByName.return_value.delete.assert_called_once()
3411
3412 @patch("salt.modules.virt.stop", return_value=True)
3413 @patch("salt.modules.virt.undefine")
3414 @patch("os.remove")
3415 def test_purge_removable(self, mock_remove, mock_undefine, mock_stop):
3416 """
3417 Test virt.purge(removables=True)
3418 """
3419 xml = """<domain type="kvm" id="7">
3420 <name>test-vm</name>
3421 <devices>
3422 <disk type='file' device='disk'>
3423 <driver name='qemu' type='qcow2'/>
3424 <source file='/disks/test.qcow2'/>
3425 <target dev='vda' bus='virtio'/>
3426 </disk>
3427 <disk type='file' device='cdrom'>
3428 <driver name='qemu' type='raw'/>
3429 <source file='/disks/test-cdrom.iso'/>
3430 <target dev='hda' bus='ide'/>
3431 <readonly/>
3432 </disk>
3433 <disk type='file' device='floppy'>
3434 <driver name='qemu' type='raw'/>
3435 <source file='/disks/test-floppy.iso'/>
3436 <target dev='hdb' bus='ide'/>
3437 <readonly/>
3438 </disk>
3439 </devices>
3440 </domain>
3441 """
3442 self.set_mock_vm("test-vm", xml)
3443
3444 qemu_infos = """[{
3445 "virtual-size": 25769803776,
3446 "filename": "/disks/test.qcow2",
3447 "cluster-size": 65536,
3448 "format": "qcow2",
3449 "actual-size": 217088,
3450 "format-specific": {
3451 "type": "qcow2",
3452 "data": {
3453 "compat": "1.1",
3454 "lazy-refcounts": false,
3455 "refcount-bits": 16,
3456 "corrupt": false
3457 }
3458 },
3459 "dirty-flag": false
3460 }]"""
3461
3462 self.mock_popen.communicate.return_value = [
3463 qemu_infos
3464 ] # pylint: disable=no-member
3465
3466 with patch.dict(os.path.__dict__, {"exists": MagicMock(return_value=True)}):
3467 res = virt.purge("test-vm", removables=True)
3468 self.assertTrue(res)
3469 mock_remove.assert_any_call("/disks/test.qcow2")
3470 mock_remove.assert_any_call("/disks/test-cdrom.iso")
3471
3472 def test_capabilities(self):
3473 """
3474 Test the virt.capabilities parsing
3475 """
3476 xml = """
3477 <capabilities>
3478 <host>
3479 <uuid>44454c4c-3400-105a-8033-b3c04f4b344a</uuid>
3480 <cpu>
3481 <arch>x86_64</arch>
3482 <model>Nehalem</model>
3483 <vendor>Intel</vendor>
3484 <microcode version='25'/>
3485 <topology sockets='1' cores='4' threads='2'/>
3486 <feature name='vme'/>
3487 <feature name='ds'/>
3488 <feature name='acpi'/>
3489 <pages unit='KiB' size='4'/>
3490 <pages unit='KiB' size='2048'/>
3491 </cpu>
3492 <power_management>
3493 <suspend_mem/>
3494 <suspend_disk/>
3495 <suspend_hybrid/>
3496 </power_management>
3497 <migration_features>
3498 <live/>
3499 <uri_transports>
3500 <uri_transport>tcp</uri_transport>
3501 <uri_transport>rdma</uri_transport>
3502 </uri_transports>
3503 </migration_features>
3504 <topology>
3505 <cells num='1'>
3506 <cell id='0'>
3507 <memory unit='KiB'>12367120</memory>
3508 <pages unit='KiB' size='4'>3091780</pages>
3509 <pages unit='KiB' size='2048'>0</pages>
3510 <distances>
3511 <sibling id='0' value='10'/>
3512 </distances>
3513 <cpus num='8'>
3514 <cpu id='0' socket_id='0' core_id='0' siblings='0,4'/>
3515 <cpu id='1' socket_id='0' core_id='1' siblings='1,5'/>
3516 <cpu id='2' socket_id='0' core_id='2' siblings='2,6'/>
3517 <cpu id='3' socket_id='0' core_id='3' siblings='3,7'/>
3518 <cpu id='4' socket_id='0' core_id='0' siblings='0,4'/>
3519 <cpu id='5' socket_id='0' core_id='1' siblings='1,5'/>
3520 <cpu id='6' socket_id='0' core_id='2' siblings='2,6'/>
3521 <cpu id='7' socket_id='0' core_id='3' siblings='3,7'/>
3522 </cpus>
3523 </cell>
3524 </cells>
3525 </topology>
3526 <cache>
3527 <bank id='0' level='3' type='both' size='8' unit='MiB' cpus='0-7'/>
3528 </cache>
3529 <secmodel>
3530 <model>apparmor</model>
3531 <doi>0</doi>
3532 </secmodel>
3533 <secmodel>
3534 <model>dac</model>
3535 <doi>0</doi>
3536 <baselabel type='kvm'>+487:+486</baselabel>
3537 <baselabel type='qemu'>+487:+486</baselabel>
3538 </secmodel>
3539 </host>
3540
3541 <guest>
3542 <os_type>hvm</os_type>
3543 <arch name='i686'>
3544 <wordsize>32</wordsize>
3545 <emulator>/usr/bin/qemu-system-i386</emulator>
3546 <machine maxCpus='255'>pc-i440fx-2.6</machine>
3547 <machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
3548 <machine maxCpus='255'>pc-0.12</machine>
3549 <domain type='qemu'/>
3550 <domain type='kvm'>
3551 <emulator>/usr/bin/qemu-kvm</emulator>
3552 <machine maxCpus='255'>pc-i440fx-2.6</machine>
3553 <machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
3554 <machine maxCpus='255'>pc-0.12</machine>
3555 </domain>
3556 </arch>
3557 <features>
3558 <cpuselection/>
3559 <deviceboot/>
3560 <disksnapshot default='on' toggle='no'/>
3561 <acpi default='off' toggle='yes'/>
3562 <apic default='on' toggle='no'/>
3563 <pae/>
3564 <nonpae/>
3565 </features>
3566 </guest>
3567
3568 <guest>
3569 <os_type>hvm</os_type>
3570 <arch name='x86_64'>
3571 <wordsize>64</wordsize>
3572 <emulator>/usr/bin/qemu-system-x86_64</emulator>
3573 <machine maxCpus='255'>pc-i440fx-2.6</machine>
3574 <machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
3575 <machine maxCpus='255'>pc-0.12</machine>
3576 <domain type='qemu'/>
3577 <domain type='kvm'>
3578 <emulator>/usr/bin/qemu-kvm</emulator>
3579 <machine maxCpus='255'>pc-i440fx-2.6</machine>
3580 <machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
3581 <machine maxCpus='255'>pc-0.12</machine>
3582 </domain>
3583 </arch>
3584 <features>
3585 <cpuselection/>
3586 <deviceboot/>
3587 <disksnapshot default='on' toggle='no'/>
3588 <acpi default='on' toggle='yes'/>
3589 <apic default='off' toggle='no'/>
3590 </features>
3591 </guest>
3592
3593 <guest>
3594 <os_type>xen</os_type>
3595 <arch name='x86_64'>
3596 <wordsize>64</wordsize>
3597 <emulator>/usr/bin/qemu-system-x86_64</emulator>
3598 <machine>xenpv</machine>
3599 <domain type='xen'/>
3600 </arch>
3601 </guest>
3602
3603 </capabilities>
3604 """
3605 self.mock_conn.getCapabilities.return_value = xml # pylint: disable=no-member
3606 caps = virt.capabilities()
3607
3608 expected = {
3609 "host": {
3610 "uuid": "44454c4c-3400-105a-8033-b3c04f4b344a",
3611 "cpu": {
3612 "arch": "x86_64",
3613 "model": "Nehalem",
3614 "vendor": "Intel",
3615 "microcode": "25",
3616 "sockets": 1,
3617 "cores": 4,
3618 "threads": 2,
3619 "features": ["vme", "ds", "acpi"],
3620 "pages": [{"size": "4 KiB"}, {"size": "2048 KiB"}],
3621 },
3622 "power_management": ["suspend_mem", "suspend_disk", "suspend_hybrid"],
3623 "migration": {"live": True, "transports": ["tcp", "rdma"]},
3624 "topology": {
3625 "cells": [
3626 {
3627 "id": 0,
3628 "memory": "12367120 KiB",
3629 "pages": [
3630 {"size": "4 KiB", "available": 3091780},
3631 {"size": "2048 KiB", "available": 0},
3632 ],
3633 "distances": {0: 10},
3634 "cpus": [
3635 {
3636 "id": 0,
3637 "socket_id": 0,
3638 "core_id": 0,
3639 "siblings": "0,4",
3640 },
3641 {
3642 "id": 1,
3643 "socket_id": 0,
3644 "core_id": 1,
3645 "siblings": "1,5",
3646 },
3647 {
3648 "id": 2,
3649 "socket_id": 0,
3650 "core_id": 2,
3651 "siblings": "2,6",
3652 },
3653 {
3654 "id": 3,
3655 "socket_id": 0,
3656 "core_id": 3,
3657 "siblings": "3,7",
3658 },
3659 {
3660 "id": 4,
3661 "socket_id": 0,
3662 "core_id": 0,
3663 "siblings": "0,4",
3664 },
3665 {
3666 "id": 5,
3667 "socket_id": 0,
3668 "core_id": 1,
3669 "siblings": "1,5",
3670 },
3671 {
3672 "id": 6,
3673 "socket_id": 0,
3674 "core_id": 2,
3675 "siblings": "2,6",
3676 },
3677 {
3678 "id": 7,
3679 "socket_id": 0,
3680 "core_id": 3,
3681 "siblings": "3,7",
3682 },
3683 ],
3684 }
3685 ]
3686 },
3687 "cache": {
3688 "banks": [
3689 {
3690 "id": 0,
3691 "level": 3,
3692 "type": "both",
3693 "size": "8 MiB",
3694 "cpus": "0-7",
3695 }
3696 ]
3697 },
3698 "security": [
3699 {"model": "apparmor", "doi": "0", "baselabels": []},
3700 {
3701 "model": "dac",
3702 "doi": "0",
3703 "baselabels": [
3704 {"type": "kvm", "label": "+487:+486"},
3705 {"type": "qemu", "label": "+487:+486"},
3706 ],
3707 },
3708 ],
3709 },
3710 "guests": [
3711 {
3712 "os_type": "hvm",
3713 "arch": {
3714 "name": "i686",
3715 "wordsize": 32,
3716 "emulator": "/usr/bin/qemu-system-i386",
3717 "machines": {
3718 "pc-i440fx-2.6": {
3719 "maxcpus": 255,
3720 "alternate_names": ["pc"],
3721 },
3722 "pc-0.12": {"maxcpus": 255, "alternate_names": []},
3723 },
3724 "domains": {
3725 "qemu": {"emulator": None, "machines": {}},
3726 "kvm": {
3727 "emulator": "/usr/bin/qemu-kvm",
3728 "machines": {
3729 "pc-i440fx-2.6": {
3730 "maxcpus": 255,
3731 "alternate_names": ["pc"],
3732 },
3733 "pc-0.12": {"maxcpus": 255, "alternate_names": []},
3734 },
3735 },
3736 },
3737 },
3738 "features": {
3739 "cpuselection": {"default": True, "toggle": False},
3740 "deviceboot": {"default": True, "toggle": False},
3741 "disksnapshot": {"default": True, "toggle": False},
3742 "acpi": {"default": False, "toggle": True},
3743 "apic": {"default": True, "toggle": False},
3744 "pae": {"default": True, "toggle": False},
3745 "nonpae": {"default": True, "toggle": False},
3746 },
3747 },
3748 {
3749 "os_type": "hvm",
3750 "arch": {
3751 "name": "x86_64",
3752 "wordsize": 64,
3753 "emulator": "/usr/bin/qemu-system-x86_64",
3754 "machines": {
3755 "pc-i440fx-2.6": {
3756 "maxcpus": 255,
3757 "alternate_names": ["pc"],
3758 },
3759 "pc-0.12": {"maxcpus": 255, "alternate_names": []},
3760 },
3761 "domains": {
3762 "qemu": {"emulator": None, "machines": {}},
3763 "kvm": {
3764 "emulator": "/usr/bin/qemu-kvm",
3765 "machines": {
3766 "pc-i440fx-2.6": {
3767 "maxcpus": 255,
3768 "alternate_names": ["pc"],
3769 },
3770 "pc-0.12": {"maxcpus": 255, "alternate_names": []},
3771 },
3772 },
3773 },
3774 },
3775 "features": {
3776 "cpuselection": {"default": True, "toggle": False},
3777 "deviceboot": {"default": True, "toggle": False},
3778 "disksnapshot": {"default": True, "toggle": False},
3779 "acpi": {"default": True, "toggle": True},
3780 "apic": {"default": False, "toggle": False},
3781 },
3782 },
3783 {
3784 "os_type": "xen",
3785 "arch": {
3786 "name": "x86_64",
3787 "wordsize": 64,
3788 "emulator": "/usr/bin/qemu-system-x86_64",
3789 "machines": {"xenpv": {"alternate_names": []}},
3790 "domains": {"xen": {"emulator": None, "machines": {}}},
3791 },
3792 },
3793 ],
3794 }
3795 self.assertEqual(expected, caps)
3796
3797 def test_network(self):
3798 """
3799 Test virt._get_net_xml()
3800 """
3801 xml_data = virt._gen_net_xml("network", "main", "bridge", "openvswitch")
3802 root = ET.fromstring(xml_data)
3803 self.assertEqual(root.find("name").text, "network")
3804 self.assertEqual(root.find("bridge").attrib["name"], "main")
3805 self.assertEqual(root.find("forward").attrib["mode"], "bridge")
3806 self.assertEqual(root.find("virtualport").attrib["type"], "openvswitch")
3807
3808 def test_network_nat(self):
3809 """
3810 Test virt._get_net_xml() in a nat setup
3811 """
3812 xml_data = virt._gen_net_xml(
3813 "network",
3814 "main",
3815 "nat",
3816 None,
3817 ip_configs=[
3818 {
3819 "cidr": "192.168.2.0/24",
3820 "dhcp_ranges": [
3821 {"start": "192.168.2.10", "end": "192.168.2.25"},
3822 {"start": "192.168.2.110", "end": "192.168.2.125"},
3823 ],
3824 }
3825 ],
3826 )
3827 root = ET.fromstring(xml_data)
3828 self.assertEqual(root.find("name").text, "network")
3829 self.assertEqual(root.find("bridge").attrib["name"], "main")
3830 self.assertEqual(root.find("forward").attrib["mode"], "nat")
3831 self.assertEqual(
3832 root.find("./ip[@address='192.168.2.0']").attrib["prefix"], "24"
3833 )
3834 self.assertEqual(
3835 root.find("./ip[@address='192.168.2.0']").attrib["family"], "ipv4"
3836 )
3837 self.assertEqual(
3838 root.find(
3839 "./ip[@address='192.168.2.0']/dhcp/range[@start='192.168.2.10']"
3840 ).attrib["end"],
3841 "192.168.2.25",
3842 )
3843 self.assertEqual(
3844 root.find(
3845 "./ip[@address='192.168.2.0']/dhcp/range[@start='192.168.2.110']"
3846 ).attrib["end"],
3847 "192.168.2.125",
3848 )
3849
3850 def test_domain_capabilities(self):
3851 """
3852 Test the virt.domain_capabilities parsing
3853 """
3854 xml = """
3855 <domainCapabilities>
3856 <path>/usr/bin/qemu-system-aarch64</path>
3857 <domain>kvm</domain>
3858 <machine>virt-2.12</machine>
3859 <arch>aarch64</arch>
3860 <vcpu max='255'/>
3861 <iothreads supported='yes'/>
3862 <os supported='yes'>
3863 <loader supported='yes'>
3864 <value>/usr/share/AAVMF/AAVMF_CODE.fd</value>
3865 <value>/usr/share/AAVMF/AAVMF32_CODE.fd</value>
3866 <value>/usr/share/OVMF/OVMF_CODE.fd</value>
3867 <enum name='type'>
3868 <value>rom</value>
3869 <value>pflash</value>
3870 </enum>
3871 <enum name='readonly'>
3872 <value>yes</value>
3873 <value>no</value>
3874 </enum>
3875 </loader>
3876 </os>
3877 <cpu>
3878 <mode name='host-passthrough' supported='yes'/>
3879 <mode name='host-model' supported='yes'>
3880 <model fallback='forbid'>sample-cpu</model>
3881 <vendor>ACME</vendor>
3882 <feature policy='require' name='vme'/>
3883 <feature policy='require' name='ss'/>
3884 </mode>
3885 <mode name='custom' supported='yes'>
3886 <model usable='unknown'>pxa262</model>
3887 <model usable='yes'>pxa270-a0</model>
3888 <model usable='no'>arm1136</model>
3889 </mode>
3890 </cpu>
3891 <devices>
3892 <disk supported='yes'>
3893 <enum name='diskDevice'>
3894 <value>disk</value>
3895 <value>cdrom</value>
3896 <value>floppy</value>
3897 <value>lun</value>
3898 </enum>
3899 <enum name='bus'>
3900 <value>fdc</value>
3901 <value>scsi</value>
3902 <value>virtio</value>
3903 <value>usb</value>
3904 <value>sata</value>
3905 </enum>
3906 </disk>
3907 <graphics supported='yes'>
3908 <enum name='type'>
3909 <value>sdl</value>
3910 <value>vnc</value>
3911 </enum>
3912 </graphics>
3913 <video supported='yes'>
3914 <enum name='modelType'>
3915 <value>vga</value>
3916 <value>virtio</value>
3917 </enum>
3918 </video>
3919 <hostdev supported='yes'>
3920 <enum name='mode'>
3921 <value>subsystem</value>
3922 </enum>
3923 <enum name='startupPolicy'>
3924 <value>default</value>
3925 <value>mandatory</value>
3926 <value>requisite</value>
3927 <value>optional</value>
3928 </enum>
3929 <enum name='subsysType'>
3930 <value>usb</value>
3931 <value>pci</value>
3932 <value>scsi</value>
3933 </enum>
3934 <enum name='capsType'/>
3935 <enum name='pciBackend'>
3936 <value>default</value>
3937 <value>kvm</value>
3938 <value>vfio</value>
3939 </enum>
3940 </hostdev>
3941 </devices>
3942 <features>
3943 <gic supported='yes'>
3944 <enum name='version'>
3945 <value>3</value>
3946 </enum>
3947 </gic>
3948 <vmcoreinfo supported='yes'/>
3949 </features>
3950 </domainCapabilities>
3951 """
3952
3953 self.mock_conn.getDomainCapabilities.return_value = (
3954 xml # pylint: disable=no-member
3955 )
3956 caps = virt.domain_capabilities()
3957
3958 expected = {
3959 "emulator": "/usr/bin/qemu-system-aarch64",
3960 "domain": "kvm",
3961 "machine": "virt-2.12",
3962 "arch": "aarch64",
3963 "max_vcpus": 255,
3964 "iothreads": True,
3965 "os": {
3966 "loader": {
3967 "type": ["rom", "pflash"],
3968 "readonly": ["yes", "no"],
3969 "values": [
3970 "/usr/share/AAVMF/AAVMF_CODE.fd",
3971 "/usr/share/AAVMF/AAVMF32_CODE.fd",
3972 "/usr/share/OVMF/OVMF_CODE.fd",
3973 ],
3974 }
3975 },
3976 "cpu": {
3977 "host-passthrough": True,
3978 "host-model": {
3979 "model": {"name": "sample-cpu", "fallback": "forbid"},
3980 "vendor": "ACME",
3981 "features": {"vme": "require", "ss": "require"},
3982 },
3983 "custom": {
3984 "models": {"pxa262": "unknown", "pxa270-a0": "yes", "arm1136": "no"}
3985 },
3986 },
3987 "devices": {
3988 "disk": {
3989 "diskDevice": ["disk", "cdrom", "floppy", "lun"],
3990 "bus": ["fdc", "scsi", "virtio", "usb", "sata"],
3991 },
3992 "graphics": {"type": ["sdl", "vnc"]},
3993 "video": {"modelType": ["vga", "virtio"]},
3994 "hostdev": {
3995 "mode": ["subsystem"],
3996 "startupPolicy": ["default", "mandatory", "requisite", "optional"],
3997 "subsysType": ["usb", "pci", "scsi"],
3998 "capsType": [],
3999 "pciBackend": ["default", "kvm", "vfio"],
4000 },
4001 },
4002 "features": {"gic": {"version": ["3"]}, "vmcoreinfo": {}},
4003 }
4004
4005 self.assertEqual(expected, caps)
4006
4007 def test_all_capabilities(self):
4008 """
4009 Test the virt.domain_capabilities default output
4010 """
4011 domainXml = """
4012 <domainCapabilities>
4013 <path>/usr/bin/qemu-system-x86_64</path>
4014 <domain>kvm</domain>
4015 <machine>virt-2.12</machine>
4016 <arch>x86_64</arch>
4017 <vcpu max='255'/>
4018 <iothreads supported='yes'/>
4019 </domainCapabilities>
4020 """
4021 hostXml = """
4022 <capabilities>
4023 <host>
4024 <uuid>44454c4c-3400-105a-8033-b3c04f4b344a</uuid>
4025 <cpu>
4026 <arch>x86_64</arch>
4027 <model>Nehalem</model>
4028 <vendor>Intel</vendor>
4029 <microcode version='25'/>
4030 <topology sockets='1' cores='4' threads='2'/>
4031 </cpu>
4032 </host>
4033 <guest>
4034 <os_type>hvm</os_type>
4035 <arch name='x86_64'>
4036 <wordsize>64</wordsize>
4037 <emulator>/usr/bin/qemu-system-x86_64</emulator>
4038 <machine maxCpus='255'>pc-i440fx-2.6</machine>
4039 <machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
4040 <machine maxCpus='255'>pc-0.12</machine>
4041 <domain type='qemu'/>
4042 <domain type='kvm'>
4043 <emulator>/usr/bin/qemu-kvm</emulator>
4044 <machine maxCpus='255'>pc-i440fx-2.6</machine>
4045 <machine canonical='pc-i440fx-2.6' maxCpus='255'>pc</machine>
4046 <machine maxCpus='255'>pc-0.12</machine>
4047 </domain>
4048 </arch>
4049 </guest>
4050 </capabilities>
4051 """
4052
4053 # pylint: disable=no-member
4054 self.mock_conn.getCapabilities.return_value = hostXml
4055 self.mock_conn.getDomainCapabilities.side_effect = [
4056 domainXml,
4057 domainXml.replace("<domain>kvm", "<domain>qemu"),
4058 ]
4059 # pylint: enable=no-member
4060
4061 caps = virt.all_capabilities()
4062 self.assertEqual(
4063 "44454c4c-3400-105a-8033-b3c04f4b344a", caps["host"]["host"]["uuid"]
4064 )
4065 self.assertEqual(
4066 {"qemu", "kvm"}, {domainCaps["domain"] for domainCaps in caps["domains"]},
4067 )
4068
4069 def test_network_tag(self):
4070 """
4071 Test virt._get_net_xml() with VLAN tag
4072 """
4073 xml_data = virt._gen_net_xml("network", "main", "bridge", "openvswitch", 1001)
4074 root = ET.fromstring(xml_data)
4075 self.assertEqual(root.find("name").text, "network")
4076 self.assertEqual(root.find("bridge").attrib["name"], "main")
4077 self.assertEqual(root.find("forward").attrib["mode"], "bridge")
4078 self.assertEqual(root.find("virtualport").attrib["type"], "openvswitch")
4079 self.assertEqual(root.find("vlan/tag").attrib["id"], "1001")
4080
4081 def test_list_networks(self):
4082 """
4083 Test virt.list_networks()
4084 """
4085 names = ["net1", "default", "net2"]
4086 net_mocks = [MagicMock(), MagicMock(), MagicMock()]
4087 for i, value in enumerate(names):
4088 net_mocks[i].name.return_value = value
4089
4090 self.mock_conn.listAllNetworks.return_value = (
4091 net_mocks # pylint: disable=no-member
4092 )
4093 actual = virt.list_networks()
4094 self.assertEqual(names, actual)
4095
4096 def test_network_info(self):
4097 """
4098 Test virt.network_info()
4099 """
4100 self.mock_libvirt.VIR_IP_ADDR_TYPE_IPV4 = 0
4101 self.mock_libvirt.VIR_IP_ADDR_TYPE_IPV6 = 1
4102
4103 net_mock = MagicMock()
4104
4105 # pylint: disable=no-member
4106 net_mock.name.return_value = "foo"
4107 net_mock.UUIDString.return_value = "some-uuid"
4108 net_mock.bridgeName.return_value = "br0"
4109 net_mock.autostart.return_value = True
4110 net_mock.isActive.return_value = False
4111 net_mock.isPersistent.return_value = True
4112 net_mock.DHCPLeases.return_value = [
4113 {
4114 "iface": "virbr0",
4115 "expirytime": 1527757552,
4116 "type": 0,
4117 "mac": "52:54:00:01:71:bd",
4118 "ipaddr": "192.168.122.45",
4119 "prefix": 24,
4120 "hostname": "py3-test",
4121 "clientid": "01:52:54:00:01:71:bd",
4122 "iaid": None,
4123 }
4124 ]
4125 self.mock_conn.listAllNetworks.return_value = [net_mock]
4126 # pylint: enable=no-member
4127
4128 net = virt.network_info("foo")
4129 self.assertEqual(
4130 {
4131 "foo": {
4132 "uuid": "some-uuid",
4133 "bridge": "br0",
4134 "autostart": True,
4135 "active": False,
4136 "persistent": True,
4137 "leases": [
4138 {
4139 "iface": "virbr0",
4140 "expirytime": 1527757552,
4141 "type": "ipv4",
4142 "mac": "52:54:00:01:71:bd",
4143 "ipaddr": "192.168.122.45",
4144 "prefix": 24,
4145 "hostname": "py3-test",
4146 "clientid": "01:52:54:00:01:71:bd",
4147 "iaid": None,
4148 }
4149 ],
4150 }
4151 },
4152 net,
4153 )
4154
4155 def test_network_info_all(self):
4156 """
4157 Test virt.network_info()
4158 """
4159 self.mock_libvirt.VIR_IP_ADDR_TYPE_IPV4 = 0
4160 self.mock_libvirt.VIR_IP_ADDR_TYPE_IPV6 = 1
4161
4162 net_mocks = []
4163 # pylint: disable=no-member
4164 for i in range(2):
4165 net_mock = MagicMock()
4166
4167 net_mock.name.return_value = "net{}".format(i)
4168 net_mock.UUIDString.return_value = "some-uuid"
4169 net_mock.bridgeName.return_value = "br{}".format(i)
4170 net_mock.autostart.return_value = True
4171 net_mock.isActive.return_value = False
4172 net_mock.isPersistent.return_value = True
4173 net_mock.DHCPLeases.return_value = []
4174 net_mocks.append(net_mock)
4175 self.mock_conn.listAllNetworks.return_value = net_mocks
4176 # pylint: enable=no-member
4177
4178 net = virt.network_info()
4179 self.assertEqual(
4180 {
4181 "net0": {
4182 "uuid": "some-uuid",
4183 "bridge": "br0",
4184 "autostart": True,
4185 "active": False,
4186 "persistent": True,
4187 "leases": [],
4188 },
4189 "net1": {
4190 "uuid": "some-uuid",
4191 "bridge": "br1",
4192 "autostart": True,
4193 "active": False,
4194 "persistent": True,
4195 "leases": [],
4196 },
4197 },
4198 net,
4199 )
4200
4201 def test_network_info_notfound(self):
4202 """
4203 Test virt.network_info() when the network can't be found
4204 """
4205 # pylint: disable=no-member
4206 self.mock_conn.listAllNetworks.return_value = []
4207 # pylint: enable=no-member
4208 net = virt.network_info("foo")
4209 self.assertEqual({}, net)
4210
4211 def test_network_get_xml(self):
4212 """
4213 Test virt.network_get_xml
4214 """
4215 network_mock = MagicMock()
4216 network_mock.XMLDesc.return_value = "<net>Raw XML</net>"
4217 self.mock_conn.networkLookupByName.return_value = network_mock
4218
4219 self.assertEqual("<net>Raw XML</net>", virt.network_get_xml("default"))
4220
4221 def test_pool(self):
4222 """
4223 Test virt._gen_pool_xml()
4224 """
4225 xml_data = virt._gen_pool_xml("pool", "logical", "/dev/base")
4226 root = ET.fromstring(xml_data)
4227 self.assertEqual(root.find("name").text, "pool")
4228 self.assertEqual(root.attrib["type"], "logical")
4229 self.assertEqual(root.find("target/path").text, "/dev/base")
4230
4231 def test_pool_with_source(self):
4232 """
4233 Test virt._gen_pool_xml() with a source device
4234 """
4235 xml_data = virt._gen_pool_xml(
4236 "pool", "logical", "/dev/base", source_devices=[{"path": "/dev/sda"}]
4237 )
4238 root = ET.fromstring(xml_data)
4239 self.assertEqual(root.find("name").text, "pool")
4240 self.assertEqual(root.attrib["type"], "logical")
4241 self.assertEqual(root.find("target/path").text, "/dev/base")
4242 self.assertEqual(root.find("source/device").attrib["path"], "/dev/sda")
4243
4244 def test_pool_with_scsi(self):
4245 """
4246 Test virt._gen_pool_xml() with a SCSI source
4247 """
4248 xml_data = virt._gen_pool_xml(
4249 "pool",
4250 "scsi",
4251 "/dev/disk/by-path",
4252 source_devices=[{"path": "/dev/sda"}],
4253 source_adapter={
4254 "type": "scsi_host",
4255 "parent_address": {
4256 "unique_id": 5,
4257 "address": {
4258 "domain": "0x0000",
4259 "bus": "0x00",
4260 "slot": "0x1f",
4261 "function": "0x2",
4262 },
4263 },
4264 },
4265 source_name="srcname",
4266 )
4267 root = ET.fromstring(xml_data)
4268 self.assertEqual(root.find("name").text, "pool")
4269 self.assertEqual(root.attrib["type"], "scsi")
4270 self.assertEqual(root.find("target/path").text, "/dev/disk/by-path")
4271 self.assertEqual(root.find("source/device"), None)
4272 self.assertEqual(root.find("source/name"), None)
4273 self.assertEqual(root.find("source/adapter").attrib["type"], "scsi_host")
4274 self.assertEqual(
4275 root.find("source/adapter/parentaddr").attrib["unique_id"], "5"
4276 )
4277 self.assertEqual(
4278 root.find("source/adapter/parentaddr/address").attrib["domain"], "0x0000"
4279 )
4280 self.assertEqual(
4281 root.find("source/adapter/parentaddr/address").attrib["bus"], "0x00"
4282 )
4283 self.assertEqual(
4284 root.find("source/adapter/parentaddr/address").attrib["slot"], "0x1f"
4285 )
4286 self.assertEqual(
4287 root.find("source/adapter/parentaddr/address").attrib["function"], "0x2"
4288 )
4289
4290 def test_pool_with_rbd(self):
4291 """
4292 Test virt._gen_pool_xml() with an RBD source
4293 """
4294 xml_data = virt._gen_pool_xml(
4295 "pool",
4296 "rbd",
4297 source_devices=[{"path": "/dev/sda"}],
4298 source_hosts=["1.2.3.4", "my.ceph.monitor:69"],
4299 source_auth={
4300 "type": "ceph",
4301 "username": "admin",
4302 "secret": {"type": "uuid", "value": "someuuid"},
4303 },
4304 source_name="srcname",
4305 source_adapter={"type": "scsi_host", "name": "host0"},
4306 source_dir="/some/dir",
4307 source_format="fmt",
4308 )
4309 root = ET.fromstring(xml_data)
4310 self.assertEqual(root.find("name").text, "pool")
4311 self.assertEqual(root.attrib["type"], "rbd")
4312 self.assertEqual(root.find("target"), None)
4313 self.assertEqual(root.find("source/device"), None)
4314 self.assertEqual(root.find("source/name").text, "srcname")
4315 self.assertEqual(root.find("source/adapter"), None)
4316 self.assertEqual(root.find("source/dir"), None)
4317 self.assertEqual(root.find("source/format"), None)
4318 self.assertEqual(root.findall("source/host")[0].attrib["name"], "1.2.3.4")
4319 self.assertTrue("port" not in root.findall("source/host")[0].attrib)
4320 self.assertEqual(
4321 root.findall("source/host")[1].attrib["name"], "my.ceph.monitor"
4322 )
4323 self.assertEqual(root.findall("source/host")[1].attrib["port"], "69")
4324 self.assertEqual(root.find("source/auth").attrib["type"], "ceph")
4325 self.assertEqual(root.find("source/auth").attrib["username"], "admin")
4326 self.assertEqual(root.find("source/auth/secret").attrib["uuid"], "someuuid")
4327
4328 def test_pool_with_netfs(self):
4329 """
4330 Test virt._gen_pool_xml() with a netfs source
4331 """
4332 xml_data = virt._gen_pool_xml(
4333 "pool",
4334 "netfs",
4335 target="/path/to/target",
4336 permissions={
4337 "mode": "0770",
4338 "owner": 1000,
4339 "group": 100,
4340 "label": "seclabel",
4341 },
4342 source_devices=[{"path": "/dev/sda"}],
4343 source_hosts=["nfs.host"],
4344 source_name="srcname",
4345 source_adapter={"type": "scsi_host", "name": "host0"},
4346 source_dir="/some/dir",
4347 source_format="nfs",
4348 )
4349 root = ET.fromstring(xml_data)
4350 self.assertEqual(root.find("name").text, "pool")
4351 self.assertEqual(root.attrib["type"], "netfs")
4352 self.assertEqual(root.find("target/path").text, "/path/to/target")
4353 self.assertEqual(root.find("target/permissions/mode").text, "0770")
4354 self.assertEqual(root.find("target/permissions/owner").text, "1000")
4355 self.assertEqual(root.find("target/permissions/group").text, "100")
4356 self.assertEqual(root.find("target/permissions/label").text, "seclabel")
4357 self.assertEqual(root.find("source/device"), None)
4358 self.assertEqual(root.find("source/name"), None)
4359 self.assertEqual(root.find("source/adapter"), None)
4360 self.assertEqual(root.find("source/dir").attrib["path"], "/some/dir")
4361 self.assertEqual(root.find("source/format").attrib["type"], "nfs")
4362 self.assertEqual(root.find("source/host").attrib["name"], "nfs.host")
4363 self.assertEqual(root.find("source/auth"), None)
4364
4365 def test_pool_with_iscsi_direct(self):
4366 """
4367 Test virt._gen_pool_xml() with a iscsi-direct source
4368 """
4369 xml_data = virt._gen_pool_xml(
4370 "pool",
4371 "iscsi-direct",
4372 source_hosts=["iscsi.example.com"],
4373 source_devices=[{"path": "iqn.2013-06.com.example:iscsi-pool"}],
4374 source_initiator="iqn.2013-06.com.example:iscsi-initiator",
4375 )
4376 root = ET.fromstring(xml_data)
4377 self.assertEqual(root.find("name").text, "pool")
4378 self.assertEqual(root.attrib["type"], "iscsi-direct")
4379 self.assertEqual(root.find("target"), None)
4380 self.assertEqual(
4381 root.find("source/device").attrib["path"],
4382 "iqn.2013-06.com.example:iscsi-pool",
4383 )
4384 self.assertEqual(
4385 root.findall("source/host")[0].attrib["name"], "iscsi.example.com"
4386 )
4387 self.assertEqual(
4388 root.find("source/initiator/iqn").attrib["name"],
4389 "iqn.2013-06.com.example:iscsi-initiator",
4390 )
4391
4392 def test_pool_define(self):
4393 """
4394 Test virt.pool_define()
4395 """
4396 mock_pool = MagicMock()
4397 mock_secret = MagicMock()
4398 mock_secret_define = MagicMock(return_value=mock_secret)
4399 self.mock_conn.secretDefineXML = mock_secret_define
4400 self.mock_conn.storagePoolCreateXML = MagicMock(return_value=mock_pool)
4401 self.mock_conn.storagePoolDefineXML = MagicMock(return_value=mock_pool)
4402
4403 mocks = [
4404 mock_pool,
4405 mock_secret,
4406 mock_secret_define,
4407 self.mock_conn.storagePoolCreateXML,
4408 self.mock_conn.secretDefineXML,
4409 self.mock_conn.storagePoolDefineXML,
4410 ]
4411
4412 # Test case with already defined secret and permanent pool
4413 self.assertTrue(
4414 virt.pool_define(
4415 "default",
4416 "rbd",
4417 source_hosts=["one.example.com", "two.example.com"],
4418 source_name="rbdvol",
4419 source_auth={
4420 "type": "ceph",
4421 "username": "admin",
4422 "secret": {"type": "uuid", "value": "someuuid"},
4423 },
4424 )
4425 )
4426 self.mock_conn.storagePoolDefineXML.assert_called_once()
4427 self.mock_conn.storagePoolCreateXML.assert_not_called()
4428 mock_pool.create.assert_called_once()
4429 mock_secret_define.assert_not_called()
4430
4431 # Test case with Ceph secret to be defined and transient pool
4432 for mock in mocks:
4433 mock.reset_mock()
4434 self.assertTrue(
4435 virt.pool_define(
4436 "default",
4437 "rbd",
4438 transient=True,
4439 source_hosts=["one.example.com", "two.example.com"],
4440 source_name="rbdvol",
4441 source_auth={"username": "admin", "password": "c2VjcmV0"},
4442 )
4443 )
4444 self.mock_conn.storagePoolDefineXML.assert_not_called()
4445
4446 pool_xml = self.mock_conn.storagePoolCreateXML.call_args[0][0]
4447 root = ET.fromstring(pool_xml)
4448 self.assertEqual(root.find("source/auth").attrib["type"], "ceph")
4449 self.assertEqual(root.find("source/auth").attrib["username"], "admin")
4450 self.assertEqual(
4451 root.find("source/auth/secret").attrib["usage"], "pool_default"
4452 )
4453 mock_pool.create.assert_not_called()
4454 mock_secret.setValue.assert_called_once_with(b"secret")
4455
4456 secret_xml = mock_secret_define.call_args[0][0]
4457 root = ET.fromstring(secret_xml)
4458 self.assertEqual(root.find("usage/name").text, "pool_default")
4459 self.assertEqual(root.find("usage").attrib["type"], "ceph")
4460 self.assertEqual(root.attrib["private"], "yes")
4461 self.assertEqual(
4462 root.find("description").text, "Passphrase for default pool created by Salt"
4463 )
4464
4465 # Test case with iscsi secret not starting
4466 for mock in mocks:
4467 mock.reset_mock()
4468 self.assertTrue(
4469 virt.pool_define(
4470 "default",
4471 "iscsi",
4472 target="/dev/disk/by-path",
4473 source_hosts=["iscsi.example.com"],
4474 source_devices=[{"path": "iqn.2013-06.com.example:iscsi-pool"}],
4475 source_auth={"username": "admin", "password": "secret"},
4476 start=False,
4477 )
4478 )
4479 self.mock_conn.storagePoolCreateXML.assert_not_called()
4480
4481 pool_xml = self.mock_conn.storagePoolDefineXML.call_args[0][0]
4482 root = ET.fromstring(pool_xml)
4483 self.assertEqual(root.find("source/auth").attrib["type"], "chap")
4484 self.assertEqual(root.find("source/auth").attrib["username"], "admin")
4485 self.assertEqual(
4486 root.find("source/auth/secret").attrib["usage"], "pool_default"
4487 )
4488 mock_pool.create.assert_not_called()
4489 mock_secret.setValue.assert_called_once_with("secret")
4490
4491 secret_xml = mock_secret_define.call_args[0][0]
4492 root = ET.fromstring(secret_xml)
4493 self.assertEqual(root.find("usage/target").text, "pool_default")
4494 self.assertEqual(root.find("usage").attrib["type"], "iscsi")
4495 self.assertEqual(root.attrib["private"], "yes")
4496 self.assertEqual(
4497 root.find("description").text, "Passphrase for default pool created by Salt"
4498 )
4499
4500 def test_list_pools(self):
4501 """
4502 Test virt.list_pools()
4503 """
4504 names = ["pool1", "default", "pool2"]
4505 pool_mocks = [MagicMock(), MagicMock(), MagicMock()]
4506 for i, value in enumerate(names):
4507 pool_mocks[i].name.return_value = value
4508
4509 self.mock_conn.listAllStoragePools.return_value = (
4510 pool_mocks # pylint: disable=no-member
4511 )
4512 actual = virt.list_pools()
4513 self.assertEqual(names, actual)
4514
4515 def test_pool_info(self):
4516 """
4517 Test virt.pool_info()
4518 """
4519 # pylint: disable=no-member
4520 pool_mock = MagicMock()
4521 pool_mock.name.return_value = "foo"
4522 pool_mock.UUIDString.return_value = "some-uuid"
4523 pool_mock.info.return_value = [0, 1234, 5678, 123]
4524 pool_mock.autostart.return_value = True
4525 pool_mock.isPersistent.return_value = True
4526 pool_mock.XMLDesc.return_value = """<pool type='dir'>
4527 <name>default</name>
4528 <uuid>d92682d0-33cf-4e10-9837-a216c463e158</uuid>
4529 <capacity unit='bytes'>854374301696</capacity>
4530 <allocation unit='bytes'>596275986432</allocation>
4531 <available unit='bytes'>258098315264</available>
4532 <source>
4533 </source>
4534 <target>
4535 <path>/srv/vms</path>
4536 <permissions>
4537 <mode>0755</mode>
4538 <owner>0</owner>
4539 <group>0</group>
4540 </permissions>
4541 </target>
4542 </pool>"""
4543 self.mock_conn.listAllStoragePools.return_value = [pool_mock]
4544 # pylint: enable=no-member
4545
4546 pool = virt.pool_info("foo")
4547 self.assertEqual(
4548 {
4549 "foo": {
4550 "uuid": "some-uuid",
4551 "state": "inactive",
4552 "capacity": 1234,
4553 "allocation": 5678,
4554 "free": 123,
4555 "autostart": True,
4556 "persistent": True,
4557 "type": "dir",
4558 "target_path": "/srv/vms",
4559 }
4560 },
4561 pool,
4562 )
4563
4564 def test_pool_info_notarget(self):
4565 """
4566 Test virt.pool_info()
4567 """
4568 # pylint: disable=no-member
4569 pool_mock = MagicMock()
4570 pool_mock.name.return_value = "ceph"
4571 pool_mock.UUIDString.return_value = "some-uuid"
4572 pool_mock.info.return_value = [0, 0, 0, 0]
4573 pool_mock.autostart.return_value = True
4574 pool_mock.isPersistent.return_value = True
4575 pool_mock.XMLDesc.return_value = """<pool type='rbd'>
4576 <name>ceph</name>
4577 <uuid>some-uuid</uuid>
4578 <capacity unit='bytes'>0</capacity>
4579 <allocation unit='bytes'>0</allocation>
4580 <available unit='bytes'>0</available>
4581 <source>
4582 <host name='localhost' port='6789'/>
4583 <host name='localhost' port='6790'/>
4584 <name>rbd</name>
4585 <auth type='ceph' username='admin'>
4586 <secret uuid='2ec115d7-3a88-3ceb-bc12-0ac909a6fd87'/>
4587 </auth>
4588 </source>
4589 </pool>"""
4590 self.mock_conn.listAllStoragePools.return_value = [pool_mock]
4591 # pylint: enable=no-member
4592
4593 pool = virt.pool_info("ceph")
4594 self.assertEqual(
4595 {
4596 "ceph": {
4597 "uuid": "some-uuid",
4598 "state": "inactive",
4599 "capacity": 0,
4600 "allocation": 0,
4601 "free": 0,
4602 "autostart": True,
4603 "persistent": True,
4604 "type": "rbd",
4605 "target_path": None,
4606 }
4607 },
4608 pool,
4609 )
4610
4611 def test_pool_info_notfound(self):
4612 """
4613 Test virt.pool_info() when the pool can't be found
4614 """
4615 # pylint: disable=no-member
4616 self.mock_conn.listAllStoragePools.return_value = []
4617 # pylint: enable=no-member
4618 pool = virt.pool_info("foo")
4619 self.assertEqual({}, pool)
4620
4621 def test_pool_info_all(self):
4622 """
4623 Test virt.pool_info()
4624 """
4625 # pylint: disable=no-member
4626 pool_mocks = []
4627 for i in range(2):
4628 pool_mock = MagicMock()
4629 pool_mock.name.return_value = "pool{}".format(i)
4630 pool_mock.UUIDString.return_value = "some-uuid-{}".format(i)
4631 pool_mock.info.return_value = [0, 1234, 5678, 123]
4632 pool_mock.autostart.return_value = True
4633 pool_mock.isPersistent.return_value = True
4634 pool_mock.XMLDesc.return_value = """<pool type='dir'>
4635 <name>default</name>
4636 <uuid>d92682d0-33cf-4e10-9837-a216c463e158</uuid>
4637 <capacity unit='bytes'>854374301696</capacity>
4638 <allocation unit='bytes'>596275986432</allocation>
4639 <available unit='bytes'>258098315264</available>
4640 <source>
4641 </source>
4642 <target>
4643 <path>/srv/vms</path>
4644 <permissions>
4645 <mode>0755</mode>
4646 <owner>0</owner>
4647 <group>0</group>
4648 </permissions>
4649 </target>
4650 </pool>"""
4651 pool_mocks.append(pool_mock)
4652 self.mock_conn.listAllStoragePools.return_value = pool_mocks
4653 # pylint: enable=no-member
4654
4655 pool = virt.pool_info()
4656 self.assertEqual(
4657 {
4658 "pool0": {
4659 "uuid": "some-uuid-0",
4660 "state": "inactive",
4661 "capacity": 1234,
4662 "allocation": 5678,
4663 "free": 123,
4664 "autostart": True,
4665 "persistent": True,
4666 "type": "dir",
4667 "target_path": "/srv/vms",
4668 },
4669 "pool1": {
4670 "uuid": "some-uuid-1",
4671 "state": "inactive",
4672 "capacity": 1234,
4673 "allocation": 5678,
4674 "free": 123,
4675 "autostart": True,
4676 "persistent": True,
4677 "type": "dir",
4678 "target_path": "/srv/vms",
4679 },
4680 },
4681 pool,
4682 )
4683
4684 def test_pool_get_xml(self):
4685 """
4686 Test virt.pool_get_xml
4687 """
4688 pool_mock = MagicMock()
4689 pool_mock.XMLDesc.return_value = "<pool>Raw XML</pool>"
4690 self.mock_conn.storagePoolLookupByName.return_value = pool_mock
4691
4692 self.assertEqual("<pool>Raw XML</pool>", virt.pool_get_xml("default"))
4693
4694 def test_pool_list_volumes(self):
4695 """
4696 Test virt.pool_list_volumes
4697 """
4698 names = ["volume1", "volume2"]
4699 mock_pool = MagicMock()
4700 # pylint: disable=no-member
4701 mock_pool.listVolumes.return_value = names
4702 self.mock_conn.storagePoolLookupByName.return_value = mock_pool
4703 # pylint: enable=no-member
4704 self.assertEqual(names, virt.pool_list_volumes("default"))
4705
4706 @patch("salt.modules.virt._is_bhyve_hyper", return_value=False)
4707 @patch("salt.modules.virt._is_kvm_hyper", return_value=True)
4708 @patch("salt.modules.virt._is_xen_hyper", return_value=False)
4709 def test_get_hypervisor(self, isxen_mock, iskvm_mock, is_bhyve_mock):
4710 """
4711 test the virt.get_hypervisor() function
4712 """
4713 self.assertEqual("kvm", virt.get_hypervisor())
4714
4715 iskvm_mock.return_value = False
4716 self.assertIsNone(virt.get_hypervisor())
4717
4718 is_bhyve_mock.return_value = False
4719 self.assertIsNone(virt.get_hypervisor())
4720
4721 isxen_mock.return_value = True
4722 self.assertEqual("xen", virt.get_hypervisor())
4723
4724 def test_pool_delete(self):
4725 """
4726 Test virt.pool_delete function
4727 """
4728 mock_pool = MagicMock()
4729 mock_pool.delete = MagicMock(return_value=0)
4730 self.mock_conn.storagePoolLookupByName = MagicMock(return_value=mock_pool)
4731
4732 res = virt.pool_delete("test-pool")
4733 self.assertTrue(res)
4734
4735 self.mock_conn.storagePoolLookupByName.assert_called_once_with("test-pool")
4736
4737 # Shouldn't be called with another parameter so far since those are not implemented
4738 # and thus throwing exceptions.
4739 mock_pool.delete.assert_called_once_with(
4740 self.mock_libvirt.VIR_STORAGE_POOL_DELETE_NORMAL
4741 )
4742
4743 def test_pool_undefine_secret(self):
4744 """
4745 Test virt.pool_undefine function where the pool has a secret
4746 """
4747 mock_pool = MagicMock()
4748 mock_pool.undefine = MagicMock(return_value=0)
4749 mock_pool.XMLDesc.return_value = """
4750 <pool type='rbd'>
4751 <name>test-ses</name>
4752 <source>
4753 <host name='myhost'/>
4754 <name>libvirt-pool</name>
4755 <auth type='ceph' username='libvirt'>
4756 <secret usage='pool_test-ses'/>
4757 </auth>
4758 </source>
4759 </pool>
4760 """
4761 self.mock_conn.storagePoolLookupByName = MagicMock(return_value=mock_pool)
4762 mock_undefine = MagicMock(return_value=0)
4763 self.mock_conn.secretLookupByUsage.return_value.undefine = mock_undefine
4764
4765 res = virt.pool_undefine("test-ses")
4766 self.assertTrue(res)
4767
4768 self.mock_conn.storagePoolLookupByName.assert_called_once_with("test-ses")
4769 mock_pool.undefine.assert_called_once_with()
4770
4771 self.mock_conn.secretLookupByUsage.assert_called_once_with(
4772 self.mock_libvirt.VIR_SECRET_USAGE_TYPE_CEPH, "pool_test-ses"
4773 )
4774 mock_undefine.assert_called_once()
4775
4776 def test_full_info(self):
4777 """
4778 Test virt.full_info
4779 """
4780 xml = """<domain type='kvm' id='7'>
4781 <uuid>28deee33-4859-4f23-891c-ee239cffec94</uuid>
4782 <name>test-vm</name>
4783 <on_poweroff>destroy</on_poweroff>
4784 <on_reboot>restart</on_reboot>
4785 <on_crash>destroy</on_crash>
4786 <devices>
4787 <disk type='file' device='disk'>
4788 <driver name='qemu' type='qcow2'/>
4789 <source file='/disks/test.qcow2'/>
4790 <target dev='vda' bus='virtio'/>
4791 </disk>
4792 <disk type='file' device='cdrom'>
4793 <driver name='qemu' type='raw'/>
4794 <source file='/disks/test-cdrom.iso'/>
4795 <target dev='hda' bus='ide'/>
4796 <readonly/>
4797 </disk>
4798 <interface type='bridge'>
4799 <mac address='ac:de:48:b6:8b:59'/>
4800 <source bridge='br0'/>
4801 <model type='virtio'/>
4802 <address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
4803 </interface>
4804 <graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'>
4805 <listen type='address' address='0.0.0.0'/>
4806 </graphics>
4807 </devices>
4808 </domain>
4809 """
4810 self.set_mock_vm("test-vm", xml)
4811
4812 qemu_infos = """[{
4813 "virtual-size": 25769803776,
4814 "filename": "/disks/test.qcow2",
4815 "cluster-size": 65536,
4816 "format": "qcow2",
4817 "actual-size": 217088,
4818 "format-specific": {
4819 "type": "qcow2",
4820 "data": {
4821 "compat": "1.1",
4822 "lazy-refcounts": false,
4823 "refcount-bits": 16,
4824 "corrupt": false
4825 }
4826 },
4827 "full-backing-filename": "/disks/mybacking.qcow2",
4828 "backing-filename": "mybacking.qcow2",
4829 "dirty-flag": false
4830 },
4831 {
4832 "virtual-size": 25769803776,
4833 "filename": "/disks/mybacking.qcow2",
4834 "cluster-size": 65536,
4835 "format": "qcow2",
4836 "actual-size": 393744384,
4837 "format-specific": {
4838 "type": "qcow2",
4839 "data": {
4840 "compat": "1.1",
4841 "lazy-refcounts": false,
4842 "refcount-bits&