test_loader.py 56 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792
  1. """
  2. unit.loader
  3. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  4. Test Salt's loader
  5. """
  6. import collections
  7. import compileall
  8. import copy
  9. import gc
  10. import imp
  11. import inspect
  12. import logging
  13. import os
  14. import shutil
  15. import sys
  16. import tempfile
  17. import textwrap
  18. import salt.config
  19. import salt.loader
  20. import salt.utils.files
  21. import salt.utils.stringutils
  22. from tests.support.case import ModuleCase
  23. from tests.support.helpers import slowTest
  24. from tests.support.mock import MagicMock, patch
  25. from tests.support.runtests import RUNTIME_VARS
  26. from tests.support.unit import TestCase
  27. log = logging.getLogger(__name__)
  28. def remove_bytecode(module_path):
  29. paths = [module_path + "c"]
  30. if hasattr(imp, "get_tag"):
  31. modname, ext = os.path.splitext(module_path.split(os.sep)[-1])
  32. paths.append(
  33. os.path.join(
  34. os.path.dirname(module_path),
  35. "__pycache__",
  36. "{}.{}.pyc".format(modname, imp.get_tag()),
  37. )
  38. )
  39. for path in paths:
  40. if os.path.exists(path):
  41. os.unlink(path)
  42. loader_template = """
  43. import os
  44. from salt.utils.decorators import depends
  45. @depends('os')
  46. def loaded():
  47. return True
  48. @depends('non_existantmodulename')
  49. def not_loaded():
  50. return True
  51. """
  52. class LazyLoaderTest(TestCase):
  53. """
  54. Test the loader
  55. """
  56. module_name = "lazyloadertest"
  57. @classmethod
  58. def setUpClass(cls):
  59. cls.opts = salt.config.minion_config(None)
  60. cls.opts["grains"] = salt.loader.grains(cls.opts)
  61. if not os.path.isdir(RUNTIME_VARS.TMP):
  62. os.makedirs(RUNTIME_VARS.TMP)
  63. cls.utils = salt.loader.utils(cls.opts)
  64. cls.proxy = salt.loader.proxy(cls.opts)
  65. cls.funcs = salt.loader.minion_mods(cls.opts, utils=cls.utils, proxy=cls.proxy)
  66. def setUp(self):
  67. # Setup the module
  68. self.module_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  69. self.addCleanup(shutil.rmtree, self.module_dir, ignore_errors=True)
  70. self.module_file = os.path.join(
  71. self.module_dir, "{}.py".format(self.module_name)
  72. )
  73. with salt.utils.files.fopen(self.module_file, "w") as fh:
  74. fh.write(salt.utils.stringutils.to_str(loader_template))
  75. fh.flush()
  76. os.fsync(fh.fileno())
  77. # Invoke the loader
  78. self.loader = salt.loader.LazyLoader(
  79. [self.module_dir],
  80. copy.deepcopy(self.opts),
  81. pack={
  82. "__utils__": self.utils,
  83. "__salt__": self.funcs,
  84. "__proxy__": self.proxy,
  85. },
  86. tag="module",
  87. )
  88. def tearDown(self):
  89. del self.module_dir
  90. del self.module_file
  91. del self.loader
  92. @classmethod
  93. def tearDownClass(cls):
  94. del cls.opts
  95. del cls.funcs
  96. del cls.utils
  97. del cls.proxy
  98. @slowTest
  99. def test_depends(self):
  100. """
  101. Test that the depends decorator works properly
  102. """
  103. # Make sure depends correctly allowed a function to load. If this
  104. # results in a KeyError, the decorator is broken.
  105. self.assertTrue(inspect.isfunction(self.loader[self.module_name + ".loaded"]))
  106. # Make sure depends correctly kept a function from loading
  107. self.assertTrue(self.module_name + ".not_loaded" not in self.loader)
  108. loader_template_module = """
  109. import my_utils
  110. def run():
  111. return my_utils.run()
  112. """
  113. loader_template_utils = """
  114. def run():
  115. return True
  116. """
  117. class LazyLoaderUtilsTest(TestCase):
  118. """
  119. Test the loader
  120. """
  121. module_name = "lazyloaderutilstest"
  122. utils_name = "my_utils"
  123. @classmethod
  124. def setUpClass(cls):
  125. cls.opts = salt.config.minion_config(None)
  126. cls.opts["grains"] = salt.loader.grains(cls.opts)
  127. if not os.path.isdir(RUNTIME_VARS.TMP):
  128. os.makedirs(RUNTIME_VARS.TMP)
  129. def setUp(self):
  130. # Setup the module
  131. self.module_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  132. self.module_file = os.path.join(
  133. self.module_dir, "{}.py".format(self.module_name)
  134. )
  135. with salt.utils.files.fopen(self.module_file, "w") as fh:
  136. fh.write(salt.utils.stringutils.to_str(loader_template_module))
  137. fh.flush()
  138. os.fsync(fh.fileno())
  139. self.utils_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  140. self.utils_file = os.path.join(self.utils_dir, "{}.py".format(self.utils_name))
  141. with salt.utils.files.fopen(self.utils_file, "w") as fh:
  142. fh.write(salt.utils.stringutils.to_str(loader_template_utils))
  143. fh.flush()
  144. os.fsync(fh.fileno())
  145. def tearDown(self):
  146. shutil.rmtree(self.module_dir)
  147. if os.path.isdir(self.module_dir):
  148. shutil.rmtree(self.module_dir)
  149. shutil.rmtree(self.utils_dir)
  150. if os.path.isdir(self.utils_dir):
  151. shutil.rmtree(self.utils_dir)
  152. del self.module_dir
  153. del self.module_file
  154. del self.utils_dir
  155. del self.utils_file
  156. if self.module_name in sys.modules:
  157. del sys.modules[self.module_name]
  158. if self.utils_name in sys.modules:
  159. del sys.modules[self.utils_name]
  160. @classmethod
  161. def tearDownClass(cls):
  162. del cls.opts
  163. def test_utils_found(self):
  164. """
  165. Test that the extra module directory is available for imports
  166. """
  167. loader = salt.loader.LazyLoader(
  168. [self.module_dir],
  169. copy.deepcopy(self.opts),
  170. tag="module",
  171. extra_module_dirs=[self.utils_dir],
  172. )
  173. self.assertTrue(inspect.isfunction(loader[self.module_name + ".run"]))
  174. self.assertTrue(loader[self.module_name + ".run"]())
  175. def test_utils_not_found(self):
  176. """
  177. Test that the extra module directory is not available for imports
  178. """
  179. loader = salt.loader.LazyLoader(
  180. [self.module_dir], copy.deepcopy(self.opts), tag="module"
  181. )
  182. self.assertTrue(self.module_name + ".run" not in loader)
  183. class LazyLoaderVirtualEnabledTest(TestCase):
  184. """
  185. Test the base loader of salt.
  186. """
  187. @classmethod
  188. def setUpClass(cls):
  189. cls.opts = salt.config.minion_config(None)
  190. cls.opts["disable_modules"] = ["pillar"]
  191. cls.opts["grains"] = salt.loader.grains(cls.opts)
  192. cls.utils = salt.loader.utils(copy.deepcopy(cls.opts))
  193. cls.proxy = salt.loader.proxy(cls.opts)
  194. cls.funcs = salt.loader.minion_mods(cls.opts, utils=cls.utils, proxy=cls.proxy)
  195. def setUp(self):
  196. self.loader = salt.loader.LazyLoader(
  197. salt.loader._module_dirs(copy.deepcopy(self.opts), "modules", "module"),
  198. copy.deepcopy(self.opts),
  199. pack={
  200. "__utils__": self.utils,
  201. "__salt__": self.funcs,
  202. "__proxy__": self.proxy,
  203. },
  204. tag="module",
  205. )
  206. def tearDown(self):
  207. del self.loader
  208. @classmethod
  209. def tearDownClass(cls):
  210. del cls.opts
  211. del cls.funcs
  212. del cls.utils
  213. del cls.proxy
  214. @slowTest
  215. def test_basic(self):
  216. """
  217. Ensure that it only loads stuff when needed
  218. """
  219. # make sure it starts empty
  220. self.assertEqual(self.loader._dict, {})
  221. # get something, and make sure its a func
  222. self.assertTrue(inspect.isfunction(self.loader["test.ping"]))
  223. # make sure we only loaded "test" functions
  224. for key, val in self.loader._dict.items():
  225. self.assertEqual(key.split(".", 1)[0], "test")
  226. # make sure the depends thing worked (double check of the depends testing,
  227. # since the loader does the calling magically
  228. self.assertFalse("test.missing_func" in self.loader._dict)
  229. def test_badkey(self):
  230. with self.assertRaises(KeyError):
  231. self.loader[None] # pylint: disable=W0104
  232. with self.assertRaises(KeyError):
  233. self.loader[1] # pylint: disable=W0104
  234. @slowTest
  235. def test_disable(self):
  236. self.assertNotIn("pillar.items", self.loader)
  237. @slowTest
  238. def test_len_load(self):
  239. """
  240. Since LazyLoader is a MutableMapping, if someone asks for len() we have
  241. to load all
  242. """
  243. self.assertEqual(self.loader._dict, {})
  244. len(self.loader) # force a load all
  245. self.assertNotEqual(self.loader._dict, {})
  246. @slowTest
  247. def test_iter_load(self):
  248. """
  249. Since LazyLoader is a MutableMapping, if someone asks to iterate we have
  250. to load all
  251. """
  252. self.assertEqual(self.loader._dict, {})
  253. # force a load all
  254. for key, func in self.loader.items():
  255. break
  256. self.assertNotEqual(self.loader._dict, {})
  257. def test_context(self):
  258. """
  259. Make sure context is shared across modules
  260. """
  261. # make sure it starts empty
  262. self.assertEqual(self.loader._dict, {})
  263. # get something, and make sure its a func
  264. func = self.loader["test.ping"]
  265. with patch.dict(func.__globals__["__context__"], {"foo": "bar"}):
  266. self.assertEqual(
  267. self.loader["test.echo"].__globals__["__context__"]["foo"], "bar"
  268. )
  269. self.assertEqual(
  270. self.loader["grains.get"].__globals__["__context__"]["foo"], "bar"
  271. )
  272. def test_globals(self):
  273. func_globals = self.loader["test.ping"].__globals__
  274. self.assertEqual(func_globals["__grains__"], self.opts.get("grains", {}))
  275. self.assertEqual(func_globals["__pillar__"], self.opts.get("pillar", {}))
  276. # the opts passed into modules is at least a subset of the whole opts
  277. for key, val in func_globals["__opts__"].items():
  278. if (
  279. key in salt.config.DEFAULT_MASTER_OPTS
  280. and key not in salt.config.DEFAULT_MINION_OPTS
  281. ):
  282. # We loaded the minion opts, but somewhere in the code, the master options got pulled in
  283. # Let's just not check for equality since the option won't even exist in the loaded
  284. # minion options
  285. continue
  286. if (
  287. key not in salt.config.DEFAULT_MASTER_OPTS
  288. and key not in salt.config.DEFAULT_MINION_OPTS
  289. ):
  290. # This isn't even a default configuration setting, lets carry on
  291. continue
  292. self.assertEqual(self.opts[key], val)
  293. def test_pack(self):
  294. self.loader.pack["__foo__"] = "bar"
  295. func_globals = self.loader["test.ping"].__globals__
  296. self.assertEqual(func_globals["__foo__"], "bar")
  297. @slowTest
  298. def test_virtual(self):
  299. self.assertNotIn("test_virtual.ping", self.loader)
  300. class LazyLoaderVirtualDisabledTest(TestCase):
  301. """
  302. Test the loader of salt without __virtual__
  303. """
  304. @classmethod
  305. def setUpClass(cls):
  306. cls.opts = salt.config.minion_config(None)
  307. cls.opts["grains"] = salt.loader.grains(cls.opts)
  308. cls.utils = salt.loader.utils(copy.deepcopy(cls.opts))
  309. cls.proxy = salt.loader.proxy(cls.opts)
  310. cls.funcs = salt.loader.minion_mods(cls.opts, utils=cls.utils, proxy=cls.proxy)
  311. def setUp(self):
  312. self.loader = salt.loader.LazyLoader(
  313. salt.loader._module_dirs(copy.deepcopy(self.opts), "modules", "module"),
  314. copy.deepcopy(self.opts),
  315. tag="module",
  316. pack={
  317. "__utils__": self.utils,
  318. "__salt__": self.funcs,
  319. "__proxy__": self.proxy,
  320. },
  321. virtual_enable=False,
  322. )
  323. def tearDown(self):
  324. del self.loader
  325. @classmethod
  326. def tearDownClass(cls):
  327. del cls.opts
  328. del cls.utils
  329. del cls.funcs
  330. del cls.proxy
  331. @slowTest
  332. def test_virtual(self):
  333. self.assertTrue(inspect.isfunction(self.loader["test_virtual.ping"]))
  334. class LazyLoaderWhitelistTest(TestCase):
  335. """
  336. Test the loader of salt with a whitelist
  337. """
  338. @classmethod
  339. def setUpClass(cls):
  340. cls.opts = salt.config.minion_config(None)
  341. cls.opts["grains"] = salt.loader.grains(cls.opts)
  342. cls.utils = salt.loader.utils(copy.deepcopy(cls.opts))
  343. cls.proxy = salt.loader.proxy(cls.opts)
  344. cls.funcs = salt.loader.minion_mods(cls.opts, utils=cls.utils, proxy=cls.proxy)
  345. def setUp(self):
  346. self.loader = salt.loader.LazyLoader(
  347. salt.loader._module_dirs(copy.deepcopy(self.opts), "modules", "module"),
  348. copy.deepcopy(self.opts),
  349. tag="module",
  350. pack={
  351. "__utils__": self.utils,
  352. "__salt__": self.funcs,
  353. "__proxy__": self.proxy,
  354. },
  355. whitelist=["test", "pillar"],
  356. )
  357. def tearDown(self):
  358. del self.loader
  359. @classmethod
  360. def tearDownClass(cls):
  361. del cls.opts
  362. del cls.funcs
  363. del cls.utils
  364. del cls.proxy
  365. @slowTest
  366. def test_whitelist(self):
  367. self.assertTrue(inspect.isfunction(self.loader["test.ping"]))
  368. self.assertTrue(inspect.isfunction(self.loader["pillar.get"]))
  369. self.assertNotIn("grains.get", self.loader)
  370. class LazyLoaderGrainsBlacklistTest(TestCase):
  371. """
  372. Test the loader of grains with a blacklist
  373. """
  374. def setUp(self):
  375. self.opts = salt.config.minion_config(None)
  376. def tearDown(self):
  377. del self.opts
  378. @slowTest
  379. def test_whitelist(self):
  380. opts = copy.deepcopy(self.opts)
  381. opts["grains_blacklist"] = ["master", "os*", "ipv[46]"]
  382. grains = salt.loader.grains(opts)
  383. self.assertNotIn("master", grains)
  384. self.assertNotIn("os", {g[:2] for g in list(grains)})
  385. self.assertNotIn("ipv4", grains)
  386. self.assertNotIn("ipv6", grains)
  387. class LazyLoaderSingleItem(TestCase):
  388. """
  389. Test loading a single item via the _load() function
  390. """
  391. @classmethod
  392. def setUpClass(cls):
  393. cls.opts = salt.config.minion_config(None)
  394. cls.opts["grains"] = salt.loader.grains(cls.opts)
  395. cls.utils = salt.loader.utils(copy.deepcopy(cls.opts))
  396. cls.proxy = salt.loader.proxy(cls.opts)
  397. cls.funcs = salt.loader.minion_mods(cls.opts, utils=cls.utils, proxy=cls.proxy)
  398. @classmethod
  399. def tearDownClass(cls):
  400. del cls.opts
  401. del cls.funcs
  402. del cls.utils
  403. del cls.proxy
  404. def setUp(self):
  405. self.loader = salt.loader.LazyLoader(
  406. salt.loader._module_dirs(copy.deepcopy(self.opts), "modules", "module"),
  407. copy.deepcopy(self.opts),
  408. pack={
  409. "__utils__": self.utils,
  410. "__salt__": self.funcs,
  411. "__proxy__": self.proxy,
  412. },
  413. tag="module",
  414. )
  415. def tearDown(self):
  416. del self.loader
  417. def test_single_item_no_dot(self):
  418. """
  419. Checks that a KeyError is raised when the function key does not contain a '.'
  420. """
  421. key = "testing_no_dot"
  422. expected = "The key '{}' should contain a '.'".format(key)
  423. with self.assertRaises(KeyError) as err:
  424. inspect.isfunction(self.loader["testing_no_dot"])
  425. result = err.exception.args[0]
  426. assert result == expected, result
  427. module_template = """
  428. __load__ = ['test', 'test_alias']
  429. __func_alias__ = dict(test_alias='working_alias')
  430. from salt.utils.decorators import depends
  431. def test():
  432. return {count}
  433. def test_alias():
  434. return True
  435. def test2():
  436. return True
  437. @depends('non_existantmodulename')
  438. def test3():
  439. return True
  440. @depends('non_existantmodulename', fallback_function=test)
  441. def test4():
  442. return True
  443. """
  444. class LazyLoaderReloadingTest(TestCase):
  445. """
  446. Test the loader of salt with changing modules
  447. """
  448. module_name = "loadertest"
  449. module_key = "loadertest.test"
  450. @classmethod
  451. def setUpClass(cls):
  452. cls.opts = salt.config.minion_config(None)
  453. cls.opts["grains"] = salt.loader.grains(cls.opts)
  454. if not os.path.isdir(RUNTIME_VARS.TMP):
  455. os.makedirs(RUNTIME_VARS.TMP)
  456. def setUp(self):
  457. self.tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  458. self.addCleanup(shutil.rmtree, self.tmp_dir, ignore_errors=True)
  459. self.count = 0
  460. opts = copy.deepcopy(self.opts)
  461. dirs = salt.loader._module_dirs(opts, "modules", "module")
  462. dirs.append(self.tmp_dir)
  463. self.utils = salt.loader.utils(opts)
  464. self.proxy = salt.loader.proxy(opts)
  465. self.minion_mods = salt.loader.minion_mods(opts)
  466. self.loader = salt.loader.LazyLoader(
  467. dirs,
  468. opts,
  469. tag="module",
  470. pack={
  471. "__utils__": self.utils,
  472. "__proxy__": self.proxy,
  473. "__salt__": self.minion_mods,
  474. },
  475. )
  476. def tearDown(self):
  477. for attrname in ("tmp_dir", "utils", "proxy", "loader", "minion_mods", "utils"):
  478. try:
  479. delattr(self, attrname)
  480. except AttributeError:
  481. continue
  482. @classmethod
  483. def tearDownClass(cls):
  484. del cls.opts
  485. def update_module(self):
  486. self.count += 1
  487. with salt.utils.files.fopen(self.module_path, "wb") as fh:
  488. fh.write(
  489. salt.utils.stringutils.to_bytes(
  490. module_template.format(count=self.count)
  491. )
  492. )
  493. fh.flush()
  494. os.fsync(fh.fileno()) # flush to disk
  495. # pyc files don't like it when we change the original quickly
  496. # since the header bytes only contain the timestamp (granularity of seconds)
  497. # TODO: don't write them? Is *much* slower on re-load (~3x)
  498. # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
  499. remove_bytecode(self.module_path)
  500. def rm_module(self):
  501. os.unlink(self.module_path)
  502. remove_bytecode(self.module_path)
  503. @property
  504. def module_path(self):
  505. return os.path.join(self.tmp_dir, "{}.py".format(self.module_name))
  506. @slowTest
  507. def test_alias(self):
  508. """
  509. Make sure that you can access alias-d modules
  510. """
  511. # ensure it doesn't exist
  512. self.assertNotIn(self.module_key, self.loader)
  513. self.update_module()
  514. self.assertNotIn("{}.test_alias".format(self.module_name), self.loader)
  515. self.assertTrue(
  516. inspect.isfunction(self.loader["{}.working_alias".format(self.module_name)])
  517. )
  518. @slowTest
  519. def test_clear(self):
  520. self.assertTrue(inspect.isfunction(self.loader["test.ping"]))
  521. self.update_module() # write out out custom module
  522. self.loader.clear() # clear the loader dict
  523. # force a load of our module
  524. self.assertTrue(inspect.isfunction(self.loader[self.module_key]))
  525. # make sure we only loaded our custom module
  526. # which means that we did correctly refresh the file mapping
  527. for k, v in self.loader._dict.items():
  528. self.assertTrue(k.startswith(self.module_name))
  529. @slowTest
  530. def test_load(self):
  531. # ensure it doesn't exist
  532. self.assertNotIn(self.module_key, self.loader)
  533. self.update_module()
  534. self.assertTrue(inspect.isfunction(self.loader[self.module_key]))
  535. @slowTest
  536. def test__load__(self):
  537. """
  538. If a module specifies __load__ we should only load/expose those modules
  539. """
  540. self.update_module()
  541. # ensure it doesn't exist
  542. self.assertNotIn(self.module_key + "2", self.loader)
  543. @slowTest
  544. def test__load__and_depends(self):
  545. """
  546. If a module specifies __load__ we should only load/expose those modules
  547. """
  548. self.update_module()
  549. # ensure it doesn't exist
  550. self.assertNotIn(self.module_key + "3", self.loader)
  551. self.assertNotIn(self.module_key + "4", self.loader)
  552. @slowTest
  553. def test_reload(self):
  554. # ensure it doesn't exist
  555. self.assertNotIn(self.module_key, self.loader)
  556. # make sure it updates correctly
  557. for x in range(1, 3):
  558. self.update_module()
  559. self.loader.clear()
  560. self.assertEqual(self.loader[self.module_key](), self.count)
  561. self.rm_module()
  562. # make sure that even if we remove the module, its still loaded until a clear
  563. self.assertEqual(self.loader[self.module_key](), self.count)
  564. self.loader.clear()
  565. self.assertNotIn(self.module_key, self.loader)
  566. def test_wrong_bytecode(self):
  567. """
  568. Checks to make sure we don't even try to load .pyc files that are for a different Python
  569. This should pass (the load should fail) all the time because we don't run Salt on Py 3.4 anymore
  570. """
  571. test_module_name = "test_module.cpython-34"
  572. filemap_save = copy.deepcopy(self.loader.file_mapping)
  573. self.loader.file_mapping = {
  574. test_module_name: (
  575. "/temp/path/does/not/matter/here/__pycache__/"
  576. + test_module_name
  577. + ".pyc",
  578. ".pyc",
  579. 0,
  580. )
  581. }
  582. self.assertFalse(self.loader._load_module(test_module_name))
  583. self.loader.file_mapping = copy.deepcopy(filemap_save)
  584. virtual_aliases = ("loadertest2", "loadertest3")
  585. virtual_alias_module_template = """
  586. __virtual_aliases__ = {}
  587. def test():
  588. return True
  589. """.format(
  590. virtual_aliases
  591. )
  592. class LazyLoaderVirtualAliasTest(TestCase):
  593. """
  594. Test the loader of salt with changing modules
  595. """
  596. module_name = "loadertest"
  597. @classmethod
  598. def setUpClass(cls):
  599. cls.opts = salt.config.minion_config(None)
  600. cls.opts["grains"] = salt.loader.grains(cls.opts)
  601. if not os.path.isdir(RUNTIME_VARS.TMP):
  602. os.makedirs(RUNTIME_VARS.TMP)
  603. def setUp(self):
  604. self.tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  605. opts = copy.deepcopy(self.opts)
  606. dirs = salt.loader._module_dirs(opts, "modules", "module")
  607. dirs.append(self.tmp_dir)
  608. self.utils = salt.loader.utils(opts)
  609. self.proxy = salt.loader.proxy(opts)
  610. self.minion_mods = salt.loader.minion_mods(opts)
  611. self.loader = salt.loader.LazyLoader(
  612. dirs,
  613. opts,
  614. tag="module",
  615. pack={
  616. "__utils__": self.utils,
  617. "__proxy__": self.proxy,
  618. "__salt__": self.minion_mods,
  619. },
  620. )
  621. def tearDown(self):
  622. del self.tmp_dir
  623. del self.utils
  624. del self.proxy
  625. del self.minion_mods
  626. del self.loader
  627. @classmethod
  628. def tearDownClass(cls):
  629. del cls.opts
  630. def update_module(self):
  631. with salt.utils.files.fopen(self.module_path, "wb") as fh:
  632. fh.write(salt.utils.stringutils.to_bytes(virtual_alias_module_template))
  633. fh.flush()
  634. os.fsync(fh.fileno()) # flush to disk
  635. # pyc files don't like it when we change the original quickly
  636. # since the header bytes only contain the timestamp (granularity of seconds)
  637. # TODO: don't write them? Is *much* slower on re-load (~3x)
  638. # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
  639. remove_bytecode(self.module_path)
  640. @property
  641. def module_path(self):
  642. return os.path.join(self.tmp_dir, "{}.py".format(self.module_name))
  643. @slowTest
  644. def test_virtual_alias(self):
  645. """
  646. Test the __virtual_alias__ feature
  647. """
  648. self.update_module()
  649. mod_names = [self.module_name] + list(virtual_aliases)
  650. for mod_name in mod_names:
  651. func_name = ".".join((mod_name, "test"))
  652. log.debug("Running %s (dict attribute)", func_name)
  653. self.assertTrue(self.loader[func_name]())
  654. log.debug("Running %s (loader attribute)", func_name)
  655. self.assertTrue(getattr(self.loader, mod_name).test())
  656. submodule_template = """
  657. from __future__ import absolute_import
  658. import {0}.lib
  659. def test():
  660. return ({count}, {0}.lib.test())
  661. """
  662. submodule_lib_template = """
  663. def test():
  664. return {count}
  665. """
  666. class LazyLoaderSubmodReloadingTest(TestCase):
  667. """
  668. Test the loader of salt with changing modules
  669. """
  670. module_name = "loadertestsubmod"
  671. module_key = "loadertestsubmod.test"
  672. @classmethod
  673. def setUpClass(cls):
  674. cls.opts = salt.config.minion_config(None)
  675. cls.opts["grains"] = salt.loader.grains(cls.opts)
  676. if not os.path.isdir(RUNTIME_VARS.TMP):
  677. os.makedirs(RUNTIME_VARS.TMP)
  678. def setUp(self):
  679. self.tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  680. self.addCleanup(shutil.rmtree, self.tmp_dir, ignore_errors=True)
  681. os.makedirs(self.module_dir)
  682. self.count = 0
  683. self.lib_count = 0
  684. opts = copy.deepcopy(self.opts)
  685. dirs = salt.loader._module_dirs(opts, "modules", "module")
  686. dirs.append(self.tmp_dir)
  687. self.utils = salt.loader.utils(opts)
  688. self.proxy = salt.loader.proxy(opts)
  689. self.minion_mods = salt.loader.minion_mods(opts)
  690. self.loader = salt.loader.LazyLoader(
  691. dirs,
  692. opts,
  693. tag="module",
  694. pack={
  695. "__utils__": self.utils,
  696. "__proxy__": self.proxy,
  697. "__salt__": self.minion_mods,
  698. },
  699. )
  700. def tearDown(self):
  701. del self.tmp_dir
  702. del self.utils
  703. del self.proxy
  704. del self.minion_mods
  705. del self.loader
  706. @classmethod
  707. def tearDownClass(cls):
  708. del cls.opts
  709. def update_module(self):
  710. self.count += 1
  711. with salt.utils.files.fopen(self.module_path, "wb") as fh:
  712. fh.write(
  713. salt.utils.stringutils.to_bytes(
  714. submodule_template.format(self.module_name, count=self.count)
  715. )
  716. )
  717. fh.flush()
  718. os.fsync(fh.fileno()) # flush to disk
  719. # pyc files don't like it when we change the original quickly
  720. # since the header bytes only contain the timestamp (granularity of seconds)
  721. # TODO: don't write them? Is *much* slower on re-load (~3x)
  722. # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
  723. remove_bytecode(self.module_path)
  724. def rm_module(self):
  725. os.unlink(self.module_path)
  726. remove_bytecode(self.module_path)
  727. def update_lib(self):
  728. self.lib_count += 1
  729. for modname in list(sys.modules):
  730. if modname.startswith(self.module_name):
  731. del sys.modules[modname]
  732. with salt.utils.files.fopen(self.lib_path, "wb") as fh:
  733. fh.write(
  734. salt.utils.stringutils.to_bytes(
  735. submodule_lib_template.format(count=self.lib_count)
  736. )
  737. )
  738. fh.flush()
  739. os.fsync(fh.fileno()) # flush to disk
  740. # pyc files don't like it when we change the original quickly
  741. # since the header bytes only contain the timestamp (granularity of seconds)
  742. # TODO: don't write them? Is *much* slower on re-load (~3x)
  743. # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
  744. remove_bytecode(self.lib_path)
  745. def rm_lib(self):
  746. for modname in list(sys.modules):
  747. if modname.startswith(self.module_name):
  748. del sys.modules[modname]
  749. os.unlink(self.lib_path)
  750. remove_bytecode(self.lib_path)
  751. @property
  752. def module_dir(self):
  753. return os.path.join(self.tmp_dir, self.module_name)
  754. @property
  755. def module_path(self):
  756. return os.path.join(self.module_dir, "__init__.py")
  757. @property
  758. def lib_path(self):
  759. return os.path.join(self.module_dir, "lib.py")
  760. @slowTest
  761. def test_basic(self):
  762. # ensure it doesn't exist
  763. self.assertNotIn(self.module_key, self.loader)
  764. self.update_module()
  765. self.update_lib()
  766. self.loader.clear()
  767. self.assertIn(self.module_key, self.loader)
  768. @slowTest
  769. def test_reload(self):
  770. # ensure it doesn't exist
  771. self.assertNotIn(self.module_key, self.loader)
  772. # update both the module and the lib
  773. for x in range(1, 3):
  774. self.update_lib()
  775. self.update_module()
  776. self.loader.clear()
  777. self.assertNotIn(self.module_key, self.loader._dict)
  778. self.assertIn(self.module_key, self.loader)
  779. self.assertEqual(
  780. self.loader[self.module_key](), (self.count, self.lib_count)
  781. )
  782. # update just the module
  783. for x in range(1, 3):
  784. self.update_module()
  785. self.loader.clear()
  786. self.assertNotIn(self.module_key, self.loader._dict)
  787. self.assertIn(self.module_key, self.loader)
  788. self.assertEqual(
  789. self.loader[self.module_key](), (self.count, self.lib_count)
  790. )
  791. # update just the lib
  792. for x in range(1, 3):
  793. self.update_lib()
  794. self.loader.clear()
  795. self.assertNotIn(self.module_key, self.loader._dict)
  796. self.assertIn(self.module_key, self.loader)
  797. self.assertEqual(
  798. self.loader[self.module_key](), (self.count, self.lib_count)
  799. )
  800. self.rm_module()
  801. # make sure that even if we remove the module, its still loaded until a clear
  802. self.assertEqual(self.loader[self.module_key](), (self.count, self.lib_count))
  803. self.loader.clear()
  804. self.assertNotIn(self.module_key, self.loader)
  805. @slowTest
  806. def test_reload_missing_lib(self):
  807. # ensure it doesn't exist
  808. self.assertNotIn(self.module_key, self.loader)
  809. # update both the module and the lib
  810. self.update_module()
  811. self.update_lib()
  812. self.loader.clear()
  813. self.assertEqual(self.loader[self.module_key](), (self.count, self.lib_count))
  814. # remove the lib, this means we should fail to load the module next time
  815. self.rm_lib()
  816. self.loader.clear()
  817. self.assertNotIn(self.module_key, self.loader)
  818. mod_template = """
  819. def test():
  820. return ({val})
  821. """
  822. class LazyLoaderModulePackageTest(TestCase):
  823. """
  824. Test the loader of salt with changing modules
  825. """
  826. module_name = "loadertestmodpkg"
  827. module_key = "loadertestmodpkg.test"
  828. @classmethod
  829. def setUpClass(cls):
  830. cls.opts = salt.config.minion_config(None)
  831. cls.opts["grains"] = salt.loader.grains(cls.opts)
  832. if not os.path.isdir(RUNTIME_VARS.TMP):
  833. os.makedirs(RUNTIME_VARS.TMP)
  834. cls.utils = salt.loader.utils(copy.deepcopy(cls.opts))
  835. cls.proxy = salt.loader.proxy(cls.opts)
  836. cls.funcs = salt.loader.minion_mods(cls.opts, utils=cls.utils, proxy=cls.proxy)
  837. def setUp(self):
  838. self.tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  839. self.addCleanup(shutil.rmtree, self.tmp_dir, ignore_errors=True)
  840. dirs = salt.loader._module_dirs(copy.deepcopy(self.opts), "modules", "module")
  841. dirs.append(self.tmp_dir)
  842. self.loader = salt.loader.LazyLoader(
  843. dirs,
  844. copy.deepcopy(self.opts),
  845. pack={
  846. "__utils__": self.utils,
  847. "__salt__": self.funcs,
  848. "__proxy__": self.proxy,
  849. },
  850. tag="module",
  851. )
  852. def tearDown(self):
  853. del self.tmp_dir
  854. del self.loader
  855. @classmethod
  856. def tearDownClass(cls):
  857. del cls.opts
  858. del cls.funcs
  859. del cls.utils
  860. del cls.proxy
  861. def update_pyfile(self, pyfile, contents):
  862. dirname = os.path.dirname(pyfile)
  863. if not os.path.exists(dirname):
  864. os.makedirs(dirname)
  865. with salt.utils.files.fopen(pyfile, "wb") as fh:
  866. fh.write(salt.utils.stringutils.to_bytes(contents))
  867. fh.flush()
  868. os.fsync(fh.fileno()) # flush to disk
  869. # pyc files don't like it when we change the original quickly
  870. # since the header bytes only contain the timestamp (granularity of seconds)
  871. # TODO: don't write them? Is *much* slower on re-load (~3x)
  872. # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
  873. remove_bytecode(pyfile)
  874. def rm_pyfile(self, pyfile):
  875. os.unlink(pyfile)
  876. remove_bytecode(pyfile)
  877. def update_module(self, relative_path, contents):
  878. self.update_pyfile(os.path.join(self.tmp_dir, relative_path), contents)
  879. def rm_module(self, relative_path):
  880. self.rm_pyfile(os.path.join(self.tmp_dir, relative_path))
  881. @slowTest
  882. def test_module(self):
  883. # ensure it doesn't exist
  884. self.assertNotIn("foo", self.loader)
  885. self.assertNotIn("foo.test", self.loader)
  886. self.update_module("foo.py", mod_template.format(val=1))
  887. self.loader.clear()
  888. self.assertIn("foo.test", self.loader)
  889. self.assertEqual(self.loader["foo.test"](), 1)
  890. @slowTest
  891. def test_package(self):
  892. # ensure it doesn't exist
  893. self.assertNotIn("foo", self.loader)
  894. self.assertNotIn("foo.test", self.loader)
  895. self.update_module("foo/__init__.py", mod_template.format(val=2))
  896. self.loader.clear()
  897. self.assertIn("foo.test", self.loader)
  898. self.assertEqual(self.loader["foo.test"](), 2)
  899. @slowTest
  900. def test_module_package_collision(self):
  901. # ensure it doesn't exist
  902. self.assertNotIn("foo", self.loader)
  903. self.assertNotIn("foo.test", self.loader)
  904. self.update_module("foo.py", mod_template.format(val=3))
  905. self.loader.clear()
  906. self.assertIn("foo.test", self.loader)
  907. self.assertEqual(self.loader["foo.test"](), 3)
  908. self.update_module("foo/__init__.py", mod_template.format(val=4))
  909. self.loader.clear()
  910. self.assertIn("foo.test", self.loader)
  911. self.assertEqual(self.loader["foo.test"](), 4)
  912. deep_init_base = """
  913. from __future__ import absolute_import
  914. import {0}.top_lib
  915. import {0}.top_lib.mid_lib
  916. import {0}.top_lib.mid_lib.bot_lib
  917. def top():
  918. return {0}.top_lib.test()
  919. def mid():
  920. return {0}.top_lib.mid_lib.test()
  921. def bot():
  922. return {0}.top_lib.mid_lib.bot_lib.test()
  923. """
  924. class LazyLoaderDeepSubmodReloadingTest(TestCase):
  925. module_name = "loadertestsubmoddeep"
  926. libs = ("top_lib", "mid_lib", "bot_lib")
  927. @classmethod
  928. def setUpClass(cls):
  929. cls.opts = salt.config.minion_config(None)
  930. cls.opts["grains"] = salt.loader.grains(cls.opts)
  931. if not os.path.isdir(RUNTIME_VARS.TMP):
  932. os.makedirs(RUNTIME_VARS.TMP)
  933. def setUp(self):
  934. self.tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  935. self.addCleanup(shutil.rmtree, self.tmp_dir, ignore_errors=True)
  936. os.makedirs(self.module_dir)
  937. self.lib_count = collections.defaultdict(int) # mapping of path -> count
  938. # bootstrap libs
  939. with salt.utils.files.fopen(
  940. os.path.join(self.module_dir, "__init__.py"), "w"
  941. ) as fh:
  942. # No .decode() needed here as deep_init_base is defined as str and
  943. # not bytes.
  944. fh.write(
  945. salt.utils.stringutils.to_str(deep_init_base.format(self.module_name))
  946. )
  947. fh.flush()
  948. os.fsync(fh.fileno()) # flush to disk
  949. self.lib_paths = {}
  950. dir_path = self.module_dir
  951. for lib_name in self.libs:
  952. dir_path = os.path.join(dir_path, lib_name)
  953. self.lib_paths[lib_name] = dir_path
  954. os.makedirs(dir_path)
  955. self.update_lib(lib_name)
  956. opts = copy.deepcopy(self.opts)
  957. dirs = salt.loader._module_dirs(opts, "modules", "module")
  958. dirs.append(self.tmp_dir)
  959. self.utils = salt.loader.utils(opts)
  960. self.proxy = salt.loader.proxy(opts)
  961. self.minion_mods = salt.loader.minion_mods(opts)
  962. self.loader = salt.loader.LazyLoader(
  963. dirs,
  964. copy.deepcopy(opts),
  965. tag="module",
  966. pack={
  967. "__utils__": self.utils,
  968. "__proxy__": self.proxy,
  969. "__salt__": self.minion_mods,
  970. },
  971. )
  972. self.assertIn("{}.top".format(self.module_name), self.loader)
  973. def tearDown(self):
  974. del self.tmp_dir
  975. del self.lib_paths
  976. del self.utils
  977. del self.proxy
  978. del self.minion_mods
  979. del self.loader
  980. del self.lib_count
  981. @classmethod
  982. def tearDownClass(cls):
  983. del cls.opts
  984. @property
  985. def module_dir(self):
  986. return os.path.join(self.tmp_dir, self.module_name)
  987. def update_lib(self, lib_name):
  988. for modname in list(sys.modules):
  989. if modname.startswith(self.module_name):
  990. del sys.modules[modname]
  991. path = os.path.join(self.lib_paths[lib_name], "__init__.py")
  992. self.lib_count[lib_name] += 1
  993. with salt.utils.files.fopen(path, "wb") as fh:
  994. fh.write(
  995. salt.utils.stringutils.to_bytes(
  996. submodule_lib_template.format(count=self.lib_count[lib_name])
  997. )
  998. )
  999. fh.flush()
  1000. os.fsync(fh.fileno()) # flush to disk
  1001. # pyc files don't like it when we change the original quickly
  1002. # since the header bytes only contain the timestamp (granularity of seconds)
  1003. # TODO: don't write them? Is *much* slower on re-load (~3x)
  1004. # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
  1005. remove_bytecode(path)
  1006. @slowTest
  1007. def test_basic(self):
  1008. self.assertIn("{}.top".format(self.module_name), self.loader)
  1009. def _verify_libs(self):
  1010. for lib in self.libs:
  1011. self.assertEqual(
  1012. self.loader[
  1013. "{}.{}".format(self.module_name, lib.replace("_lib", ""))
  1014. ](),
  1015. self.lib_count[lib],
  1016. )
  1017. @slowTest
  1018. def test_reload(self):
  1019. """
  1020. Make sure that we can reload all libraries of arbitrary depth
  1021. """
  1022. self._verify_libs()
  1023. # update them all
  1024. for lib in self.libs:
  1025. for x in range(5):
  1026. self.update_lib(lib)
  1027. self.loader.clear()
  1028. self._verify_libs()
  1029. class LoaderMultipleGlobalTest(ModuleCase):
  1030. """
  1031. Tests when using multiple lazyloaders
  1032. """
  1033. def setUp(self):
  1034. opts = salt.config.minion_config(None)
  1035. self.loader1 = salt.loader.LazyLoader(
  1036. salt.loader._module_dirs(copy.deepcopy(opts), "modules", "module"),
  1037. copy.deepcopy(opts),
  1038. pack={},
  1039. tag="module",
  1040. loaded_base_name="salt.loader1",
  1041. )
  1042. self.loader2 = salt.loader.LazyLoader(
  1043. salt.loader._module_dirs(copy.deepcopy(opts), "modules", "module"),
  1044. copy.deepcopy(opts),
  1045. pack={},
  1046. tag="module",
  1047. loaded_base_name="salt.loader2",
  1048. )
  1049. def tearDown(self):
  1050. del self.loader1
  1051. del self.loader2
  1052. def test_loader_globals(self):
  1053. """
  1054. Test to ensure loaders do not edit
  1055. each others loader's namespace
  1056. """
  1057. self.loader1.pack["__foo__"] = "bar1"
  1058. func1 = self.loader1["test.ping"]
  1059. self.loader2.pack["__foo__"] = "bar2"
  1060. func2 = self.loader2["test.ping"]
  1061. assert func1.__globals__["__foo__"] == "bar1"
  1062. assert func2.__globals__["__foo__"] == "bar2"
  1063. class LoaderCleanupTest(ModuleCase):
  1064. """
  1065. Tests the loader cleanup procedures
  1066. """
  1067. def setUp(self):
  1068. opts = salt.config.minion_config(None)
  1069. self.loader1 = salt.loader.LazyLoader(
  1070. salt.loader._module_dirs(copy.deepcopy(opts), "modules", "module"),
  1071. copy.deepcopy(opts),
  1072. pack={},
  1073. tag="module",
  1074. loaded_base_name="salt.test",
  1075. )
  1076. def tearDown(self):
  1077. del self.loader1
  1078. def test_loader_gc_cleanup(self):
  1079. loaded_base_name = self.loader1.loaded_base_name
  1080. for name in list(sys.modules):
  1081. if name.startswith(loaded_base_name):
  1082. break
  1083. else:
  1084. self.fail(
  1085. "Did not find any modules in sys.modules matching {!r}".format(
  1086. loaded_base_name
  1087. )
  1088. )
  1089. # Assert that the weakref.finalizer is alive
  1090. assert self.loader1._gc_finalizer.alive is True
  1091. gc.collect()
  1092. # Even after a gc.collect call, we should still have our module in sys.modules
  1093. for name in list(sys.modules):
  1094. if name.startswith(loaded_base_name):
  1095. if sys.modules[name] is None:
  1096. self.fail(
  1097. "Found at least one module in sys.modules matching {!r} prematurely set to None".format(
  1098. loaded_base_name
  1099. )
  1100. )
  1101. break
  1102. else:
  1103. self.fail(
  1104. "Did not find any modules in sys.modules matching {!r}".format(
  1105. loaded_base_name
  1106. )
  1107. )
  1108. # Should still be true because there's still at least one reference to self.loader1
  1109. assert self.loader1._gc_finalizer.alive is True
  1110. # Now we remove our refence to loader and trigger GC, thus triggering the loader weakref finalizer
  1111. self.loader1 = None
  1112. gc.collect()
  1113. for name in list(sys.modules):
  1114. if name.startswith(loaded_base_name):
  1115. if sys.modules[name] is not None:
  1116. self.fail(
  1117. "Found a real module reference in sys.modules matching {!r}.".format(
  1118. loaded_base_name,
  1119. )
  1120. )
  1121. break
  1122. else:
  1123. self.fail(
  1124. "Did not find any modules in sys.modules matching {!r}".format(
  1125. loaded_base_name
  1126. )
  1127. )
  1128. def test_loader_clean_modules(self):
  1129. loaded_base_name = self.loader1.loaded_base_name
  1130. self.loader1.clean_modules()
  1131. for name in list(sys.modules):
  1132. if name.startswith(loaded_base_name):
  1133. self.fail(
  1134. "Found a real module reference in sys.modules matching {!r}".format(
  1135. loaded_base_name
  1136. )
  1137. )
  1138. break
  1139. # Additionally, assert that the weakref.finalizer is now dead
  1140. assert self.loader1._gc_finalizer.alive is False
  1141. class LoaderGlobalsTest(ModuleCase):
  1142. """
  1143. Test all of the globals that the loader is responsible for adding to modules
  1144. This shouldn't be done here, but should rather be done per module type (in the cases where they are used)
  1145. so they can check ALL globals that they have (or should have) access to.
  1146. This is intended as a shorter term way of testing these so we don't break the loader
  1147. """
  1148. def _verify_globals(self, mod_dict):
  1149. """
  1150. Verify that the globals listed in the doc string (from the test) are in these modules
  1151. """
  1152. # find the globals
  1153. global_vars = {}
  1154. for val in mod_dict.values():
  1155. # only find salty globals
  1156. if val.__module__.startswith("salt.loaded"):
  1157. if hasattr(val, "__globals__"):
  1158. if hasattr(val, "__wrapped__") or "__wrapped__" in val.__globals__:
  1159. global_vars[val.__module__] = sys.modules[
  1160. val.__module__
  1161. ].__dict__
  1162. else:
  1163. global_vars[val.__module__] = val.__globals__
  1164. # if we couldn't find any, then we have no modules -- so something is broken
  1165. self.assertNotEqual(global_vars, {}, msg="No modules were loaded.")
  1166. # get the names of the globals you should have
  1167. func_name = inspect.stack()[1][3]
  1168. names = next(
  1169. iter(salt.utils.yaml.safe_load(getattr(self, func_name).__doc__).values())
  1170. )
  1171. # Now, test each module!
  1172. for item in global_vars.values():
  1173. for name in names:
  1174. self.assertIn(name, list(item.keys()))
  1175. def test_auth(self):
  1176. """
  1177. Test that auth mods have:
  1178. - __pillar__
  1179. - __grains__
  1180. - __salt__
  1181. - __context__
  1182. """
  1183. self._verify_globals(salt.loader.auth(self.master_opts))
  1184. def test_runners(self):
  1185. """
  1186. Test that runners have:
  1187. - __pillar__
  1188. - __salt__
  1189. - __opts__
  1190. - __grains__
  1191. - __context__
  1192. """
  1193. self._verify_globals(salt.loader.runner(self.master_opts))
  1194. def test_returners(self):
  1195. """
  1196. Test that returners have:
  1197. - __salt__
  1198. - __opts__
  1199. - __pillar__
  1200. - __grains__
  1201. - __context__
  1202. """
  1203. self._verify_globals(salt.loader.returners(self.master_opts, {}))
  1204. def test_pillars(self):
  1205. """
  1206. Test that pillars have:
  1207. - __salt__
  1208. - __opts__
  1209. - __pillar__
  1210. - __grains__
  1211. - __context__
  1212. """
  1213. self._verify_globals(salt.loader.pillars(self.master_opts, {}))
  1214. def test_tops(self):
  1215. """
  1216. Test that tops have: []
  1217. """
  1218. self._verify_globals(salt.loader.tops(self.master_opts))
  1219. def test_outputters(self):
  1220. """
  1221. Test that outputters have:
  1222. - __opts__
  1223. - __pillar__
  1224. - __grains__
  1225. - __context__
  1226. """
  1227. self._verify_globals(salt.loader.outputters(self.master_opts))
  1228. def test_serializers(self):
  1229. """
  1230. Test that serializers have: []
  1231. """
  1232. self._verify_globals(salt.loader.serializers(self.master_opts))
  1233. @slowTest
  1234. def test_states(self):
  1235. """
  1236. Test that states have:
  1237. - __pillar__
  1238. - __salt__
  1239. - __opts__
  1240. - __grains__
  1241. - __context__
  1242. """
  1243. opts = salt.config.minion_config(None)
  1244. opts["grains"] = salt.loader.grains(opts)
  1245. utils = salt.loader.utils(opts)
  1246. proxy = salt.loader.proxy(opts)
  1247. funcs = salt.loader.minion_mods(opts, utils=utils, proxy=proxy)
  1248. self._verify_globals(salt.loader.states(opts, funcs, utils, {}, proxy=proxy))
  1249. def test_renderers(self):
  1250. """
  1251. Test that renderers have:
  1252. - __salt__ # Execution functions (i.e. __salt__['test.echo']('foo'))
  1253. - __grains__ # Grains (i.e. __grains__['os'])
  1254. - __pillar__ # Pillar data (i.e. __pillar__['foo'])
  1255. - __opts__ # Minion configuration options
  1256. - __context__ # Context dict shared amongst all modules of the same type
  1257. """
  1258. self._verify_globals(salt.loader.render(self.master_opts, {}))
  1259. class RawModTest(TestCase):
  1260. """
  1261. Test the interface of raw_mod
  1262. """
  1263. def setUp(self):
  1264. self.opts = salt.config.minion_config(None)
  1265. def tearDown(self):
  1266. del self.opts
  1267. @slowTest
  1268. def test_basic(self):
  1269. testmod = salt.loader.raw_mod(self.opts, "test", None)
  1270. for k, v in testmod.items():
  1271. self.assertEqual(k.split(".")[0], "test")
  1272. def test_bad_name(self):
  1273. testmod = salt.loader.raw_mod(self.opts, "module_we_do_not_have", None)
  1274. self.assertEqual(testmod, {})
  1275. class NetworkUtilsTestCase(ModuleCase):
  1276. def test_is_private(self):
  1277. mod = salt.loader.raw_mod(self.minion_opts, "network", None)
  1278. self.assertTrue(mod["network.is_private"]("10.0.0.1"), True)
  1279. def test_is_loopback(self):
  1280. mod = salt.loader.raw_mod(self.minion_opts, "network", None)
  1281. self.assertTrue(mod["network.is_loopback"]("127.0.0.1"), True)
  1282. class LazyLoaderOptimizationOrderTest(TestCase):
  1283. """
  1284. Test the optimization order priority in the loader (PY3)
  1285. """
  1286. module_name = "lazyloadertest"
  1287. module_content = textwrap.dedent(
  1288. """\
  1289. # -*- coding: utf-8 -*-
  1290. from __future__ import absolute_import
  1291. def test():
  1292. return True
  1293. """
  1294. )
  1295. @classmethod
  1296. def setUpClass(cls):
  1297. cls.opts = salt.config.minion_config(None)
  1298. cls.opts["grains"] = salt.loader.grains(cls.opts)
  1299. cls.utils = salt.loader.utils(copy.deepcopy(cls.opts))
  1300. cls.proxy = salt.loader.proxy(cls.opts)
  1301. cls.funcs = salt.loader.minion_mods(cls.opts, utils=cls.utils, proxy=cls.proxy)
  1302. @classmethod
  1303. def tearDownClass(cls):
  1304. del cls.opts
  1305. del cls.funcs
  1306. del cls.utils
  1307. del cls.proxy
  1308. def setUp(self):
  1309. # Setup the module
  1310. self.module_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  1311. self.addCleanup(shutil.rmtree, self.module_dir, ignore_errors=True)
  1312. self.module_file = os.path.join(
  1313. self.module_dir, "{}.py".format(self.module_name)
  1314. )
  1315. def tearDown(self):
  1316. try:
  1317. delattr(self, "loader")
  1318. except AttributeError:
  1319. pass
  1320. def _get_loader(self, order=None):
  1321. opts = copy.deepcopy(self.opts)
  1322. if order is not None:
  1323. opts["optimization_order"] = order
  1324. # Return a loader
  1325. return salt.loader.LazyLoader(
  1326. [self.module_dir],
  1327. opts,
  1328. pack={
  1329. "__utils__": self.utils,
  1330. "__salt__": self.funcs,
  1331. "__proxy__": self.proxy,
  1332. },
  1333. tag="module",
  1334. )
  1335. def _get_module_filename(self):
  1336. # The act of referencing the loader entry forces the module to be
  1337. # loaded by the LazyDict.
  1338. mod_fullname = self.loader[next(iter(self.loader))].__module__
  1339. return sys.modules[mod_fullname].__file__
  1340. def _expected(self, optimize=0):
  1341. return "lazyloadertest.cpython-{}{}{}.pyc".format(
  1342. sys.version_info[0],
  1343. sys.version_info[1],
  1344. "" if not optimize else ".opt-{}".format(optimize),
  1345. )
  1346. def _write_module_file(self):
  1347. with salt.utils.files.fopen(self.module_file, "w") as fh:
  1348. fh.write(self.module_content)
  1349. fh.flush()
  1350. os.fsync(fh.fileno())
  1351. def _byte_compile(self):
  1352. if salt.loader.USE_IMPORTLIB:
  1353. # Skip this check as "optimize" is unique to PY3's compileall
  1354. # module, and this will be a false error when Pylint is run on
  1355. # Python 2.
  1356. # pylint: disable=unexpected-keyword-arg
  1357. compileall.compile_file(self.module_file, quiet=1, optimize=0)
  1358. compileall.compile_file(self.module_file, quiet=1, optimize=1)
  1359. compileall.compile_file(self.module_file, quiet=1, optimize=2)
  1360. # pylint: enable=unexpected-keyword-arg
  1361. else:
  1362. compileall.compile_file(self.module_file, quiet=1)
  1363. def _test_optimization_order(self, order):
  1364. self._write_module_file()
  1365. self._byte_compile()
  1366. # Clean up the original file so that we can be assured we're only
  1367. # loading the byte-compiled files(s).
  1368. os.remove(self.module_file)
  1369. self.loader = self._get_loader(order)
  1370. filename = self._get_module_filename()
  1371. basename = os.path.basename(filename)
  1372. assert basename == self._expected(order[0]), basename
  1373. if not salt.loader.USE_IMPORTLIB:
  1374. # We are only testing multiple optimization levels on Python 3.5+
  1375. return
  1376. # Remove the file and make a new loader. We should now load the
  1377. # byte-compiled file with an optimization level matching the 2nd
  1378. # element of the order list.
  1379. os.remove(filename)
  1380. self.loader = self._get_loader(order)
  1381. filename = self._get_module_filename()
  1382. basename = os.path.basename(filename)
  1383. assert basename == self._expected(order[1]), basename
  1384. # Remove the file and make a new loader. We should now load the
  1385. # byte-compiled file with an optimization level matching the 3rd
  1386. # element of the order list.
  1387. os.remove(filename)
  1388. self.loader = self._get_loader(order)
  1389. filename = self._get_module_filename()
  1390. basename = os.path.basename(filename)
  1391. assert basename == self._expected(order[2]), basename
  1392. def test_optimization_order(self):
  1393. """
  1394. Test the optimization_order config param
  1395. """
  1396. self._test_optimization_order([0, 1, 2])
  1397. self._test_optimization_order([0, 2, 1])
  1398. if salt.loader.USE_IMPORTLIB:
  1399. # optimization_order only supported on Python 3.5+, earlier
  1400. # releases only support unoptimized .pyc files.
  1401. self._test_optimization_order([1, 2, 0])
  1402. self._test_optimization_order([1, 0, 2])
  1403. self._test_optimization_order([2, 0, 1])
  1404. self._test_optimization_order([2, 1, 0])
  1405. def test_load_source_file(self):
  1406. """
  1407. Make sure that .py files are preferred over .pyc files
  1408. """
  1409. self._write_module_file()
  1410. self._byte_compile()
  1411. self.loader = self._get_loader()
  1412. filename = self._get_module_filename()
  1413. basename = os.path.basename(filename)
  1414. expected = "lazyloadertest.py"
  1415. assert basename == expected, basename
  1416. class LoaderLoadCachedGrainsTest(TestCase):
  1417. """
  1418. Test how the loader works with cached grains
  1419. """
  1420. @classmethod
  1421. def setUpClass(cls):
  1422. cls.opts = salt.config.minion_config(None)
  1423. if not os.path.isdir(RUNTIME_VARS.TMP):
  1424. os.makedirs(RUNTIME_VARS.TMP)
  1425. def setUp(self):
  1426. self.cache_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  1427. self.addCleanup(shutil.rmtree, self.cache_dir, ignore_errors=True)
  1428. self.opts["cachedir"] = self.cache_dir
  1429. self.opts["grains_cache"] = True
  1430. self.opts["grains"] = salt.loader.grains(self.opts)
  1431. @slowTest
  1432. def test_osrelease_info_has_correct_type(self):
  1433. """
  1434. Make sure osrelease_info is tuple after caching
  1435. """
  1436. grains = salt.loader.grains(self.opts)
  1437. osrelease_info = grains["osrelease_info"]
  1438. assert isinstance(osrelease_info, tuple), osrelease_info
  1439. class LazyLoaderRefreshFileMappingTest(TestCase):
  1440. """
  1441. Test that _refresh_file_mapping is called using acquiring LazyLoader._lock
  1442. """
  1443. @classmethod
  1444. def setUpClass(cls):
  1445. cls.opts = salt.config.minion_config(None)
  1446. cls.opts["grains"] = salt.loader.grains(cls.opts)
  1447. cls.utils = salt.loader.utils(copy.deepcopy(cls.opts))
  1448. cls.proxy = salt.loader.proxy(cls.opts)
  1449. cls.funcs = salt.loader.minion_mods(cls.opts, utils=cls.utils, proxy=cls.proxy)
  1450. def setUp(self):
  1451. class LazyLoaderMock(salt.loader.LazyLoader):
  1452. pass
  1453. self.LOADER_CLASS = LazyLoaderMock
  1454. def __init_loader(self):
  1455. return self.LOADER_CLASS(
  1456. salt.loader._module_dirs(copy.deepcopy(self.opts), "modules", "module"),
  1457. copy.deepcopy(self.opts),
  1458. tag="module",
  1459. pack={
  1460. "__utils__": self.utils,
  1461. "__salt__": self.funcs,
  1462. "__proxy__": self.proxy,
  1463. },
  1464. )
  1465. @classmethod
  1466. def tearDownClass(cls):
  1467. del cls.opts
  1468. del cls.utils
  1469. del cls.funcs
  1470. del cls.proxy
  1471. def test_lazyloader_refresh_file_mapping_called_with_lock_at___init__(self):
  1472. func_mock = MagicMock()
  1473. lock_mock = MagicMock()
  1474. lock_mock.__enter__ = MagicMock()
  1475. self.LOADER_CLASS._refresh_file_mapping = func_mock
  1476. with patch("threading.RLock", MagicMock(return_value=lock_mock)):
  1477. loader = self.__init_loader()
  1478. lock_mock.__enter__.assert_called()
  1479. func_mock.assert_called()
  1480. assert len(func_mock.call_args_list) == len(lock_mock.__enter__.call_args_list)
  1481. del loader
  1482. def test_lazyloader_zip_modules(self):
  1483. self.opts["enable_zip_modules"] = True
  1484. try:
  1485. loader = self.__init_loader()
  1486. assert ".zip" in loader.suffix_map
  1487. assert ".zip" in loader.suffix_order
  1488. finally:
  1489. self.opts["enable_zip_modules"] = False
  1490. loader = self.__init_loader()
  1491. assert ".zip" not in loader.suffix_map
  1492. assert ".zip" not in loader.suffix_order
  1493. def test_lazyloader_pyx_modules(self):
  1494. self.opts["cython_enable"] = True
  1495. try:
  1496. loader = self.__init_loader()
  1497. # Don't assert if the current environment has no pyximport
  1498. if salt.loader.pyximport is not None:
  1499. assert ".pyx" in loader.suffix_map
  1500. assert ".pyx" in loader.suffix_order
  1501. finally:
  1502. self.opts["cython_enable"] = False
  1503. loader = self.__init_loader()
  1504. assert ".pyx" not in loader.suffix_map
  1505. assert ".pyx" not in loader.suffix_order