launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #14091
[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