← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~jameinel/maas/1.2-remove-kernel-opts into lp:maas

 

John A Meinel has proposed merging lp:~jameinel/maas/1.2-remove-kernel-opts into lp:maas.

Commit message:
This removes the recent patches to add kernel_opts to the 1.2 branch.

We'll land it instead on trunk, and then it will get backported in the future once a bugfix only SRU was done.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~jameinel/maas/1.2-remove-kernel-opts/+merge/133424
-- 
https://code.launchpad.net/~jameinel/maas/1.2-remove-kernel-opts/+merge/133424
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~jameinel/maas/1.2-remove-kernel-opts into lp:maas.
=== modified file 'etc/celeryconfig_common.py'
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py	2012-11-08 09:12:44 +0000
+++ src/maasserver/api.py	2012-11-08 09:19:27 +0000
@@ -745,9 +745,22 @@
 
         When a node has been added to MAAS by an admin MAAS user, it is
         ready for allocation to services running on the MAAS.
-        The minimum data required is:
-        architecture=<arch string> (e.g "i386/generic")
-        mac_address=<value>
+<<<<<<< TREE
+        The minimum data required is:
+        architecture=<arch string> (e.g "i386/generic")
+        mac_address=<value>
+=======
+        The minimum data required is:
+        architecture=<arch string> (e.g "i386/generic")
+        mac_address=<value>
+
+        :param architecture: A string containing the architecture type of
+            the node.
+        :param mac_address: The MAC address of the node.
+        :param hostname: A hostname. If not given, one will be generated.
+        :param powertype: A power management type, if applicable (e.g.
+            "virsh", "ipmi").
+>>>>>>> MERGE-SOURCE
         """
         node = create_node(request)
         if request.user.is_superuser:

=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py	2012-11-01 15:13:10 +0000
+++ src/maasserver/forms.py	2012-11-08 09:19:27 +0000
@@ -394,6 +394,7 @@
         node.save()
         for mac in self.cleaned_data['mac_addresses']:
             node.add_mac_address(mac)
+<<<<<<< TREE
         hostname = self.cleaned_data['hostname']
         stripped_hostname = strip_domain(hostname)
         # Generate a hostname for this node if the provided hostname is
@@ -404,6 +405,18 @@
             IP_BASED_HOSTNAME_REGEXP.match(stripped_hostname) != None)
         if generate_hostname:
             node.set_random_hostname()
+=======
+        hostname = self.cleaned_data['hostname']
+        stripped_hostname = strip_domain(hostname)
+        # Generate a hostname for this node if the provided hostname is
+        # IP-based (because this means that this name comes from a DNS
+        # reverse query to the MAAS DNS) or an empty string.
+        generate_hostname = (
+            hostname == "" or
+            IP_BASED_HOSTNAME_REGEXP.match(stripped_hostname) != None)
+        if generate_hostname:
+            node.set_mac_based_hostname(self.cleaned_data['mac_addresses'][0])
+>>>>>>> MERGE-SOURCE
         return node
 
 

=== modified file 'src/maasserver/models/node.py'
=== modified file 'src/maasserver/models/nodegroup.py'
=== modified file 'src/maasserver/templates/maasserver/node_list.html'
--- src/maasserver/templates/maasserver/node_list.html	2012-10-24 12:51:10 +0000
+++ src/maasserver/templates/maasserver/node_list.html	2012-11-08 09:19:27 +0000
@@ -38,8 +38,13 @@
     {% if input_query_error %}
     <p class="form-errors">{{input_query_error}}</p>
     {% endif %}
+<<<<<<< TREE
     {% include "maasserver/nodes_listing.html" with sorting="true" %}
     {% include "maasserver/pagination.html" %}
+=======
+    {% include "maasserver/nodes_listing.html" %}
+    {% include "maasserver/pagination.html" %}
+>>>>>>> MERGE-SOURCE
     <a id="addnode" href="#" class="button right space-top">+ Add node</a>
     <div class="clear"></div>
     <a class="right space-top" href="{% url "enlist-preseed-view" %}">View enlistment preseed</a>

=== modified file 'src/maasserver/testing/factory.py'
=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py	2012-11-08 09:12:44 +0000
+++ src/maasserver/tests/test_api.py	2012-11-08 09:19:27 +0000
@@ -122,11 +122,18 @@
     NodeUserData,
     )
 from metadataserver.nodeinituser import get_node_init_user
+<<<<<<< TREE
 from mock import (
     ANY,
     call,
     Mock,
     )
+=======
+from mock import (
+    ANY,
+    Mock,
+    )
+>>>>>>> MERGE-SOURCE
 from provisioningserver import (
     boot_images,
     kernel_opts,
@@ -4433,6 +4440,7 @@
         self.assertSetEqual(
             {"doc", "handlers", "resources"}, set(description))
         self.assertIsInstance(description["handlers"], list)
+<<<<<<< TREE
 
 
 class TestDescribeAbsoluteURIs(AnonAPITestCase):
@@ -4513,3 +4521,51 @@
         resources = description["resources"]
         self.assertNotEqual([], resources)
         self.assertThat(resources, AllMatch(expected_resource))
+=======
+
+
+class TestDescribeAbsoluteURIs(AnonAPITestCase):
+    """Tests for the `describe` view's URI manipulation."""
+
+    scenarios_schemes = (
+        ("http", dict(scheme="http")),
+        ("https", dict(scheme="https")),
+        )
+
+    scenarios_paths = (
+        ("script-at-root", dict(script_name="", path_info="")),
+        ("script-below-root-1", dict(script_name="/foo/bar", path_info="")),
+        ("script-below-root-2", dict(script_name="/foo", path_info="/bar")),
+        )
+
+    scenarios = multiply_scenarios(
+        scenarios_schemes, scenarios_paths)
+
+    def test_handler_uris_are_absolute(self):
+        server = factory.make_name("server").lower()
+        extra = {
+            "PATH_INFO": self.path_info,
+            "SCRIPT_NAME": self.script_name,
+            "SERVER_NAME": server,
+            "wsgi.url_scheme": self.scheme,
+            }
+        request = RequestFactory().get(
+            "/%s/describe" % factory.make_name("path"), **extra)
+        response = describe(request)
+        self.assertEqual(httplib.OK, response.status_code, response.content)
+        description = json.loads(response.content)
+        expected_uri = AfterPreprocessing(
+            urlparse, MatchesStructure(
+                scheme=Equals(self.scheme), hostname=Equals(server),
+                # The path is always the script name followed by "api/"
+                # because all API calls are within the "api" tree.
+                path=StartsWith(self.script_name + "/api/")))
+        expected_handler = MatchesAny(
+            Is(None), AfterPreprocessing(itemgetter("uri"), expected_uri))
+        expected_resource = MatchesAll(
+            AfterPreprocessing(itemgetter("anon"), expected_handler),
+            AfterPreprocessing(itemgetter("auth"), expected_handler))
+        resources = description["resources"]
+        self.assertNotEqual([], resources)
+        self.assertThat(resources, AllMatch(expected_resource))
+>>>>>>> MERGE-SOURCE

=== modified file 'src/maasserver/tests/test_dhcplease.py'
=== modified file 'src/maasserver/tests/test_forms.py'
--- src/maasserver/tests/test_forms.py	2012-11-01 11:11:48 +0000
+++ src/maasserver/tests/test_forms.py	2012-11-08 09:19:27 +0000
@@ -350,36 +350,81 @@
             after_commissioning_action, node.after_commissioning_action)
         self.assertEqual(power_type, node.power_type)
 
-    def test_AdminNodeForm_refuses_to_update_hostname_on_allocated_node(self):
-        old_name = factory.make_name('old-hostname')
-        new_name = factory.make_name('new-hostname')
-        node = factory.make_node(
-            hostname=old_name, status=NODE_STATUS.ALLOCATED)
-        form = AdminNodeForm(
-            data={
-                'hostname': new_name,
-                'architecture': node.architecture,
-                },
-            instance=node)
-        self.assertFalse(form.is_valid())
-        self.assertEqual(
-            ["Can't change hostname to %s: node is in use." % new_name],
-            form._errors['hostname'])
-
-    def test_AdminNodeForm_accepts_unchanged_hostname_on_allocated_node(self):
-        old_name = factory.make_name('old-hostname')
-        node = factory.make_node(
-            hostname=old_name, status=NODE_STATUS.ALLOCATED)
-        form = AdminNodeForm(
-            data={
-                'hostname': old_name,
-                'architecture': node.architecture,
-            },
-            instance=node)
-        self.assertTrue(form.is_valid(), form._errors)
-        form.save()
-        self.assertEqual(old_name, reload_object(node).hostname)
-
+<<<<<<< TREE
+    def test_AdminNodeForm_refuses_to_update_hostname_on_allocated_node(self):
+        old_name = factory.make_name('old-hostname')
+        new_name = factory.make_name('new-hostname')
+        node = factory.make_node(
+            hostname=old_name, status=NODE_STATUS.ALLOCATED)
+        form = AdminNodeForm(
+            data={
+                'hostname': new_name,
+                'architecture': node.architecture,
+                },
+            instance=node)
+        self.assertFalse(form.is_valid())
+        self.assertEqual(
+            ["Can't change hostname to %s: node is in use." % new_name],
+            form._errors['hostname'])
+
+    def test_AdminNodeForm_accepts_unchanged_hostname_on_allocated_node(self):
+        old_name = factory.make_name('old-hostname')
+        node = factory.make_node(
+            hostname=old_name, status=NODE_STATUS.ALLOCATED)
+        form = AdminNodeForm(
+            data={
+                'hostname': old_name,
+                'architecture': node.architecture,
+            },
+            instance=node)
+        self.assertTrue(form.is_valid(), form._errors)
+        form.save()
+        self.assertEqual(old_name, reload_object(node).hostname)
+
+=======
+    def test_AdminNodeForm_refuses_to_update_hostname_on_allocated_node(self):
+        old_name = factory.make_name('old-hostname')
+        new_name = factory.make_name('new-hostname')
+        node = factory.make_node(
+            hostname=old_name, status=NODE_STATUS.ALLOCATED)
+        form = AdminNodeForm(
+            data={
+                'hostname': new_name,
+                'architecture': node.architecture,
+                },
+            instance=node)
+        self.assertFalse(form.is_valid())
+        self.assertEqual(
+            ["Can't change hostname to %s: node is in use." % new_name],
+            form._errors['hostname'])
+
+    def test_AdminNodeForm_accepts_unchanged_hostname_on_allocated_node(self):
+        old_name = factory.make_name('old-hostname')
+        node = factory.make_node(
+            hostname=old_name, status=NODE_STATUS.ALLOCATED)
+        form = AdminNodeForm(
+            data={
+                'hostname': old_name,
+                'architecture': node.architecture,
+            },
+            instance=node)
+        self.assertTrue(form.is_valid(), form._errors)
+        form.save()
+        self.assertEqual(old_name, reload_object(node).hostname)
+
+    def test_AdminNodeForm_accepts_omitted_hostname_on_allocated_node(self):
+        node = factory.make_node(status=NODE_STATUS.ALLOCATED)
+        old_name = node.hostname
+        form = AdminNodeForm(
+            data={
+                'architecture': node.architecture,
+                },
+            instance=node)
+        self.assertTrue(form.is_valid())
+        form.save()
+        self.assertEqual(old_name, reload_object(node).hostname)
+
+>>>>>>> MERGE-SOURCE
     def test_remove_None_values_removes_None_values_in_dict(self):
         random_input = factory.getRandomString()
         self.assertEqual(

=== modified file 'src/maasserver/tests/test_node.py'
=== modified file 'src/maasserver/tests/test_views_nodes.py'
--- src/maasserver/tests/test_views_nodes.py	2012-11-01 15:59:22 +0000
+++ src/maasserver/tests/test_views_nodes.py	2012-11-08 09:19:27 +0000
@@ -532,6 +532,7 @@
             "//div[@id='nodes']/table//td/a/@href")
         self.assertEqual([node2_link], node_links)
 
+<<<<<<< TREE
     def test_node_list_paginates(self):
         """Node listing is split across multiple pages with links"""
         # Set a very small page size to save creating lots of nodes
@@ -591,6 +592,65 @@
             [(a.text.lower(), a.get("href"))
                 for a in document.xpath("//div[@class='pagination']//a")])
 
+=======
+    def test_node_list_paginates(self):
+        """Node listing is split across multiple pages with links"""
+        # Set a very small page size to save creating lots of nodes
+        page_size = 2
+        self.patch(nodes_views.NodeListView, 'paginate_by', page_size)
+        nodes = [factory.make_node(created="2012-10-12 12:00:%02d" % i)
+            for i in range(page_size * 2 + 1)]
+        # Order node links with newest first as the view is expected to
+        node_links = [reverse('node-view', args=[node.system_id])
+            for node in reversed(nodes)]
+        expr_node_links = XPath("//div[@id='nodes']/table//a/@href")
+        expr_page_anchors = XPath("//div[@class='pagination']//a")
+        # Fetch first page, should link newest two nodes and page 2
+        response = self.client.get(reverse('node-list'))
+        page1 = fromstring(response.content)
+        self.assertEqual(node_links[:page_size], expr_node_links(page1))
+        self.assertEqual([("next", "?page=2"), ("last", "?page=3")],
+            [(a.text.lower(), a.get("href"))
+                for a in expr_page_anchors(page1)])
+        # Fetch second page, should link next nodes and adjacent pages
+        response = self.client.get(reverse('node-list'), {"page": 2})
+        page2 = fromstring(response.content)
+        self.assertEqual(node_links[page_size:page_size * 2],
+            expr_node_links(page2))
+        self.assertEqual([("first", "."), ("previous", "."),
+                ("next", "?page=3"), ("last", "?page=3")],
+            [(a.text.lower(), a.get("href"))
+                for a in expr_page_anchors(page2)])
+        # Fetch third page, should link oldest node and node list page
+        response = self.client.get(reverse('node-list'), {"page": 3})
+        page3 = fromstring(response.content)
+        self.assertEqual(node_links[page_size * 2:], expr_node_links(page3))
+        self.assertEqual([("first", "."), ("previous", "?page=2")],
+            [(a.text.lower(), a.get("href"))
+                for a in expr_page_anchors(page3)])
+
+    def test_node_list_query_paginates(self):
+        """Node list query subset is split across multiple pages with links"""
+        # Set a very small page size to save creating lots of nodes
+        self.patch(nodes_views.NodeListView, 'paginate_by', 2)
+        nodes = [factory.make_node(created="2012-10-12 12:00:%02d" % i)
+            for i in range(10)]
+        tag = factory.make_tag("odd")
+        for node in nodes[::2]:
+            node.tags = [tag]
+        last_node_link = reverse('node-view', args=[nodes[0].system_id])
+        response = self.client.get(reverse('node-list'),
+            {"query": "maas-tags=odd", "page": 3})
+        document = fromstring(response.content)
+        self.assertIn("5 matching nodes", document.xpath("string(//h1)"))
+        self.assertEqual([last_node_link],
+            document.xpath("//div[@id='nodes']/table//a/@href"))
+        self.assertEqual([("first", "?query=maas-tags%3Dodd"),
+                ("previous", "?query=maas-tags%3Dodd&page=2")],
+            [(a.text.lower(), a.get("href"))
+                for a in document.xpath("//div[@class='pagination']//a")])
+
+>>>>>>> MERGE-SOURCE
 
 class NodePreseedViewTest(LoggedInTestCase):
 

=== modified file 'src/maasserver/tests/test_views_settings.py'
=== modified file 'src/maasserver/utils/__init__.py'
--- src/maasserver/utils/__init__.py	2012-11-07 19:00:22 +0000
+++ src/maasserver/utils/__init__.py	2012-11-08 09:19:27 +0000
@@ -84,6 +84,7 @@
     if query is not None:
         url += '?%s' % urlencode(query, doseq=True)
     return url
+<<<<<<< TREE
 
 
 def build_absolute_uri(request, path):
@@ -103,3 +104,27 @@
 def strip_domain(hostname):
     """Return `hostname` with the domain part removed."""
     return hostname.split('.', 1)[0]
+=======
+
+
+def build_absolute_uri(request, path):
+    """Given a full app-relative path, returns an absolute URI.
+
+    The path ordinarily starts with a forward-slash... imagine that you've
+    magically chroot'ed to your Django application; the path is the absolute
+    path to the view within that environment.
+
+    The URI returned uses the request to figure out how to make an absolute
+    URL. This means that the URI returned will use the same IP address or
+    alias that the request came in on.
+    """
+    script_name = request.META["SCRIPT_NAME"]
+    return "%s://%s%s%s" % (
+        "https" if request.is_secure() else "http",
+        request.get_host(), script_name, path)
+
+
+def strip_domain(hostname):
+    """Return `hostname` with the domain part removed."""
+    return hostname.split('.', 1)[0]
+>>>>>>> MERGE-SOURCE

=== modified file 'src/maasserver/utils/tests/test_utils.py'
--- src/maasserver/utils/tests/test_utils.py	2012-11-07 19:00:22 +0000
+++ src/maasserver/utils/tests/test_utils.py	2012-11-08 09:19:27 +0000
@@ -107,6 +107,7 @@
             NODE_STATUS_CHOICES, but_not=[status])
         node.status = another_status
         self.assertEqual(status, get_db_state(node, 'status'))
+<<<<<<< TREE
 
 
 class TestBuildAbsoluteURI(TestCase):
@@ -175,3 +176,80 @@
         inputs = [input for input, _ in input_and_results]
         results = [result for _, result in input_and_results]
         self.assertEqual(results, map(strip_domain, inputs))
+=======
+
+
+class TestBuildAbsoluteURI(TestCase):
+    """Tests for `build_absolute_uri`."""
+
+    def make_request(self, host="example.com", port=80, script_name="",
+                     is_secure=False):
+        """Return a :class:`HttpRequest` with the given parameters."""
+        request = HttpRequest()
+        request.META["SERVER_NAME"] = host
+        request.META["SERVER_PORT"] = port
+        request.META["SCRIPT_NAME"] = script_name
+        request.is_secure = lambda: is_secure
+        return request
+
+    def test_simple(self):
+        request = self.make_request()
+        self.assertEqual(
+            "http://example.com/fred";,
+            build_absolute_uri(request, "/fred"))
+
+    def test_different_port(self):
+        request = self.make_request(port=1234)
+        self.assertEqual(
+            "http://example.com:1234/fred";,
+            build_absolute_uri(request, "/fred"))
+
+    def test_script_name_is_prefixed(self):
+        # The script name is always prefixed to the given path.
+        request = self.make_request(script_name="/foo/bar")
+        self.assertEqual(
+            "http://example.com/foo/bar/fred";,
+            build_absolute_uri(request, "/fred"))
+
+    def test_secure(self):
+        request = self.make_request(port=443, is_secure=True)
+        self.assertEqual(
+            "https://example.com/fred";,
+            build_absolute_uri(request, "/fred"))
+
+    def test_different_port_and_secure(self):
+        request = self.make_request(port=9443, is_secure=True)
+        self.assertEqual(
+            "https://example.com:9443/fred";,
+            build_absolute_uri(request, "/fred"))
+
+    def test_no_leading_forward_slash(self):
+        # No attempt is made to ensure that the given path is separated from
+        # the to-be-prefixed path.
+        request = self.make_request(script_name="/foo")
+        self.assertEqual(
+            "http://example.com/foobar";,
+            build_absolute_uri(request, "bar"))
+
+    def test_preserve_two_leading_slashes(self):
+        # Whilst this shouldn't ordinarily happen, two leading slashes in the
+        # path should be preserved, and not treated specially.
+        request = self.make_request(script_name="//foo")
+        self.assertEqual(
+            "http://example.com//foo/fred";,
+            build_absolute_uri(request, "/fred"))
+
+
+class TestStripDomain(TestCase):
+
+    def test_strip_domain(self):
+        input_and_results = [
+            ('name.domain',  'name'),
+            ('name', 'name'),
+            ('name.domain.what', 'name'),
+            ('name..domain', 'name'),
+            ]
+        inputs = [input for input, _ in input_and_results]
+        results = [result for _, result in input_and_results]
+        self.assertEqual(results, map(strip_domain, inputs))
+>>>>>>> MERGE-SOURCE

=== modified file 'src/maasserver/views/nodes.py'
=== modified file 'src/provisioningserver/boot_images.py'
--- src/provisioningserver/boot_images.py	2012-10-25 07:27:24 +0000
+++ src/provisioningserver/boot_images.py	2012-11-08 09:19:27 +0000
@@ -32,10 +32,14 @@
     )
 from provisioningserver.config import Config
 from provisioningserver.pxe import tftppath
+<<<<<<< TREE
 from provisioningserver.start_cluster_controller import get_cluster_uuid
 
 
 task_logger = get_task_logger(name=__name__)
+=======
+from provisioningserver.start_cluster_controller import get_cluster_uuid
+>>>>>>> MERGE-SOURCE
 
 
 def get_cached_knowledge():

=== modified file 'src/provisioningserver/dns/config.py'
=== modified file 'src/provisioningserver/dns/tests/test_config.py'
=== modified file 'src/provisioningserver/tasks.py'
--- src/provisioningserver/tasks.py	2012-11-07 10:39:19 +0000
+++ src/provisioningserver/tasks.py	2012-11-08 09:19:27 +0000
@@ -369,6 +369,7 @@
                 exc=exc, countdown=UPDATE_NODE_TAGS_RETRY_DELAY)
         else:
             raise
+<<<<<<< TREE
 
 
 # =====================================================================
@@ -382,3 +383,18 @@
         env['http_proxy'] = http_proxy
         env['https_proxy'] = http_proxy
     check_call(['sudo', '-n', 'maas-import-pxe-files'], env=env)
+=======
+
+
+# =====================================================================
+# Image importing-related tasks
+# =====================================================================
+
+@task
+def import_pxe_files(http_proxy=None):
+    env = dict(os.environ)
+    if http_proxy is not None:
+        env['http_proxy'] = http_proxy
+        env['https_proxy'] = http_proxy
+    check_call(['maas-import-pxe-files'], env=env)
+>>>>>>> MERGE-SOURCE

=== modified file 'src/provisioningserver/tests/test_tasks.py'
--- src/provisioningserver/tests/test_tasks.py	2012-11-07 10:39:19 +0000
+++ src/provisioningserver/tests/test_tasks.py	2012-11-08 09:19:27 +0000
@@ -64,7 +64,11 @@
 from provisioningserver.tags import MissingCredentials
 from provisioningserver.tasks import (
     add_new_dhcp_host_map,
+<<<<<<< TREE
     import_boot_images,
+=======
+    import_pxe_files,
+>>>>>>> MERGE-SOURCE
     Omshell,
     power_off,
     power_on,
@@ -536,6 +540,7 @@
         self.assertRaises(
             MissingCredentials, update_node_tags.delay, tag,
             '//node', retry=True)
+<<<<<<< TREE
 
 
 class TestImportPxeFiles(PservTestCase):
@@ -560,3 +565,28 @@
         expected_env = dict(os.environ, http_proxy=proxy, https_proxy=proxy)
         recorder.assert_called_once_with(
             ['sudo', '-n', 'maas-import-pxe-files'], env=expected_env)
+=======
+
+
+class TestImportPxeFiles(PservTestCase):
+
+    def test_import_pxe_files(self):
+        recorder = self.patch(tasks, 'check_call', Mock())
+        import_pxe_files()
+        recorder.assert_called_once_with(['maas-import-pxe-files'], env=ANY)
+        self.assertIsInstance(import_pxe_files, Task)
+
+    def test_import_pxe_files_preserves_environment(self):
+        recorder = self.patch(tasks, 'check_call', Mock())
+        import_pxe_files()
+        recorder.assert_called_once_with(
+            ['maas-import-pxe-files'], env=os.environ)
+
+    def test_import_pxe_files_sets_proxy(self):
+        recorder = self.patch(tasks, 'check_call', Mock())
+        proxy = factory.getRandomString()
+        import_pxe_files(http_proxy=proxy)
+        expected_env = dict(os.environ, http_proxy=proxy, https_proxy=proxy)
+        recorder.assert_called_once_with(
+            ['maas-import-pxe-files'], env=expected_env)
+>>>>>>> MERGE-SOURCE