~lutostag/ubuntu/trusty/maas/1.5.2+packagefix

« back to all changes in this revision

Viewing changes to src/provisioningserver/tests/test_tftp.py

  • Committer: Package Import Robot
  • Author(s): Andres Rodriguez
  • Date: 2014-03-28 10:43:53 UTC
  • mto: This revision was merged to the branch mainline in revision 57.
  • Revision ID: package-import@ubuntu.com-20140328104353-ekpolg0pm5xnvq2s
Tags: upstream-1.5+bzr2204
ImportĀ upstreamĀ versionĀ 1.5+bzr2204

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2005-2013 Canonical Ltd.  This software is licensed under the
 
1
# Copyright 2005-2014 Canonical Ltd.  This software is licensed under the
2
2
# GNU Affero General Public License version 3 (see the file LICENSE).
3
3
 
4
4
"""Tests for the maastftp Twisted plugin."""
24
24
    )
25
25
 
26
26
from maastesting.factory import factory
 
27
from maastesting.matchers import MockCalledOnceWith
27
28
from maastesting.testcase import MAASTestCase
 
29
import mock
28
30
from provisioningserver import tftp as tftp_module
29
 
from provisioningserver.pxe.tftppath import compose_config_path
 
31
from provisioningserver.boot.pxe import PXEBootMethod
 
32
from provisioningserver.boot.tests.test_pxe import compose_config_path
30
33
from provisioningserver.tests.test_kernel_opts import make_kernel_parameters
31
34
from provisioningserver.tftp import (
32
35
    BytesReader,
77
80
        self.assertRaises(ValueError, reader.read, 1)
78
81
 
79
82
 
80
 
class TestTFTPBackendRegex(MAASTestCase):
81
 
    """Tests for `provisioningserver.tftp.TFTPBackend.re_config_file`."""
82
 
 
83
 
    @staticmethod
84
 
    def get_example_path_and_components():
85
 
        """Return a plausible path and its components.
86
 
 
87
 
        The path is intended to match `re_config_file`, and the components are
88
 
        the expected groups from a match.
89
 
        """
90
 
        components = {"mac": factory.getRandomMACAddress("-"),
91
 
                      "arch": None,
92
 
                      "subarch": None}
93
 
        config_path = compose_config_path(components["mac"])
94
 
        return config_path, components
95
 
 
96
 
    def test_re_config_file_is_compatible_with_config_path_generator(self):
97
 
        # The regular expression for extracting components of the file path is
98
 
        # compatible with the PXE config path generator.
99
 
        regex = TFTPBackend.re_config_file
100
 
        for iteration in range(10):
101
 
            config_path, args = self.get_example_path_and_components()
102
 
            match = regex.match(config_path)
103
 
            self.assertIsNotNone(match, config_path)
104
 
            self.assertEqual(args, match.groupdict())
105
 
 
106
 
    def test_re_config_file_with_leading_slash(self):
107
 
        # The regular expression for extracting components of the file path
108
 
        # doesn't care if there's a leading forward slash; the TFTP server is
109
 
        # easy on this point, so it makes sense to be also.
110
 
        config_path, args = self.get_example_path_and_components()
111
 
        # Ensure there's a leading slash.
112
 
        config_path = "/" + config_path.lstrip("/")
113
 
        match = TFTPBackend.re_config_file.match(config_path)
114
 
        self.assertIsNotNone(match, config_path)
115
 
        self.assertEqual(args, match.groupdict())
116
 
 
117
 
    def test_re_config_file_without_leading_slash(self):
118
 
        # The regular expression for extracting components of the file path
119
 
        # doesn't care if there's no leading forward slash; the TFTP server is
120
 
        # easy on this point, so it makes sense to be also.
121
 
        config_path, args = self.get_example_path_and_components()
122
 
        # Ensure there's no leading slash.
123
 
        config_path = config_path.lstrip("/")
124
 
        match = TFTPBackend.re_config_file.match(config_path)
125
 
        self.assertIsNotNone(match, config_path)
126
 
        self.assertEqual(args, match.groupdict())
127
 
 
128
 
    def test_re_config_file_matches_classic_pxelinux_cfg(self):
129
 
        # The default config path is simply "pxelinux.cfg" (without
130
 
        # leading slash).  The regex matches this.
131
 
        mac = 'aa-bb-cc-dd-ee-ff'
132
 
        match = TFTPBackend.re_config_file.match('pxelinux.cfg/01-%s' % mac)
133
 
        self.assertIsNotNone(match)
134
 
        self.assertEqual({'mac': mac, 'arch': None, 'subarch': None},
135
 
                         match.groupdict())
136
 
 
137
 
    def test_re_config_file_matches_pxelinux_cfg_with_leading_slash(self):
138
 
        mac = 'aa-bb-cc-dd-ee-ff'
139
 
        match = TFTPBackend.re_config_file.match('/pxelinux.cfg/01-%s' % mac)
140
 
        self.assertIsNotNone(match)
141
 
        self.assertEqual({'mac': mac, 'arch': None, 'subarch': None},
142
 
                         match.groupdict())
143
 
 
144
 
    def test_re_config_file_does_not_match_non_config_file(self):
145
 
        self.assertIsNone(
146
 
            TFTPBackend.re_config_file.match('pxelinux.cfg/kernel'))
147
 
 
148
 
    def test_re_config_file_does_not_match_file_in_root(self):
149
 
        self.assertIsNone(
150
 
            TFTPBackend.re_config_file.match('01-aa-bb-cc-dd-ee-ff'))
151
 
 
152
 
    def test_re_config_file_does_not_match_file_not_in_pxelinux_cfg(self):
153
 
        self.assertIsNone(
154
 
            TFTPBackend.re_config_file.match('foo/01-aa-bb-cc-dd-ee-ff'))
155
 
 
156
 
    def test_re_config_file_with_default(self):
157
 
        match = TFTPBackend.re_config_file.match('pxelinux.cfg/default')
158
 
        self.assertIsNotNone(match)
159
 
        self.assertEqual(
160
 
            {'mac': None, 'arch': None, 'subarch': None},
161
 
            match.groupdict())
162
 
 
163
 
    def test_re_config_file_with_default_arch(self):
164
 
        arch = factory.make_name('arch', sep='')
165
 
        match = TFTPBackend.re_config_file.match('pxelinux.cfg/default.%s' %
166
 
                                                 arch)
167
 
        self.assertIsNotNone(match)
168
 
        self.assertEqual(
169
 
            {'mac': None, 'arch': arch, 'subarch': None},
170
 
            match.groupdict())
171
 
 
172
 
    def test_re_config_file_with_default_arch_and_subarch(self):
173
 
        arch = factory.make_name('arch', sep='')
174
 
        subarch = factory.make_name('subarch', sep='')
175
 
        match = TFTPBackend.re_config_file.match(
176
 
            'pxelinux.cfg/default.%s-%s' % (arch, subarch))
177
 
        self.assertIsNotNone(match)
178
 
        self.assertEqual(
179
 
            {'mac': None, 'arch': arch, 'subarch': subarch},
180
 
            match.groupdict())
181
 
 
182
 
 
183
83
class TestTFTPBackend(MAASTestCase):
184
84
    """Tests for `provisioningserver.tftp.TFTPBackend`."""
185
85
 
248
148
            }
249
149
 
250
150
        @partial(self.patch, backend, "get_config_reader")
251
 
        def get_config_reader(params):
 
151
        def get_config_reader(boot_method, params):
252
152
            params_json = json.dumps(params)
253
153
            params_json_reader = BytesReader(params_json)
254
154
            return succeed(params_json_reader)
282
182
        get_page_patch = self.patch(backend, "get_page")
283
183
        get_page_patch.return_value = succeed(fake_get_page_result)
284
184
 
285
 
        # Stub render_pxe_config to return the render parameters.
 
185
        # Stub render_config to return the render parameters.
 
186
        method = PXEBootMethod()
286
187
        fake_render_result = factory.make_name("render")
287
 
        render_patch = self.patch(backend, "render_pxe_config")
 
188
        render_patch = self.patch(method, "render_config")
288
189
        render_patch.return_value = fake_render_result
289
190
 
290
191
        # Get the rendered configuration, which will actually be a JSON dump
291
192
        # of the render-time parameters.
292
 
        reader = yield backend.get_config_reader(fake_params)
 
193
        reader = yield backend.get_config_reader(method, fake_params)
293
194
        self.addCleanup(reader.finish)
294
195
        self.assertIsInstance(reader, BytesReader)
295
196
        output = reader.read(10000)
296
197
 
297
198
        # The kernel parameters were fetched using `backend.get_page`.
298
 
        backend.get_page.assert_called_once()
 
199
        self.assertThat(backend.get_page, MockCalledOnceWith(mock.ANY))
299
200
 
300
 
        # The result has been rendered by `backend.render_pxe_config`.
 
201
        # The result has been rendered by `backend.render_config`.
301
202
        self.assertEqual(fake_render_result.encode("utf-8"), output)
302
 
        backend.render_pxe_config.assert_called_once_with(
303
 
            kernel_params=fake_kernel_params, **fake_params)
 
203
        self.assertThat(method.render_config, MockCalledOnceWith(
 
204
            kernel_params=fake_kernel_params, **fake_params))
 
205
 
 
206
    @inlineCallbacks
 
207
    def test_get_config_reader_substitutes_armhf_in_params(self):
 
208
        # get_config_reader() should substitute "arm" for "armhf" in the
 
209
        # arch field of the parameters (mapping from pxe to maas
 
210
        # namespace).
 
211
        cluster_uuid = factory.getRandomUUID()
 
212
        self.patch(tftp_module, 'get_cluster_uuid').return_value = (
 
213
            cluster_uuid)
 
214
        config_path = "pxelinux.cfg/default-arm"
 
215
        backend = TFTPBackend(self.make_dir(), b"http://example.com/")
 
216
        # python-tx-tftp sets up call context so that backends can discover
 
217
        # more about the environment in which they're running.
 
218
        call_context = {
 
219
            "local": (
 
220
                factory.getRandomIPAddress(),
 
221
                factory.getRandomPort()),
 
222
            "remote": (
 
223
                factory.getRandomIPAddress(),
 
224
                factory.getRandomPort()),
 
225
            }
 
226
 
 
227
        @partial(self.patch, backend, "get_config_reader")
 
228
        def get_config_reader(boot_method, params):
 
229
            params_json = json.dumps(params)
 
230
            params_json_reader = BytesReader(params_json)
 
231
            return succeed(params_json_reader)
 
232
 
 
233
        reader = yield context.call(
 
234
            call_context, backend.get_reader, config_path)
 
235
        output = reader.read(10000)
 
236
        observed_params = json.loads(output)
 
237
        self.assertEqual("armhf", observed_params["arch"])
304
238
 
305
239
 
306
240
class TestTFTPService(MAASTestCase):
318
252
        example_generator = "http://example.com/generator"
319
253
        example_port = factory.getRandomPort()
320
254
        tftp_service = TFTPService(
321
 
            root=example_root, generator=example_generator,
 
255
            resource_root=example_root, generator=example_generator,
322
256
            port=example_port)
323
257
        tftp_service.updateServers()
324
258
        # The "tftp" service is a multi-service containing UDP servers for
370
304
            lambda: interfaces)
371
305
 
372
306
        tftp_service = TFTPService(
373
 
            root=self.make_dir(), generator="http://mighty/wind",
 
307
            resource_root=self.make_dir(), generator="http://mighty/wind",
374
308
            port=factory.getRandomPort())
375
309
        tftp_service.updateServers()
376
310