test_virt.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. """
  2. Validate the virt module
  3. """
  4. import logging
  5. from numbers import Number
  6. from xml.etree import ElementTree
  7. import pytest
  8. from saltfactories.utils import cli_scripts
  9. from tests.support.helpers import skip_if_binaries_missing, slowTest
  10. from tests.support.virt import SaltVirtMinionContainerFactory
  11. docker = pytest.importorskip("docker")
  12. log = logging.getLogger(__name__)
  13. @pytest.fixture(scope="module")
  14. def docker_client():
  15. urllib3_connectionpool_handler = logging.getLogger("urllib3.connectionpool")
  16. urllib3_connectionpool_handler_level = urllib3_connectionpool_handler.level
  17. urllib3_connectionpool_handler.setLevel(logging.INFO)
  18. try:
  19. client = docker.from_env()
  20. connectable = SaltVirtMinionContainerFactory.client_connectable(client)
  21. if connectable is not True: # pragma: no cover
  22. pytest.skip(connectable)
  23. client.images.pull("quay.io/rst0git/virt-minion")
  24. yield client
  25. finally:
  26. urllib3_connectionpool_handler.setLevel(urllib3_connectionpool_handler_level)
  27. @pytest.fixture(scope="module")
  28. def salt_minion_script_path(salt_factories):
  29. return cli_scripts.generate_script(
  30. salt_factories.scripts_dir,
  31. "salt-minion",
  32. code_dir=salt_factories.code_dir,
  33. inject_coverage=salt_factories.inject_coverage,
  34. inject_sitecustomize=salt_factories.inject_sitecustomize,
  35. )
  36. @pytest.fixture(scope="module")
  37. def virt_minion_0_id():
  38. return "virt-minion-0"
  39. @pytest.fixture(scope="module")
  40. def virt_minion_1_id():
  41. return "virt-minion-1"
  42. @pytest.fixture(scope="module")
  43. def virt_minion_0(
  44. salt_factories,
  45. salt_master,
  46. docker_client,
  47. salt_minion_script_path,
  48. virt_minion_0_id,
  49. virt_minion_1_id,
  50. ):
  51. root_dir = salt_factories.get_root_dir_for_daemon(virt_minion_0_id)
  52. config_defaults = {
  53. "root_dir": str(root_dir),
  54. "id": virt_minion_0_id,
  55. "open_mode": True,
  56. "transport": salt_master.config["transport"],
  57. }
  58. config_overrides = {"user": "root"}
  59. config = SaltVirtMinionContainerFactory.configure(
  60. factories_manager=salt_factories,
  61. daemon_id=virt_minion_0_id,
  62. root_dir=root_dir,
  63. config_defaults=config_defaults,
  64. config_overrides=config_overrides,
  65. master=salt_master,
  66. )
  67. salt_factories.final_minion_config_tweaks(config)
  68. loaded_config = SaltVirtMinionContainerFactory.write_config(config)
  69. factory = SaltVirtMinionContainerFactory(
  70. name=virt_minion_0_id,
  71. image="quay.io/rst0git/virt-minion",
  72. docker_client=docker_client,
  73. config=loaded_config,
  74. cli_script_name=salt_minion_script_path,
  75. start_timeout=60,
  76. factories_manager=salt_factories,
  77. event_listener=salt_factories.event_listener,
  78. container_run_kwargs={
  79. "extra_hosts": {
  80. virt_minion_0_id: "127.0.0.1",
  81. virt_minion_1_id: "127.0.0.1",
  82. }
  83. },
  84. )
  85. factory.register_after_terminate_callback(
  86. pytest.helpers.remove_stale_minion_key, salt_master, factory.id
  87. )
  88. with factory.started():
  89. yield factory
  90. @pytest.fixture(scope="module")
  91. def virt_minion_1(
  92. salt_factories,
  93. salt_master,
  94. docker_client,
  95. salt_minion_script_path,
  96. virt_minion_0_id,
  97. virt_minion_1_id,
  98. ):
  99. root_dir = salt_factories.get_root_dir_for_daemon(virt_minion_1_id)
  100. config_defaults = {
  101. "root_dir": str(root_dir),
  102. "id": virt_minion_1_id,
  103. "open_mode": True,
  104. "transport": salt_master.config["transport"],
  105. }
  106. config_overrides = {"user": "root"}
  107. config = SaltVirtMinionContainerFactory.configure(
  108. factories_manager=salt_factories,
  109. daemon_id=virt_minion_1_id,
  110. root_dir=root_dir,
  111. config_defaults=config_defaults,
  112. config_overrides=config_overrides,
  113. master=salt_master,
  114. )
  115. salt_factories.final_minion_config_tweaks(config)
  116. loaded_config = SaltVirtMinionContainerFactory.write_config(config)
  117. factory = SaltVirtMinionContainerFactory(
  118. name=virt_minion_1_id,
  119. image="quay.io/rst0git/virt-minion",
  120. docker_client=docker_client,
  121. config=loaded_config,
  122. cli_script_name=salt_minion_script_path,
  123. start_timeout=60,
  124. factories_manager=salt_factories,
  125. event_listener=salt_factories.event_listener,
  126. container_run_kwargs={
  127. "extra_hosts": {
  128. virt_minion_0_id: "127.0.0.1",
  129. virt_minion_1_id: "127.0.0.1",
  130. }
  131. },
  132. )
  133. factory.register_after_terminate_callback(
  134. pytest.helpers.remove_stale_minion_key, salt_master, factory.id
  135. )
  136. with factory.started():
  137. yield factory
  138. @pytest.fixture(scope="module")
  139. def salt_cli(salt_master, virt_minion_0, virt_minion_1):
  140. return salt_master.get_salt_cli()
  141. @skip_if_binaries_missing("docker")
  142. @slowTest
  143. class TestVirtTest:
  144. """
  145. Test virt routines
  146. """
  147. cpu_models = [
  148. "none",
  149. "armv7l",
  150. "armv7b",
  151. "aarch64",
  152. "i686",
  153. "ppc64",
  154. "ppc64le",
  155. "riscv32",
  156. "riscv64",
  157. "s390",
  158. "s390x",
  159. "x86_64",
  160. ]
  161. def test_default_kvm_profile(self, salt_cli, virt_minion_0):
  162. """
  163. Test virt.get_profiles with the KVM profile
  164. """
  165. ret = salt_cli.run("virt.get_profiles", "kvm", minion_tgt=virt_minion_0.id)
  166. assert ret.exitcode == 0, ret
  167. profiles = ret.json
  168. assert isinstance(profiles, dict)
  169. nic = profiles["nic"]["default"][0]
  170. disk = profiles["disk"]["default"][0]
  171. assert nic["name"] == "eth0"
  172. assert nic["type"] == "bridge"
  173. assert nic["model"] == "virtio"
  174. assert nic["source"] == "br0"
  175. assert disk["name"] == "system"
  176. assert disk["model"] == "virtio"
  177. assert disk["size"] == 8192
  178. def test_default_vmware_profile(self, salt_cli, virt_minion_0):
  179. """
  180. Test virt.get_profiles with the VMware profile
  181. """
  182. ret = salt_cli.run("virt.get_profiles", "vmware", minion_tgt=virt_minion_0.id)
  183. assert ret.exitcode == 0, ret
  184. profiles = ret.json
  185. assert isinstance(profiles, dict)
  186. nic = profiles["nic"]["default"][0]
  187. disk = profiles["disk"]["default"][0]
  188. assert nic["name"] == "eth0"
  189. assert nic["type"] == "bridge"
  190. assert nic["model"] == "e1000"
  191. assert nic["source"] == "DEFAULT"
  192. assert disk["name"] == "system"
  193. assert disk["model"] == "scsi"
  194. assert disk["format"] == "vmdk"
  195. assert disk["size"] == 8192
  196. def test_default_xen_profile(self, salt_cli, virt_minion_0):
  197. """
  198. Test virt.get_profiles with the XEN profile
  199. """
  200. ret = salt_cli.run("virt.get_profiles", "xen", minion_tgt=virt_minion_0.id)
  201. assert ret.exitcode == 0, ret
  202. profiles = ret.json
  203. assert isinstance(profiles, dict)
  204. nic = profiles["nic"]["default"][0]
  205. disk = profiles["disk"]["default"][0]
  206. assert nic["name"] == "eth0"
  207. assert nic["type"] == "bridge"
  208. assert nic["model"] is None
  209. assert nic["source"] == "br0"
  210. assert disk["name"] == "system"
  211. assert disk["model"] == "xen"
  212. assert disk["size"] == 8192
  213. def test_default_bhyve_profile(self, salt_cli, virt_minion_0):
  214. """
  215. Test virt.get_profiles with the Bhyve profile
  216. """
  217. ret = salt_cli.run("virt.get_profiles", "bhyve", minion_tgt=virt_minion_0.id)
  218. assert ret.exitcode == 0, ret
  219. profiles = ret.json
  220. assert isinstance(profiles, dict)
  221. nic = profiles["nic"]["default"][0]
  222. disk = profiles["disk"]["default"][0]
  223. assert nic["name"] == "eth0"
  224. assert nic["type"] == "bridge"
  225. assert nic["model"] == "virtio"
  226. assert nic["source"] == "bridge0"
  227. assert disk["name"] == "system"
  228. assert disk["model"] == "virtio"
  229. assert disk["format"] == "raw"
  230. assert disk["sparse_volume"] is False
  231. assert disk["size"] == 8192
  232. def test_all_capabilities(self, salt_cli, virt_minion_0):
  233. """
  234. Test virt.all_capabilities
  235. """
  236. ret = salt_cli.run("virt.all_capabilities", minion_tgt=virt_minion_0.id)
  237. assert ret.exitcode == 0, ret
  238. caps = ret.json
  239. assert isinstance(caps, dict)
  240. assert isinstance(caps["host"]["host"]["uuid"], str)
  241. assert len(caps["host"]["host"]["uuid"]) == 36
  242. assert "qemu" in [domainCaps["domain"] for domainCaps in caps["domains"]]
  243. def test_capabilities(self, salt_cli, virt_minion_0):
  244. """
  245. Test virt.capabilities
  246. """
  247. ret = salt_cli.run("virt.capabilities", minion_tgt=virt_minion_0.id)
  248. assert ret.exitcode == 0, ret
  249. caps = ret.json
  250. assert isinstance(caps, dict)
  251. assert isinstance(caps["host"]["uuid"], str)
  252. assert len(caps["host"]["uuid"]) == 36
  253. assert len(caps["guests"]) >= 1
  254. assert caps["guests"][0]["os_type"] in ["hvm", "xen", "xenpvh", "exe"]
  255. def test_cpu_baseline(self, salt_cli, virt_minion_0):
  256. """
  257. Test virt.cpu_baseline
  258. """
  259. vendors = ["Intel", "ARM", "AMD"]
  260. ret = salt_cli.run(
  261. "virt.cpu_baseline", out="libvirt", minion_tgt=virt_minion_0.id
  262. )
  263. assert ret.exitcode == 0, ret
  264. cpu_baseline = ret.json
  265. assert isinstance(cpu_baseline, str)
  266. cpu_baseline = ElementTree.fromstring(cpu_baseline)
  267. assert cpu_baseline.find("vendor").text in vendors
  268. ret = salt_cli.run("virt.cpu_baseline", out="salt", minion_tgt=virt_minion_0.id)
  269. assert ret.exitcode == 0, ret
  270. cpu_baseline = ret.json
  271. assert isinstance(cpu_baseline, dict)
  272. assert cpu_baseline["vendor"] in vendors
  273. def test_freemem(self, salt_cli, virt_minion_0):
  274. """
  275. Test virt.freemem
  276. """
  277. ret = salt_cli.run("virt.freemem", minion_tgt=virt_minion_0.id)
  278. assert ret.exitcode == 0, ret
  279. available_memory = ret.json
  280. assert isinstance(available_memory, Number)
  281. assert available_memory > 0
  282. def test_freecpu(self, salt_cli, virt_minion_0):
  283. """
  284. Test virt.freecpu
  285. """
  286. ret = salt_cli.run("virt.freecpu", minion_tgt=virt_minion_0.id)
  287. assert ret.exitcode == 0, ret
  288. available_cpus = ret.json
  289. assert isinstance(available_cpus, Number)
  290. assert available_cpus > 0
  291. def test_full_info(self, salt_cli, virt_minion_0):
  292. """
  293. Test virt.full_info
  294. """
  295. ret = salt_cli.run("virt.full_info", minion_tgt=virt_minion_0.id)
  296. assert ret.exitcode == 0, ret
  297. info = ret.json
  298. assert isinstance(info, dict)
  299. assert isinstance(info["vm_info"], dict)
  300. assert isinstance(info["freecpu"], Number)
  301. assert isinstance(info["freemem"], Number)
  302. assert info["freecpu"] > 0
  303. assert info["freemem"] > 0
  304. assert isinstance(info["node_info"], dict)
  305. assert isinstance(info["node_info"]["cpucores"], Number)
  306. assert isinstance(info["node_info"]["cpumhz"], Number)
  307. assert isinstance(info["node_info"]["cpus"], Number)
  308. assert isinstance(info["node_info"]["cputhreads"], Number)
  309. assert isinstance(info["node_info"]["numanodes"], Number)
  310. assert isinstance(info["node_info"]["phymemory"], Number)
  311. assert info["node_info"]["cpumodel"] in self.cpu_models
  312. def test_node_info(self, salt_cli, virt_minion_0):
  313. """
  314. Test virt.node_info
  315. """
  316. ret = salt_cli.run("virt.node_info", minion_tgt=virt_minion_0.id)
  317. assert ret.exitcode == 0, ret
  318. info = ret.json
  319. assert isinstance(info, dict)
  320. assert isinstance(info["cpucores"], Number)
  321. assert isinstance(info["cpumhz"], Number)
  322. assert isinstance(info["cpus"], Number)
  323. assert isinstance(info["cputhreads"], Number)
  324. assert isinstance(info["numanodes"], Number)
  325. assert isinstance(info["phymemory"], Number)
  326. assert isinstance(info["sockets"], Number)
  327. assert info["cpumodel"] in self.cpu_models
  328. @pytest.fixture(scope="module")
  329. def virt_domain():
  330. return "core-vm"
  331. @pytest.fixture
  332. def prep_virt(salt_cli, virt_minion_0, virt_minion_1, virt_domain):
  333. try:
  334. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_0.id)
  335. assert ret.exitcode == 0, ret
  336. domains = ret.json
  337. for domain in domains:
  338. salt_cli.run("virt.stop", virt_domain, minion_tgt=virt_minion_0.id)
  339. salt_cli.run("virt.undefine", virt_domain, minion_tgt=virt_minion_0.id)
  340. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_1.id)
  341. assert ret.exitcode == 0, ret
  342. domains = ret.json
  343. for domain in domains:
  344. salt_cli.run("virt.stop", virt_domain, minion_tgt=virt_minion_1.id)
  345. salt_cli.run("virt.undefine", virt_domain, minion_tgt=virt_minion_1.id)
  346. ret = salt_cli.run(
  347. "virt.define_xml_path",
  348. "/{}.xml".format(virt_domain),
  349. minion_tgt=virt_minion_0.id,
  350. )
  351. assert ret.exitcode == 0, ret
  352. ret = salt_cli.run("virt.start", virt_domain, minion_tgt=virt_minion_0.id)
  353. assert ret.exitcode == 0, ret
  354. # Run tests
  355. yield
  356. finally:
  357. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_0.id)
  358. assert ret.exitcode == 0, ret
  359. domains = ret.json
  360. for domain in domains:
  361. salt_cli.run("virt.stop", virt_domain, minion_tgt=virt_minion_0.id)
  362. salt_cli.run("virt.undefine", virt_domain, minion_tgt=virt_minion_0.id)
  363. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_1.id)
  364. assert ret.exitcode == 0, ret
  365. domains = ret.json
  366. for domain in domains:
  367. salt_cli.run("virt.stop", virt_domain, minion_tgt=virt_minion_1.id)
  368. salt_cli.run("virt.undefine", virt_domain, minion_tgt=virt_minion_1.id)
  369. @skip_if_binaries_missing("docker")
  370. @slowTest
  371. class TestVirtMigrateTest:
  372. def test_define_xml_path(self, salt_cli, virt_minion_0, virt_domain):
  373. """
  374. Define a new domain with virt.define_xml_path,
  375. verify that the new domain is shown with virt.list_domains,
  376. remove the domain with virt.undefine, and verifies that
  377. domain is no longer shown with virt.list_domains.
  378. """
  379. ret = salt_cli.run(
  380. "virt.define_xml_path",
  381. "/{}.xml".format(virt_domain),
  382. minion_tgt=virt_minion_0.id,
  383. )
  384. assert ret.exitcode == 0, ret
  385. result = ret.json
  386. assert isinstance(result, bool), result
  387. assert result is True, result
  388. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_0.id)
  389. assert ret.exitcode == 0, ret
  390. domains = ret.json
  391. assert isinstance(domains, list)
  392. assert domains == [virt_domain]
  393. ret = salt_cli.run("virt.undefine", virt_domain, minion_tgt=virt_minion_0.id)
  394. assert ret.exitcode == 0, ret
  395. result = ret.json
  396. assert isinstance(result, bool)
  397. assert result is True
  398. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_0.id)
  399. assert ret.exitcode == 0, ret
  400. domains = ret.json
  401. assert isinstance(domains, list)
  402. assert domains == []
  403. def test_ssh_migration(
  404. self, salt_cli, virt_minion_0, virt_minion_1, prep_virt, virt_domain
  405. ):
  406. """
  407. Test domain migration over SSH, TCP and TLS transport protocol
  408. """
  409. ret = salt_cli.run("virt.list_active_vms", minion_tgt=virt_minion_0.id)
  410. assert ret.exitcode == 0, ret
  411. ret = salt_cli.run("virt.list_active_vms", minion_tgt=virt_minion_1.id)
  412. assert ret.exitcode == 0, ret
  413. ret = salt_cli.run("virt.vm_info", virt_domain, minion_tgt=virt_minion_0.id)
  414. assert ret.exitcode == 0, ret
  415. # Verify that the VM has been created
  416. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_0.id)
  417. assert ret.exitcode == 0, ret
  418. domains = ret.json
  419. assert isinstance(domains, list)
  420. assert domains == [virt_domain]
  421. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_1.id)
  422. assert ret.exitcode == 0, ret
  423. domains = ret.json
  424. assert isinstance(domains, list)
  425. assert domains == []
  426. ret = salt_cli.run(
  427. "virt.migrate",
  428. virt_domain,
  429. virt_minion_1.uri,
  430. ssh=True,
  431. minion_tgt=virt_minion_0.id,
  432. )
  433. assert ret.exitcode == 0, ret
  434. result = ret.json
  435. assert isinstance(result, bool)
  436. assert result is True
  437. # Verify that the VM has been migrated
  438. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_0.id)
  439. assert ret.exitcode == 0, ret
  440. domains = ret.json
  441. assert isinstance(domains, list)
  442. assert domains == [], "Failed to migrate VM"
  443. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_1.id)
  444. assert ret.exitcode == 0, ret
  445. domains = ret.json
  446. assert isinstance(domains, list)
  447. assert domains == [virt_domain], "Failed to migrate VM"
  448. def test_tcp_migration(
  449. self, salt_cli, virt_minion_0, virt_minion_1, prep_virt, virt_domain
  450. ):
  451. """
  452. Test domain migration over SSH, TCP and TLS transport protocol
  453. """
  454. # Verify that the VM has been created
  455. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_0.id)
  456. assert ret.exitcode == 0, ret
  457. domains = ret.json
  458. assert isinstance(domains, list)
  459. assert domains == [virt_domain]
  460. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_1.id)
  461. assert ret.exitcode == 0, ret
  462. domains = ret.json
  463. assert isinstance(domains, list)
  464. assert domains == []
  465. ret = salt_cli.run(
  466. "virt.migrate",
  467. virt_domain,
  468. virt_minion_1.tcp_uri,
  469. minion_tgt=virt_minion_0.id,
  470. )
  471. assert ret.exitcode == 0, ret
  472. result = ret.json
  473. assert isinstance(result, bool)
  474. assert result is True
  475. # Verify that the VM has been migrated
  476. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_0.id)
  477. assert ret.exitcode == 0, ret
  478. domains = ret.json
  479. assert isinstance(domains, list)
  480. assert domains == [], "Failed to migrate VM"
  481. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_1.id)
  482. assert ret.exitcode == 0, ret
  483. domains = ret.json
  484. assert isinstance(domains, list)
  485. assert domains == [virt_domain], "Failed to migrate VM"
  486. def test_tls_migration(
  487. self, salt_cli, virt_minion_0, virt_minion_1, prep_virt, virt_domain
  488. ):
  489. """
  490. Test domain migration over SSH, TCP and TLS transport protocol
  491. """
  492. # Verify that the VM has been created
  493. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_0.id)
  494. assert ret.exitcode == 0, ret
  495. domains = ret.json
  496. assert isinstance(domains, list)
  497. assert domains == [virt_domain]
  498. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_1.id)
  499. assert ret.exitcode == 0, ret
  500. domains = ret.json
  501. assert isinstance(domains, list)
  502. assert domains == []
  503. ret = salt_cli.run(
  504. "virt.migrate",
  505. virt_domain,
  506. virt_minion_1.tls_uri,
  507. minion_tgt=virt_minion_0.id,
  508. )
  509. assert ret.exitcode == 0, ret
  510. result = ret.json
  511. assert isinstance(result, bool)
  512. assert result is True
  513. # Verify that the VM has been migrated
  514. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_0.id)
  515. assert ret.exitcode == 0, ret
  516. domains = ret.json
  517. assert isinstance(domains, list)
  518. assert domains == [], "Failed to migrate VM"
  519. ret = salt_cli.run("virt.list_domains", minion_tgt=virt_minion_1.id)
  520. assert ret.exitcode == 0, ret
  521. domains = ret.json
  522. assert isinstance(domains, list)
  523. assert domains == [virt_domain], "Failed to migrate VM"