~ubuntu-branches/ubuntu/trusty/maas/trusty-security

« back to all changes in this revision

Viewing changes to src/provisioningserver/boot/tests/test_powernv.py

  • Committer: Package Import Robot
  • Author(s): Greg Lutostanski
  • Date: 2014-08-29 13:27:34 UTC
  • mto: (61.1.4 trusty-proposed)
  • mto: This revision was merged to the branch mainline in revision 62.
  • Revision ID: package-import@ubuntu.com-20140829132734-d47evihju2etdwcy
Tags: upstream-1.5.4+bzr2294
ImportĀ upstreamĀ versionĀ 1.5.4+bzr2294

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2014 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
"""Tests for `provisioningserver.boot.powernv`."""
 
5
 
 
6
from __future__ import (
 
7
    absolute_import,
 
8
    print_function,
 
9
    unicode_literals,
 
10
    )
 
11
 
 
12
str = None
 
13
 
 
14
__metaclass__ = type
 
15
__all__ = []
 
16
 
 
17
import os
 
18
import re
 
19
 
 
20
from maastesting.factory import factory
 
21
from maastesting.testcase import MAASTestCase
 
22
from provisioningserver.boot import BytesReader
 
23
from provisioningserver.boot.powernv import (
 
24
    ARP_HTYPE,
 
25
    format_bootif,
 
26
    PowerNVBootMethod,
 
27
    re_config_file,
 
28
    )
 
29
from provisioningserver.boot.tests.test_pxe import parse_pxe_config
 
30
from provisioningserver.boot.tftppath import compose_image_path
 
31
from provisioningserver.testing.config import set_tftp_root
 
32
from provisioningserver.tests.test_kernel_opts import make_kernel_parameters
 
33
from provisioningserver.tftp import TFTPBackend
 
34
from testtools.matchers import (
 
35
    IsInstance,
 
36
    MatchesAll,
 
37
    MatchesRegex,
 
38
    Not,
 
39
    StartsWith,
 
40
    )
 
41
 
 
42
 
 
43
def compose_config_path(mac):
 
44
    """Compose the TFTP path for a PowerNV PXE configuration file.
 
45
 
 
46
    The path returned is relative to the TFTP root, as it would be
 
47
    identified by clients on the network.
 
48
 
 
49
    :param mac: A MAC address, in IEEE 802 hyphen-separated form,
 
50
        corresponding to the machine for which this configuration is
 
51
        relevant. This relates to PXELINUX's lookup protocol.
 
52
    :return: Path for the corresponding PXE config file as exposed over
 
53
        TFTP.
 
54
    """
 
55
    # Not using os.path.join: this is a TFTP path, not a native path. Yes, in
 
56
    # practice for us they're the same. We always assume that the ARP HTYPE
 
57
    # (hardware type) that PXELINUX sends is Ethernet.
 
58
    return "ppc64el/pxelinux.cfg/{htype:02x}-{mac}".format(
 
59
        htype=ARP_HTYPE.ETHERNET, mac=mac)
 
60
 
 
61
 
 
62
def get_example_path_and_components():
 
63
    """Return a plausible path and its components.
 
64
 
 
65
    The path is intended to match `re_config_file`, and the components are
 
66
    the expected groups from a match.
 
67
    """
 
68
    components = {"mac": factory.getRandomMACAddress("-")}
 
69
    config_path = compose_config_path(components["mac"])
 
70
    return config_path, components
 
71
 
 
72
 
 
73
class TestPowerNVBootMethod(MAASTestCase):
 
74
 
 
75
    def make_tftp_root(self):
 
76
        """Set, and return, a temporary TFTP root directory."""
 
77
        tftproot = self.make_dir()
 
78
        self.useFixture(set_tftp_root(tftproot))
 
79
        return tftproot
 
80
 
 
81
    def test_compose_config_path_follows_maas_pxe_directory_layout(self):
 
82
        name = factory.make_name('config')
 
83
        self.assertEqual(
 
84
            'ppc64el/pxelinux.cfg/%02x-%s' % (ARP_HTYPE.ETHERNET, name),
 
85
            compose_config_path(name))
 
86
 
 
87
    def test_compose_config_path_does_not_include_tftp_root(self):
 
88
        tftproot = self.make_tftp_root()
 
89
        name = factory.make_name('config')
 
90
        self.assertThat(
 
91
            compose_config_path(name),
 
92
            Not(StartsWith(tftproot)))
 
93
 
 
94
    def test_bootloader_path(self):
 
95
        method = PowerNVBootMethod()
 
96
        self.assertEqual('pxelinux.0', method.bootloader_path)
 
97
 
 
98
    def test_bootloader_path_does_not_include_tftp_root(self):
 
99
        tftproot = self.make_tftp_root()
 
100
        method = PowerNVBootMethod()
 
101
        self.assertThat(
 
102
            method.bootloader_path,
 
103
            Not(StartsWith(tftproot)))
 
104
 
 
105
    def test_name(self):
 
106
        method = PowerNVBootMethod()
 
107
        self.assertEqual('powernv', method.name)
 
108
 
 
109
    def test_template_subdir(self):
 
110
        method = PowerNVBootMethod()
 
111
        self.assertEqual('pxe', method.template_subdir)
 
112
 
 
113
    def test_arch_octet(self):
 
114
        method = PowerNVBootMethod()
 
115
        self.assertEqual('00:0E', method.arch_octet)
 
116
 
 
117
    def test_path_prefix(self):
 
118
        method = PowerNVBootMethod()
 
119
        self.assertEqual('ppc64el/', method.path_prefix)
 
120
 
 
121
 
 
122
class TestPowerNVBootMethodMatchPath(MAASTestCase):
 
123
    """Tests for
 
124
    `provisioningserver.boot.powernv.PowerNVBootMethod.match_path`.
 
125
    """
 
126
 
 
127
    def test_match_path_pxe_config_with_mac(self):
 
128
        method = PowerNVBootMethod()
 
129
        config_path, expected = get_example_path_and_components()
 
130
        params = method.match_path(None, config_path)
 
131
        expected['arch'] = 'ppc64el'
 
132
        self.assertEqual(expected, params)
 
133
 
 
134
    def test_match_path_pxe_config_without_mac(self):
 
135
        method = PowerNVBootMethod()
 
136
        fake_mac = factory.getRandomMACAddress()
 
137
        self.patch(method, 'get_remote_mac').return_value = fake_mac
 
138
        config_path = 'ppc64el/pxelinux.cfg/default'
 
139
        params = method.match_path(None, config_path)
 
140
        expected = {
 
141
            'arch': 'ppc64el',
 
142
            'mac': fake_mac,
 
143
            }
 
144
        self.assertEqual(expected, params)
 
145
 
 
146
    def test_match_path_pxe_prefix_request(self):
 
147
        method = PowerNVBootMethod()
 
148
        fake_mac = factory.getRandomMACAddress()
 
149
        self.patch(method, 'get_remote_mac').return_value = fake_mac
 
150
        file_path = 'ppc64el/file'
 
151
        params = method.match_path(None, file_path)
 
152
        expected = {
 
153
            'arch': 'ppc64el',
 
154
            'mac': fake_mac,
 
155
            'path': file_path,
 
156
            }
 
157
        self.assertEqual(expected, params)
 
158
 
 
159
 
 
160
class TestPowerNVBootMethodRenderConfig(MAASTestCase):
 
161
    """Tests for
 
162
    `provisioningserver.boot.powernv.PowerNVBootMethod.get_reader`
 
163
    """
 
164
 
 
165
    def test_get_reader_install(self):
 
166
        # Given the right configuration options, the PXE configuration is
 
167
        # correctly rendered.
 
168
        method = PowerNVBootMethod()
 
169
        params = make_kernel_parameters(self, purpose="install")
 
170
        output = method.get_reader(backend=None, kernel_params=params)
 
171
        # The output is a BytesReader.
 
172
        self.assertThat(output, IsInstance(BytesReader))
 
173
        output = output.read(10000)
 
174
        # The template has rendered without error. PXELINUX configurations
 
175
        # typically start with a DEFAULT line.
 
176
        self.assertThat(output, StartsWith("DEFAULT "))
 
177
        # The PXE parameters are all set according to the options.
 
178
        image_dir = compose_image_path(
 
179
            arch=params.arch, subarch=params.subarch,
 
180
            release=params.release, label=params.label)
 
181
        self.assertThat(
 
182
            output, MatchesAll(
 
183
                MatchesRegex(
 
184
                    r'.*^\s+KERNEL %s/di-kernel$' % re.escape(image_dir),
 
185
                    re.MULTILINE | re.DOTALL),
 
186
                MatchesRegex(
 
187
                    r'.*^\s+INITRD %s/di-initrd$' % re.escape(image_dir),
 
188
                    re.MULTILINE | re.DOTALL),
 
189
                MatchesRegex(
 
190
                    r'.*^\s+APPEND .+?$',
 
191
                    re.MULTILINE | re.DOTALL)))
 
192
 
 
193
    def test_get_reader_with_extra_arguments_does_not_affect_output(self):
 
194
        # get_reader() allows any keyword arguments as a safety valve.
 
195
        method = PowerNVBootMethod()
 
196
        options = {
 
197
            "backend": None,
 
198
            "kernel_params": make_kernel_parameters(self, purpose="install"),
 
199
        }
 
200
        # Capture the output before sprinking in some random options.
 
201
        output_before = method.get_reader(**options).read(10000)
 
202
        # Sprinkle some magic in.
 
203
        options.update(
 
204
            (factory.make_name("name"), factory.make_name("value"))
 
205
            for _ in range(10))
 
206
        # Capture the output after sprinking in some random options.
 
207
        output_after = method.get_reader(**options).read(10000)
 
208
        # The generated template is the same.
 
209
        self.assertEqual(output_before, output_after)
 
210
 
 
211
    def test_get_reader_with_local_purpose(self):
 
212
        # If purpose is "local", output should be empty string.
 
213
        method = PowerNVBootMethod()
 
214
        options = {
 
215
            "backend": None,
 
216
            "kernel_params": make_kernel_parameters(purpose="local"),
 
217
            }
 
218
        output = method.get_reader(**options).read(10000)
 
219
        self.assertIn("", output)
 
220
 
 
221
    def test_get_reader_appends_bootif(self):
 
222
        method = PowerNVBootMethod()
 
223
        fake_mac = factory.getRandomMACAddress()
 
224
        params = make_kernel_parameters(self, purpose="install")
 
225
        output = method.get_reader(
 
226
            backend=None, kernel_params=params, arch='ppc64el', mac=fake_mac)
 
227
        output = output.read(10000)
 
228
        config = parse_pxe_config(output)
 
229
        expected = 'BOOTIF=%s' % format_bootif(fake_mac)
 
230
        self.assertIn(expected, config['execute']['APPEND'])
 
231
 
 
232
 
 
233
class TestPowerNVBootMethodPathPrefix(MAASTestCase):
 
234
    """Tests for
 
235
    `provisioningserver.boot.powernv.PowerNVBootMethod.get_reader`.
 
236
    """
 
237
 
 
238
    def test_get_reader_path_prefix(self):
 
239
        data = factory.getRandomString().encode("ascii")
 
240
        temp_file = self.make_file(name="example", contents=data)
 
241
        temp_dir = os.path.dirname(temp_file)
 
242
        backend = TFTPBackend(temp_dir, "http://nowhere.example.com/")
 
243
        method = PowerNVBootMethod()
 
244
        options = {
 
245
            'backend': backend,
 
246
            'kernel_params': make_kernel_parameters(),
 
247
            'path': 'ppc64el/example',
 
248
        }
 
249
        reader = method.get_reader(**options)
 
250
        self.addCleanup(reader.finish)
 
251
        self.assertEqual(len(data), reader.size)
 
252
        self.assertEqual(data, reader.read(len(data)))
 
253
        self.assertEqual(b"", reader.read(1))
 
254
 
 
255
    def test_get_reader_path_prefix_only_removes_first_occurrence(self):
 
256
        data = factory.getRandomString().encode("ascii")
 
257
        temp_dir = self.make_dir()
 
258
        temp_subdir = os.path.join(temp_dir, 'ppc64el')
 
259
        os.mkdir(temp_subdir)
 
260
        factory.make_file(temp_subdir, "example", data)
 
261
        backend = TFTPBackend(temp_dir, "http://nowhere.example.com/")
 
262
        method = PowerNVBootMethod()
 
263
        options = {
 
264
            'backend': backend,
 
265
            'kernel_params': make_kernel_parameters(),
 
266
            'path': 'ppc64el/ppc64el/example',
 
267
        }
 
268
        reader = method.get_reader(**options)
 
269
        self.addCleanup(reader.finish)
 
270
        self.assertEqual(len(data), reader.size)
 
271
        self.assertEqual(data, reader.read(len(data)))
 
272
        self.assertEqual(b"", reader.read(1))
 
273
 
 
274
 
 
275
class TestPowerNVBootMethodRegex(MAASTestCase):
 
276
    """Tests for
 
277
    `provisioningserver.boot.powernv.PowerNVBootMethod.re_config_file`.
 
278
    """
 
279
 
 
280
    def test_re_config_file_is_compatible_with_config_path_generator(self):
 
281
        # The regular expression for extracting components of the file path is
 
282
        # compatible with the PXE config path generator.
 
283
        for iteration in range(10):
 
284
            config_path, args = get_example_path_and_components()
 
285
            match = re_config_file.match(config_path)
 
286
            self.assertIsNotNone(match, config_path)
 
287
            self.assertEqual(args, match.groupdict())
 
288
 
 
289
    def test_re_config_file_with_leading_slash(self):
 
290
        # The regular expression for extracting components of the file path
 
291
        # doesn't care if there's a leading forward slash; the TFTP server is
 
292
        # easy on this point, so it makes sense to be also.
 
293
        config_path, args = get_example_path_and_components()
 
294
        # Ensure there's a leading slash.
 
295
        config_path = "/" + config_path.lstrip("/")
 
296
        match = re_config_file.match(config_path)
 
297
        self.assertIsNotNone(match, config_path)
 
298
        self.assertEqual(args, match.groupdict())
 
299
 
 
300
    def test_re_config_file_without_leading_slash(self):
 
301
        # The regular expression for extracting components of the file path
 
302
        # doesn't care if there's no leading forward slash; the TFTP server is
 
303
        # easy on this point, so it makes sense to be also.
 
304
        config_path, args = get_example_path_and_components()
 
305
        # Ensure there's no leading slash.
 
306
        config_path = config_path.lstrip("/")
 
307
        match = re_config_file.match(config_path)
 
308
        self.assertIsNotNone(match, config_path)
 
309
        self.assertEqual(args, match.groupdict())
 
310
 
 
311
    def test_re_config_file_matches_classic_pxelinux_cfg(self):
 
312
        # The default config path is simply "pxelinux.cfg" (without
 
313
        # leading slash).  The regex matches this.
 
314
        mac = 'aa-bb-cc-dd-ee-ff'
 
315
        match = re_config_file.match('ppc64el/pxelinux.cfg/01-%s' % mac)
 
316
        self.assertIsNotNone(match)
 
317
        self.assertEqual({'mac': mac}, match.groupdict())
 
318
 
 
319
    def test_re_config_file_matches_pxelinux_cfg_with_leading_slash(self):
 
320
        mac = 'aa-bb-cc-dd-ee-ff'
 
321
        match = re_config_file.match('/ppc64el/pxelinux.cfg/01-%s' % mac)
 
322
        self.assertIsNotNone(match)
 
323
        self.assertEqual({'mac': mac}, match.groupdict())
 
324
 
 
325
    def test_re_config_file_does_not_match_non_config_file(self):
 
326
        self.assertIsNone(re_config_file.match('ppc64el/pxelinux.cfg/kernel'))
 
327
 
 
328
    def test_re_config_file_does_not_match_file_in_root(self):
 
329
        self.assertIsNone(re_config_file.match('01-aa-bb-cc-dd-ee-ff'))
 
330
 
 
331
    def test_re_config_file_does_not_match_file_not_in_pxelinux_cfg(self):
 
332
        self.assertIsNone(re_config_file.match('foo/01-aa-bb-cc-dd-ee-ff'))
 
333
 
 
334
    def test_re_config_file_with_default(self):
 
335
        match = re_config_file.match('ppc64el/pxelinux.cfg/default')
 
336
        self.assertIsNotNone(match)
 
337
        self.assertEqual({'mac': None}, match.groupdict())