fixtures.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. # -*- coding: utf-8 -*-
  2. """
  3. tests.support.pytest.fixtures
  4. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  5. The purpose of this fixtures module is provide the same set of available fixture for the old unittest
  6. test suite under ``test/integration``, ``tests/multimaster`` and ``tests/unit``.
  7. Please refrain from adding fixtures to this module and instead add them to the appropriate
  8. ``conftest.py`` file.
  9. """
  10. import os
  11. import shutil
  12. import stat
  13. import sys
  14. import pytest
  15. import salt.utils.files
  16. from salt.serializers import yaml
  17. from salt.utils.immutabletypes import freeze
  18. from tests.support.runtests import RUNTIME_VARS
  19. def _get_virtualenv_binary_path():
  20. try:
  21. return _get_virtualenv_binary_path.__virtualenv_binary__
  22. except AttributeError:
  23. # Under windows we can't seem to properly create a virtualenv off of another
  24. # virtualenv, we can on linux but we will still point to the virtualenv binary
  25. # outside the virtualenv running the test suite, if that's the case.
  26. try:
  27. real_prefix = sys.real_prefix
  28. # The above attribute exists, this is a virtualenv
  29. if salt.utils.platform.is_windows():
  30. virtualenv_binary = os.path.join(
  31. real_prefix, "Scripts", "virtualenv.exe"
  32. )
  33. else:
  34. # We need to remove the virtualenv from PATH or we'll get the virtualenv binary
  35. # from within the virtualenv, we don't want that
  36. path = os.environ.get("PATH")
  37. if path is not None:
  38. path_items = path.split(os.pathsep)
  39. for item in path_items[:]:
  40. if item.startswith(sys.base_prefix):
  41. path_items.remove(item)
  42. os.environ["PATH"] = os.pathsep.join(path_items)
  43. virtualenv_binary = salt.utils.path.which("virtualenv")
  44. if path is not None:
  45. # Restore previous environ PATH
  46. os.environ["PATH"] = path
  47. if not virtualenv_binary.startswith(real_prefix):
  48. virtualenv_binary = None
  49. if virtualenv_binary and not os.path.exists(virtualenv_binary):
  50. # It doesn't exist?!
  51. virtualenv_binary = None
  52. except AttributeError:
  53. # We're not running inside a virtualenv
  54. virtualenv_binary = None
  55. _get_virtualenv_binary_path.__virtualenv_binary__ = virtualenv_binary
  56. return virtualenv_binary
  57. @pytest.fixture(scope="session")
  58. def integration_files_dir(salt_factories):
  59. """
  60. Fixture which returns the salt integration files directory path.
  61. Creates the directory if it does not yet exist.
  62. """
  63. dirname = salt_factories.root_dir.join("integration-files")
  64. dirname.ensure(dir=True)
  65. return dirname
  66. @pytest.fixture(scope="session")
  67. def state_tree_root_dir(integration_files_dir):
  68. """
  69. Fixture which returns the salt state tree root directory path.
  70. Creates the directory if it does not yet exist.
  71. """
  72. dirname = integration_files_dir.join("state-tree")
  73. dirname.ensure(dir=True)
  74. return dirname
  75. @pytest.fixture(scope="session")
  76. def pillar_tree_root_dir(integration_files_dir):
  77. """
  78. Fixture which returns the salt pillar tree root directory path.
  79. Creates the directory if it does not yet exist.
  80. """
  81. dirname = integration_files_dir.join("pillar-tree")
  82. dirname.ensure(dir=True)
  83. return dirname
  84. @pytest.fixture(scope="session")
  85. def base_env_state_tree_root_dir(state_tree_root_dir):
  86. """
  87. Fixture which returns the salt base environment state tree directory path.
  88. Creates the directory if it does not yet exist.
  89. """
  90. dirname = state_tree_root_dir.join("base")
  91. dirname.ensure(dir=True)
  92. RUNTIME_VARS.TMP_STATE_TREE = dirname.realpath().strpath
  93. RUNTIME_VARS.TMP_BASEENV_STATE_TREE = RUNTIME_VARS.TMP_STATE_TREE
  94. return dirname
  95. @pytest.fixture(scope="session")
  96. def prod_env_state_tree_root_dir(state_tree_root_dir):
  97. """
  98. Fixture which returns the salt prod environment state tree directory path.
  99. Creates the directory if it does not yet exist.
  100. """
  101. dirname = state_tree_root_dir.join("prod")
  102. dirname.ensure(dir=True)
  103. RUNTIME_VARS.TMP_PRODENV_STATE_TREE = dirname.realpath().strpath
  104. return dirname
  105. @pytest.fixture(scope="session")
  106. def base_env_pillar_tree_root_dir(pillar_tree_root_dir):
  107. """
  108. Fixture which returns the salt base environment pillar tree directory path.
  109. Creates the directory if it does not yet exist.
  110. """
  111. dirname = pillar_tree_root_dir.join("base")
  112. dirname.ensure(dir=True)
  113. RUNTIME_VARS.TMP_PILLAR_TREE = dirname.realpath().strpath
  114. RUNTIME_VARS.TMP_BASEENV_PILLAR_TREE = RUNTIME_VARS.TMP_PILLAR_TREE
  115. return dirname
  116. @pytest.fixture(scope="session")
  117. def prod_env_pillar_tree_root_dir(pillar_tree_root_dir):
  118. """
  119. Fixture which returns the salt prod environment pillar tree directory path.
  120. Creates the directory if it does not yet exist.
  121. """
  122. dirname = pillar_tree_root_dir.join("prod")
  123. dirname.ensure(dir=True)
  124. RUNTIME_VARS.TMP_PRODENV_PILLAR_TREE = dirname.realpath().strpath
  125. return dirname
  126. @pytest.fixture(scope="session")
  127. def salt_syndic_master_config(request, salt_factories):
  128. root_dir = salt_factories._get_root_dir_for_daemon("syndic_master")
  129. with salt.utils.files.fopen(
  130. os.path.join(RUNTIME_VARS.CONF_DIR, "syndic_master")
  131. ) as rfh:
  132. config_defaults = yaml.deserialize(rfh.read())
  133. tests_known_hosts_file = root_dir.join("salt_ssh_known_hosts").strpath
  134. with salt.utils.files.fopen(tests_known_hosts_file, "w") as known_hosts:
  135. known_hosts.write("")
  136. config_defaults["root_dir"] = root_dir.strpath
  137. config_defaults["known_hosts_file"] = tests_known_hosts_file
  138. config_defaults["syndic_master"] = "localhost"
  139. config_defaults["transport"] = request.config.getoption("--transport")
  140. config_overrides = {}
  141. ext_pillar = []
  142. if salt.utils.platform.is_windows():
  143. ext_pillar.append(
  144. {
  145. "cmd_yaml": "type {0}".format(
  146. os.path.join(RUNTIME_VARS.FILES, "ext.yaml")
  147. )
  148. }
  149. )
  150. else:
  151. ext_pillar.append(
  152. {"cmd_yaml": "cat {0}".format(os.path.join(RUNTIME_VARS.FILES, "ext.yaml"))}
  153. )
  154. # We need to copy the extension modules into the new master root_dir or
  155. # it will be prefixed by it
  156. extension_modules_path = root_dir.join("extension_modules").strpath
  157. if not os.path.exists(extension_modules_path):
  158. shutil.copytree(
  159. os.path.join(RUNTIME_VARS.FILES, "extension_modules"),
  160. extension_modules_path,
  161. )
  162. # Copy the autosign_file to the new master root_dir
  163. autosign_file_path = root_dir.join("autosign_file").strpath
  164. shutil.copyfile(
  165. os.path.join(RUNTIME_VARS.FILES, "autosign_file"), autosign_file_path
  166. )
  167. # all read, only owner write
  168. autosign_file_permissions = (
  169. stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR
  170. )
  171. os.chmod(autosign_file_path, autosign_file_permissions)
  172. config_overrides.update(
  173. {
  174. "ext_pillar": ext_pillar,
  175. "extension_modules": extension_modules_path,
  176. "file_roots": {
  177. "base": [
  178. RUNTIME_VARS.TMP_STATE_TREE,
  179. os.path.join(RUNTIME_VARS.FILES, "file", "base"),
  180. ],
  181. # Alternate root to test __env__ choices
  182. "prod": [
  183. RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
  184. os.path.join(RUNTIME_VARS.FILES, "file", "prod"),
  185. ],
  186. },
  187. "pillar_roots": {
  188. "base": [
  189. RUNTIME_VARS.TMP_PILLAR_TREE,
  190. os.path.join(RUNTIME_VARS.FILES, "pillar", "base"),
  191. ],
  192. "prod": [RUNTIME_VARS.TMP_PRODENV_PILLAR_TREE],
  193. },
  194. }
  195. )
  196. return salt_factories.configure_master(
  197. request,
  198. "syndic_master",
  199. order_masters=True,
  200. config_defaults=config_defaults,
  201. config_overrides=config_overrides,
  202. )
  203. @pytest.fixture(scope="session")
  204. def salt_syndic_config(request, salt_factories, salt_syndic_master_config):
  205. return salt_factories.configure_syndic(
  206. request, "syndic", master_of_masters_id="syndic_master"
  207. )
  208. @pytest.fixture(scope="session")
  209. def salt_master_config(request, salt_factories, salt_syndic_master_config):
  210. root_dir = salt_factories._get_root_dir_for_daemon("master")
  211. conf_dir = root_dir.join("conf").ensure(dir=True)
  212. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, "master")) as rfh:
  213. config_defaults = yaml.deserialize(rfh.read())
  214. tests_known_hosts_file = root_dir.join("salt_ssh_known_hosts").strpath
  215. with salt.utils.files.fopen(tests_known_hosts_file, "w") as known_hosts:
  216. known_hosts.write("")
  217. config_defaults["root_dir"] = root_dir.strpath
  218. config_defaults["known_hosts_file"] = tests_known_hosts_file
  219. config_defaults["syndic_master"] = "localhost"
  220. config_defaults["transport"] = request.config.getoption("--transport")
  221. config_defaults["reactor"] = [
  222. {"salt/test/reactor": [os.path.join(RUNTIME_VARS.FILES, "reactor-test.sls")]}
  223. ]
  224. config_overrides = {}
  225. ext_pillar = []
  226. if salt.utils.platform.is_windows():
  227. ext_pillar.append(
  228. {
  229. "cmd_yaml": "type {0}".format(
  230. os.path.join(RUNTIME_VARS.FILES, "ext.yaml")
  231. )
  232. }
  233. )
  234. else:
  235. ext_pillar.append(
  236. {"cmd_yaml": "cat {0}".format(os.path.join(RUNTIME_VARS.FILES, "ext.yaml"))}
  237. )
  238. ext_pillar.append(
  239. {
  240. "file_tree": {
  241. "root_dir": os.path.join(RUNTIME_VARS.PILLAR_DIR, "base", "file_tree"),
  242. "follow_dir_links": False,
  243. "keep_newline": True,
  244. }
  245. }
  246. )
  247. config_overrides["pillar_opts"] = True
  248. # We need to copy the extension modules into the new master root_dir or
  249. # it will be prefixed by it
  250. extension_modules_path = root_dir.join("extension_modules").strpath
  251. if not os.path.exists(extension_modules_path):
  252. shutil.copytree(
  253. os.path.join(RUNTIME_VARS.FILES, "extension_modules"),
  254. extension_modules_path,
  255. )
  256. # Copy the autosign_file to the new master root_dir
  257. autosign_file_path = root_dir.join("autosign_file").strpath
  258. shutil.copyfile(
  259. os.path.join(RUNTIME_VARS.FILES, "autosign_file"), autosign_file_path
  260. )
  261. # all read, only owner write
  262. autosign_file_permissions = (
  263. stat.S_IRUSR | stat.S_IRGRP | stat.S_IROTH | stat.S_IWUSR
  264. )
  265. os.chmod(autosign_file_path, autosign_file_permissions)
  266. config_overrides.update(
  267. {
  268. "ext_pillar": ext_pillar,
  269. "extension_modules": extension_modules_path,
  270. "file_roots": {
  271. "base": [
  272. RUNTIME_VARS.TMP_STATE_TREE,
  273. os.path.join(RUNTIME_VARS.FILES, "file", "base"),
  274. ],
  275. # Alternate root to test __env__ choices
  276. "prod": [
  277. RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
  278. os.path.join(RUNTIME_VARS.FILES, "file", "prod"),
  279. ],
  280. },
  281. "pillar_roots": {
  282. "base": [
  283. RUNTIME_VARS.TMP_PILLAR_TREE,
  284. os.path.join(RUNTIME_VARS.FILES, "pillar", "base"),
  285. ],
  286. "prod": [RUNTIME_VARS.TMP_PRODENV_PILLAR_TREE],
  287. },
  288. }
  289. )
  290. # Let's copy over the test cloud config files and directories into the running master config directory
  291. for entry in os.listdir(RUNTIME_VARS.CONF_DIR):
  292. if not entry.startswith("cloud"):
  293. continue
  294. source = os.path.join(RUNTIME_VARS.CONF_DIR, entry)
  295. dest = conf_dir.join(entry).strpath
  296. if os.path.isdir(source):
  297. shutil.copytree(source, dest)
  298. else:
  299. shutil.copyfile(source, dest)
  300. return salt_factories.configure_master(
  301. request,
  302. "master",
  303. master_of_masters_id="syndic_master",
  304. config_defaults=config_defaults,
  305. config_overrides=config_overrides,
  306. )
  307. @pytest.fixture(scope="session")
  308. def salt_minion_config(request, salt_factories, salt_master_config):
  309. with salt.utils.files.fopen(os.path.join(RUNTIME_VARS.CONF_DIR, "minion")) as rfh:
  310. config_defaults = yaml.deserialize(rfh.read())
  311. config_defaults["hosts.file"] = os.path.join(RUNTIME_VARS.TMP, "hosts")
  312. config_defaults["aliases.file"] = os.path.join(RUNTIME_VARS.TMP, "aliases")
  313. config_defaults["transport"] = request.config.getoption("--transport")
  314. config_overrides = {
  315. "file_roots": {
  316. "base": [
  317. RUNTIME_VARS.TMP_STATE_TREE,
  318. os.path.join(RUNTIME_VARS.FILES, "file", "base"),
  319. ],
  320. # Alternate root to test __env__ choices
  321. "prod": [
  322. RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
  323. os.path.join(RUNTIME_VARS.FILES, "file", "prod"),
  324. ],
  325. },
  326. "pillar_roots": {
  327. "base": [
  328. RUNTIME_VARS.TMP_PILLAR_TREE,
  329. os.path.join(RUNTIME_VARS.FILES, "pillar", "base"),
  330. ],
  331. "prod": [RUNTIME_VARS.TMP_PRODENV_PILLAR_TREE],
  332. },
  333. }
  334. virtualenv_binary = _get_virtualenv_binary_path()
  335. if virtualenv_binary:
  336. config_overrides["venv_bin"] = virtualenv_binary
  337. return salt_factories.configure_minion(
  338. request,
  339. "minion",
  340. master_id="master",
  341. config_defaults=config_defaults,
  342. config_overrides=config_overrides,
  343. )
  344. @pytest.fixture(scope="session")
  345. def salt_sub_minion_config(request, salt_factories, salt_master_config):
  346. with salt.utils.files.fopen(
  347. os.path.join(RUNTIME_VARS.CONF_DIR, "sub_minion")
  348. ) as rfh:
  349. config_defaults = yaml.deserialize(rfh.read())
  350. config_defaults["hosts.file"] = os.path.join(RUNTIME_VARS.TMP, "hosts")
  351. config_defaults["aliases.file"] = os.path.join(RUNTIME_VARS.TMP, "aliases")
  352. config_defaults["transport"] = request.config.getoption("--transport")
  353. config_overrides = {
  354. "file_roots": {
  355. "base": [
  356. RUNTIME_VARS.TMP_STATE_TREE,
  357. os.path.join(RUNTIME_VARS.FILES, "file", "base"),
  358. ],
  359. # Alternate root to test __env__ choices
  360. "prod": [
  361. RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
  362. os.path.join(RUNTIME_VARS.FILES, "file", "prod"),
  363. ],
  364. },
  365. "pillar_roots": {
  366. "base": [
  367. RUNTIME_VARS.TMP_PILLAR_TREE,
  368. os.path.join(RUNTIME_VARS.FILES, "pillar", "base"),
  369. ],
  370. "prod": [RUNTIME_VARS.TMP_PRODENV_PILLAR_TREE],
  371. },
  372. }
  373. virtualenv_binary = _get_virtualenv_binary_path()
  374. if virtualenv_binary:
  375. config_overrides["venv_bin"] = virtualenv_binary
  376. return salt_factories.configure_minion(
  377. request,
  378. "sub_minion",
  379. master_id="master",
  380. config_defaults=config_defaults,
  381. config_overrides=config_overrides,
  382. )
  383. @pytest.hookspec(firstresult=True)
  384. def pytest_saltfactories_syndic_configuration_defaults(
  385. request, factories_manager, root_dir, syndic_id, syndic_master_port
  386. ):
  387. """
  388. Hook which should return a dictionary tailored for the provided syndic_id with 3 keys:
  389. * `master`: The default config for the master running along with the syndic
  390. * `minion`: The default config for the master running along with the syndic
  391. * `syndic`: The default config for the master running along with the syndic
  392. Stops at the first non None result
  393. """
  394. factory_opts = {"master": None, "minion": None, "syndic": None}
  395. if syndic_id == "syndic":
  396. with salt.utils.files.fopen(
  397. os.path.join(RUNTIME_VARS.CONF_DIR, "syndic")
  398. ) as rfh:
  399. opts = yaml.deserialize(rfh.read())
  400. opts["hosts.file"] = os.path.join(RUNTIME_VARS.TMP, "hosts")
  401. opts["aliases.file"] = os.path.join(RUNTIME_VARS.TMP, "aliases")
  402. opts["transport"] = request.config.getoption("--transport")
  403. factory_opts["syndic"] = opts
  404. return factory_opts
  405. @pytest.hookspec(firstresult=True)
  406. def pytest_saltfactories_syndic_configuration_overrides(
  407. request, factories_manager, syndic_id, config_defaults
  408. ):
  409. """
  410. Hook which should return a dictionary tailored for the provided syndic_id.
  411. This dictionary will override the default_options dictionary.
  412. The returned dictionary should contain 3 keys:
  413. * `master`: The config overrides for the master running along with the syndic
  414. * `minion`: The config overrides for the master running along with the syndic
  415. * `syndic`: The config overridess for the master running along with the syndic
  416. The `default_options` parameter be None or have 3 keys, `master`, `minion`, `syndic`,
  417. while will contain the default options for each of the daemons.
  418. Stops at the first non None result
  419. """
  420. @pytest.fixture(scope="session", autouse=True)
  421. def bridge_pytest_and_runtests(
  422. reap_stray_processes,
  423. base_env_state_tree_root_dir,
  424. prod_env_state_tree_root_dir,
  425. base_env_pillar_tree_root_dir,
  426. prod_env_pillar_tree_root_dir,
  427. salt_factories,
  428. salt_syndic_master_config,
  429. salt_syndic_config,
  430. salt_master_config,
  431. salt_minion_config,
  432. salt_sub_minion_config,
  433. ):
  434. # Make sure unittest2 uses the pytest generated configuration
  435. RUNTIME_VARS.RUNTIME_CONFIGS["master"] = freeze(salt_master_config)
  436. RUNTIME_VARS.RUNTIME_CONFIGS["minion"] = freeze(salt_minion_config)
  437. RUNTIME_VARS.RUNTIME_CONFIGS["sub_minion"] = freeze(salt_sub_minion_config)
  438. RUNTIME_VARS.RUNTIME_CONFIGS["syndic_master"] = freeze(salt_syndic_master_config)
  439. RUNTIME_VARS.RUNTIME_CONFIGS["syndic"] = freeze(salt_syndic_config)
  440. RUNTIME_VARS.RUNTIME_CONFIGS["client_config"] = freeze(
  441. salt.config.client_config(salt_master_config["conf_file"])
  442. )
  443. # Make sure unittest2 classes know their paths
  444. RUNTIME_VARS.TMP_ROOT_DIR = salt_factories.root_dir.realpath().strpath
  445. RUNTIME_VARS.TMP_CONF_DIR = os.path.dirname(salt_master_config["conf_file"])
  446. RUNTIME_VARS.TMP_MINION_CONF_DIR = os.path.dirname(salt_minion_config["conf_file"])
  447. RUNTIME_VARS.TMP_SUB_MINION_CONF_DIR = os.path.dirname(
  448. salt_sub_minion_config["conf_file"]
  449. )
  450. RUNTIME_VARS.TMP_SYNDIC_MASTER_CONF_DIR = os.path.dirname(
  451. salt_syndic_master_config["conf_file"]
  452. )
  453. RUNTIME_VARS.TMP_SYNDIC_MINION_CONF_DIR = os.path.dirname(
  454. salt_syndic_config["conf_file"]
  455. )
  456. # Only allow star importing the functions defined in this module
  457. __all__ = [
  458. name
  459. for (name, func) in locals().items()
  460. if getattr(func, "__module__", None) == __name__
  461. ]