test_with_versions.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. # -*- coding: utf-8 -*-
  2. """
  3. tests.e2e.compat.test_with_versions
  4. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  5. Test current salt master with older salt minions
  6. """
  7. import io
  8. import logging
  9. import os
  10. import pathlib
  11. import attr
  12. import pytest
  13. from saltfactories.factories.daemons.docker import MinionDockerFactory
  14. from tests.support.helpers import random_string
  15. from tests.support.runtests import RUNTIME_VARS
  16. docker = pytest.importorskip("docker")
  17. log = logging.getLogger(__name__)
  18. @attr.s(kw_only=True, slots=True)
  19. class PySaltCombo:
  20. python_version = attr.ib()
  21. salt_version = attr.ib()
  22. DOCKERFILE = """
  23. FROM {from_container}
  24. ENV LANG=en_US.UTF8
  25. ENV VIRTUAL_ENV={virtualenv_path}
  26. RUN virtualenv --python=python{python_version} $VIRTUAL_ENV
  27. ENV PATH="$VIRTUAL_ENV/bin:$PATH"
  28. RUN pip install salt=={salt_version}
  29. {extra}
  30. CMD . $VIRTUAL_ENV/bin/activate
  31. """
  32. def _get_test_versions():
  33. test_versions = []
  34. for python_version in ("2", "3"):
  35. for salt_version in ("2017.7.8", "2018.3.5", "2019.2.4"):
  36. test_versions.append(
  37. PySaltCombo(python_version=python_version, salt_version=salt_version)
  38. )
  39. test_versions.append(PySaltCombo(python_version="3", salt_version="3000.1"))
  40. return test_versions
  41. def _get_test_versions_ids(pysaltcombo):
  42. return "Py{}-SaltMinion=={}".format(
  43. pysaltcombo.python_version, pysaltcombo.salt_version
  44. )
  45. @pytest.fixture(
  46. params=_get_test_versions(), ids=_get_test_versions_ids, scope="function"
  47. )
  48. def pysaltcombo(request):
  49. return request.param
  50. @pytest.fixture(scope="function")
  51. def container_virtualen_path():
  52. return "/tmp/venv"
  53. @pytest.fixture(scope="function")
  54. def minion_container_name(pysaltcombo):
  55. return "salt-py{}-{}".format(pysaltcombo.python_version, pysaltcombo.salt_version)
  56. @pytest.fixture(scope="function")
  57. def minion_container(
  58. docker_client, pysaltcombo, container_virtualen_path, minion_container_name
  59. ):
  60. extra = ""
  61. if pysaltcombo.salt_version.startswith(("2017.7.", "2018.3.", "2019.2.")):
  62. # We weren't pinning higher versions which we now know are problematic
  63. extra = "RUN pip install --ignore-installed --progress-bar=off -U "
  64. extra += (
  65. '"pyzmq<17.1.0,>=2.2.0" "tornado<5.0,>=4.2.1" "msgpack>=0.5,!=0.5.5,<1.0.0"'
  66. )
  67. dockerfile_contents = DOCKERFILE.format(
  68. from_container="saltstack/ci-centos-7",
  69. python_version=pysaltcombo.python_version,
  70. salt_version=pysaltcombo.salt_version,
  71. extra=extra,
  72. virtualenv_path=container_virtualen_path,
  73. )
  74. log.warning("GENERATED Dockerfile:\n%s", dockerfile_contents)
  75. dockerfile_fh = io.BytesIO(dockerfile_contents.encode("utf-8"))
  76. docker_image, logs = docker_client.images.build(
  77. fileobj=dockerfile_fh, tag=minion_container_name, pull=True,
  78. )
  79. log.warning("Image %s built. Logs:\n%s", minion_container_name, list(logs))
  80. return minion_container_name
  81. @pytest.fixture(scope="function")
  82. def minion_id(pysaltcombo):
  83. return random_string(
  84. "py{}-{}-".format(pysaltcombo.python_version, pysaltcombo.salt_version),
  85. uppercase=False,
  86. )
  87. @pytest.fixture(scope="function")
  88. def artefacts_path(minion_id):
  89. with pytest.helpers.temp_directory(minion_id) as temp_directory:
  90. yield temp_directory
  91. @pytest.mark.skip_if_binaries_missing("docker")
  92. @pytest.fixture(scope="function")
  93. def salt_minion(
  94. request,
  95. salt_factories,
  96. pysaltcombo,
  97. minion_id,
  98. salt_master,
  99. docker_client,
  100. artefacts_path,
  101. container_virtualen_path,
  102. minion_container,
  103. host_docker_network_ip_address,
  104. ):
  105. config_overrides = {
  106. "master": salt_master.config["interface"],
  107. "user": False,
  108. "pytest-minion": {"log": {"host": host_docker_network_ip_address}},
  109. }
  110. try:
  111. yield salt_factories.spawn_minion(
  112. request,
  113. minion_id,
  114. master_id=salt_master.config["id"],
  115. # config_defaults=config_defaults,
  116. config_overrides=config_overrides,
  117. factory_class=MinionDockerFactory,
  118. docker_client=docker_client,
  119. image=minion_container,
  120. name=minion_id,
  121. start_timeout=120,
  122. container_run_kwargs={
  123. "volumes": {artefacts_path: {"bind": "/artefacts", "mode": "z"}}
  124. },
  125. )
  126. finally:
  127. minion_key_file = os.path.join(
  128. salt_master.config["pki_dir"], "minions", minion_id
  129. )
  130. log.debug("Minion %r KEY FILE: %s", minion_id, minion_key_file)
  131. if os.path.exists(minion_key_file):
  132. os.unlink(minion_key_file)
  133. @pytest.fixture(scope="function")
  134. def package_name():
  135. return "comps-extras"
  136. @pytest.fixture
  137. def populated_state_tree(minion_id, package_name):
  138. module_contents = """
  139. def get_test_package_name():
  140. return "{}"
  141. """.format(
  142. package_name
  143. )
  144. top_file_contents = """
  145. base:
  146. {}:
  147. - install-package
  148. """.format(
  149. minion_id
  150. )
  151. install_package_sls_contents = """
  152. state-entry-does-not-contain-unicode:
  153. pkg.installed:
  154. - name: {{ salt.pkgnames.get_test_package_name() }}
  155. """
  156. with pytest.helpers.temp_file(
  157. name="pkgnames.py",
  158. directory=os.path.join(RUNTIME_VARS.TMP_BASEENV_STATE_TREE, "_modules"),
  159. contents=module_contents,
  160. ):
  161. with pytest.helpers.temp_state_file("top.sls", contents=top_file_contents):
  162. with pytest.helpers.temp_state_file(
  163. "install-package.sls", contents=install_package_sls_contents
  164. ):
  165. # Run the test
  166. yield
  167. @pytest.fixture
  168. def populated_state_tree_unicode(pysaltcombo, minion_id, package_name):
  169. if pysaltcombo.salt_version.startswith("2017.7."):
  170. pytest.xfail("2017.7 is know for problematic unicode handling on state files")
  171. module_contents = """
  172. def get_test_package_name():
  173. return "{}"
  174. """.format(
  175. package_name
  176. )
  177. top_file_contents = """
  178. base:
  179. {}:
  180. - install-package
  181. """.format(
  182. minion_id
  183. )
  184. install_package_sls_contents = """
  185. state-entry-contém-unicode:
  186. pkg.installed:
  187. - name: {{ salt.pkgnames.get_test_package_name() }}
  188. """
  189. with pytest.helpers.temp_file(
  190. name="pkgnames.py",
  191. directory=os.path.join(RUNTIME_VARS.TMP_BASEENV_STATE_TREE, "_modules"),
  192. contents=module_contents,
  193. ):
  194. with pytest.helpers.temp_state_file("top.sls", contents=top_file_contents):
  195. with pytest.helpers.temp_state_file(
  196. "install-package.sls", contents=install_package_sls_contents
  197. ):
  198. # Run the test
  199. yield
  200. def test_ping(salt_cli, minion_id, salt_minion):
  201. assert salt_minion.is_running()
  202. ret = salt_cli.run("test.ping", minion_tgt=minion_id)
  203. assert ret.exitcode == 0, ret
  204. assert ret.json is True
  205. def test_highstate(
  206. salt_cli, minion_id, salt_minion, package_name, populated_state_tree
  207. ):
  208. """
  209. Assert a state.highstate with a newer master runs properly on older minions.
  210. """
  211. assert salt_minion.is_running()
  212. ret = salt_cli.run("state.highstate", minion_tgt=minion_id, _timeout=240)
  213. assert ret.exitcode == 0, ret
  214. assert ret.json is not None
  215. assert isinstance(ret.json, dict), ret.json
  216. state_return = next(iter(ret.json.values()))
  217. assert package_name in state_return["changes"], state_return
  218. def test_highstate_with_unicode(
  219. salt_cli,
  220. minion_id,
  221. salt_minion,
  222. package_name,
  223. populated_state_tree_unicode,
  224. pysaltcombo,
  225. ):
  226. """
  227. Assert a state.highstate with a newer master runs properly on older minions.
  228. The highstate tree additionally contains unicode chars to assert they're properly hanbled
  229. """
  230. assert salt_minion.is_running()
  231. ret = salt_cli.run("state.highstate", minion_tgt=minion_id, _timeout=240)
  232. assert ret.exitcode == 0, ret
  233. assert ret.json is not None
  234. assert isinstance(ret.json, dict), ret.json
  235. state_return = next(iter(ret.json.values()))
  236. assert package_name in state_return["changes"], state_return
  237. @pytest.fixture
  238. def cp_file_source(pysaltcombo):
  239. if pysaltcombo.salt_version.startswith("2018.3."):
  240. pytest.xfail("2018.3 is know for unicode issues when copying files")
  241. source = pathlib.Path(RUNTIME_VARS.BASE_FILES) / "cheese"
  242. with pytest.helpers.temp_file(contents=source.read_text()) as temp_file:
  243. yield pathlib.Path(temp_file)
  244. def test_cp(
  245. salt_cp_cli, minion_id, salt_minion, package_name, artefacts_path, cp_file_source
  246. ):
  247. """
  248. Assert proper behaviour for salt-cp with a newer master and older minions.
  249. """
  250. assert salt_minion.is_running()
  251. remote_path = "/artefacts/cheese"
  252. ret = salt_cp_cli.run(
  253. str(cp_file_source), remote_path, minion_tgt=minion_id, _timeout=240
  254. )
  255. assert ret.exitcode == 0, ret
  256. assert ret.json is not None
  257. assert isinstance(ret.json, dict), ret.json
  258. assert ret.json == {remote_path: True}
  259. cp_file_dest = pathlib.Path(artefacts_path) / "cheese"
  260. assert cp_file_source.read_text() == cp_file_dest.read_text()
  261. @pytest.fixture
  262. def cp_file_source_unicode(pysaltcombo):
  263. source = pathlib.Path(RUNTIME_VARS.BASE_FILES) / "cheese"
  264. contents = source.read_text().replace("ee", "æ")
  265. with pytest.helpers.temp_file(contents=contents) as temp_file:
  266. yield pathlib.Path(temp_file)
  267. def test_cp_unicode(
  268. salt_cp_cli,
  269. minion_id,
  270. salt_minion,
  271. package_name,
  272. artefacts_path,
  273. cp_file_source_unicode,
  274. ):
  275. """
  276. Assert proper behaviour for salt-cp with a newer master and older minions.
  277. """
  278. assert salt_minion.is_running()
  279. remote_path = "/artefacts/cheese"
  280. ret = salt_cp_cli.run(
  281. str(cp_file_source_unicode), remote_path, minion_tgt=minion_id, _timeout=240
  282. )
  283. assert ret.exitcode == 0, ret
  284. assert ret.json is not None
  285. assert isinstance(ret.json, dict), ret.json
  286. assert ret.json == {remote_path: True}
  287. cp_file_dest = pathlib.Path(artefacts_path) / "cheese"
  288. assert cp_file_source_unicode.read_text() == cp_file_dest.read_text()