12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244 |
- # -*- coding: utf-8 -*-
- '''
- unit.loader
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
- Test Salt's loader
- '''
- # Import Python libs
- from __future__ import absolute_import, print_function, unicode_literals
- import collections
- import compileall
- import copy
- import imp
- import inspect
- import logging
- import os
- import shutil
- import sys
- import tempfile
- import textwrap
- # Import Salt Testing libs
- from tests.support.case import ModuleCase
- from tests.support.unit import TestCase
- from tests.support.mock import patch
- from tests.support.paths import TMP
- # Import Salt libs
- import salt.config
- import salt.loader
- import salt.utils.files
- import salt.utils.stringutils
- # pylint: disable=import-error,no-name-in-module,redefined-builtin
- from salt.ext import six
- from salt.ext.six.moves import range
- # pylint: enable=no-name-in-module,redefined-builtin
- log = logging.getLogger(__name__)
- def remove_bytecode(module_path):
- paths = [module_path + 'c']
- if hasattr(imp, 'get_tag'):
- modname, ext = os.path.splitext(module_path.split(os.sep)[-1])
- paths.append(
- os.path.join(os.path.dirname(module_path),
- '__pycache__',
- '{}.{}.pyc'.format(modname, imp.get_tag())))
- for path in paths:
- if os.path.exists(path):
- os.unlink(path)
- loader_template = '''
- import os
- from salt.utils.decorators import depends
- @depends('os')
- def loaded():
- return True
- @depends('non_existantmodulename')
- def not_loaded():
- return True
- '''
- class LazyLoaderTest(TestCase):
- '''
- Test the loader
- '''
- module_name = 'lazyloadertest'
- @classmethod
- def setUpClass(cls):
- cls.opts = salt.config.minion_config(None)
- cls.opts['grains'] = salt.loader.grains(cls.opts)
- if not os.path.isdir(TMP):
- os.makedirs(TMP)
- def setUp(self):
- # Setup the module
- self.module_dir = tempfile.mkdtemp(dir=TMP)
- self.module_file = os.path.join(self.module_dir,
- '{0}.py'.format(self.module_name))
- with salt.utils.files.fopen(self.module_file, 'w') as fh:
- fh.write(salt.utils.stringutils.to_str(loader_template))
- fh.flush()
- os.fsync(fh.fileno())
- # Invoke the loader
- self.loader = salt.loader.LazyLoader([self.module_dir], copy.deepcopy(self.opts), tag='module')
- def tearDown(self):
- shutil.rmtree(self.module_dir)
- if os.path.isdir(self.module_dir):
- shutil.rmtree(self.module_dir)
- del self.module_dir
- del self.module_file
- del self.loader
- @classmethod
- def tearDownClass(cls):
- del cls.opts
- def test_depends(self):
- '''
- Test that the depends decorator works properly
- '''
- # Make sure depends correctly allowed a function to load. If this
- # results in a KeyError, the decorator is broken.
- self.assertTrue(
- inspect.isfunction(
- self.loader[self.module_name + '.loaded']
- )
- )
- # Make sure depends correctly kept a function from loading
- self.assertTrue(self.module_name + '.not_loaded' not in self.loader)
- class LazyLoaderVirtualEnabledTest(TestCase):
- '''
- Test the base loader of salt.
- '''
- @classmethod
- def setUpClass(cls):
- cls.opts = salt.config.minion_config(None)
- cls.opts['disable_modules'] = ['pillar']
- cls.opts['grains'] = salt.loader.grains(cls.opts)
- def setUp(self):
- self.loader = salt.loader.LazyLoader(
- salt.loader._module_dirs(copy.deepcopy(self.opts), 'modules', 'module'),
- copy.deepcopy(self.opts),
- tag='module')
- def tearDown(self):
- del self.loader
- @classmethod
- def tearDownClass(cls):
- del cls.opts
- def test_basic(self):
- '''
- Ensure that it only loads stuff when needed
- '''
- # make sure it starts empty
- self.assertEqual(self.loader._dict, {})
- # get something, and make sure its a func
- self.assertTrue(inspect.isfunction(self.loader['test.ping']))
- # make sure we only loaded "test" functions
- for key, val in six.iteritems(self.loader._dict):
- self.assertEqual(key.split('.', 1)[0], 'test')
- # make sure the depends thing worked (double check of the depends testing,
- # since the loader does the calling magically
- self.assertFalse('test.missing_func' in self.loader._dict)
- def test_badkey(self):
- with self.assertRaises(KeyError):
- self.loader[None] # pylint: disable=W0104
- with self.assertRaises(KeyError):
- self.loader[1] # pylint: disable=W0104
- def test_disable(self):
- self.assertNotIn('pillar.items', self.loader)
- def test_len_load(self):
- '''
- Since LazyLoader is a MutableMapping, if someone asks for len() we have
- to load all
- '''
- self.assertEqual(self.loader._dict, {})
- len(self.loader) # force a load all
- self.assertNotEqual(self.loader._dict, {})
- def test_iter_load(self):
- '''
- Since LazyLoader is a MutableMapping, if someone asks to iterate we have
- to load all
- '''
- self.assertEqual(self.loader._dict, {})
- # force a load all
- for key, func in six.iteritems(self.loader):
- break
- self.assertNotEqual(self.loader._dict, {})
- def test_context(self):
- '''
- Make sure context is shared across modules
- '''
- # make sure it starts empty
- self.assertEqual(self.loader._dict, {})
- # get something, and make sure its a func
- func = self.loader['test.ping']
- with patch.dict(func.__globals__['__context__'], {'foo': 'bar'}):
- self.assertEqual(self.loader['test.echo'].__globals__['__context__']['foo'], 'bar')
- self.assertEqual(self.loader['grains.get'].__globals__['__context__']['foo'], 'bar')
- def test_globals(self):
- func_globals = self.loader['test.ping'].__globals__
- self.assertEqual(func_globals['__grains__'], self.opts.get('grains', {}))
- self.assertEqual(func_globals['__pillar__'], self.opts.get('pillar', {}))
- # the opts passed into modules is at least a subset of the whole opts
- for key, val in six.iteritems(func_globals['__opts__']):
- if key in salt.config.DEFAULT_MASTER_OPTS and key not in salt.config.DEFAULT_MINION_OPTS:
- # We loaded the minion opts, but somewhere in the code, the master options got pulled in
- # Let's just not check for equality since the option won't even exist in the loaded
- # minion options
- continue
- if key not in salt.config.DEFAULT_MASTER_OPTS and key not in salt.config.DEFAULT_MINION_OPTS:
- # This isn't even a default configuration setting, lets carry on
- continue
- self.assertEqual(self.opts[key], val)
- def test_pack(self):
- self.loader.pack['__foo__'] = 'bar'
- func_globals = self.loader['test.ping'].__globals__
- self.assertEqual(func_globals['__foo__'], 'bar')
- def test_virtual(self):
- self.assertNotIn('test_virtual.ping', self.loader)
- class LazyLoaderVirtualDisabledTest(TestCase):
- '''
- Test the loader of salt without __virtual__
- '''
- @classmethod
- def setUpClass(cls):
- cls.opts = salt.config.minion_config(None)
- cls.opts['grains'] = salt.loader.grains(cls.opts)
- def setUp(self):
- self.loader = salt.loader.LazyLoader(
- salt.loader._module_dirs(copy.deepcopy(self.opts), 'modules', 'module'),
- copy.deepcopy(self.opts),
- tag='module',
- virtual_enable=False)
- def tearDown(self):
- del self.loader
- @classmethod
- def tearDownClass(cls):
- del cls.opts
- def test_virtual(self):
- self.assertTrue(inspect.isfunction(self.loader['test_virtual.ping']))
- class LazyLoaderWhitelistTest(TestCase):
- '''
- Test the loader of salt with a whitelist
- '''
- @classmethod
- def setUpClass(cls):
- cls.opts = salt.config.minion_config(None)
- cls.opts['grains'] = salt.loader.grains(cls.opts)
- def setUp(self):
- self.loader = salt.loader.LazyLoader(
- salt.loader._module_dirs(copy.deepcopy(self.opts), 'modules', 'module'),
- copy.deepcopy(self.opts),
- tag='module',
- whitelist=['test', 'pillar'])
- def tearDown(self):
- del self.loader
- @classmethod
- def tearDownClass(cls):
- del cls.opts
- def test_whitelist(self):
- self.assertTrue(inspect.isfunction(self.loader['test.ping']))
- self.assertTrue(inspect.isfunction(self.loader['pillar.get']))
- self.assertNotIn('grains.get', self.loader)
- class LazyLoaderSingleItem(TestCase):
- '''
- Test loading a single item via the _load() function
- '''
- @classmethod
- def setUpClass(cls):
- cls.opts = salt.config.minion_config(None)
- cls.opts['grains'] = salt.loader.grains(cls.opts)
- def setUp(self):
- self.loader = salt.loader.LazyLoader(
- salt.loader._module_dirs(copy.deepcopy(self.opts), 'modules', 'module'),
- copy.deepcopy(self.opts),
- tag='module')
- def tearDown(self):
- del self.loader
- def test_single_item_no_dot(self):
- '''
- Checks that a KeyError is raised when the function key does not contain a '.'
- '''
- key = 'testing_no_dot'
- expected = "The key '{0}' should contain a '.'".format(key)
- with self.assertRaises(KeyError) as err:
- inspect.isfunction(self.loader['testing_no_dot'])
- result = err.exception.args[0]
- assert result == expected, result
- module_template = '''
- __load__ = ['test', 'test_alias']
- __func_alias__ = dict(test_alias='working_alias')
- from salt.utils.decorators import depends
- def test():
- return {count}
- def test_alias():
- return True
- def test2():
- return True
- @depends('non_existantmodulename')
- def test3():
- return True
- @depends('non_existantmodulename', fallback_function=test)
- def test4():
- return True
- '''
- class LazyLoaderReloadingTest(TestCase):
- '''
- Test the loader of salt with changing modules
- '''
- module_name = 'loadertest'
- module_key = 'loadertest.test'
- @classmethod
- def setUpClass(cls):
- cls.opts = salt.config.minion_config(None)
- cls.opts['grains'] = salt.loader.grains(cls.opts)
- if not os.path.isdir(TMP):
- os.makedirs(TMP)
- def setUp(self):
- self.tmp_dir = tempfile.mkdtemp(dir=TMP)
- self.count = 0
- opts = copy.deepcopy(self.opts)
- dirs = salt.loader._module_dirs(opts, 'modules', 'module')
- dirs.append(self.tmp_dir)
- self.utils = salt.loader.utils(opts)
- self.proxy = salt.loader.proxy(opts)
- self.minion_mods = salt.loader.minion_mods(opts)
- self.loader = salt.loader.LazyLoader(
- dirs,
- opts,
- tag='module',
- pack={'__utils__': self.utils,
- '__proxy__': self.proxy,
- '__salt__': self.minion_mods})
- def tearDown(self):
- shutil.rmtree(self.tmp_dir)
- for attrname in ('tmp_dir', 'utils', 'proxy', 'loader', 'minion_mods', 'utils'):
- try:
- delattr(self, attrname)
- except AttributeError:
- continue
- @classmethod
- def tearDownClass(cls):
- del cls.opts
- def update_module(self):
- self.count += 1
- with salt.utils.files.fopen(self.module_path, 'wb') as fh:
- fh.write(
- salt.utils.stringutils.to_bytes(
- module_template.format(count=self.count)
- )
- )
- fh.flush()
- os.fsync(fh.fileno()) # flush to disk
- # pyc files don't like it when we change the original quickly
- # since the header bytes only contain the timestamp (granularity of seconds)
- # TODO: don't write them? Is *much* slower on re-load (~3x)
- # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
- remove_bytecode(self.module_path)
- def rm_module(self):
- os.unlink(self.module_path)
- remove_bytecode(self.module_path)
- @property
- def module_path(self):
- return os.path.join(self.tmp_dir, '{0}.py'.format(self.module_name))
- def test_alias(self):
- '''
- Make sure that you can access alias-d modules
- '''
- # ensure it doesn't exist
- self.assertNotIn(self.module_key, self.loader)
- self.update_module()
- self.assertNotIn('{0}.test_alias'.format(self.module_name), self.loader)
- self.assertTrue(inspect.isfunction(self.loader['{0}.working_alias'.format(self.module_name)]))
- def test_clear(self):
- self.assertTrue(inspect.isfunction(self.loader['test.ping']))
- self.update_module() # write out out custom module
- self.loader.clear() # clear the loader dict
- # force a load of our module
- self.assertTrue(inspect.isfunction(self.loader[self.module_key]))
- # make sure we only loaded our custom module
- # which means that we did correctly refresh the file mapping
- for k, v in six.iteritems(self.loader._dict):
- self.assertTrue(k.startswith(self.module_name))
- def test_load(self):
- # ensure it doesn't exist
- self.assertNotIn(self.module_key, self.loader)
- self.update_module()
- self.assertTrue(inspect.isfunction(self.loader[self.module_key]))
- def test__load__(self):
- '''
- If a module specifies __load__ we should only load/expose those modules
- '''
- self.update_module()
- # ensure it doesn't exist
- self.assertNotIn(self.module_key + '2', self.loader)
- def test__load__and_depends(self):
- '''
- If a module specifies __load__ we should only load/expose those modules
- '''
- self.update_module()
- # ensure it doesn't exist
- self.assertNotIn(self.module_key + '3', self.loader)
- self.assertNotIn(self.module_key + '4', self.loader)
- def test_reload(self):
- # ensure it doesn't exist
- self.assertNotIn(self.module_key, self.loader)
- # make sure it updates correctly
- for x in range(1, 3):
- self.update_module()
- self.loader.clear()
- self.assertEqual(self.loader[self.module_key](), self.count)
- self.rm_module()
- # make sure that even if we remove the module, its still loaded until a clear
- self.assertEqual(self.loader[self.module_key](), self.count)
- self.loader.clear()
- self.assertNotIn(self.module_key, self.loader)
- virtual_aliases = ('loadertest2', 'loadertest3')
- virtual_alias_module_template = '''
- __virtual_aliases__ = {0}
- def test():
- return True
- '''.format(virtual_aliases)
- class LazyLoaderVirtualAliasTest(TestCase):
- '''
- Test the loader of salt with changing modules
- '''
- module_name = 'loadertest'
- @classmethod
- def setUpClass(cls):
- cls.opts = salt.config.minion_config(None)
- cls.opts['grains'] = salt.loader.grains(cls.opts)
- if not os.path.isdir(TMP):
- os.makedirs(TMP)
- def setUp(self):
- self.tmp_dir = tempfile.mkdtemp(dir=TMP)
- opts = copy.deepcopy(self.opts)
- dirs = salt.loader._module_dirs(opts, 'modules', 'module')
- dirs.append(self.tmp_dir)
- self.utils = salt.loader.utils(opts)
- self.proxy = salt.loader.proxy(opts)
- self.minion_mods = salt.loader.minion_mods(opts)
- self.loader = salt.loader.LazyLoader(
- dirs,
- opts,
- tag='module',
- pack={'__utils__': self.utils,
- '__proxy__': self.proxy,
- '__salt__': self.minion_mods})
- def tearDown(self):
- del self.tmp_dir
- del self.utils
- del self.proxy
- del self.minion_mods
- del self.loader
- @classmethod
- def tearDownClass(cls):
- del cls.opts
- def update_module(self):
- with salt.utils.files.fopen(self.module_path, 'wb') as fh:
- fh.write(salt.utils.stringutils.to_bytes(virtual_alias_module_template))
- fh.flush()
- os.fsync(fh.fileno()) # flush to disk
- # pyc files don't like it when we change the original quickly
- # since the header bytes only contain the timestamp (granularity of seconds)
- # TODO: don't write them? Is *much* slower on re-load (~3x)
- # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
- remove_bytecode(self.module_path)
- @property
- def module_path(self):
- return os.path.join(self.tmp_dir, '{0}.py'.format(self.module_name))
- def test_virtual_alias(self):
- '''
- Test the __virtual_alias__ feature
- '''
- self.update_module()
- mod_names = [self.module_name] + list(virtual_aliases)
- for mod_name in mod_names:
- func_name = '.'.join((mod_name, 'test'))
- log.debug('Running %s (dict attribute)', func_name)
- self.assertTrue(self.loader[func_name]())
- log.debug('Running %s (loader attribute)', func_name)
- self.assertTrue(getattr(self.loader, mod_name).test())
- submodule_template = '''
- from __future__ import absolute_import
- import {0}.lib
- def test():
- return ({count}, {0}.lib.test())
- '''
- submodule_lib_template = '''
- def test():
- return {count}
- '''
- class LazyLoaderSubmodReloadingTest(TestCase):
- '''
- Test the loader of salt with changing modules
- '''
- module_name = 'loadertestsubmod'
- module_key = 'loadertestsubmod.test'
- @classmethod
- def setUpClass(cls):
- cls.opts = salt.config.minion_config(None)
- cls.opts['grains'] = salt.loader.grains(cls.opts)
- if not os.path.isdir(TMP):
- os.makedirs(TMP)
- def setUp(self):
- self.tmp_dir = tempfile.mkdtemp(dir=TMP)
- os.makedirs(self.module_dir)
- self.count = 0
- self.lib_count = 0
- opts = copy.deepcopy(self.opts)
- dirs = salt.loader._module_dirs(opts, 'modules', 'module')
- dirs.append(self.tmp_dir)
- self.utils = salt.loader.utils(opts)
- self.proxy = salt.loader.proxy(opts)
- self.minion_mods = salt.loader.minion_mods(opts)
- self.loader = salt.loader.LazyLoader(
- dirs,
- opts,
- tag='module',
- pack={'__utils__': self.utils,
- '__proxy__': self.proxy,
- '__salt__': self.minion_mods})
- def tearDown(self):
- shutil.rmtree(self.tmp_dir)
- del self.tmp_dir
- del self.utils
- del self.proxy
- del self.minion_mods
- del self.loader
- @classmethod
- def tearDownClass(cls):
- del cls.opts
- def update_module(self):
- self.count += 1
- with salt.utils.files.fopen(self.module_path, 'wb') as fh:
- fh.write(
- salt.utils.stringutils.to_bytes(
- submodule_template.format(self.module_name, count=self.count)
- )
- )
- fh.flush()
- os.fsync(fh.fileno()) # flush to disk
- # pyc files don't like it when we change the original quickly
- # since the header bytes only contain the timestamp (granularity of seconds)
- # TODO: don't write them? Is *much* slower on re-load (~3x)
- # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
- remove_bytecode(self.module_path)
- def rm_module(self):
- os.unlink(self.module_path)
- remove_bytecode(self.module_path)
- def update_lib(self):
- self.lib_count += 1
- for modname in list(sys.modules):
- if modname.startswith(self.module_name):
- del sys.modules[modname]
- with salt.utils.files.fopen(self.lib_path, 'wb') as fh:
- fh.write(
- salt.utils.stringutils.to_bytes(
- submodule_lib_template.format(count=self.lib_count)
- )
- )
- fh.flush()
- os.fsync(fh.fileno()) # flush to disk
- # pyc files don't like it when we change the original quickly
- # since the header bytes only contain the timestamp (granularity of seconds)
- # TODO: don't write them? Is *much* slower on re-load (~3x)
- # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
- remove_bytecode(self.lib_path)
- def rm_lib(self):
- for modname in list(sys.modules):
- if modname.startswith(self.module_name):
- del sys.modules[modname]
- os.unlink(self.lib_path)
- remove_bytecode(self.lib_path)
- @property
- def module_dir(self):
- return os.path.join(self.tmp_dir, self.module_name)
- @property
- def module_path(self):
- return os.path.join(self.module_dir, '__init__.py')
- @property
- def lib_path(self):
- return os.path.join(self.module_dir, 'lib.py')
- def test_basic(self):
- # ensure it doesn't exist
- self.assertNotIn(self.module_key, self.loader)
- self.update_module()
- self.update_lib()
- self.loader.clear()
- self.assertIn(self.module_key, self.loader)
- def test_reload(self):
- # ensure it doesn't exist
- self.assertNotIn(self.module_key, self.loader)
- # update both the module and the lib
- for x in range(1, 3):
- self.update_lib()
- self.update_module()
- self.loader.clear()
- self.assertNotIn(self.module_key, self.loader._dict)
- self.assertIn(self.module_key, self.loader)
- self.assertEqual(self.loader[self.module_key](), (self.count, self.lib_count))
- # update just the module
- for x in range(1, 3):
- self.update_module()
- self.loader.clear()
- self.assertNotIn(self.module_key, self.loader._dict)
- self.assertIn(self.module_key, self.loader)
- self.assertEqual(self.loader[self.module_key](), (self.count, self.lib_count))
- # update just the lib
- for x in range(1, 3):
- self.update_lib()
- self.loader.clear()
- self.assertNotIn(self.module_key, self.loader._dict)
- self.assertIn(self.module_key, self.loader)
- self.assertEqual(self.loader[self.module_key](), (self.count, self.lib_count))
- self.rm_module()
- # make sure that even if we remove the module, its still loaded until a clear
- self.assertEqual(self.loader[self.module_key](), (self.count, self.lib_count))
- self.loader.clear()
- self.assertNotIn(self.module_key, self.loader)
- def test_reload_missing_lib(self):
- # ensure it doesn't exist
- self.assertNotIn(self.module_key, self.loader)
- # update both the module and the lib
- self.update_module()
- self.update_lib()
- self.loader.clear()
- self.assertEqual(self.loader[self.module_key](), (self.count, self.lib_count))
- # remove the lib, this means we should fail to load the module next time
- self.rm_lib()
- self.loader.clear()
- self.assertNotIn(self.module_key, self.loader)
- mod_template = '''
- def test():
- return ({val})
- '''
- class LazyLoaderModulePackageTest(TestCase):
- '''
- Test the loader of salt with changing modules
- '''
- module_name = 'loadertestmodpkg'
- module_key = 'loadertestmodpkg.test'
- @classmethod
- def setUpClass(cls):
- cls.opts = salt.config.minion_config(None)
- cls.opts['grains'] = salt.loader.grains(cls.opts)
- if not os.path.isdir(TMP):
- os.makedirs(TMP)
- def setUp(self):
- self.tmp_dir = tempfile.mkdtemp(dir=TMP)
- dirs = salt.loader._module_dirs(copy.deepcopy(self.opts), 'modules', 'module')
- dirs.append(self.tmp_dir)
- self.loader = salt.loader.LazyLoader(
- dirs,
- copy.deepcopy(self.opts),
- tag='module')
- def tearDown(self):
- shutil.rmtree(self.tmp_dir)
- del self.tmp_dir
- del self.loader
- @classmethod
- def tearDownClass(cls):
- del cls.opts
- def update_pyfile(self, pyfile, contents):
- dirname = os.path.dirname(pyfile)
- if not os.path.exists(dirname):
- os.makedirs(dirname)
- with salt.utils.files.fopen(pyfile, 'wb') as fh:
- fh.write(salt.utils.stringutils.to_bytes(contents))
- fh.flush()
- os.fsync(fh.fileno()) # flush to disk
- # pyc files don't like it when we change the original quickly
- # since the header bytes only contain the timestamp (granularity of seconds)
- # TODO: don't write them? Is *much* slower on re-load (~3x)
- # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
- remove_bytecode(pyfile)
- def rm_pyfile(self, pyfile):
- os.unlink(pyfile)
- remove_bytecode(pyfile)
- def update_module(self, relative_path, contents):
- self.update_pyfile(os.path.join(self.tmp_dir, relative_path), contents)
- def rm_module(self, relative_path):
- self.rm_pyfile(os.path.join(self.tmp_dir, relative_path))
- def test_module(self):
- # ensure it doesn't exist
- self.assertNotIn('foo', self.loader)
- self.assertNotIn('foo.test', self.loader)
- self.update_module('foo.py', mod_template.format(val=1))
- self.loader.clear()
- self.assertIn('foo.test', self.loader)
- self.assertEqual(self.loader['foo.test'](), 1)
- def test_package(self):
- # ensure it doesn't exist
- self.assertNotIn('foo', self.loader)
- self.assertNotIn('foo.test', self.loader)
- self.update_module('foo/__init__.py', mod_template.format(val=2))
- self.loader.clear()
- self.assertIn('foo.test', self.loader)
- self.assertEqual(self.loader['foo.test'](), 2)
- def test_module_package_collision(self):
- # ensure it doesn't exist
- self.assertNotIn('foo', self.loader)
- self.assertNotIn('foo.test', self.loader)
- self.update_module('foo.py', mod_template.format(val=3))
- self.loader.clear()
- self.assertIn('foo.test', self.loader)
- self.assertEqual(self.loader['foo.test'](), 3)
- self.update_module('foo/__init__.py', mod_template.format(val=4))
- self.loader.clear()
- self.assertIn('foo.test', self.loader)
- self.assertEqual(self.loader['foo.test'](), 4)
- deep_init_base = '''
- from __future__ import absolute_import
- import {0}.top_lib
- import {0}.top_lib.mid_lib
- import {0}.top_lib.mid_lib.bot_lib
- def top():
- return {0}.top_lib.test()
- def mid():
- return {0}.top_lib.mid_lib.test()
- def bot():
- return {0}.top_lib.mid_lib.bot_lib.test()
- '''
- class LazyLoaderDeepSubmodReloadingTest(TestCase):
- module_name = 'loadertestsubmoddeep'
- libs = ('top_lib', 'mid_lib', 'bot_lib')
- @classmethod
- def setUpClass(cls):
- cls.opts = salt.config.minion_config(None)
- cls.opts['grains'] = salt.loader.grains(cls.opts)
- if not os.path.isdir(TMP):
- os.makedirs(TMP)
- def setUp(self):
- self.tmp_dir = tempfile.mkdtemp(dir=TMP)
- os.makedirs(self.module_dir)
- self.lib_count = collections.defaultdict(int) # mapping of path -> count
- # bootstrap libs
- with salt.utils.files.fopen(os.path.join(self.module_dir, '__init__.py'), 'w') as fh:
- # No .decode() needed here as deep_init_base is defined as str and
- # not bytes.
- fh.write(
- salt.utils.stringutils.to_str(
- deep_init_base.format(self.module_name)
- )
- )
- fh.flush()
- os.fsync(fh.fileno()) # flush to disk
- self.lib_paths = {}
- dir_path = self.module_dir
- for lib_name in self.libs:
- dir_path = os.path.join(dir_path, lib_name)
- self.lib_paths[lib_name] = dir_path
- os.makedirs(dir_path)
- self.update_lib(lib_name)
- opts = copy.deepcopy(self.opts)
- dirs = salt.loader._module_dirs(opts, 'modules', 'module')
- dirs.append(self.tmp_dir)
- self.utils = salt.loader.utils(opts)
- self.proxy = salt.loader.proxy(opts)
- self.minion_mods = salt.loader.minion_mods(opts)
- self.loader = salt.loader.LazyLoader(
- dirs,
- copy.deepcopy(opts),
- tag='module',
- pack={'__utils__': self.utils,
- '__proxy__': self.proxy,
- '__salt__': self.minion_mods})
- self.assertIn('{0}.top'.format(self.module_name), self.loader)
- def tearDown(self):
- shutil.rmtree(self.tmp_dir)
- del self.tmp_dir
- del self.lib_paths
- del self.utils
- del self.proxy
- del self.minion_mods
- del self.loader
- del self.lib_count
- @classmethod
- def tearDownClass(cls):
- del cls.opts
- @property
- def module_dir(self):
- return os.path.join(self.tmp_dir, self.module_name)
- def update_lib(self, lib_name):
- for modname in list(sys.modules):
- if modname.startswith(self.module_name):
- del sys.modules[modname]
- path = os.path.join(self.lib_paths[lib_name], '__init__.py')
- self.lib_count[lib_name] += 1
- with salt.utils.files.fopen(path, 'wb') as fh:
- fh.write(
- salt.utils.stringutils.to_bytes(
- submodule_lib_template.format(count=self.lib_count[lib_name])
- )
- )
- fh.flush()
- os.fsync(fh.fileno()) # flush to disk
- # pyc files don't like it when we change the original quickly
- # since the header bytes only contain the timestamp (granularity of seconds)
- # TODO: don't write them? Is *much* slower on re-load (~3x)
- # https://docs.python.org/2/library/sys.html#sys.dont_write_bytecode
- remove_bytecode(path)
- def test_basic(self):
- self.assertIn('{0}.top'.format(self.module_name), self.loader)
- def _verify_libs(self):
- for lib in self.libs:
- self.assertEqual(self.loader['{0}.{1}'.format(self.module_name, lib.replace('_lib', ''))](),
- self.lib_count[lib])
- def test_reload(self):
- '''
- Make sure that we can reload all libraries of arbitrary depth
- '''
- self._verify_libs()
- # update them all
- for lib in self.libs:
- for x in range(5):
- self.update_lib(lib)
- self.loader.clear()
- self._verify_libs()
- class LoaderGlobalsTest(ModuleCase):
- '''
- Test all of the globals that the loader is responsible for adding to modules
- This shouldn't be done here, but should rather be done per module type (in the cases where they are used)
- so they can check ALL globals that they have (or should have) access to.
- This is intended as a shorter term way of testing these so we don't break the loader
- '''
- def _verify_globals(self, mod_dict):
- '''
- Verify that the globals listed in the doc string (from the test) are in these modules
- '''
- # find the globals
- global_vars = []
- for val in six.itervalues(mod_dict):
- # only find salty globals
- if val.__module__.startswith('salt.loaded'):
- if hasattr(val, '__globals__'):
- if hasattr(val, '__wrapped__') or '__wrapped__' in val.__globals__:
- global_vars.append(sys.modules[val.__module__].__dict__)
- else:
- global_vars.append(val.__globals__)
- # if we couldn't find any, then we have no modules -- so something is broken
- self.assertNotEqual(global_vars, [], msg='No modules were loaded.')
- # get the names of the globals you should have
- func_name = inspect.stack()[1][3]
- names = next(six.itervalues(salt.utils.yaml.safe_load(getattr(self, func_name).__doc__)))
- # Now, test each module!
- for item in global_vars:
- for name in names:
- self.assertIn(name, list(item.keys()))
- def test_auth(self):
- '''
- Test that auth mods have:
- - __pillar__
- - __grains__
- - __salt__
- - __context__
- '''
- self._verify_globals(salt.loader.auth(self.master_opts))
- def test_runners(self):
- '''
- Test that runners have:
- - __pillar__
- - __salt__
- - __opts__
- - __grains__
- - __context__
- '''
- self._verify_globals(salt.loader.runner(self.master_opts))
- def test_returners(self):
- '''
- Test that returners have:
- - __salt__
- - __opts__
- - __pillar__
- - __grains__
- - __context__
- '''
- self._verify_globals(salt.loader.returners(self.master_opts, {}))
- def test_pillars(self):
- '''
- Test that pillars have:
- - __salt__
- - __opts__
- - __pillar__
- - __grains__
- - __context__
- '''
- self._verify_globals(salt.loader.pillars(self.master_opts, {}))
- def test_tops(self):
- '''
- Test that tops have: []
- '''
- self._verify_globals(salt.loader.tops(self.master_opts))
- def test_outputters(self):
- '''
- Test that outputters have:
- - __opts__
- - __pillar__
- - __grains__
- - __context__
- '''
- self._verify_globals(salt.loader.outputters(self.master_opts))
- def test_serializers(self):
- '''
- Test that serializers have: []
- '''
- self._verify_globals(salt.loader.serializers(self.master_opts))
- def test_states(self):
- '''
- Test that states have:
- - __pillar__
- - __salt__
- - __opts__
- - __grains__
- - __context__
- '''
- self._verify_globals(salt.loader.states(self.master_opts, {}, {}, {}))
- def test_renderers(self):
- '''
- Test that renderers have:
- - __salt__ # Execution functions (i.e. __salt__['test.echo']('foo'))
- - __grains__ # Grains (i.e. __grains__['os'])
- - __pillar__ # Pillar data (i.e. __pillar__['foo'])
- - __opts__ # Minion configuration options
- - __context__ # Context dict shared amongst all modules of the same type
- '''
- self._verify_globals(salt.loader.render(self.master_opts, {}))
- class RawModTest(TestCase):
- '''
- Test the interface of raw_mod
- '''
- def setUp(self):
- self.opts = salt.config.minion_config(None)
- def tearDown(self):
- del self.opts
- def test_basic(self):
- testmod = salt.loader.raw_mod(self.opts, 'test', None)
- for k, v in six.iteritems(testmod):
- self.assertEqual(k.split('.')[0], 'test')
- def test_bad_name(self):
- testmod = salt.loader.raw_mod(self.opts, 'module_we_do_not_have', None)
- self.assertEqual(testmod, {})
- class NetworkUtilsTestCase(ModuleCase):
- def test_is_private(self):
- mod = salt.loader.raw_mod(self.minion_opts, 'network', None)
- self.assertTrue(mod['network.is_private']('10.0.0.1'), True)
- def test_is_loopback(self):
- mod = salt.loader.raw_mod(self.minion_opts, 'network', None)
- self.assertTrue(mod['network.is_loopback']('127.0.0.1'), True)
- class LazyLoaderOptimizationOrderTest(TestCase):
- '''
- Test the optimization order priority in the loader (PY3)
- '''
- module_name = 'lazyloadertest'
- module_content = textwrap.dedent('''\
- # -*- coding: utf-8 -*-
- from __future__ import absolute_import
- def test():
- return True
- ''')
- @classmethod
- def setUpClass(cls):
- cls.opts = salt.config.minion_config(None)
- cls.opts['grains'] = salt.loader.grains(cls.opts)
- def setUp(self):
- # Setup the module
- self.module_dir = tempfile.mkdtemp(dir=TMP)
- self.module_file = os.path.join(self.module_dir,
- '{0}.py'.format(self.module_name))
- def _get_loader(self, order=None):
- opts = copy.deepcopy(self.opts)
- if order is not None:
- opts['optimization_order'] = order
- # Return a loader
- return salt.loader.LazyLoader([self.module_dir], opts, tag='module')
- def _get_module_filename(self):
- # The act of referencing the loader entry forces the module to be
- # loaded by the LazyDict.
- mod_fullname = self.loader[next(iter(self.loader))].__module__
- return sys.modules[mod_fullname].__file__
- def _expected(self, optimize=0):
- if six.PY3:
- return 'lazyloadertest.cpython-{0}{1}{2}.pyc'.format(
- sys.version_info[0],
- sys.version_info[1],
- '' if not optimize else '.opt-{0}'.format(optimize)
- )
- else:
- return 'lazyloadertest.pyc'
- def _write_module_file(self):
- with salt.utils.files.fopen(self.module_file, 'w') as fh:
- fh.write(self.module_content)
- fh.flush()
- os.fsync(fh.fileno())
- def _byte_compile(self):
- if salt.loader.USE_IMPORTLIB:
- # Skip this check as "optimize" is unique to PY3's compileall
- # module, and this will be a false error when Pylint is run on
- # Python 2.
- # pylint: disable=unexpected-keyword-arg
- compileall.compile_file(self.module_file, quiet=1, optimize=0)
- compileall.compile_file(self.module_file, quiet=1, optimize=1)
- compileall.compile_file(self.module_file, quiet=1, optimize=2)
- # pylint: enable=unexpected-keyword-arg
- else:
- compileall.compile_file(self.module_file, quiet=1)
- def _test_optimization_order(self, order):
- self._write_module_file()
- self._byte_compile()
- # Clean up the original file so that we can be assured we're only
- # loading the byte-compiled files(s).
- os.remove(self.module_file)
- self.loader = self._get_loader(order)
- filename = self._get_module_filename()
- basename = os.path.basename(filename)
- assert basename == self._expected(order[0]), basename
- if not salt.loader.USE_IMPORTLIB:
- # We are only testing multiple optimization levels on Python 3.5+
- return
- # Remove the file and make a new loader. We should now load the
- # byte-compiled file with an optimization level matching the 2nd
- # element of the order list.
- os.remove(filename)
- self.loader = self._get_loader(order)
- filename = self._get_module_filename()
- basename = os.path.basename(filename)
- assert basename == self._expected(order[1]), basename
- # Remove the file and make a new loader. We should now load the
- # byte-compiled file with an optimization level matching the 3rd
- # element of the order list.
- os.remove(filename)
- self.loader = self._get_loader(order)
- filename = self._get_module_filename()
- basename = os.path.basename(filename)
- assert basename == self._expected(order[2]), basename
- def test_optimization_order(self):
- '''
- Test the optimization_order config param
- '''
- self._test_optimization_order([0, 1, 2])
- self._test_optimization_order([0, 2, 1])
- if salt.loader.USE_IMPORTLIB:
- # optimization_order only supported on Python 3.5+, earlier
- # releases only support unoptimized .pyc files.
- self._test_optimization_order([1, 2, 0])
- self._test_optimization_order([1, 0, 2])
- self._test_optimization_order([2, 0, 1])
- self._test_optimization_order([2, 1, 0])
- def test_load_source_file(self):
- '''
- Make sure that .py files are preferred over .pyc files
- '''
- self._write_module_file()
- self._byte_compile()
- self.loader = self._get_loader()
- filename = self._get_module_filename()
- basename = os.path.basename(filename)
- expected = 'lazyloadertest.py' if six.PY3 else 'lazyloadertest.pyc'
- assert basename == expected, basename
|