test_loader.py 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269
  1. # -*- coding: utf-8 -*-
  2. '''
  3. unit.loader
  4. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  5. Test Salt's loader
  6. '''
  7. # Import Python libs
  8. from __future__ import absolute_import, print_function, unicode_literals
  9. import collections
  10. import compileall
  11. import copy
  12. import imp
  13. import inspect
  14. import logging
  15. import os
  16. import shutil
  17. import sys
  18. import tempfile
  19. import textwrap
  20. # Import Salt Testing libs
  21. from tests.support.runtests import RUNTIME_VARS
  22. from tests.support.case import ModuleCase
  23. from tests.support.unit import TestCase
  24. from tests.support.mock import patch
  25. # Import Salt libs
  26. import salt.config
  27. import salt.loader
  28. import salt.utils.files
  29. import salt.utils.stringutils
  30. # pylint: disable=import-error,no-name-in-module,redefined-builtin
  31. from salt.ext import six
  32. from salt.ext.six.moves import range
  33. # pylint: enable=no-name-in-module,redefined-builtin
  34. log = logging.getLogger(__name__)
  35. def remove_bytecode(module_path):
  36. paths = [module_path + 'c']
  37. if hasattr(imp, 'get_tag'):
  38. modname, ext = os.path.splitext(module_path.split(os.sep)[-1])
  39. paths.append(
  40. os.path.join(os.path.dirname(module_path),
  41. '__pycache__',
  42. '{}.{}.pyc'.format(modname, imp.get_tag())))
  43. for path in paths:
  44. if os.path.exists(path):
  45. os.unlink(path)
  46. loader_template = '''
  47. import os
  48. from salt.utils.decorators import depends
  49. @depends('os')
  50. def loaded():
  51. return True
  52. @depends('non_existantmodulename')
  53. def not_loaded():
  54. return True
  55. '''
  56. class LazyLoaderTest(TestCase):
  57. '''
  58. Test the loader
  59. '''
  60. module_name = 'lazyloadertest'
  61. @classmethod
  62. def setUpClass(cls):
  63. cls.opts = salt.config.minion_config(None)
  64. cls.opts['grains'] = salt.loader.grains(cls.opts)
  65. if not os.path.isdir(RUNTIME_VARS.TMP):
  66. os.makedirs(RUNTIME_VARS.TMP)
  67. def setUp(self):
  68. # Setup the module
  69. self.module_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  70. self.module_file = os.path.join(self.module_dir,
  71. '{0}.py'.format(self.module_name))
  72. with salt.utils.files.fopen(self.module_file, 'w') as fh:
  73. fh.write(salt.utils.stringutils.to_str(loader_template))
  74. fh.flush()
  75. os.fsync(fh.fileno())
  76. # Invoke the loader
  77. self.loader = salt.loader.LazyLoader([self.module_dir], copy.deepcopy(self.opts), tag='module')
  78. def tearDown(self):
  79. shutil.rmtree(self.module_dir)
  80. if os.path.isdir(self.module_dir):
  81. shutil.rmtree(self.module_dir)
  82. del self.module_dir
  83. del self.module_file
  84. del self.loader
  85. @classmethod
  86. def tearDownClass(cls):
  87. del cls.opts
  88. def test_depends(self):
  89. '''
  90. Test that the depends decorator works properly
  91. '''
  92. # Make sure depends correctly allowed a function to load. If this
  93. # results in a KeyError, the decorator is broken.
  94. self.assertTrue(
  95. inspect.isfunction(
  96. self.loader[self.module_name + '.loaded']
  97. )
  98. )
  99. # Make sure depends correctly kept a function from loading
  100. self.assertTrue(self.module_name + '.not_loaded' not in self.loader)
  101. class LazyLoaderVirtualEnabledTest(TestCase):
  102. '''
  103. Test the base loader of salt.
  104. '''
  105. @classmethod
  106. def setUpClass(cls):
  107. cls.opts = salt.config.minion_config(None)
  108. cls.opts['disable_modules'] = ['pillar']
  109. cls.opts['grains'] = salt.loader.grains(cls.opts)
  110. def setUp(self):
  111. self.loader = salt.loader.LazyLoader(
  112. salt.loader._module_dirs(copy.deepcopy(self.opts), 'modules', 'module'),
  113. copy.deepcopy(self.opts),
  114. tag='module')
  115. def tearDown(self):
  116. del self.loader
  117. @classmethod
  118. def tearDownClass(cls):
  119. del cls.opts
  120. def test_basic(self):
  121. '''
  122. Ensure that it only loads stuff when needed
  123. '''
  124. # make sure it starts empty
  125. self.assertEqual(self.loader._dict, {})
  126. # get something, and make sure its a func
  127. self.assertTrue(inspect.isfunction(self.loader['test.ping']))
  128. # make sure we only loaded "test" functions
  129. for key, val in six.iteritems(self.loader._dict):
  130. self.assertEqual(key.split('.', 1)[0], 'test')
  131. # make sure the depends thing worked (double check of the depends testing,
  132. # since the loader does the calling magically
  133. self.assertFalse('test.missing_func' in self.loader._dict)
  134. def test_badkey(self):
  135. with self.assertRaises(KeyError):
  136. self.loader[None] # pylint: disable=W0104
  137. with self.assertRaises(KeyError):
  138. self.loader[1] # pylint: disable=W0104
  139. def test_disable(self):
  140. self.assertNotIn('pillar.items', self.loader)
  141. def test_len_load(self):
  142. '''
  143. Since LazyLoader is a MutableMapping, if someone asks for len() we have
  144. to load all
  145. '''
  146. self.assertEqual(self.loader._dict, {})
  147. len(self.loader) # force a load all
  148. self.assertNotEqual(self.loader._dict, {})
  149. def test_iter_load(self):
  150. '''
  151. Since LazyLoader is a MutableMapping, if someone asks to iterate we have
  152. to load all
  153. '''
  154. self.assertEqual(self.loader._dict, {})
  155. # force a load all
  156. for key, func in six.iteritems(self.loader):
  157. break
  158. self.assertNotEqual(self.loader._dict, {})
  159. def test_context(self):
  160. '''
  161. Make sure context is shared across modules
  162. '''
  163. # make sure it starts empty
  164. self.assertEqual(self.loader._dict, {})
  165. # get something, and make sure its a func
  166. func = self.loader['test.ping']
  167. with patch.dict(func.__globals__['__context__'], {'foo': 'bar'}):
  168. self.assertEqual(self.loader['test.echo'].__globals__['__context__']['foo'], 'bar')
  169. self.assertEqual(self.loader['grains.get'].__globals__['__context__']['foo'], 'bar')
  170. def test_globals(self):
  171. func_globals = self.loader['test.ping'].__globals__
  172. self.assertEqual(func_globals['__grains__'], self.opts.get('grains', {}))
  173. self.assertEqual(func_globals['__pillar__'], self.opts.get('pillar', {}))
  174. # the opts passed into modules is at least a subset of the whole opts
  175. for key, val in six.iteritems(func_globals['__opts__']):
  176. if key in salt.config.DEFAULT_MASTER_OPTS and key not in salt.config.DEFAULT_MINION_OPTS:
  177. # We loaded the minion opts, but somewhere in the code, the master options got pulled in
  178. # Let's just not check for equality since the option won't even exist in the loaded
  179. # minion options
  180. continue
  181. if key not in salt.config.DEFAULT_MASTER_OPTS and key not in salt.config.DEFAULT_MINION_OPTS:
  182. # This isn't even a default configuration setting, lets carry on
  183. continue
  184. self.assertEqual(self.opts[key], val)
  185. def test_pack(self):
  186. self.loader.pack['__foo__'] = 'bar'
  187. func_globals = self.loader['test.ping'].__globals__
  188. self.assertEqual(func_globals['__foo__'], 'bar')
  189. def test_virtual(self):
  190. self.assertNotIn('test_virtual.ping', self.loader)
  191. class LazyLoaderVirtualDisabledTest(TestCase):
  192. '''
  193. Test the loader of salt without __virtual__
  194. '''
  195. @classmethod
  196. def setUpClass(cls):
  197. cls.opts = salt.config.minion_config(None)
  198. cls.opts['grains'] = salt.loader.grains(cls.opts)
  199. def setUp(self):
  200. self.loader = salt.loader.LazyLoader(
  201. salt.loader._module_dirs(copy.deepcopy(self.opts), 'modules', 'module'),
  202. copy.deepcopy(self.opts),
  203. tag='module',
  204. virtual_enable=False)
  205. def tearDown(self):
  206. del self.loader
  207. @classmethod
  208. def tearDownClass(cls):
  209. del cls.opts
  210. def test_virtual(self):
  211. self.assertTrue(inspect.isfunction(self.loader['test_virtual.ping']))
  212. class LazyLoaderWhitelistTest(TestCase):
  213. '''
  214. Test the loader of salt with a whitelist
  215. '''
  216. @classmethod
  217. def setUpClass(cls):
  218. cls.opts = salt.config.minion_config(None)
  219. cls.opts['grains'] = salt.loader.grains(cls.opts)
  220. def setUp(self):
  221. self.loader = salt.loader.LazyLoader(
  222. salt.loader._module_dirs(copy.deepcopy(self.opts), 'modules', 'module'),
  223. copy.deepcopy(self.opts),
  224. tag='module',
  225. whitelist=['test', 'pillar'])
  226. def tearDown(self):
  227. del self.loader
  228. @classmethod
  229. def tearDownClass(cls):
  230. del cls.opts
  231. def test_whitelist(self):
  232. self.assertTrue(inspect.isfunction(self.loader['test.ping']))
  233. self.assertTrue(inspect.isfunction(self.loader['pillar.get']))
  234. self.assertNotIn('grains.get', self.loader)
  235. class LazyLoaderGrainsBlacklistTest(TestCase):
  236. '''
  237. Test the loader of grains with a blacklist
  238. '''
  239. def setUp(self):
  240. self.opts = salt.config.minion_config(None)
  241. def tearDown(self):
  242. del self.opts
  243. def test_whitelist(self):
  244. opts = copy.deepcopy(self.opts)
  245. opts['grains_blacklist'] = [
  246. 'master',
  247. 'os*',
  248. 'ipv[46]'
  249. ]
  250. grains = salt.loader.grains(opts)
  251. self.assertNotIn('master', grains)
  252. self.assertNotIn('os', set([g[:2] for g in list(grains)]))
  253. self.assertNotIn('ipv4', grains)
  254. self.assertNotIn('ipv6', grains)
  255. class LazyLoaderSingleItem(TestCase):
  256. '''
  257. Test loading a single item via the _load() function
  258. '''
  259. @classmethod
  260. def setUpClass(cls):
  261. cls.opts = salt.config.minion_config(None)
  262. cls.opts['grains'] = salt.loader.grains(cls.opts)
  263. def setUp(self):
  264. self.loader = salt.loader.LazyLoader(
  265. salt.loader._module_dirs(copy.deepcopy(self.opts), 'modules', 'module'),
  266. copy.deepcopy(self.opts),
  267. tag='module')
  268. def tearDown(self):
  269. del self.loader
  270. def test_single_item_no_dot(self):
  271. '''
  272. Checks that a KeyError is raised when the function key does not contain a '.'
  273. '''
  274. key = 'testing_no_dot'
  275. expected = "The key '{0}' should contain a '.'".format(key)
  276. with self.assertRaises(KeyError) as err:
  277. inspect.isfunction(self.loader['testing_no_dot'])
  278. result = err.exception.args[0]
  279. assert result == expected, result
  280. module_template = '''
  281. __load__ = ['test', 'test_alias']
  282. __func_alias__ = dict(test_alias='working_alias')
  283. from salt.utils.decorators import depends
  284. def test():
  285. return {count}
  286. def test_alias():
  287. return True
  288. def test2():
  289. return True
  290. @depends('non_existantmodulename')
  291. def test3():
  292. return True
  293. @depends('non_existantmodulename', fallback_function=test)
  294. def test4():
  295. return True
  296. '''
  297. class LazyLoaderReloadingTest(TestCase):
  298. '''
  299. Test the loader of salt with changing modules
  300. '''
  301. module_name = 'loadertest'
  302. module_key = 'loadertest.test'
  303. @classmethod
  304. def setUpClass(cls):
  305. cls.opts = salt.config.minion_config(None)
  306. cls.opts['grains'] = salt.loader.grains(cls.opts)
  307. if not os.path.isdir(RUNTIME_VARS.TMP):
  308. os.makedirs(RUNTIME_VARS.TMP)
  309. def setUp(self):
  310. self.tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  311. self.count = 0
  312. opts = copy.deepcopy(self.opts)
  313. dirs = salt.loader._module_dirs(opts, 'modules', 'module')
  314. dirs.append(self.tmp_dir)
  315. self.utils = salt.loader.utils(opts)
  316. self.proxy = salt.loader.proxy(opts)
  317. self.minion_mods = salt.loader.minion_mods(opts)
  318. self.loader = salt.loader.LazyLoader(
  319. dirs,
  320. opts,
  321. tag='module',
  322. pack={'__utils__': self.utils,
  323. '__proxy__': self.proxy,
  324. '__salt__': self.minion_mods})
  325. def tearDown(self):
  326. shutil.rmtree(self.tmp_dir)
  327. for attrname in ('tmp_dir', 'utils', 'proxy', 'loader', 'minion_mods', 'utils'):
  328. try:
  329. delattr(self, attrname)
  330. except AttributeError:
  331. continue
  332. @classmethod
  333. def tearDownClass(cls):
  334. del cls.opts
  335. def update_module(self):
  336. self.count += 1
  337. with salt.utils.files.fopen(self.module_path, 'wb') as fh:
  338. fh.write(
  339. salt.utils.stringutils.to_bytes(
  340. module_template.format(count=self.count)
  341. )
  342. )
  343. fh.flush()
  344. os.fsync(fh.fileno()) # flush to disk
  345. # pyc files don't like it when we change the original quickly
  346. # since the header bytes only contain the timestamp (granularity of seconds)
  347. # TODO: don't write them? Is *much* slower on re-load (~3x)
  348. # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
  349. remove_bytecode(self.module_path)
  350. def rm_module(self):
  351. os.unlink(self.module_path)
  352. remove_bytecode(self.module_path)
  353. @property
  354. def module_path(self):
  355. return os.path.join(self.tmp_dir, '{0}.py'.format(self.module_name))
  356. def test_alias(self):
  357. '''
  358. Make sure that you can access alias-d modules
  359. '''
  360. # ensure it doesn't exist
  361. self.assertNotIn(self.module_key, self.loader)
  362. self.update_module()
  363. self.assertNotIn('{0}.test_alias'.format(self.module_name), self.loader)
  364. self.assertTrue(inspect.isfunction(self.loader['{0}.working_alias'.format(self.module_name)]))
  365. def test_clear(self):
  366. self.assertTrue(inspect.isfunction(self.loader['test.ping']))
  367. self.update_module() # write out out custom module
  368. self.loader.clear() # clear the loader dict
  369. # force a load of our module
  370. self.assertTrue(inspect.isfunction(self.loader[self.module_key]))
  371. # make sure we only loaded our custom module
  372. # which means that we did correctly refresh the file mapping
  373. for k, v in six.iteritems(self.loader._dict):
  374. self.assertTrue(k.startswith(self.module_name))
  375. def test_load(self):
  376. # ensure it doesn't exist
  377. self.assertNotIn(self.module_key, self.loader)
  378. self.update_module()
  379. self.assertTrue(inspect.isfunction(self.loader[self.module_key]))
  380. def test__load__(self):
  381. '''
  382. If a module specifies __load__ we should only load/expose those modules
  383. '''
  384. self.update_module()
  385. # ensure it doesn't exist
  386. self.assertNotIn(self.module_key + '2', self.loader)
  387. def test__load__and_depends(self):
  388. '''
  389. If a module specifies __load__ we should only load/expose those modules
  390. '''
  391. self.update_module()
  392. # ensure it doesn't exist
  393. self.assertNotIn(self.module_key + '3', self.loader)
  394. self.assertNotIn(self.module_key + '4', self.loader)
  395. def test_reload(self):
  396. # ensure it doesn't exist
  397. self.assertNotIn(self.module_key, self.loader)
  398. # make sure it updates correctly
  399. for x in range(1, 3):
  400. self.update_module()
  401. self.loader.clear()
  402. self.assertEqual(self.loader[self.module_key](), self.count)
  403. self.rm_module()
  404. # make sure that even if we remove the module, its still loaded until a clear
  405. self.assertEqual(self.loader[self.module_key](), self.count)
  406. self.loader.clear()
  407. self.assertNotIn(self.module_key, self.loader)
  408. virtual_aliases = ('loadertest2', 'loadertest3')
  409. virtual_alias_module_template = '''
  410. __virtual_aliases__ = {0}
  411. def test():
  412. return True
  413. '''.format(virtual_aliases)
  414. class LazyLoaderVirtualAliasTest(TestCase):
  415. '''
  416. Test the loader of salt with changing modules
  417. '''
  418. module_name = 'loadertest'
  419. @classmethod
  420. def setUpClass(cls):
  421. cls.opts = salt.config.minion_config(None)
  422. cls.opts['grains'] = salt.loader.grains(cls.opts)
  423. if not os.path.isdir(RUNTIME_VARS.TMP):
  424. os.makedirs(RUNTIME_VARS.TMP)
  425. def setUp(self):
  426. self.tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  427. opts = copy.deepcopy(self.opts)
  428. dirs = salt.loader._module_dirs(opts, 'modules', 'module')
  429. dirs.append(self.tmp_dir)
  430. self.utils = salt.loader.utils(opts)
  431. self.proxy = salt.loader.proxy(opts)
  432. self.minion_mods = salt.loader.minion_mods(opts)
  433. self.loader = salt.loader.LazyLoader(
  434. dirs,
  435. opts,
  436. tag='module',
  437. pack={'__utils__': self.utils,
  438. '__proxy__': self.proxy,
  439. '__salt__': self.minion_mods})
  440. def tearDown(self):
  441. del self.tmp_dir
  442. del self.utils
  443. del self.proxy
  444. del self.minion_mods
  445. del self.loader
  446. @classmethod
  447. def tearDownClass(cls):
  448. del cls.opts
  449. def update_module(self):
  450. with salt.utils.files.fopen(self.module_path, 'wb') as fh:
  451. fh.write(salt.utils.stringutils.to_bytes(virtual_alias_module_template))
  452. fh.flush()
  453. os.fsync(fh.fileno()) # flush to disk
  454. # pyc files don't like it when we change the original quickly
  455. # since the header bytes only contain the timestamp (granularity of seconds)
  456. # TODO: don't write them? Is *much* slower on re-load (~3x)
  457. # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
  458. remove_bytecode(self.module_path)
  459. @property
  460. def module_path(self):
  461. return os.path.join(self.tmp_dir, '{0}.py'.format(self.module_name))
  462. def test_virtual_alias(self):
  463. '''
  464. Test the __virtual_alias__ feature
  465. '''
  466. self.update_module()
  467. mod_names = [self.module_name] + list(virtual_aliases)
  468. for mod_name in mod_names:
  469. func_name = '.'.join((mod_name, 'test'))
  470. log.debug('Running %s (dict attribute)', func_name)
  471. self.assertTrue(self.loader[func_name]())
  472. log.debug('Running %s (loader attribute)', func_name)
  473. self.assertTrue(getattr(self.loader, mod_name).test())
  474. submodule_template = '''
  475. from __future__ import absolute_import
  476. import {0}.lib
  477. def test():
  478. return ({count}, {0}.lib.test())
  479. '''
  480. submodule_lib_template = '''
  481. def test():
  482. return {count}
  483. '''
  484. class LazyLoaderSubmodReloadingTest(TestCase):
  485. '''
  486. Test the loader of salt with changing modules
  487. '''
  488. module_name = 'loadertestsubmod'
  489. module_key = 'loadertestsubmod.test'
  490. @classmethod
  491. def setUpClass(cls):
  492. cls.opts = salt.config.minion_config(None)
  493. cls.opts['grains'] = salt.loader.grains(cls.opts)
  494. if not os.path.isdir(RUNTIME_VARS.TMP):
  495. os.makedirs(RUNTIME_VARS.TMP)
  496. def setUp(self):
  497. self.tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  498. os.makedirs(self.module_dir)
  499. self.count = 0
  500. self.lib_count = 0
  501. opts = copy.deepcopy(self.opts)
  502. dirs = salt.loader._module_dirs(opts, 'modules', 'module')
  503. dirs.append(self.tmp_dir)
  504. self.utils = salt.loader.utils(opts)
  505. self.proxy = salt.loader.proxy(opts)
  506. self.minion_mods = salt.loader.minion_mods(opts)
  507. self.loader = salt.loader.LazyLoader(
  508. dirs,
  509. opts,
  510. tag='module',
  511. pack={'__utils__': self.utils,
  512. '__proxy__': self.proxy,
  513. '__salt__': self.minion_mods})
  514. def tearDown(self):
  515. shutil.rmtree(self.tmp_dir)
  516. del self.tmp_dir
  517. del self.utils
  518. del self.proxy
  519. del self.minion_mods
  520. del self.loader
  521. @classmethod
  522. def tearDownClass(cls):
  523. del cls.opts
  524. def update_module(self):
  525. self.count += 1
  526. with salt.utils.files.fopen(self.module_path, 'wb') as fh:
  527. fh.write(
  528. salt.utils.stringutils.to_bytes(
  529. submodule_template.format(self.module_name, count=self.count)
  530. )
  531. )
  532. fh.flush()
  533. os.fsync(fh.fileno()) # flush to disk
  534. # pyc files don't like it when we change the original quickly
  535. # since the header bytes only contain the timestamp (granularity of seconds)
  536. # TODO: don't write them? Is *much* slower on re-load (~3x)
  537. # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
  538. remove_bytecode(self.module_path)
  539. def rm_module(self):
  540. os.unlink(self.module_path)
  541. remove_bytecode(self.module_path)
  542. def update_lib(self):
  543. self.lib_count += 1
  544. for modname in list(sys.modules):
  545. if modname.startswith(self.module_name):
  546. del sys.modules[modname]
  547. with salt.utils.files.fopen(self.lib_path, 'wb') as fh:
  548. fh.write(
  549. salt.utils.stringutils.to_bytes(
  550. submodule_lib_template.format(count=self.lib_count)
  551. )
  552. )
  553. fh.flush()
  554. os.fsync(fh.fileno()) # flush to disk
  555. # pyc files don't like it when we change the original quickly
  556. # since the header bytes only contain the timestamp (granularity of seconds)
  557. # TODO: don't write them? Is *much* slower on re-load (~3x)
  558. # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
  559. remove_bytecode(self.lib_path)
  560. def rm_lib(self):
  561. for modname in list(sys.modules):
  562. if modname.startswith(self.module_name):
  563. del sys.modules[modname]
  564. os.unlink(self.lib_path)
  565. remove_bytecode(self.lib_path)
  566. @property
  567. def module_dir(self):
  568. return os.path.join(self.tmp_dir, self.module_name)
  569. @property
  570. def module_path(self):
  571. return os.path.join(self.module_dir, '__init__.py')
  572. @property
  573. def lib_path(self):
  574. return os.path.join(self.module_dir, 'lib.py')
  575. def test_basic(self):
  576. # ensure it doesn't exist
  577. self.assertNotIn(self.module_key, self.loader)
  578. self.update_module()
  579. self.update_lib()
  580. self.loader.clear()
  581. self.assertIn(self.module_key, self.loader)
  582. def test_reload(self):
  583. # ensure it doesn't exist
  584. self.assertNotIn(self.module_key, self.loader)
  585. # update both the module and the lib
  586. for x in range(1, 3):
  587. self.update_lib()
  588. self.update_module()
  589. self.loader.clear()
  590. self.assertNotIn(self.module_key, self.loader._dict)
  591. self.assertIn(self.module_key, self.loader)
  592. self.assertEqual(self.loader[self.module_key](), (self.count, self.lib_count))
  593. # update just the module
  594. for x in range(1, 3):
  595. self.update_module()
  596. self.loader.clear()
  597. self.assertNotIn(self.module_key, self.loader._dict)
  598. self.assertIn(self.module_key, self.loader)
  599. self.assertEqual(self.loader[self.module_key](), (self.count, self.lib_count))
  600. # update just the lib
  601. for x in range(1, 3):
  602. self.update_lib()
  603. self.loader.clear()
  604. self.assertNotIn(self.module_key, self.loader._dict)
  605. self.assertIn(self.module_key, self.loader)
  606. self.assertEqual(self.loader[self.module_key](), (self.count, self.lib_count))
  607. self.rm_module()
  608. # make sure that even if we remove the module, its still loaded until a clear
  609. self.assertEqual(self.loader[self.module_key](), (self.count, self.lib_count))
  610. self.loader.clear()
  611. self.assertNotIn(self.module_key, self.loader)
  612. def test_reload_missing_lib(self):
  613. # ensure it doesn't exist
  614. self.assertNotIn(self.module_key, self.loader)
  615. # update both the module and the lib
  616. self.update_module()
  617. self.update_lib()
  618. self.loader.clear()
  619. self.assertEqual(self.loader[self.module_key](), (self.count, self.lib_count))
  620. # remove the lib, this means we should fail to load the module next time
  621. self.rm_lib()
  622. self.loader.clear()
  623. self.assertNotIn(self.module_key, self.loader)
  624. mod_template = '''
  625. def test():
  626. return ({val})
  627. '''
  628. class LazyLoaderModulePackageTest(TestCase):
  629. '''
  630. Test the loader of salt with changing modules
  631. '''
  632. module_name = 'loadertestmodpkg'
  633. module_key = 'loadertestmodpkg.test'
  634. @classmethod
  635. def setUpClass(cls):
  636. cls.opts = salt.config.minion_config(None)
  637. cls.opts['grains'] = salt.loader.grains(cls.opts)
  638. if not os.path.isdir(RUNTIME_VARS.TMP):
  639. os.makedirs(RUNTIME_VARS.TMP)
  640. def setUp(self):
  641. self.tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  642. dirs = salt.loader._module_dirs(copy.deepcopy(self.opts), 'modules', 'module')
  643. dirs.append(self.tmp_dir)
  644. self.loader = salt.loader.LazyLoader(
  645. dirs,
  646. copy.deepcopy(self.opts),
  647. tag='module')
  648. def tearDown(self):
  649. shutil.rmtree(self.tmp_dir)
  650. del self.tmp_dir
  651. del self.loader
  652. @classmethod
  653. def tearDownClass(cls):
  654. del cls.opts
  655. def update_pyfile(self, pyfile, contents):
  656. dirname = os.path.dirname(pyfile)
  657. if not os.path.exists(dirname):
  658. os.makedirs(dirname)
  659. with salt.utils.files.fopen(pyfile, 'wb') as fh:
  660. fh.write(salt.utils.stringutils.to_bytes(contents))
  661. fh.flush()
  662. os.fsync(fh.fileno()) # flush to disk
  663. # pyc files don't like it when we change the original quickly
  664. # since the header bytes only contain the timestamp (granularity of seconds)
  665. # TODO: don't write them? Is *much* slower on re-load (~3x)
  666. # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
  667. remove_bytecode(pyfile)
  668. def rm_pyfile(self, pyfile):
  669. os.unlink(pyfile)
  670. remove_bytecode(pyfile)
  671. def update_module(self, relative_path, contents):
  672. self.update_pyfile(os.path.join(self.tmp_dir, relative_path), contents)
  673. def rm_module(self, relative_path):
  674. self.rm_pyfile(os.path.join(self.tmp_dir, relative_path))
  675. def test_module(self):
  676. # ensure it doesn't exist
  677. self.assertNotIn('foo', self.loader)
  678. self.assertNotIn('foo.test', self.loader)
  679. self.update_module('foo.py', mod_template.format(val=1))
  680. self.loader.clear()
  681. self.assertIn('foo.test', self.loader)
  682. self.assertEqual(self.loader['foo.test'](), 1)
  683. def test_package(self):
  684. # ensure it doesn't exist
  685. self.assertNotIn('foo', self.loader)
  686. self.assertNotIn('foo.test', self.loader)
  687. self.update_module('foo/__init__.py', mod_template.format(val=2))
  688. self.loader.clear()
  689. self.assertIn('foo.test', self.loader)
  690. self.assertEqual(self.loader['foo.test'](), 2)
  691. def test_module_package_collision(self):
  692. # ensure it doesn't exist
  693. self.assertNotIn('foo', self.loader)
  694. self.assertNotIn('foo.test', self.loader)
  695. self.update_module('foo.py', mod_template.format(val=3))
  696. self.loader.clear()
  697. self.assertIn('foo.test', self.loader)
  698. self.assertEqual(self.loader['foo.test'](), 3)
  699. self.update_module('foo/__init__.py', mod_template.format(val=4))
  700. self.loader.clear()
  701. self.assertIn('foo.test', self.loader)
  702. self.assertEqual(self.loader['foo.test'](), 4)
  703. deep_init_base = '''
  704. from __future__ import absolute_import
  705. import {0}.top_lib
  706. import {0}.top_lib.mid_lib
  707. import {0}.top_lib.mid_lib.bot_lib
  708. def top():
  709. return {0}.top_lib.test()
  710. def mid():
  711. return {0}.top_lib.mid_lib.test()
  712. def bot():
  713. return {0}.top_lib.mid_lib.bot_lib.test()
  714. '''
  715. class LazyLoaderDeepSubmodReloadingTest(TestCase):
  716. module_name = 'loadertestsubmoddeep'
  717. libs = ('top_lib', 'mid_lib', 'bot_lib')
  718. @classmethod
  719. def setUpClass(cls):
  720. cls.opts = salt.config.minion_config(None)
  721. cls.opts['grains'] = salt.loader.grains(cls.opts)
  722. if not os.path.isdir(RUNTIME_VARS.TMP):
  723. os.makedirs(RUNTIME_VARS.TMP)
  724. def setUp(self):
  725. self.tmp_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  726. os.makedirs(self.module_dir)
  727. self.lib_count = collections.defaultdict(int) # mapping of path -> count
  728. # bootstrap libs
  729. with salt.utils.files.fopen(os.path.join(self.module_dir, '__init__.py'), 'w') as fh:
  730. # No .decode() needed here as deep_init_base is defined as str and
  731. # not bytes.
  732. fh.write(
  733. salt.utils.stringutils.to_str(
  734. deep_init_base.format(self.module_name)
  735. )
  736. )
  737. fh.flush()
  738. os.fsync(fh.fileno()) # flush to disk
  739. self.lib_paths = {}
  740. dir_path = self.module_dir
  741. for lib_name in self.libs:
  742. dir_path = os.path.join(dir_path, lib_name)
  743. self.lib_paths[lib_name] = dir_path
  744. os.makedirs(dir_path)
  745. self.update_lib(lib_name)
  746. opts = copy.deepcopy(self.opts)
  747. dirs = salt.loader._module_dirs(opts, 'modules', 'module')
  748. dirs.append(self.tmp_dir)
  749. self.utils = salt.loader.utils(opts)
  750. self.proxy = salt.loader.proxy(opts)
  751. self.minion_mods = salt.loader.minion_mods(opts)
  752. self.loader = salt.loader.LazyLoader(
  753. dirs,
  754. copy.deepcopy(opts),
  755. tag='module',
  756. pack={'__utils__': self.utils,
  757. '__proxy__': self.proxy,
  758. '__salt__': self.minion_mods})
  759. self.assertIn('{0}.top'.format(self.module_name), self.loader)
  760. def tearDown(self):
  761. shutil.rmtree(self.tmp_dir)
  762. del self.tmp_dir
  763. del self.lib_paths
  764. del self.utils
  765. del self.proxy
  766. del self.minion_mods
  767. del self.loader
  768. del self.lib_count
  769. @classmethod
  770. def tearDownClass(cls):
  771. del cls.opts
  772. @property
  773. def module_dir(self):
  774. return os.path.join(self.tmp_dir, self.module_name)
  775. def update_lib(self, lib_name):
  776. for modname in list(sys.modules):
  777. if modname.startswith(self.module_name):
  778. del sys.modules[modname]
  779. path = os.path.join(self.lib_paths[lib_name], '__init__.py')
  780. self.lib_count[lib_name] += 1
  781. with salt.utils.files.fopen(path, 'wb') as fh:
  782. fh.write(
  783. salt.utils.stringutils.to_bytes(
  784. submodule_lib_template.format(count=self.lib_count[lib_name])
  785. )
  786. )
  787. fh.flush()
  788. os.fsync(fh.fileno()) # flush to disk
  789. # pyc files don't like it when we change the original quickly
  790. # since the header bytes only contain the timestamp (granularity of seconds)
  791. # TODO: don't write them? Is *much* slower on re-load (~3x)
  792. # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
  793. remove_bytecode(path)
  794. def test_basic(self):
  795. self.assertIn('{0}.top'.format(self.module_name), self.loader)
  796. def _verify_libs(self):
  797. for lib in self.libs:
  798. self.assertEqual(self.loader['{0}.{1}'.format(self.module_name, lib.replace('_lib', ''))](),
  799. self.lib_count[lib])
  800. def test_reload(self):
  801. '''
  802. Make sure that we can reload all libraries of arbitrary depth
  803. '''
  804. self._verify_libs()
  805. # update them all
  806. for lib in self.libs:
  807. for x in range(5):
  808. self.update_lib(lib)
  809. self.loader.clear()
  810. self._verify_libs()
  811. class LoaderGlobalsTest(ModuleCase):
  812. '''
  813. Test all of the globals that the loader is responsible for adding to modules
  814. This shouldn't be done here, but should rather be done per module type (in the cases where they are used)
  815. so they can check ALL globals that they have (or should have) access to.
  816. This is intended as a shorter term way of testing these so we don't break the loader
  817. '''
  818. def _verify_globals(self, mod_dict):
  819. '''
  820. Verify that the globals listed in the doc string (from the test) are in these modules
  821. '''
  822. # find the globals
  823. global_vars = []
  824. for val in six.itervalues(mod_dict):
  825. # only find salty globals
  826. if val.__module__.startswith('salt.loaded'):
  827. if hasattr(val, '__globals__'):
  828. if hasattr(val, '__wrapped__') or '__wrapped__' in val.__globals__:
  829. global_vars.append(sys.modules[val.__module__].__dict__)
  830. else:
  831. global_vars.append(val.__globals__)
  832. # if we couldn't find any, then we have no modules -- so something is broken
  833. self.assertNotEqual(global_vars, [], msg='No modules were loaded.')
  834. # get the names of the globals you should have
  835. func_name = inspect.stack()[1][3]
  836. names = next(six.itervalues(salt.utils.yaml.safe_load(getattr(self, func_name).__doc__)))
  837. # Now, test each module!
  838. for item in global_vars:
  839. for name in names:
  840. self.assertIn(name, list(item.keys()))
  841. def test_auth(self):
  842. '''
  843. Test that auth mods have:
  844. - __pillar__
  845. - __grains__
  846. - __salt__
  847. - __context__
  848. '''
  849. self._verify_globals(salt.loader.auth(self.master_opts))
  850. def test_runners(self):
  851. '''
  852. Test that runners have:
  853. - __pillar__
  854. - __salt__
  855. - __opts__
  856. - __grains__
  857. - __context__
  858. '''
  859. self._verify_globals(salt.loader.runner(self.master_opts))
  860. def test_returners(self):
  861. '''
  862. Test that returners have:
  863. - __salt__
  864. - __opts__
  865. - __pillar__
  866. - __grains__
  867. - __context__
  868. '''
  869. self._verify_globals(salt.loader.returners(self.master_opts, {}))
  870. def test_pillars(self):
  871. '''
  872. Test that pillars have:
  873. - __salt__
  874. - __opts__
  875. - __pillar__
  876. - __grains__
  877. - __context__
  878. '''
  879. self._verify_globals(salt.loader.pillars(self.master_opts, {}))
  880. def test_tops(self):
  881. '''
  882. Test that tops have: []
  883. '''
  884. self._verify_globals(salt.loader.tops(self.master_opts))
  885. def test_outputters(self):
  886. '''
  887. Test that outputters have:
  888. - __opts__
  889. - __pillar__
  890. - __grains__
  891. - __context__
  892. '''
  893. self._verify_globals(salt.loader.outputters(self.master_opts))
  894. def test_serializers(self):
  895. '''
  896. Test that serializers have: []
  897. '''
  898. self._verify_globals(salt.loader.serializers(self.master_opts))
  899. def test_states(self):
  900. '''
  901. Test that states have:
  902. - __pillar__
  903. - __salt__
  904. - __opts__
  905. - __grains__
  906. - __context__
  907. '''
  908. self._verify_globals(salt.loader.states(self.master_opts, {}, {}, {}))
  909. def test_renderers(self):
  910. '''
  911. Test that renderers have:
  912. - __salt__ # Execution functions (i.e. __salt__['test.echo']('foo'))
  913. - __grains__ # Grains (i.e. __grains__['os'])
  914. - __pillar__ # Pillar data (i.e. __pillar__['foo'])
  915. - __opts__ # Minion configuration options
  916. - __context__ # Context dict shared amongst all modules of the same type
  917. '''
  918. self._verify_globals(salt.loader.render(self.master_opts, {}))
  919. class RawModTest(TestCase):
  920. '''
  921. Test the interface of raw_mod
  922. '''
  923. def setUp(self):
  924. self.opts = salt.config.minion_config(None)
  925. def tearDown(self):
  926. del self.opts
  927. def test_basic(self):
  928. testmod = salt.loader.raw_mod(self.opts, 'test', None)
  929. for k, v in six.iteritems(testmod):
  930. self.assertEqual(k.split('.')[0], 'test')
  931. def test_bad_name(self):
  932. testmod = salt.loader.raw_mod(self.opts, 'module_we_do_not_have', None)
  933. self.assertEqual(testmod, {})
  934. class NetworkUtilsTestCase(ModuleCase):
  935. def test_is_private(self):
  936. mod = salt.loader.raw_mod(self.minion_opts, 'network', None)
  937. self.assertTrue(mod['network.is_private']('10.0.0.1'), True)
  938. def test_is_loopback(self):
  939. mod = salt.loader.raw_mod(self.minion_opts, 'network', None)
  940. self.assertTrue(mod['network.is_loopback']('127.0.0.1'), True)
  941. class LazyLoaderOptimizationOrderTest(TestCase):
  942. '''
  943. Test the optimization order priority in the loader (PY3)
  944. '''
  945. module_name = 'lazyloadertest'
  946. module_content = textwrap.dedent('''\
  947. # -*- coding: utf-8 -*-
  948. from __future__ import absolute_import
  949. def test():
  950. return True
  951. ''')
  952. @classmethod
  953. def setUpClass(cls):
  954. cls.opts = salt.config.minion_config(None)
  955. cls.opts['grains'] = salt.loader.grains(cls.opts)
  956. def setUp(self):
  957. # Setup the module
  958. self.module_dir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
  959. self.module_file = os.path.join(self.module_dir,
  960. '{0}.py'.format(self.module_name))
  961. def _get_loader(self, order=None):
  962. opts = copy.deepcopy(self.opts)
  963. if order is not None:
  964. opts['optimization_order'] = order
  965. # Return a loader
  966. return salt.loader.LazyLoader([self.module_dir], opts, tag='module')
  967. def _get_module_filename(self):
  968. # The act of referencing the loader entry forces the module to be
  969. # loaded by the LazyDict.
  970. mod_fullname = self.loader[next(iter(self.loader))].__module__
  971. return sys.modules[mod_fullname].__file__
  972. def _expected(self, optimize=0):
  973. if six.PY3:
  974. return 'lazyloadertest.cpython-{0}{1}{2}.pyc'.format(
  975. sys.version_info[0],
  976. sys.version_info[1],
  977. '' if not optimize else '.opt-{0}'.format(optimize)
  978. )
  979. else:
  980. return 'lazyloadertest.pyc'
  981. def _write_module_file(self):
  982. with salt.utils.files.fopen(self.module_file, 'w') as fh:
  983. fh.write(self.module_content)
  984. fh.flush()
  985. os.fsync(fh.fileno())
  986. def _byte_compile(self):
  987. if salt.loader.USE_IMPORTLIB:
  988. # Skip this check as "optimize" is unique to PY3's compileall
  989. # module, and this will be a false error when Pylint is run on
  990. # Python 2.
  991. # pylint: disable=unexpected-keyword-arg
  992. compileall.compile_file(self.module_file, quiet=1, optimize=0)
  993. compileall.compile_file(self.module_file, quiet=1, optimize=1)
  994. compileall.compile_file(self.module_file, quiet=1, optimize=2)
  995. # pylint: enable=unexpected-keyword-arg
  996. else:
  997. compileall.compile_file(self.module_file, quiet=1)
  998. def _test_optimization_order(self, order):
  999. self._write_module_file()
  1000. self._byte_compile()
  1001. # Clean up the original file so that we can be assured we're only
  1002. # loading the byte-compiled files(s).
  1003. os.remove(self.module_file)
  1004. self.loader = self._get_loader(order)
  1005. filename = self._get_module_filename()
  1006. basename = os.path.basename(filename)
  1007. assert basename == self._expected(order[0]), basename
  1008. if not salt.loader.USE_IMPORTLIB:
  1009. # We are only testing multiple optimization levels on Python 3.5+
  1010. return
  1011. # Remove the file and make a new loader. We should now load the
  1012. # byte-compiled file with an optimization level matching the 2nd
  1013. # element of the order list.
  1014. os.remove(filename)
  1015. self.loader = self._get_loader(order)
  1016. filename = self._get_module_filename()
  1017. basename = os.path.basename(filename)
  1018. assert basename == self._expected(order[1]), basename
  1019. # Remove the file and make a new loader. We should now load the
  1020. # byte-compiled file with an optimization level matching the 3rd
  1021. # element of the order list.
  1022. os.remove(filename)
  1023. self.loader = self._get_loader(order)
  1024. filename = self._get_module_filename()
  1025. basename = os.path.basename(filename)
  1026. assert basename == self._expected(order[2]), basename
  1027. def test_optimization_order(self):
  1028. '''
  1029. Test the optimization_order config param
  1030. '''
  1031. self._test_optimization_order([0, 1, 2])
  1032. self._test_optimization_order([0, 2, 1])
  1033. if salt.loader.USE_IMPORTLIB:
  1034. # optimization_order only supported on Python 3.5+, earlier
  1035. # releases only support unoptimized .pyc files.
  1036. self._test_optimization_order([1, 2, 0])
  1037. self._test_optimization_order([1, 0, 2])
  1038. self._test_optimization_order([2, 0, 1])
  1039. self._test_optimization_order([2, 1, 0])
  1040. def test_load_source_file(self):
  1041. '''
  1042. Make sure that .py files are preferred over .pyc files
  1043. '''
  1044. self._write_module_file()
  1045. self._byte_compile()
  1046. self.loader = self._get_loader()
  1047. filename = self._get_module_filename()
  1048. basename = os.path.basename(filename)
  1049. expected = 'lazyloadertest.py' if six.PY3 else 'lazyloadertest.pyc'
  1050. assert basename == expected, basename