sminion.py 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. # -*- coding: utf-8 -*-
  2. """
  3. tests.support.sminion
  4. ~~~~~~~~~~~~~~~~~~~~~
  5. SMinion's support functions
  6. """
  7. from __future__ import absolute_import, print_function, unicode_literals
  8. import fnmatch
  9. import hashlib
  10. import logging
  11. import os
  12. import shutil
  13. import sys
  14. import salt.minion
  15. import salt.utils.path
  16. import salt.utils.stringutils
  17. from tests.support.runtests import RUNTIME_VARS
  18. log = logging.getLogger(__name__)
  19. DEFAULT_SMINION_ID = "pytest-internal-sminion"
  20. def build_minion_opts(
  21. minion_id=None,
  22. root_dir=None,
  23. initial_conf_file=None,
  24. minion_opts_overrides=None,
  25. skip_cached_opts=False,
  26. cache_opts=True,
  27. minion_role=None,
  28. ):
  29. if minion_id is None:
  30. minion_id = DEFAULT_SMINION_ID
  31. if skip_cached_opts is False:
  32. try:
  33. opts_cache = build_minion_opts.__cached_opts__
  34. except AttributeError:
  35. opts_cache = build_minion_opts.__cached_opts__ = {}
  36. cached_opts = opts_cache.get(minion_id)
  37. if cached_opts:
  38. return cached_opts
  39. log.info("Generating testing minion %r configuration...", minion_id)
  40. if root_dir is None:
  41. hashed_minion_id = hashlib.sha1()
  42. hashed_minion_id.update(salt.utils.stringutils.to_bytes(minion_id))
  43. root_dir = os.path.join(
  44. RUNTIME_VARS.TMP_ROOT_DIR, hashed_minion_id.hexdigest()[:6]
  45. )
  46. if initial_conf_file is not None:
  47. minion_opts = salt.config._read_conf_file(
  48. initial_conf_file
  49. ) # pylint: disable=protected-access
  50. else:
  51. minion_opts = {}
  52. conf_dir = os.path.join(root_dir, "conf")
  53. conf_file = os.path.join(conf_dir, "minion")
  54. minion_opts["id"] = minion_id
  55. minion_opts["conf_file"] = conf_file
  56. minion_opts["root_dir"] = root_dir
  57. minion_opts["cachedir"] = "cache"
  58. minion_opts["user"] = RUNTIME_VARS.RUNNING_TESTS_USER
  59. minion_opts["pki_dir"] = "pki"
  60. minion_opts["hosts.file"] = os.path.join(RUNTIME_VARS.TMP_ROOT_DIR, "hosts")
  61. minion_opts["aliases.file"] = os.path.join(RUNTIME_VARS.TMP_ROOT_DIR, "aliases")
  62. minion_opts["file_client"] = "local"
  63. minion_opts["server_id_use_crc"] = "adler32"
  64. minion_opts["pillar_roots"] = {"base": [RUNTIME_VARS.TMP_PILLAR_TREE]}
  65. minion_opts["file_roots"] = {
  66. "base": [
  67. # Let's support runtime created files that can be used like:
  68. # salt://my-temp-file.txt
  69. RUNTIME_VARS.TMP_STATE_TREE
  70. ],
  71. # Alternate root to test __env__ choices
  72. "prod": [
  73. os.path.join(RUNTIME_VARS.FILES, "file", "prod"),
  74. RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
  75. ],
  76. }
  77. if initial_conf_file and initial_conf_file.startswith(RUNTIME_VARS.FILES):
  78. # We assume we were passed a minion configuration file defined fo testing and, as such
  79. # we define the file and pillar roots to include the testing states/pillar trees
  80. minion_opts["pillar_roots"]["base"].append(
  81. os.path.join(RUNTIME_VARS.FILES, "pillar", "base"),
  82. )
  83. minion_opts["file_roots"]["base"].append(
  84. os.path.join(RUNTIME_VARS.FILES, "file", "base"),
  85. )
  86. minion_opts["file_roots"]["prod"].append(
  87. os.path.join(RUNTIME_VARS.FILES, "file", "prod"),
  88. )
  89. # We need to copy the extension modules into the new master root_dir or
  90. # it will be prefixed by it
  91. extension_modules_path = os.path.join(root_dir, "extension_modules")
  92. if not os.path.exists(extension_modules_path):
  93. shutil.copytree(
  94. os.path.join(RUNTIME_VARS.FILES, "extension_modules"),
  95. extension_modules_path,
  96. )
  97. minion_opts["extension_modules"] = extension_modules_path
  98. # Custom grains
  99. if "grains" not in minion_opts:
  100. minion_opts["grains"] = {}
  101. if minion_role is not None:
  102. minion_opts["grains"]["role"] = minion_role
  103. # Under windows we can't seem to properly create a virtualenv off of another
  104. # virtualenv, we can on linux but we will still point to the virtualenv binary
  105. # outside the virtualenv running the test suite, if that's the case.
  106. try:
  107. real_prefix = sys.real_prefix
  108. # The above attribute exists, this is a virtualenv
  109. if salt.utils.platform.is_windows():
  110. virtualenv_binary = os.path.join(real_prefix, "Scripts", "virtualenv.exe")
  111. else:
  112. # We need to remove the virtualenv from PATH or we'll get the virtualenv binary
  113. # from within the virtualenv, we don't want that
  114. path = os.environ.get("PATH")
  115. if path is not None:
  116. path_items = path.split(os.pathsep)
  117. for item in path_items[:]:
  118. if item.startswith(sys.base_prefix):
  119. path_items.remove(item)
  120. os.environ["PATH"] = os.pathsep.join(path_items)
  121. virtualenv_binary = salt.utils.path.which("virtualenv")
  122. if path is not None:
  123. # Restore previous environ PATH
  124. os.environ["PATH"] = path
  125. if not virtualenv_binary.startswith(real_prefix):
  126. virtualenv_binary = None
  127. if virtualenv_binary and not os.path.exists(virtualenv_binary):
  128. # It doesn't exist?!
  129. virtualenv_binary = None
  130. except AttributeError:
  131. # We're not running inside a virtualenv
  132. virtualenv_binary = None
  133. if virtualenv_binary:
  134. minion_opts["venv_bin"] = virtualenv_binary
  135. # Override minion_opts with minion_opts_overrides
  136. if minion_opts_overrides:
  137. minion_opts.update(minion_opts_overrides)
  138. if not os.path.exists(conf_dir):
  139. os.makedirs(conf_dir)
  140. with salt.utils.files.fopen(conf_file, "w") as fp_:
  141. salt.utils.yaml.safe_dump(minion_opts, fp_, default_flow_style=False)
  142. log.info("Generating testing minion %r configuration completed.", minion_id)
  143. minion_opts = salt.config.minion_config(
  144. conf_file, minion_id=minion_id, cache_minion_id=True
  145. )
  146. salt.utils.verify.verify_env(
  147. [
  148. os.path.join(minion_opts["pki_dir"], "accepted"),
  149. os.path.join(minion_opts["pki_dir"], "rejected"),
  150. os.path.join(minion_opts["pki_dir"], "pending"),
  151. os.path.dirname(minion_opts["log_file"]),
  152. minion_opts["extension_modules"],
  153. minion_opts["cachedir"],
  154. minion_opts["sock_dir"],
  155. RUNTIME_VARS.TMP_STATE_TREE,
  156. RUNTIME_VARS.TMP_PILLAR_TREE,
  157. RUNTIME_VARS.TMP_PRODENV_STATE_TREE,
  158. RUNTIME_VARS.TMP,
  159. ],
  160. RUNTIME_VARS.RUNNING_TESTS_USER,
  161. root_dir=root_dir,
  162. )
  163. if cache_opts:
  164. try:
  165. opts_cache = build_minion_opts.__cached_opts__
  166. except AttributeError:
  167. opts_cache = build_minion_opts.__cached_opts__ = {}
  168. opts_cache[minion_id] = minion_opts
  169. return minion_opts
  170. def create_sminion(
  171. minion_id=None,
  172. root_dir=None,
  173. initial_conf_file=None,
  174. sminion_cls=salt.minion.SMinion,
  175. minion_opts_overrides=None,
  176. skip_cached_minion=False,
  177. cache_sminion=True,
  178. ):
  179. if minion_id is None:
  180. minion_id = DEFAULT_SMINION_ID
  181. if skip_cached_minion is False:
  182. try:
  183. minions_cache = create_sminion.__cached_minions__
  184. except AttributeError:
  185. create_sminion.__cached_minions__ = {}
  186. cached_minion = create_sminion.__cached_minions__.get(minion_id)
  187. if cached_minion:
  188. return cached_minion
  189. minion_opts = build_minion_opts(
  190. minion_id=minion_id,
  191. root_dir=root_dir,
  192. initial_conf_file=initial_conf_file,
  193. minion_opts_overrides=minion_opts_overrides,
  194. skip_cached_opts=skip_cached_minion,
  195. cache_opts=cache_sminion,
  196. )
  197. log.info("Instantiating a testing %s(%s)", sminion_cls.__name__, minion_id)
  198. sminion = sminion_cls(minion_opts)
  199. if cache_sminion:
  200. try:
  201. minions_cache = create_sminion.__cached_minions__
  202. except AttributeError:
  203. minions_cache = create_sminion.__cached_minions__ = {}
  204. minions_cache[minion_id] = sminion
  205. return sminion
  206. def check_required_sminion_attributes(sminion_attr, required_items):
  207. """
  208. :param sminion_attr: The name of the sminion attribute to check, such as 'functions' or 'states'
  209. :param required_items: The items that must be part of the designated sminion attribute for the decorated test
  210. :return The packages that are not available
  211. """
  212. required_salt_items = set(required_items)
  213. sminion = create_sminion(minion_id=DEFAULT_SMINION_ID)
  214. available_items = list(getattr(sminion, sminion_attr))
  215. not_available_items = set()
  216. name = "__not_available_{items}s__".format(items=sminion_attr)
  217. if not hasattr(sminion, name):
  218. setattr(sminion, name, set())
  219. cached_not_available_items = getattr(sminion, name)
  220. for not_available_item in cached_not_available_items:
  221. if not_available_item in required_salt_items:
  222. not_available_items.add(not_available_item)
  223. required_salt_items.remove(not_available_item)
  224. for required_item_name in required_salt_items:
  225. search_name = required_item_name
  226. if "." not in search_name:
  227. search_name += ".*"
  228. if not fnmatch.filter(available_items, search_name):
  229. not_available_items.add(required_item_name)
  230. cached_not_available_items.add(required_item_name)
  231. return not_available_items