← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~allenap/maas/pxe-intel-arch-config-2 into lp:maas

 

Gavin Panella has proposed merging lp:~allenap/maas/pxe-intel-arch-config-2 into lp:maas with lp:~allenap/maas/pxe-intel-arch-config as a prerequisite.

Requested reviews:
  MAAS Maintainers (maas-maintainers)

For more details, see:
https://code.launchpad.net/~allenap/maas/pxe-intel-arch-config-2/+merge/121097
-- 
https://code.launchpad.net/~allenap/maas/pxe-intel-arch-config-2/+merge/121097
Your team MAAS Maintainers is requested to review the proposed merge of lp:~allenap/maas/pxe-intel-arch-config-2 into lp:maas.
=== modified file 'src/provisioningserver/pxe/config.py'
--- src/provisioningserver/pxe/config.py	2012-08-17 11:12:09 +0000
+++ src/provisioningserver/pxe/config.py	2012-08-23 22:27:20 +0000
@@ -25,6 +25,7 @@
 from os import path
 
 import posixpath
+from provisioningserver.kernel_opts import compose_kernel_command_line_new
 from provisioningserver.pxe.tftppath import compose_image_path
 import tempita
 
@@ -71,26 +72,25 @@
             "No PXE template found in %r!" % template_dir)
 
 
-def render_pxe_config(
-    arch, subarch, release, purpose, append, bootpath, **extra):
+def render_pxe_config(bootpath, kernel_params, **extra):
     """Render a PXE configuration file as a unicode string.
 
-    :param arch: Main machine architecture.
-    :param subarch: Sub-architecture, or "generic" if there is none.
-    :param release: The OS release, e.g. "precise".
-    :param purpose: What's the purpose of this boot, e.g. "install".
-    :param append: Additional kernel parameters.
     :param bootpath: The directory path of `pxelinux.0`.
+    :param kernel_params: An instance of `KernelParameters`.
     :param extra: Allow for other arguments. This is a safety valve;
         parameters generated in another component (for example, see
         `TFTPBackend.get_config_reader`) won't cause this to break.
     """
-    template = get_pxe_template(purpose, arch, subarch)
-    image_dir = compose_image_path(arch, subarch, release, purpose)
+    template = get_pxe_template(
+        kernel_params.purpose, kernel_params.arch,
+        kernel_params.subarch)
+    image_dir = compose_image_path(
+        kernel_params.arch, kernel_params.subarch,
+        kernel_params.release, kernel_params.purpose)
     # The locations of the kernel image and the initrd are defined by
     # update_install_files(), in scripts/maas-import-pxe-files.
     namespace = {
-        "append": append,
+        "append": compose_kernel_command_line_new(kernel_params),
         "initrd": "%s/initrd.gz" % image_dir,
         "kernel": "%s/linux" % image_dir,
         "relpath": partial(posixpath.relpath, start=bootpath),

=== modified file 'src/provisioningserver/pxe/tests/test_config.py'
--- src/provisioningserver/pxe/tests/test_config.py	2012-08-17 11:12:09 +0000
+++ src/provisioningserver/pxe/tests/test_config.py	2012-08-23 22:27:20 +0000
@@ -23,6 +23,7 @@
 from provisioningserver.pxe import config
 from provisioningserver.pxe.config import render_pxe_config
 from provisioningserver.pxe.tftppath import compose_image_path
+from provisioningserver.tests.test_kernel_opts import make_kernel_parameters
 from testtools.matchers import (
     IsInstance,
     MatchesAll,
@@ -101,15 +102,9 @@
     def test_render(self):
         # Given the right configuration options, the PXE configuration is
         # correctly rendered.
-        options = {
-            "arch": factory.make_name("arch"),
-            "subarch": factory.make_name("subarch"),
-            "release": factory.make_name("release"),
-            "purpose": factory.make_name("purpose"),
-            "append": factory.make_name("append"),
-            }
-        options["bootpath"] = "maas/%(arch)s/%(subarch)s" % options
-        output = render_pxe_config(**options)
+        bootpath = factory.make_name("bootpath")
+        params = make_kernel_parameters()
+        output = render_pxe_config(bootpath=bootpath, kernel_params=params)
         # The output is always a Unicode string.
         self.assertThat(output, IsInstance(unicode))
         # The template has rendered without error. PXELINUX configurations
@@ -117,9 +112,9 @@
         self.assertThat(output, StartsWith("DEFAULT "))
         # The PXE parameters are all set according to the options.
         image_dir = compose_image_path(
-            arch=options["arch"], subarch=options["subarch"],
-            release=options["release"], purpose=options["purpose"])
-        image_dir = posixpath.relpath(image_dir, options["bootpath"])
+            arch=params.arch, subarch=params.subarch,
+            release=params.release, purpose=params.purpose)
+        image_dir = posixpath.relpath(image_dir, bootpath)
         self.assertThat(
             output, MatchesAll(
                 MatchesRegex(
@@ -129,18 +124,14 @@
                     r'.*^\s+INITRD %s/initrd[.]gz$' % re.escape(image_dir),
                     re.MULTILINE | re.DOTALL),
                 MatchesRegex(
-                    r'.*^\s+APPEND %s$' % re.escape(options["append"]),
+                    r'.*^\s+APPEND .+?$',
                     re.MULTILINE | re.DOTALL)))
 
     def test_render_with_extra_arguments_does_not_affect_output(self):
         # render_pxe_config() allows any keyword arguments as a safety valve.
         options = {
-            "arch": factory.make_name("arch"),
-            "subarch": factory.make_name("subarch"),
-            "release": factory.make_name("release"),
-            "purpose": factory.make_name("purpose"),
             "bootpath": factory.make_name("bootpath"),
-            "append": factory.make_name("append"),
+            "kernel_params": make_kernel_parameters(),
             }
         # Capture the output before sprinking in some random options.
         output_before = render_pxe_config(**options)
@@ -157,12 +148,9 @@
         # If purpose is "local", the config.localboot.template should be
         # used.
         options = {
-            "arch": factory.make_name("arch"),
-            "subarch": factory.make_name("subarch"),
-            "release": factory.make_name("release"),
-            "purpose": "local",
             "bootpath": factory.make_name("bootpath"),
-            "append": factory.make_name("append"),
+            "kernel_params":
+                make_kernel_parameters()._replace(purpose="local"),
             }
         output = render_pxe_config(**options)
         self.assertIn("LOCALBOOT 0", output)
@@ -171,12 +159,9 @@
         # Intel i386 is a special case and needs to use the chain.c32
         # loader as the LOCALBOOT PXE directive is unreliable.
         options = {
-            "arch": "i386",
-            "subarch": factory.make_name("subarch"),
-            "release": factory.make_name("release"),
-            "purpose": "local",
             "bootpath": factory.make_name("bootpath"),
-            "append": factory.make_name("append"),
+            "kernel_params": make_kernel_parameters()._replace(
+                arch="i386", purpose="local"),
             }
         output = render_pxe_config(**options)
         self.assertIn("chain.c32", output)
@@ -186,12 +171,9 @@
         # Intel amd64 is a special case and needs to use the chain.c32
         # loader as the LOCALBOOT PXE directive is unreliable.
         options = {
-            "arch": "amd64",
-            "subarch": factory.make_name("subarch"),
-            "release": factory.make_name("release"),
-            "purpose": "local",
             "bootpath": factory.make_name("bootpath"),
-            "append": factory.make_name("append"),
+            "kernel_params": make_kernel_parameters()._replace(
+                arch="amd64", purpose="local"),
             }
         output = render_pxe_config(**options)
         self.assertIn("chain.c32", output)

=== modified file 'src/provisioningserver/tests/test_kernel_opts.py'
--- src/provisioningserver/tests/test_kernel_opts.py	2012-08-23 08:32:27 +0000
+++ src/provisioningserver/tests/test_kernel_opts.py	2012-08-23 22:27:20 +0000
@@ -10,7 +10,9 @@
     )
 
 __metaclass__ = type
-__all__ = []
+__all__ = [
+    "make_kernel_parameters",
+    ]
 
 import os
 

=== modified file 'src/provisioningserver/tests/test_tftp.py'
--- src/provisioningserver/tests/test_tftp.py	2012-08-17 13:55:00 +0000
+++ src/provisioningserver/tests/test_tftp.py	2012-08-23 22:27:20 +0000
@@ -23,7 +23,9 @@
 
 from maastesting.factory import factory
 from maastesting.testcase import TestCase
+import mock
 from provisioningserver.pxe.tftppath import compose_config_path
+from provisioningserver.tests.test_kernel_opts import make_kernel_parameters
 from provisioningserver.tftp import (
     BytesReader,
     TFTPBackend,
@@ -126,21 +128,19 @@
     def test_get_generator_url(self):
         # get_generator_url() merges the parameters obtained from the request
         # file path (arch, subarch, name) into the configured generator URL.
-        arch = factory.make_name("arch").encode("ascii")
-        subarch = factory.make_name("subarch").encode("ascii")
+        bootpath = factory.make_name("bootpath")
         mac = factory.getRandomMACAddress(b"-")
-        append = factory.make_name("append").encode("ascii")
-        backend_url = b"http://example.com/?"; + urlencode({b"append": append})
+        dummy = factory.make_name("dummy").encode("ascii")
+        backend_url = b"http://example.com/?"; + urlencode({b"dummy": dummy})
         backend = TFTPBackend(self.make_dir(), backend_url)
         # params is an example of the parameters obtained from a request.
-        params = {"arch": arch, "subarch": subarch, "mac": mac}
+        params = {"bootpath": bootpath, "mac": mac}
         generator_url = urlparse(backend.get_generator_url(params))
         self.assertEqual("example.com", generator_url.hostname)
         query = parse_qsl(generator_url.query)
         query_expected = [
-            ("append", append),
-            ("arch", arch),
-            ("subarch", subarch),
+            ("bootpath", bootpath),
+            ("dummy", dummy),
             ("mac", mac),
             ]
         self.assertItemsEqual(query_expected, query)
@@ -187,36 +187,34 @@
         # `IReader` of a PXE configuration, rendered by `render_pxe_config`.
         backend = TFTPBackend(self.make_dir(), b"http://example.com/";)
         # Fake configuration parameters, as discovered from the file path.
-        fake_params = dict(
-            arch=factory.make_name("arch"),
-            subarch=factory.make_name("subarch"),
-            mac=factory.getRandomMACAddress(b"-"))
-        fake_params.update(
-            bootpath="maas/%(arch)s/%(subarch)s" % fake_params)
-        # Fake configuration parameters, as returned from the API call.
-        fake_api_params = dict(fake_params)
-        fake_api_params.update(
-            append=factory.make_name("append"),
-            purpose=factory.make_name("purpose"),
-            release=factory.make_name("release"))
-        # Add a purpose to the first set of parameters. This will later help
-        # demonstrate that the API parameters take precedence over the file
-        # path parameters.
-        fake_params["purpose"] = factory.make_name("original-purpose")
+        fake_params = {
+            "bootpath": "maas",
+            "mac": factory.getRandomMACAddress(b"-"),
+            }
+        # Fake kernel configuration parameters, as returned from the API call.
+        fake_kernel_params = make_kernel_parameters()
+
         # Stub get_page to return the fake API configuration parameters.
-        fake_api_params_json = json.dumps(fake_api_params)
-        backend.get_page = lambda url: succeed(fake_api_params_json)
+        fake_get_page_result = json.dumps(fake_kernel_params._asdict())
+        get_page_patch = mock.patch.object(backend, "get_page")
+        get_page_patch.start().return_value = succeed(fake_get_page_result)
+
         # Stub render_pxe_config to return the render parameters.
-        backend.render_pxe_config = lambda **kwargs: json.dumps(kwargs)
+        fake_render_result = factory.make_name("render")
+        render_patch = mock.patch.object(backend, "render_pxe_config")
+        render_patch.start().return_value = fake_render_result
+
         # Get the rendered configuration, which will actually be a JSON dump
         # of the render-time parameters.
-        reader = yield backend.get_config_reader(fake_api_params)
+        reader = yield backend.get_config_reader(fake_params)
         self.addCleanup(reader.finish)
         self.assertIsInstance(reader, BytesReader)
         output = reader.read(10000)
-        # The expected render-time parameters are a merge of previous
-        # parameters. Note that the API parameters take precedence.
-        expected_render_params = dict(fake_params)
-        expected_render_params.update(fake_api_params)
-        observed_render_params = json.loads(output)
-        self.assertEqual(expected_render_params, observed_render_params)
+
+        # The kernel parameters were fetched using `backend.get_page`.
+        backend.get_page.assert_called_once()
+
+        # The result has been rendered by `backend.render_pxe_config`.
+        self.assertEqual(fake_render_result.encode("utf-8"), output)
+        backend.render_pxe_config.assert_called_once_with(
+            kernel_params=fake_kernel_params, **fake_params)

=== modified file 'src/provisioningserver/tftp.py'
--- src/provisioningserver/tftp.py	2012-08-17 13:47:24 +0000
+++ src/provisioningserver/tftp.py	2012-08-23 22:27:20 +0000
@@ -25,6 +25,7 @@
     )
 
 from provisioningserver.enum import ARP_HTYPE
+from provisioningserver.kernel_opts import KernelParameters
 from provisioningserver.pxe.config import render_pxe_config
 from provisioningserver.utils import deferred
 from tftp.backend import (
@@ -130,21 +131,36 @@
         return url.geturl().encode("ascii")
 
     @deferred
+    def get_kernel_params(self, params):
+        """Return kernel parameters obtained from the API.
+
+        :param params: Parameters so far obtained, typically from the file
+            path requested.
+        :return: A `KernelParameters` instance.
+        """
+        url = self.get_generator_url(params)
+
+        def reassemble(data):
+            return KernelParameters(**data)
+
+        d = self.get_page(url)
+        d.addCallback(json.loads)
+        d.addCallback(reassemble)
+        return d
+
+    @deferred
     def get_config_reader(self, params):
         """Return an `IReader` for a PXE config.
 
         :param params: Parameters so far obtained, typically from the file
             path requested.
         """
-        url = self.get_generator_url(params)
-
-        def generate_config(api_params):
-            params.update(api_params)
-            config = self.render_pxe_config(**params)
+        def generate_config(kernel_params):
+            config = self.render_pxe_config(
+                kernel_params=kernel_params, **params)
             return config.encode("utf-8")
 
-        d = self.get_page(url)
-        d.addCallback(json.loads)
+        d = self.get_kernel_params(params)
         d.addCallback(generate_config)
         d.addCallback(BytesReader)
         return d