1
# Copyright 2014 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
"""Tests for the `upgrade-cluster` command."""
6
from __future__ import (
17
from argparse import ArgumentParser
18
from os import makedirs
20
from textwrap import dedent
22
from maastesting.factory import factory
23
from maastesting.matchers import (
27
from maastesting.testcase import MAASTestCase
29
from provisioningserver import upgrade_cluster
30
from provisioningserver.config import BootConfig
31
from provisioningserver.testing.config import (
35
from testtools.matchers import (
41
class TestUpgradeCluster(MAASTestCase):
42
"""Tests for the `upgrade-cluster` command itself."""
44
def run_command(self):
45
parser = ArgumentParser()
46
upgrade_cluster.add_arguments(parser)
47
upgrade_cluster.run(parser.parse_args(()))
49
def patch_upgrade_hooks(self, hooks=None):
50
"""Temporarily replace the upgrade hooks."""
53
self.patch(upgrade_cluster, 'UPGRADE_HOOKS', hooks)
55
def test_calls_hooks(self):
57
self.patch_upgrade_hooks([upgrade_hook])
59
self.assertThat(upgrade_hook, MockCalledOnceWith())
61
def test_calls_hooks_in_order(self):
64
# Define some hooks. They will be run in the order in which they are
65
# listed (not in the order in which they are defined, or alphabetical
66
# order, or any other order).
75
calls.append('middle')
77
self.patch_upgrade_hooks([first_hook, middle_hook, last_hook])
79
self.assertEqual(['first', 'middle', 'last'], calls)
82
class TestGenerateBootResourcesConfig(MAASTestCase):
83
"""Tests for the `generate_boot_resources_config` upgrade."""
85
def patch_rewrite_boot_resources_config(self):
86
"""Patch `rewrite_boot_resources_config` with a mock."""
87
return self.patch(upgrade_cluster, 'rewrite_boot_resources_config')
89
def patch_boot_config(self, config):
90
"""Replace the bootresources config with the given fake."""
91
fixture = BootConfigFixture(config)
92
self.useFixture(fixture)
93
path = fixture.filename
94
self.patch(upgrade_cluster, 'locate_config').return_value = path
97
def test_hook_does_nothing_if_configure_me_is_False(self):
98
self.patch_boot_config({'boot': {'configure_me': False}})
99
rewrite_config = self.patch_rewrite_boot_resources_config()
100
upgrade_cluster.generate_boot_resources_config()
101
self.assertThat(rewrite_config, MockNotCalled())
103
def test_hook_does_nothing_if_configure_me_is_missing(self):
104
self.patch_boot_config({'boot': {}})
105
rewrite_config = self.patch_rewrite_boot_resources_config()
106
upgrade_cluster.generate_boot_resources_config()
107
self.assertThat(rewrite_config, MockNotCalled())
109
def test_hook_rewrites_if_configure_me_is_True(self):
110
config_file = self.patch_boot_config({'boot': {'configure_me': True}})
111
rewrite_config = self.patch_rewrite_boot_resources_config()
112
upgrade_cluster.generate_boot_resources_config()
113
self.assertThat(rewrite_config, MockCalledOnceWith(config_file))
115
def test_find_old_imports_returns_empty_if_no_tftproot(self):
116
non_dir = os.path.join(self.make_dir(), factory.make_name('nonesuch'))
117
self.assertEqual(set(), upgrade_cluster.find_old_imports(non_dir))
119
def test_find_old_imports_returns_empty_if_tftproot_is_empty(self):
122
upgrade_cluster.find_old_imports(self.make_dir()))
124
def test_find_old_imports_finds_image(self):
125
tftproot = self.make_dir()
126
arch = factory.make_name('arch')
127
subarch = factory.make_name('subarch')
128
release = factory.make_name('release')
129
purpose = factory.make_name('purpose')
130
makedirs(os.path.join(tftproot, arch, subarch, release, purpose))
132
{(arch, subarch, release)},
133
upgrade_cluster.find_old_imports(tftproot))
135
def test_generate_selections_returns_None_if_no_images_found(self):
136
self.assertIsNone(upgrade_cluster.generate_selections([]))
138
def test_generate_selections_matches_image(self):
139
arch = factory.make_name('arch')
140
subarch = factory.make_name('subarch')
141
release = factory.make_name('release')
147
'subarches': [subarch],
150
upgrade_cluster.generate_selections([(arch, subarch, release)]))
152
def test_generate_selections_sorts_output(self):
155
factory.make_name('arch'),
156
factory.make_name('subarch'),
157
factory.make_name('release'),
162
upgrade_cluster.generate_selections(sorted(images)),
163
upgrade_cluster.generate_selections(sorted(images, reverse=True)))
165
def test_generate_updated_config_clears_configure_me_if_no_images(self):
166
config = {'boot': {'configure_me': True, 'sources': []}}
169
upgrade_cluster.generate_updated_config(config, None)['boot'])
171
def test_generate_updated_config_clears_configure_me_if_has_images(self):
173
factory.make_name('arch'),
174
factory.make_name('subarch'),
175
factory.make_name('release'),
177
config = {'boot': {'configure_me': True, 'sources': []}}
180
upgrade_cluster.generate_updated_config(config, [image])['boot'])
182
def test_generate_updated_config_leaves_static_entries_intact(self):
183
storage = factory.make_name('storage')
184
path = factory.make_name('path')
185
keyring = factory.make_name('keyring')
188
'configure_me': True,
198
# Set configure_me; generate_updated_config expects it.
199
config['boot']['configure_me'] = True
201
result = upgrade_cluster.generate_updated_config(config, [])
202
self.assertEqual(storage, result['boot']['storage'])
203
self.assertEqual(path, result['boot']['sources'][0]['path'])
204
self.assertEqual(keyring, result['boot']['sources'][0]['keyring'])
206
def test_generate_updated_config_updates_sources(self):
207
arch = factory.make_name('arch')
208
subarch = factory.make_name('subarch')
209
release = factory.make_name('release')
210
path1 = factory.make_name('path')
211
path2 = factory.make_name('path')
214
'configure_me': True,
215
# There are two sources. Both will have their selections set.
222
result = upgrade_cluster.generate_updated_config(
223
config, [(arch, subarch, release)])
232
'subarches': [subarch],
242
'subarches': [subarch],
247
result['boot']['sources'])
249
def test_generate_updated_config_does_not_touch_sources_if_no_images(self):
250
path = factory.make_name('path')
251
arches = [factory.make_name('arch') for _ in range(2)]
254
'configure_me': True,
258
'selections': [{'arches': arches}],
264
result = upgrade_cluster.generate_updated_config(config, no_images)
269
'selections': [{'arches': arches}],
272
result['boot']['sources'])
274
def test_extract_top_comment_reads_up_to_first_non_comment_text(self):
278
# Comment after blank line.
281
filename = self.make_file(contents=(header + 'text#'))
282
self.assertEqual(header, upgrade_cluster.extract_top_comment(filename))
284
def test_update_config_file_rewrites_file_in_place(self):
285
old_storage = factory.make_name('old')
286
new_storage = factory.make_name('new')
287
original_file = dedent("""\
293
expected_file = dedent("""\
298
config_file = self.make_file(contents=original_file)
300
upgrade_cluster.update_config_file(
301
config_file, {'boot': {'storage': new_storage}})
303
self.assertThat(config_file, FileContains(expected_file))
305
def test_update_config_file_flushes_config_cache(self):
306
self.patch(BootConfig, 'flush_cache')
307
config_file = self.make_file()
308
upgrade_cluster.update_config_file(config_file, {})
310
BootConfig.flush_cache, MockCalledOnceWith(config_file))
312
def test_rewrite_boot_resources_config_integrates(self):
313
tftproot = self.make_dir()
314
# Fake pre-existing images in a pre-Simplestreams TFTP directory tree.
315
self.useFixture(ConfigFixture({'tftp': {'root': tftproot}}))
316
arch = factory.make_name('arch')
317
subarch = factory.make_name('subarch')
318
release = factory.make_name('release')
319
purpose = factory.make_name('purpose')
320
makedirs(os.path.join(tftproot, arch, subarch, release, purpose))
322
config_file = self.make_file(contents=dedent("""\
323
# Boot resources configuration file.
325
# Configuration follows.
328
# This setting will be removed during rewrite.
331
storage: "/var/lib/maas/boot-resources/"
334
- path: "http://maas.ubuntu.com/images/somewhere"
335
keyring: "/usr/share/keyrings/ubuntu-cloudimage-keyring.gpg"
341
upgrade_cluster.rewrite_boot_resources_config(config_file)
345
FileContains(matcher=StartsWith(dedent("""\
346
# Boot resources configuration file.
348
# Configuration follows.