Charmed-Kubernetes/nrpe/mod/charmhelpers/tests/contrib/openstack/test_os_templating.py

375 lines
14 KiB
Python

import os
import unittest
from mock import patch, call, MagicMock
import charmhelpers.contrib.openstack.templating as templating
from jinja2.exceptions import TemplateNotFound
import six
if not six.PY3:
builtin_open = '__builtin__.open'
else:
builtin_open = 'builtins.open'
class FakeContextGenerator(object):
interfaces = None
def set(self, interfaces, context):
self.interfaces = interfaces
self.context = context
def __call__(self):
return self.context
class FakeLoader(object):
def set(self, template):
self.template = template
def get(self, name):
return self.template
class MockFSLoader(object):
def __init__(self, dirs):
self.searchpath = [dirs]
class MockChoiceLoader(object):
def __init__(self, loaders):
self.loaders = loaders
def MockTemplate():
templ = MagicMock()
templ.render = MagicMock()
return templ
class TemplatingTests(unittest.TestCase):
def setUp(self):
path = os.path.dirname(__file__)
self.loader = FakeLoader()
self.context = FakeContextGenerator()
self.addCleanup(patch.object(templating, 'apt_install').start().stop())
self.addCleanup(patch.object(templating, 'log').start().stop())
templating.FileSystemLoader = MockFSLoader
templating.ChoiceLoader = MockChoiceLoader
templating.Environment = MagicMock
self.renderer = templating.OSConfigRenderer(templates_dir=path,
openstack_release='folsom')
@patch.object(templating, 'apt_install')
def test_initializing_a_render_ensures_jinja2_present(self, apt):
'''Creatinga new renderer object installs jinja2 if needed'''
# temp. undo the patching from setUp
templating.FileSystemLoader = None
templating.ChoiceLoader = None
templating.Environment = None
templating.OSConfigRenderer(templates_dir='/tmp',
openstack_release='foo')
templating.FileSystemLoader = MockFSLoader
templating.ChoiceLoader = MockChoiceLoader
templating.Environment = MagicMock
if six.PY2:
apt.assert_called_with('python-jinja2')
else:
apt.assert_called_with('python3-jinja2')
def test_create_renderer_invalid_templates_dir(self):
'''Ensure OSConfigRenderer checks templates_dir'''
self.assertRaises(templating.OSConfigException,
templating.OSConfigRenderer,
templates_dir='/tmp/foooo0',
openstack_release='grizzly')
def test_render_unregistered_config(self):
'''Ensure cannot render an unregistered config file'''
self.assertRaises(templating.OSConfigException,
self.renderer.render,
config_file='/tmp/foo')
def test_write_unregistered_config(self):
'''Ensure cannot write an unregistered config file'''
self.assertRaises(templating.OSConfigException,
self.renderer.write,
config_file='/tmp/foo')
def test_render_complete_context(self):
'''It renders a template when provided a complete context'''
self.loader.set('{{ foo }}')
self.context.set(interfaces=['fooservice'], context={'foo': 'bar'})
self.renderer.register('/tmp/foo', [self.context])
with patch.object(self.renderer, '_get_template') as _get_t:
fake_tmpl = MockTemplate()
_get_t.return_value = fake_tmpl
self.renderer.render('/tmp/foo')
fake_tmpl.render.assert_called_with(self.context())
self.assertIn('fooservice', self.renderer.complete_contexts())
def test_render_incomplete_context_with_template(self):
'''It renders a template when provided an incomplete context'''
self.context.set(interfaces=['fooservice'], context={})
self.renderer.register('/tmp/foo', [self.context])
with patch.object(self.renderer, '_get_template') as _get_t:
fake_tmpl = MockTemplate()
_get_t.return_value = fake_tmpl
self.renderer.render('/tmp/foo')
fake_tmpl.render.assert_called_with({})
self.assertNotIn('fooservice', self.renderer.complete_contexts())
def test_render_template_registered_but_not_found(self):
'''It loads a template by basename of config file first'''
path = os.path.dirname(__file__)
renderer = templating.OSConfigRenderer(templates_dir=path,
openstack_release='folsom')
e = TemplateNotFound('')
renderer._get_template = MagicMock()
renderer._get_template.side_effect = e
renderer.register('/etc/nova/nova.conf', contexts=[])
self.assertRaises(
TemplateNotFound, renderer.render, '/etc/nova/nova.conf')
def test_render_template_by_basename_first(self):
'''It loads a template by basename of config file first'''
path = os.path.dirname(__file__)
renderer = templating.OSConfigRenderer(templates_dir=path,
openstack_release='folsom')
renderer._get_template = MagicMock()
renderer.register('/etc/nova/nova.conf', contexts=[])
renderer.render('/etc/nova/nova.conf')
self.assertEquals(1, len(renderer._get_template.call_args_list))
self.assertEquals(
[call('nova.conf')], renderer._get_template.call_args_list)
def test_render_template_by_munged_full_path_last(self):
'''It loads a template by full path of config file second'''
path = os.path.dirname(__file__)
renderer = templating.OSConfigRenderer(templates_dir=path,
openstack_release='folsom')
tmp = MagicMock()
tmp.render = MagicMock()
e = TemplateNotFound('')
renderer._get_template = MagicMock()
renderer._get_template.side_effect = [e, tmp]
renderer.register('/etc/nova/nova.conf', contexts=[])
renderer.render('/etc/nova/nova.conf')
self.assertEquals(2, len(renderer._get_template.call_args_list))
self.assertEquals(
[call('nova.conf'), call('etc_nova_nova.conf')],
renderer._get_template.call_args_list)
def test_render_template_by_basename(self):
'''It renders template if it finds it by config file basename'''
@patch(builtin_open)
@patch.object(templating, 'get_loader')
def test_write_out_config(self, loader, _open):
'''It writes a templated config when provided a complete context'''
self.context.set(interfaces=['fooservice'], context={'foo': 'bar'})
self.renderer.register('/tmp/foo', [self.context])
with patch.object(self.renderer, '_get_template') as _get_t:
fake_tmpl = MockTemplate()
_get_t.return_value = fake_tmpl
self.renderer.write('/tmp/foo')
_open.assert_called_with('/tmp/foo', 'wb')
def test_write_all(self):
'''It writes out all configuration files at once'''
self.context.set(interfaces=['fooservice'], context={'foo': 'bar'})
self.renderer.register('/tmp/foo', [self.context])
self.renderer.register('/tmp/bar', [self.context])
ex_calls = [
call('/tmp/bar'),
call('/tmp/foo'),
]
with patch.object(self.renderer, 'write') as _write:
self.renderer.write_all()
self.assertEquals(sorted(ex_calls), sorted(_write.call_args_list))
pass
@patch.object(templating, 'get_loader')
def test_reset_template_loader_for_new_os_release(self, loader):
self.loader.set('')
self.context.set(interfaces=['fooservice'], context={})
loader.return_value = MockFSLoader('/tmp/foo')
self.renderer.register('/tmp/foo', [self.context])
self.renderer.render('/tmp/foo')
loader.assert_called_with(os.path.dirname(__file__), 'folsom')
self.renderer.set_release(openstack_release='grizzly')
self.renderer.render('/tmp/foo')
loader.assert_called_with(os.path.dirname(__file__), 'grizzly')
@patch.object(templating, 'get_loader')
def test_incomplete_context_not_reported_complete(self, loader):
'''It does not recognize an incomplete context as a complete context'''
self.context.set(interfaces=['fooservice'], context={})
self.renderer.register('/tmp/foo', [self.context])
self.assertNotIn('fooservice', self.renderer.complete_contexts())
@patch.object(templating, 'get_loader')
def test_complete_context_reported_complete(self, loader):
'''It recognizes a complete context as a complete context'''
self.context.set(interfaces=['fooservice'], context={'foo': 'bar'})
self.renderer.register('/tmp/foo', [self.context])
self.assertIn('fooservice', self.renderer.complete_contexts())
@patch('os.path.isdir')
def test_get_loader_no_templates_dir(self, isdir):
'''Ensure getting loader fails with no template dir'''
isdir.return_value = False
self.assertRaises(templating.OSConfigException,
templating.get_loader,
templates_dir='/tmp/foo', os_release='foo')
@patch('os.path.isdir')
def test_get_loader_all_search_paths(self, isdir):
'''Ensure loader reverse searches of all release template dirs'''
isdir.return_value = True
choice_loader = templating.get_loader('/tmp/foo',
os_release='icehouse')
dirs = [l.searchpath for l in choice_loader.loaders]
common_tmplts = os.path.join(os.path.dirname(templating.__file__),
'templates')
expected = [['/tmp/foo/icehouse'],
['/tmp/foo/havana'],
['/tmp/foo/grizzly'],
['/tmp/foo/folsom'],
['/tmp/foo/essex'],
['/tmp/foo/diablo'],
['/tmp/foo'],
[common_tmplts]]
self.assertEquals(dirs, expected)
@patch('os.path.isdir')
def test_get_loader_some_search_paths(self, isdir):
'''Ensure loader reverse searches of some release template dirs'''
isdir.return_value = True
choice_loader = templating.get_loader('/tmp/foo', os_release='grizzly')
dirs = [l.searchpath for l in choice_loader.loaders]
common_tmplts = os.path.join(os.path.dirname(templating.__file__),
'templates')
expected = [['/tmp/foo/grizzly'],
['/tmp/foo/folsom'],
['/tmp/foo/essex'],
['/tmp/foo/diablo'],
['/tmp/foo'],
[common_tmplts]]
self.assertEquals(dirs, expected)
def test_register_template_with_list_of_contexts(self):
'''Ensure registering a template with a list of context generators'''
def _c1():
pass
def _c2():
pass
tmpl = templating.OSConfigTemplate(config_file='/tmp/foo',
contexts=[_c1, _c2])
self.assertEquals(tmpl.contexts, [_c1, _c2])
def test_register_template_with_single_context(self):
'''Ensure registering a template with a single non-list context'''
def _c1():
pass
tmpl = templating.OSConfigTemplate(config_file='/tmp/foo',
contexts=_c1)
self.assertEquals(tmpl.contexts, [_c1])
class TemplatingStringTests(unittest.TestCase):
def setUp(self):
path = os.path.dirname(__file__)
self.loader = FakeLoader()
self.context = FakeContextGenerator()
self.addCleanup(patch.object(templating,
'apt_install').start().stop())
self.addCleanup(patch.object(templating, 'log').start().stop())
templating.FileSystemLoader = MockFSLoader
templating.ChoiceLoader = MockChoiceLoader
self.config_file = '/etc/confd/extensible.d/drop-in.conf'
self.config_template = 'use: {{ fake_key }}'
self.renderer = templating.OSConfigRenderer(templates_dir=path,
openstack_release='folsom')
def test_render_template_from_string_full_context(self):
'''
Test rendering a specified config file with a string template
and a context.
'''
context = {'fake_key': 'fake_val'}
self.context.set(
interfaces=['fooservice'],
context=context
)
expected_output = 'use: {}'.format(context['fake_key'])
self.renderer.register(
config_file=self.config_file,
contexts=[self.context],
config_template=self.config_template
)
# should return a string given we render from an in-memory
# template source
output = self.renderer.render(self.config_file)
self.assertEquals(output, expected_output)
def test_render_template_from_string_incomplete_context(self):
'''
Test rendering a specified config file with a string template
and a context.
'''
self.context.set(
interfaces=['fooservice'],
context={}
)
expected_output = 'use: '
self.renderer.register(
config_file=self.config_file,
contexts=[self.context],
config_template=self.config_template
)
# should return a string given we render from an in-memory
# template source
output = self.renderer.render(self.config_file)
self.assertEquals(output, expected_output)
def test_register_string_template_with_single_context(self):
'''Template rendering from a provided string with a context'''
def _c1():
pass
config_file = '/etc/confdir/custom-drop-in.conf'
config_template = 'use: {{ key_available_in_c1 }}'
tmpl = templating.OSConfigTemplate(
config_file=config_file,
contexts=_c1,
config_template=config_template
)
self.assertEquals(tmpl.contexts, [_c1])
self.assertEquals(tmpl.config_file, config_file)
self.assertEquals(tmpl.config_template, config_template)