launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #14481
[Merge] lp:~rvb/maas/enlist-nodegroup-bug-1081695 into lp:maas
Raphaël Badin has proposed merging lp:~rvb/maas/enlist-nodegroup-bug-1081695 into lp:maas.
Commit message:
Auto-detect the nodegroup to which a node should be attached to based on the information on the originating available from the request.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #1081695 in MAAS: "New nodes always get enlisted in the default nodegroup."
https://bugs.launchpad.net/maas/+bug/1081695
For more details, see:
https://code.launchpad.net/~rvb/maas/enlist-nodegroup-bug-1081695/+merge/135690
Turns out the logic to actually fetch to right nodegroup from an IP address was already there… hooking it up in the API code was the only missing part.
= Pre-imp =
This was discussed with the whole team during this morning's stand up call.
--
https://code.launchpad.net/~rvb/maas/enlist-nodegroup-bug-1081695/+merge/135690
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/maas/enlist-nodegroup-bug-1081695 into lp:maas.
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2012-11-21 17:55:05 +0000
+++ src/maasserver/api.py 2012-11-22 13:48:21 +0000
@@ -166,6 +166,7 @@
from maasserver.utils import (
absolute_reverse,
build_absolute_uri,
+ get_origin_ip,
map_enum,
strip_domain,
)
@@ -645,6 +646,14 @@
# assume 'generic'.
altered_query_data['architecture'] += '/generic'
+ if 'nodegroup' not in altered_query_data:
+ # If 'nodegroup' is not explicitely specified, get the origin of the
+ # request to figure out which nodegroup the new node should be
+ # attached to.
+ origin_ip = get_origin_ip(request)
+ if origin_ip is not None:
+ altered_query_data['nodegroup'] = origin_ip
+
Form = get_node_create_form(request.user)
form = Form(altered_query_data)
if form.is_valid():
=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py 2012-11-21 10:24:27 +0000
+++ src/maasserver/tests/test_api.py 2012-11-22 13:48:21 +0000
@@ -126,6 +126,7 @@
ANY,
Mock,
)
+from netaddr import IPNetwork
from provisioningserver import (
boot_images,
kernel_opts,
@@ -740,6 +741,45 @@
self.assertEqual(
expected_hostname, parsed_result.get('hostname'))
+ def test_created_node_nodegroup_is_inferred_from_origin_network(self):
+ network = IPNetwork('192.168.0.3/24')
+ origin_ip = factory.getRandomIPInNetwork(network)
+ NodeGroup.objects.ensure_master()
+ nodegroup = factory.make_node_group(network=network)
+ response = self.client.post(
+ self.get_uri('nodes/'),
+ data={
+ 'op': 'new',
+ 'hostname': factory.make_name('hostname'),
+ 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
+ 'after_commissioning_action':
+ NODE_AFTER_COMMISSIONING_ACTION.DEFAULT,
+ 'mac_addresses': [factory.getRandomMACAddress()],
+ },
+ HTTP_HOST=origin_ip + ':90')
+ self.assertEqual(httplib.OK, response.status_code, response.content)
+ parsed_result = json.loads(response.content)
+ node = Node.objects.get(system_id=parsed_result.get('system_id'))
+ self.assertEqual(nodegroup, node.nodegroup)
+
+ def test_created_node_uses_default_nodegroup_if_origin_not_found(self):
+ unknown_host = factory.make_name('host')
+ response = self.client.post(
+ self.get_uri('nodes/'),
+ data={
+ 'op': 'new',
+ 'hostname': factory.make_name('hostname'),
+ 'architecture': factory.getRandomChoice(ARCHITECTURE_CHOICES),
+ 'after_commissioning_action':
+ NODE_AFTER_COMMISSIONING_ACTION.DEFAULT,
+ 'mac_addresses': [factory.getRandomMACAddress()],
+ },
+ HTTP_HOST=unknown_host)
+ self.assertEqual(httplib.OK, response.status_code, response.content)
+ parsed_result = json.loads(response.content)
+ node = Node.objects.get(system_id=parsed_result.get('system_id'))
+ self.assertEqual(NodeGroup.objects.ensure_master(), node.nodegroup)
+
class NonAdminEnlistmentAPITest(APIv10TestMixin, MultipleUsersScenarios,
TestCase):
=== modified file 'src/maasserver/utils/__init__.py'
--- src/maasserver/utils/__init__.py 2012-11-08 10:11:03 +0000
+++ src/maasserver/utils/__init__.py 2012-11-22 13:48:21 +0000
@@ -14,11 +14,13 @@
'absolute_reverse',
'build_absolute_uri',
'get_db_state',
+ 'get_origin_ip',
'ignore_unused',
'map_enum',
'strip_domain',
]
+import socket
from urllib import urlencode
from urlparse import urljoin
@@ -103,3 +105,16 @@
def strip_domain(hostname):
"""Return `hostname` with the domain part removed."""
return hostname.split('.', 1)[0]
+
+
+def get_origin_ip(request):
+ """Return the IP address of the originating host of the request.
+
+ Return the IP address obtained by resolving the host given by
+ request.get_host().
+ """
+ host = request.get_host().split(':')[0]
+ try:
+ return socket.gethostbyname(host)
+ except socket.error:
+ return None
=== modified file 'src/maasserver/utils/tests/test_utils.py'
--- src/maasserver/utils/tests/test_utils.py 2012-11-08 10:11:03 +0000
+++ src/maasserver/utils/tests/test_utils.py 2012-11-22 13:48:21 +0000
@@ -12,11 +12,13 @@
__metaclass__ = type
__all__ = []
+import socket
from urllib import urlencode
from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import HttpRequest
+from django.test.client import RequestFactory
from maasserver.enum import NODE_STATUS_CHOICES
from maasserver.testing.factory import factory
from maasserver.testing.testcase import TestCase as DjangoTestCase
@@ -24,10 +26,15 @@
absolute_reverse,
build_absolute_uri,
get_db_state,
+ get_origin_ip,
map_enum,
strip_domain,
)
from maastesting.testcase import TestCase
+from mock import (
+ call,
+ Mock,
+ )
class TestEnum(TestCase):
@@ -175,3 +182,38 @@
inputs = [input for input, _ in input_and_results]
results = [result for _, result in input_and_results]
self.assertEqual(results, map(strip_domain, inputs))
+
+
+class TestGetOriginIP(TestCase):
+
+ def get_request(self, server_name, server_port='80'):
+ return RequestFactory().post(
+ '/', SERVER_NAME=server_name, SERVER_PORT=server_port)
+
+ def test_get_origin_ip_returns_ip(self):
+ ip = factory.getRandomIPAddress()
+ request = self.get_request(ip)
+ self.assertEqual(ip, get_origin_ip(request))
+
+ def test_get_origin_ip_strips_port(self):
+ ip = factory.getRandomIPAddress()
+ request = self.get_request(ip, '8888')
+ self.assertEqual(ip, get_origin_ip(request))
+
+ def test_get_origin_ip_resolves_hostname(self):
+ ip = factory.getRandomIPAddress()
+ hostname = factory.make_name('hostname')
+ request = self.get_request(hostname)
+ resolver = self.patch(socket, 'gethostbyname', Mock(return_value=ip))
+ self.assertEqual(
+ (ip, call(hostname)),
+ (get_origin_ip(request), resolver.call_args))
+
+ def test_get_origin_ip_returns_None_if_hostname_cannot_get_resolved(self):
+ hostname = factory.make_name('hostname')
+ request = self.get_request(hostname)
+ resolver = self.patch(
+ socket, 'gethostbyname', Mock(side_effect=socket.error))
+ self.assertEqual(
+ (None, call(hostname)),
+ (get_origin_ip(request), resolver.call_args))