5
from mock import patch, call, MagicMock
7
import charmhelpers.contrib.openstack.templating as templating
10
class FakeContextGenerator(object):
13
def set(self, interfaces, context):
14
self.interfaces = interfaces
15
self.context = context
21
class FakeLoader(object):
22
def set(self, template):
23
self.template = template
29
class MockFSLoader(object):
30
def __init__(self, dirs):
31
self.searchpath = [dirs]
34
class MockChoiceLoader(object):
35
def __init__(self, loaders):
36
self.loaders = loaders
41
templ.render = MagicMock()
45
class TemplatingTests(unittest.TestCase):
47
path = os.path.dirname(__file__)
48
self.loader = FakeLoader()
49
self.context = FakeContextGenerator()
51
self.addCleanup(patch.object(templating, 'apt_install').start().stop())
52
self.addCleanup(patch.object(templating, 'log').start().stop())
54
templating.FileSystemLoader = MockFSLoader
55
templating.ChoiceLoader = MockChoiceLoader
56
templating.Environment = MagicMock
58
self.renderer = templating.OSConfigRenderer(templates_dir=path,
59
openstack_release='folsom')
61
@patch.object(templating, 'apt_install')
62
def test_initializing_a_render_ensures_jinja2_present(self, apt):
63
'''Creatinga new renderer object installs jinja2 if needed'''
64
# temp. undo the patching from setUp
65
templating.FileSystemLoader = None
66
templating.ChoiceLoader = None
67
templating.Environment = None
68
templating.OSConfigRenderer(templates_dir='/tmp',
69
openstack_release='foo')
70
templating.FileSystemLoader = MockFSLoader
71
templating.ChoiceLoader = MockChoiceLoader
72
templating.Environment = MagicMock
73
apt.assert_called_with('python-jinja2')
75
def test_create_renderer_invalid_templates_dir(self):
76
'''Ensure OSConfigRenderer checks templates_dir'''
77
self.assertRaises(templating.OSConfigException,
78
templating.OSConfigRenderer,
79
templates_dir='/tmp/foooo0',
80
openstack_release='grizzly')
82
def test_render_unregistered_config(self):
83
'''Ensure cannot render an unregistered config file'''
84
self.assertRaises(templating.OSConfigException,
86
config_file='/tmp/foo')
88
def test_write_unregistered_config(self):
89
'''Ensure cannot write an unregistered config file'''
90
self.assertRaises(templating.OSConfigException,
92
config_file='/tmp/foo')
94
def test_render_complete_context(self):
95
'''It renders a template when provided a complete context'''
96
self.loader.set('{{ foo }}')
97
self.context.set(interfaces=['fooservice'], context={'foo': 'bar'})
98
self.renderer.register('/tmp/foo', [self.context])
99
with patch.object(self.renderer, '_get_template') as _get_t:
100
fake_tmpl = MockTemplate()
101
_get_t.return_value = fake_tmpl
102
self.renderer.render('/tmp/foo')
103
fake_tmpl.render.assert_called_with(self.context())
104
self.assertIn('fooservice', self.renderer.complete_contexts())
106
def test_render_incomplete_context_with_template(self):
107
'''It renders a template when provided an incomplete context'''
108
self.context.set(interfaces=['fooservice'], context={})
109
self.renderer.register('/tmp/foo', [self.context])
110
with patch.object(self.renderer, '_get_template') as _get_t:
111
fake_tmpl = MockTemplate()
112
_get_t.return_value = fake_tmpl
113
self.renderer.render('/tmp/foo')
114
fake_tmpl.render.assert_called_with({})
115
self.assertNotIn('fooservice', self.renderer.complete_contexts())
117
@patch('__builtin__.open')
118
@patch.object(templating, 'get_loader')
119
def test_write_out_config(self, loader, _open):
120
'''It writes a templated config when provided a complete context'''
121
self.context.set(interfaces=['fooservice'], context={'foo': 'bar'})
122
self.renderer.register('/tmp/foo', [self.context])
123
with patch.object(self.renderer, '_get_template') as _get_t:
124
fake_tmpl = MockTemplate()
125
_get_t.return_value = fake_tmpl
126
self.renderer.write('/tmp/foo')
127
_open.assert_called_with('/tmp/foo', 'wb')
129
def test_write_all(self):
130
'''It writes out all configuration files at once'''
131
self.context.set(interfaces=['fooservice'], context={'foo': 'bar'})
132
self.renderer.register('/tmp/foo', [self.context])
133
self.renderer.register('/tmp/bar', [self.context])
138
with patch.object(self.renderer, 'write') as _write:
139
self.renderer.write_all()
140
self.assertEquals(ex_calls, _write.call_args_list)
143
@patch.object(templating, 'get_loader')
144
def test_reset_template_loader_for_new_os_release(self, loader):
146
self.context.set(interfaces=['fooservice'], context={})
147
loader.return_value = MockFSLoader('/tmp/foo')
148
self.renderer.register('/tmp/foo', [self.context])
149
self.renderer.render('/tmp/foo')
150
loader.assert_called_with(os.path.dirname(__file__), 'folsom')
151
self.renderer.set_release(openstack_release='grizzly')
152
self.renderer.render('/tmp/foo')
153
loader.assert_called_with(os.path.dirname(__file__), 'grizzly')
155
@patch.object(templating, 'get_loader')
156
def test_incomplete_context_not_reported_complete(self, loader):
157
'''It does not recognize an incomplete context as a complete context'''
158
self.context.set(interfaces=['fooservice'], context={})
159
self.renderer.register('/tmp/foo', [self.context])
160
self.assertNotIn('fooservice', self.renderer.complete_contexts())
162
@patch.object(templating, 'get_loader')
163
def test_complete_context_reported_complete(self, loader):
164
'''It recognizes a complete context as a complete context'''
165
self.context.set(interfaces=['fooservice'], context={'foo': 'bar'})
166
self.renderer.register('/tmp/foo', [self.context])
167
self.assertIn('fooservice', self.renderer.complete_contexts())
169
@patch('os.path.isdir')
170
def test_get_loader_no_templates_dir(self, isdir):
171
'''Ensure getting loader fails with no template dir'''
172
isdir.return_value = False
173
self.assertRaises(templating.OSConfigException,
174
templating.get_loader,
175
templates_dir='/tmp/foo', os_release='foo')
177
@patch('os.path.isdir')
178
def test_get_loader_all_search_paths(self, isdir):
179
'''Ensure loader reverse searches of all release template dirs'''
180
isdir.return_value = True
181
choice_loader = templating.get_loader('/tmp/foo',
182
os_release='icehouse')
183
dirs = [l.searchpath for l in choice_loader.loaders]
185
common_tmplts = os.path.join(os.path.dirname(templating.__file__),
187
expected = [['/tmp/foo/icehouse'],
189
['/tmp/foo/grizzly'],
195
self.assertEquals(dirs, expected)
197
@patch('os.path.isdir')
198
def test_get_loader_some_search_paths(self, isdir):
199
'''Ensure loader reverse searches of some release template dirs'''
200
isdir.return_value = True
201
choice_loader = templating.get_loader('/tmp/foo', os_release='grizzly')
202
dirs = [l.searchpath for l in choice_loader.loaders]
204
common_tmplts = os.path.join(os.path.dirname(templating.__file__),
207
expected = [['/tmp/foo/grizzly'],
213
self.assertEquals(dirs, expected)
215
def test_register_template_with_list_of_contexts(self):
216
'''Ensure registering a template with a list of context generators'''
222
tmpl = templating.OSConfigTemplate(config_file='/tmp/foo',
224
self.assertEquals(tmpl.contexts, [_c1, _c2])
226
def test_register_template_with_single_context(self):
227
'''Ensure registering a template with a single non-list context'''
230
tmpl = templating.OSConfigTemplate(config_file='/tmp/foo',
232
self.assertEquals(tmpl.contexts, [_c1])