Merge lp:~mpontillo/maas/ip-allocation-bugs-1.9 into lp:maas/1.9

Proposed by Mike Pontillo
Status: Merged
Approved by: Mike Pontillo
Approved revision: no longer in the source branch.
Merged at revision: 4520
Proposed branch: lp:~mpontillo/maas/ip-allocation-bugs-1.9
Merge into: lp:maas/1.9
Diff against target: 935 lines (+533/-60)
13 files modified
.idea/encodings.xml (+3/-1)
.idea/vcs.xml (+0/-6)
src/maasserver/api/devices.py (+17/-7)
src/maasserver/api/tests/test_devices.py (+125/-11)
src/maasserver/models/interface.py (+96/-18)
src/maasserver/models/node.py (+5/-1)
src/maasserver/models/nodegroupinterface.py (+37/-3)
src/maasserver/models/staticipaddress.py (+9/-4)
src/maasserver/models/subnet.py (+8/-1)
src/maasserver/models/tests/test_interface.py (+114/-4)
src/maasserver/models/tests/test_nodegroupinterface.py (+35/-0)
src/maasserver/testing/factory.py (+14/-4)
utilities/remote-reinstall (+70/-0)
To merge this branch: bzr merge lp:~mpontillo/maas/ip-allocation-bugs-1.9
Reviewer Review Type Date Requested Status
Blake Rouse (community) Approve
Review via email: mp+278925@code.launchpad.net

Commit message

Clean up the claiming of AUTO IP addresses.

 * In the case of an UNMANAGED subnet which could not be linked to its NodeGroupInterface at migration time (for example, due to the lack of having a subnet mask), add a query to check if any NodeGroupInterface has static or dynamic ranges within the subnet we are about to allocate for.
 * Throw an error if a NodeGroupInterface being deployed with an AUTO IP address has a dynamic range without a static range. (this means the user needs to define a static range, or select a different IP allocation method.) Note that subnets can still be fully MAAS-managed if no dynamic range is defined.
 * Throw an error if the linked Subnet for an AUTO IP address cannot be found. (this should not happen, but if it does, we want a nicer error.)
 * Run the algorithm to determine ancillary IP addresses on a subnet before handing out an AUTO IP address on a managed or unmanaged subnet. (this fixes the issue where a known router address will be used on a fully unmanaged network)
 * Add logging in maas.log for when an AUTO IP address is allocated.
 * Fix typo in function name (dyanamic -> dynamic)
 * Add a utility script (remote-reinstall) to easily overwrite an installed version of MAAS on a remote host with a development version of MAAS. Requires SSH access to the root account on that host. (This greatly speeds development when iterative manual testing is required.)

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

I'm going to put this up for review feedback even though I haven't done the unit tests, because I've spent most of the afternoon manually testing and iterating on this.

This will have unit tests before I land it.

Revision history for this message
Mike Pontillo (mpontillo) wrote :

Also, I plan to test and review this on 1.9 first. But I will merge and land this on trunk before landing this on 1.9.

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. Just a few comments that need looking at. Please change the WebUI adjustment before landing.

Also make sure that you land this in trunk as well. We need to be very sure that every branch we land in 1.9 without going through trunk first also lands in trunk. Without it, it means a regression in the next release of MAAS. I would prefer to see this land in trunk first.

review: Approve
Revision history for this message
Blake Rouse (blake-rouse) wrote :

Ah just hit me right after I set it to approved. You have not added any tests, so this needs to have unit tests before this can land. Silly me.

review: Needs Fixing
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Thanks for the review. Yeah, I'll get those tests in so this can land. (but first I want to finish triaging that juju issue)

Revision history for this message
Mike Pontillo (mpontillo) wrote :

Blake, I've added some unit tests. I think this is ready for another look. Thanks!

Revision history for this message
Blake Rouse (blake-rouse) wrote :

Looks good. Don't forget to land this in trunk as well.

review: Approve
Revision history for this message
Mike Pontillo (mpontillo) wrote :

It looks like the juju bug may need further triage work; I'm going to land this now so I can base any additional fixes on top of this.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.idea/encodings.xml'
--- .idea/encodings.xml 2015-05-14 04:26:55 +0000
+++ .idea/encodings.xml 2015-12-03 19:55:35 +0000
@@ -1,4 +1,6 @@
1<?xml version="1.0" encoding="UTF-8"?>1<?xml version="1.0" encoding="UTF-8"?>
2<project version="4">2<project version="4">
3 <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />3 <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
4 <file url="PROJECT" charset="UTF-8" />
5 </component>
4</project>6</project>
5\ No newline at end of file7\ No newline at end of file
68
=== removed file '.idea/vcs.xml'
--- .idea/vcs.xml 2015-05-14 04:26:55 +0000
+++ .idea/vcs.xml 1970-01-01 00:00:00 +0000
@@ -1,6 +0,0 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<project version="4">
3 <component name="VcsDirectoryMappings">
4 <mapping directory="" vcs="" />
5 </component>
6</project>
7\ No newline at end of file0\ No newline at end of file
81
=== modified file 'src/maasserver/api/devices.py'
--- src/maasserver/api/devices.py 2015-09-10 18:28:45 +0000
+++ src/maasserver/api/devices.py 2015-12-03 19:55:35 +0000
@@ -29,6 +29,7 @@
29from maasserver.exceptions import (29from maasserver.exceptions import (
30 MAASAPIBadRequest,30 MAASAPIBadRequest,
31 MAASAPIValidationError,31 MAASAPIValidationError,
32 StaticIPAddressExhaustion,
32)33)
33from maasserver.fields import MAC_RE34from maasserver.fields import MAC_RE
34from maasserver.forms import (35from maasserver.forms import (
@@ -169,7 +170,7 @@
169 Returns 400 if the mac_address is not found on the device.170 Returns 400 if the mac_address is not found on the device.
170 Returns 503 if there are not enough IPs left on the cluster interface171 Returns 503 if there are not enough IPs left on the cluster interface
171 to which the mac_address is linked.172 to which the mac_address is linked.
172 Returns 503 if the requested_address falls in a dynamic range.173 Returns 503 if the interface does not have an associated subnet.
173 Returns 503 if the requested_address falls in a dynamic range.174 Returns 503 if the requested_address falls in a dynamic range.
174 Returns 503 if the requested_address is already allocated.175 Returns 503 if the requested_address is already allocated.
175 """176 """
@@ -192,8 +193,7 @@
192 "mac_address %s not found on the device" % raw_mac)193 "mac_address %s not found on the device" % raw_mac)
193 requested_address = request.POST.get('requested_address', None)194 requested_address = request.POST.get('requested_address', None)
194 if requested_address is None:195 if requested_address is None:
195 sticky_ips = interface.claim_static_ips(196 sticky_ips = interface.claim_static_ips()
196 requested_address=requested_address)
197 else:197 else:
198 subnet = Subnet.objects.get_best_subnet_for_ip(198 subnet = Subnet.objects.get_best_subnet_for_ip(
199 requested_address)199 requested_address)
@@ -203,9 +203,16 @@
203 ip_address=requested_address),203 ip_address=requested_address),
204 ]204 ]
205205
206 maaslog.info(206 if len(sticky_ips) == 0:
207 "%s: Sticky IP address(es) allocated: %s", device.hostname,207 raise StaticIPAddressExhaustion(
208 ', '.join(allocation.ip for allocation in sticky_ips))208 "%s: An IP address could not be claimed at this time. "
209 "Check your subnet ranges and utilization and try again." %
210 device.hostname)
211 else:
212 maaslog.info(
213 "%s: Sticky IP address(es) allocated: %s", device.hostname,
214 ', '.join(allocation.ip for allocation in sticky_ips))
215
209 return device216 return device
210217
211 @operation(idempotent=False)218 @operation(idempotent=False)
@@ -272,7 +279,10 @@
272 form = DeviceWithMACsForm(data=request.data, request=request)279 form = DeviceWithMACsForm(data=request.data, request=request)
273 if form.is_valid():280 if form.is_valid():
274 device = form.save()281 device = form.save()
275 maaslog.info("%s: Added new device", device.hostname)282 parent = device.parent
283 maaslog.info(
284 "%s: Added new device%s", device.hostname,
285 "" if not parent else " (parent: %s)" % parent.hostname)
276 return device286 return device
277 else:287 else:
278 raise MAASAPIValidationError(form.errors)288 raise MAASAPIValidationError(form.errors)
279289
=== modified file 'src/maasserver/api/tests/test_devices.py'
--- src/maasserver/api/tests/test_devices.py 2015-09-10 18:28:45 +0000
+++ src/maasserver/api/tests/test_devices.py 2015-12-03 19:55:35 +0000
@@ -24,8 +24,12 @@
24 INTERFACE_TYPE,24 INTERFACE_TYPE,
25 IPADDRESS_TYPE,25 IPADDRESS_TYPE,
26 NODE_STATUS,26 NODE_STATUS,
27 NODEGROUP_STATUS,
28 NODEGROUPINTERFACE_MANAGEMENT,
27)29)
28from maasserver.models import (30from maasserver.models import (
31 Device,
32 Interface,
29 interface as interface_module,33 interface as interface_module,
30 Node,34 Node,
31 NodeGroup,35 NodeGroup,
@@ -37,6 +41,7 @@
37)41)
38from maasserver.testing.factory import factory42from maasserver.testing.factory import factory
39from maasserver.testing.orm import reload_object43from maasserver.testing.orm import reload_object
44from mock import patch
40from testtools.matchers import (45from testtools.matchers import (
41 HasLength,46 HasLength,
42 Not,47 Not,
@@ -285,17 +290,93 @@
285class TestClaimStickyIpAddressAPI(APITestCase):290class TestClaimStickyIpAddressAPI(APITestCase):
286 """Tests for /api/1.0/devices/?op=claim_sticky_ip_address."""291 """Tests for /api/1.0/devices/?op=claim_sticky_ip_address."""
287292
288 def test__claims_ip_address_from_cluster_interface(self):293 def test__claims_ip_address_from_cluster_interface_static_range(self):
289 parent = factory.make_Node_with_Interface_on_Subnet()294 ng = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
290 device = factory.make_Node(295 ngi = factory.make_NodeGroupInterface(
291 installable=False, parent=parent, interface=True,296 ng, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
292 disable_ipv4=False, owner=self.logged_in_user)297 parent = factory.make_Node_with_Interface_on_Subnet(
293 # Silence 'update_host_maps'.298 nodegroup=ng, subnet=ngi.subnet)
294 self.patch_autospec(interface_module, "update_host_maps")299 device = factory.make_Node(
295 response = self.client.post(300 installable=False, parent=parent, interface=True,
296 get_device_uri(device), {'op': 'claim_sticky_ip_address'})301 disable_ipv4=False, owner=self.logged_in_user)
297 self.assertEqual(httplib.OK, response.status_code, response.content)302 # Silence 'update_host_maps'.
298 parsed_device = json.loads(response.content)303 self.patch_autospec(interface_module, "update_host_maps")
304 response = self.client.post(
305 get_device_uri(device), {'op': 'claim_sticky_ip_address'})
306 self.assertEqual(httplib.OK, response.status_code, response.content)
307 parsed_device = json.loads(response.content)
308 [returned_ip] = parsed_device["ip_addresses"]
309 static_ip = StaticIPAddress.objects.filter(ip=returned_ip).first()
310 self.assertIsNotNone(static_ip)
311 self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type)
312
313 def test__claims_ip_address_from_unmanaged_cluster_interface(self):
314 ng = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
315 ngi = factory.make_NodeGroupInterface(
316 ng, management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED)
317 parent = factory.make_Node_with_Interface_on_Subnet(
318 nodegroup=ng, subnet=ngi.subnet)
319 device = factory.make_Node(
320 installable=False, parent=parent, interface=True,
321 disable_ipv4=False, owner=self.logged_in_user)
322 # Silence 'update_host_maps'.
323 self.patch_autospec(interface_module, "update_host_maps")
324 response = self.client.post(
325 get_device_uri(device), {'op': 'claim_sticky_ip_address'})
326 self.assertEqual(httplib.OK, response.status_code, response.content)
327 parsed_device = json.loads(response.content)
328 [returned_ip] = parsed_device["ip_addresses"]
329 static_ip = StaticIPAddress.objects.filter(ip=returned_ip).first()
330 self.assertIsNotNone(static_ip)
331 self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type)
332
333 def test__claims_ip_address_from_detached_cluster_interface(self):
334 ng = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
335 ngi = factory.make_NodeGroupInterface(
336 ng, management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED)
337 subnet = ngi.subnet
338 ngi.subnet = None
339 ngi.save()
340 parent = factory.make_Node_with_Interface_on_Subnet(
341 nodegroup=ng, subnet=subnet, unmanaged=True)
342 device = factory.make_Node(
343 installable=False, parent=parent, interface=True,
344 disable_ipv4=False, owner=self.logged_in_user)
345 # Silence 'update_host_maps'.
346 self.patch_autospec(interface_module, "update_host_maps")
347 response = self.client.post(
348 get_device_uri(device), {'op': 'claim_sticky_ip_address'})
349 self.assertEqual(httplib.OK, response.status_code, response.content)
350 parsed_device = json.loads(response.content)
351 [returned_ip] = parsed_device["ip_addresses"]
352 static_ip = StaticIPAddress.objects.filter(ip=returned_ip).first()
353 self.assertIsNotNone(static_ip)
354 self.assertEquals(IPADDRESS_TYPE.STICKY, static_ip.alloc_type)
355
356 def test__claims_ip_address_after_devices_new(self):
357 ng = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
358 ngi = factory.make_NodeGroupInterface(
359 ng, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS)
360 parent = factory.make_Node_with_Interface_on_Subnet(
361 nodegroup=ng, subnet=ngi.subnet)
362 # Run 'devices new', as a sanity check to ensure the object is created
363 # the same way as it is when juju does it.
364 self.client.post(
365 reverse('devices_handler'),
366 {
367 'op': 'new',
368 'hostname': "lxc-1",
369 'mac_addresses': "01:02:03:04:05:06",
370 'parent': parent.system_id,
371 })
372 # Silence 'update_host_maps'.
373 device = Device.objects.first()
374 self.patch_autospec(interface_module, "update_host_maps")
375 response = self.client.post(
376 get_device_uri(device), {'op': 'claim_sticky_ip_address'})
377 self.assertEqual(httplib.OK, response.status_code, response.content)
378 parsed_device = json.loads(response.content)
379 # import pdb; pdb.set_trace()
299 [returned_ip] = parsed_device["ip_addresses"]380 [returned_ip] = parsed_device["ip_addresses"]
300 static_ip = StaticIPAddress.objects.filter(ip=returned_ip).first()381 static_ip = StaticIPAddress.objects.filter(ip=returned_ip).first()
301 self.assertIsNotNone(static_ip)382 self.assertIsNotNone(static_ip)
@@ -333,6 +414,39 @@
333 (returned_ip, returned_ip, given_ip.alloc_type)414 (returned_ip, returned_ip, given_ip.alloc_type)
334 )415 )
335416
417 def test_503_if_no_subnet_found(self):
418 device = factory.make_Node(
419 installable=False, interface=True, disable_ipv4=False,
420 owner=self.logged_in_user)
421 # Silence 'update_host_maps'.
422 self.patch_autospec(interface_module, "update_host_maps")
423 response = self.client.post(
424 get_device_uri(device),
425 {
426 'op': 'claim_sticky_ip_address',
427 })
428 self.assertEqual(
429 httplib.SERVICE_UNAVAILABLE, response.status_code,
430 response.content)
431
432 @patch.object(Interface, 'claim_static_ips')
433 def test_503_if_no_ip_found(self, claim_static_ips):
434 claim_static_ips.side_effect = [list()]
435
436 device = factory.make_Node(
437 installable=False, interface=True, disable_ipv4=False,
438 owner=self.logged_in_user)
439 # Silence 'update_host_maps'.
440 self.patch_autospec(interface_module, "update_host_maps")
441 response = self.client.post(
442 get_device_uri(device),
443 {
444 'op': 'claim_sticky_ip_address',
445 })
446 self.assertEqual(
447 httplib.SERVICE_UNAVAILABLE, response.status_code,
448 response.content)
449
336 def test_creates_ip_for_specific_mac(self):450 def test_creates_ip_for_specific_mac(self):
337 requested_address = factory.make_ip_address()451 requested_address = factory.make_ip_address()
338 device = factory.make_Node(452 device = factory.make_Node(
339453
=== modified file 'src/maasserver/models/interface.py'
--- src/maasserver/models/interface.py 2015-11-11 01:11:12 +0000
+++ src/maasserver/models/interface.py 2015-12-03 19:55:35 +0000
@@ -52,6 +52,7 @@
52 NODEGROUPINTERFACE_MANAGEMENT,52 NODEGROUPINTERFACE_MANAGEMENT,
53)53)
54from maasserver.exceptions import (54from maasserver.exceptions import (
55 StaticIPAddressExhaustion,
55 StaticIPAddressOutOfRange,56 StaticIPAddressOutOfRange,
56 StaticIPAddressUnavailable,57 StaticIPAddressUnavailable,
57)58)
@@ -350,6 +351,13 @@
350 def get_node(self):351 def get_node(self):
351 return self.node352 return self.node
352353
354 def get_log_string(self):
355 hostname = "<unknown-node>"
356 node = self.get_node()
357 if node is not None:
358 hostname = node.hostname
359 return "%s on %s" % (self.get_name(), hostname)
360
353 def get_name(self):361 def get_name(self):
354 return self.name362 return self.name
355363
@@ -879,6 +887,10 @@
879 be identified then its just set to DHCP.887 be identified then its just set to DHCP.
880 """888 """
881 found_subnet = None889 found_subnet = None
890 # XXX mpontillo 2015-11-29: since we tend to dump a large number of
891 # subnets into the default VLAN, this assumption might be incorrect in
892 # many cases, leading to interfaces being configured as AUTO when
893 # they should be configured as DHCP.
882 for subnet in self.vlan.subnet_set.all():894 for subnet in self.vlan.subnet_set.all():
883 ngi = subnet.get_managed_cluster_interface()895 ngi = subnet.get_managed_cluster_interface()
884 if ngi is not None:896 if ngi is not None:
@@ -1109,44 +1121,92 @@
1109 auto_ip, exclude_addresses)1121 auto_ip, exclude_addresses)
1110 if ngi is not None:1122 if ngi is not None:
1111 affected_nodegroups.add(ngi.nodegroup)1123 affected_nodegroups.add(ngi.nodegroup)
1112 assigned_addresses.append(assigned_ip)1124 if assigned_ip is not None:
1113 exclude_addresses.add(unicode(assigned_ip.ip))1125 assigned_addresses.append(assigned_ip)
1126 exclude_addresses.add(unicode(assigned_ip.ip))
1114 self._update_dns_zones(affected_nodegroups)1127 self._update_dns_zones(affected_nodegroups)
1115 return assigned_addresses1128 return assigned_addresses
11161129
1117 def _claim_auto_ip(self, auto_ip, exclude_addresses=[]):1130 def _claim_auto_ip(self, auto_ip, exclude_addresses=[]):
1118 """Claim an IP address for the `auto_ip`."""1131 """Claim an IP address for the `auto_ip`.
1132
1133 :returns:NodeGroupInterface, new_ip_address
1134 """
1119 # Check if already has a hostmap allocated for this MAC address.1135 # Check if already has a hostmap allocated for this MAC address.
1120 subnet = auto_ip.subnet1136 subnet = auto_ip.subnet
1137 if subnet is None:
1138 maaslog.error(
1139 "Could not find subnet for interface %s." %
1140 (self.get_log_string()))
1141 raise StaticIPAddressUnavailable(
1142 "Automatic IP address cannot be configured on interface %s "
1143 "without an associated subnet." % self.get_name())
1144
1121 ngi = subnet.get_managed_cluster_interface()1145 ngi = subnet.get_managed_cluster_interface()
1146 if ngi is None:
1147 # Couldn't find a managed cluster interface for this node. So look
1148 # for any interface (must be an UNMANAGED interface, since any
1149 # managed NodeGroupInterface MUST have a Subnet link) whose
1150 # static or dynamic range is within the given subnet.
1151 ngi = NodeGroupInterface.objects.get_by_managed_range_for_subnet(
1152 subnet)
1153
1154 has_existing_mapping = False
1155 has_static_range = False
1156 has_dynamic_range = False
1157
1122 if ngi is not None:1158 if ngi is not None:
1123 has_allocations = self._has_static_allocation_on_cluster(1159 has_existing_mapping = self._has_static_allocation_on_cluster(
1124 ngi.nodegroup, get_subnet_family(subnet))1160 ngi.nodegroup, get_subnet_family(subnet))
1125 else:1161 has_static_range = ngi.has_static_ip_range()
1126 has_allocations = False1162 has_dynamic_range = ngi.has_dynamic_ip_range()
11271163
1128 # Create a new AUTO IP.1164 if not has_static_range and has_dynamic_range:
1129 if ngi is not None:1165 # This means we found a matching NodeGroupInterface, but only its
1166 # dynamic range is defined. Since a dynamic range is defined, that
1167 # means this subnet is NOT managed by MAAS (or it's misconfigured),
1168 # so we cannot just hand out a random IP address and risk a
1169 # duplicate IP address.
1170 maaslog.error(
1171 "Found matching NodeGroupInterface, but no static range has "
1172 "been defined for %s. (did you mean to configure DHCP?) " %
1173 (self.get_log_string()))
1174 raise StaticIPAddressUnavailable(
1175 "Cluster interface for %s only has a dynamic range. Configure "
1176 "a static range, or reconfigure the interface." %
1177 (self.get_name()))
1178
1179 if has_static_range:
1180 # Allocate a new AUTO address from the static range.
1130 network = ngi.network1181 network = ngi.network
1131 static_ip_range_low = ngi.static_ip_range_low1182 static_ip_range_low = ngi.static_ip_range_low
1132 static_ip_range_high = ngi.static_ip_range_high1183 static_ip_range_high = ngi.static_ip_range_high
1133 else:1184 else:
1185 # We either found a NodeGroupInterface with no static or dynamic
1186 # range, or we have a Subnet not associated with a
1187 # NodeGroupInterface. This implies that it's okay to assign any
1188 # unused IP address on the subnet.
1134 network = subnet.get_ipnetwork()1189 network = subnet.get_ipnetwork()
1135 static_ip_range_low, static_ip_range_high = (1190 static_ip_range_low, static_ip_range_high = (
1136 get_first_and_last_usable_host_in_network(network))1191 get_first_and_last_usable_host_in_network(network))
1192 in_use_ipset = subnet.get_ipranges_in_use()
1137 new_ip = StaticIPAddress.objects.allocate_new(1193 new_ip = StaticIPAddress.objects.allocate_new(
1138 network, static_ip_range_low, static_ip_range_high,1194 network, static_ip_range_low, static_ip_range_high,
1139 None, None, alloc_type=IPADDRESS_TYPE.AUTO,1195 None, None, alloc_type=IPADDRESS_TYPE.AUTO,
1140 subnet=subnet, exclude_addresses=exclude_addresses)1196 subnet=subnet, exclude_addresses=exclude_addresses,
1197 in_use_ipset=in_use_ipset)
1141 self.ip_addresses.add(new_ip)1198 self.ip_addresses.add(new_ip)
1199 maaslog.info("Allocated automatic%s IP address %s for %s." % (
1200 " static" if has_static_range else "", new_ip.ip,
1201 self.get_log_string()))
11421202
1143 # Update the hostmap for the new IP address if needed.1203 if ngi is not None and not has_existing_mapping:
1144 if ngi is not None and not has_allocations:1204 # Update DHCP (if needed).
1145 self._update_host_maps(ngi.nodegroup, new_ip)1205 self._update_host_maps(ngi.nodegroup, new_ip)
11461206
1147 # Made it this far then the AUTO IP address has been assigned and the1207 # If we made it this far, then the AUTO IP address has been assigned
1148 # hostmap has been updated if needed. We can now remove the original1208 # and the hostmap has been updated if needed. We can now remove the
1149 # empty AUTO IP address.1209 # original empty AUTO IP address.
1150 auto_ip.delete()1210 auto_ip.delete()
1151 return ngi, new_ip1211 return ngi, new_ip
11521212
@@ -1257,6 +1317,23 @@
1257 if ngi is not None and ngi.subnet is not None:1317 if ngi is not None and ngi.subnet is not None:
1258 discovered_subnets.append(ngi.subnet)1318 discovered_subnets.append(ngi.subnet)
12591319
1320 if len(discovered_subnets) == 0:
1321 node = self.node
1322 if parent is not None:
1323 node = parent
1324 if node is None:
1325 hostname = "<unknown>"
1326 else:
1327 hostname = "'%s'" % node.hostname
1328 log_string = (
1329 "%s: Attempted to claim a static IP address, but no "
1330 "associated subnet could be found. (Recommission node %s "
1331 "in order for MAAS to discover the subnet.)" %
1332 (self.get_log_string(), hostname)
1333 )
1334 maaslog.warning(log_string)
1335 raise StaticIPAddressExhaustion(log_string)
1336
1260 if requested_address is None:1337 if requested_address is None:
1261 # No requested address so claim a STATIC IP on all DISCOVERED1338 # No requested address so claim a STATIC IP on all DISCOVERED
1262 # subnets for this interface.1339 # subnets for this interface.
@@ -1271,8 +1348,8 @@
1271 # No valid subnets could be used to claim a STATIC IP address.1348 # No valid subnets could be used to claim a STATIC IP address.
1272 if not any(static_ips):1349 if not any(static_ips):
1273 maaslog.error(1350 maaslog.error(
1274 "Tried to allocate an IP to interface <%s>, but its "1351 "Attempted sticky IP allocation failed for %s: could not "
1275 "cluster interface is not known.", unicode(self))1352 "find a cluster interface.", self.get_log_string())
1276 return []1353 return []
1277 else:1354 else:
1278 return static_ips1355 return static_ips
@@ -1294,7 +1371,8 @@
1294 else:1371 else:
1295 raise StaticIPAddressOutOfRange(1372 raise StaticIPAddressOutOfRange(
1296 "requested_address '%s' is not in a managed subnet for "1373 "requested_address '%s' is not in a managed subnet for "
1297 "this interface '%s'" % (requested_address, self.name))1374 "interface '%s'." % (
1375 requested_address, self.get_name()))
12981376
1299 def _get_parent_node(self):1377 def _get_parent_node(self):
1300 """Return the parent node for this interface, if it exists (and this1378 """Return the parent node for this interface, if it exists (and this
13011379
=== modified file 'src/maasserver/models/node.py'
--- src/maasserver/models/node.py 2015-11-21 02:16:17 +0000
+++ src/maasserver/models/node.py 2015-12-03 19:55:35 +0000
@@ -2530,7 +2530,9 @@
2530 # You can't start a node you don't own unless you're an admin.2530 # You can't start a node you don't own unless you're an admin.
2531 raise PermissionDenied()2531 raise PermissionDenied()
2532 event = EVENT_TYPES.REQUEST_NODE_START2532 event = EVENT_TYPES.REQUEST_NODE_START
2533 # if status is ALLOCATED, this start is actually for a deployment2533 # If status is ALLOCATED, this start is actually for a deployment.
2534 # (Note: this is true even when nodes are being deployed from READY
2535 # state. See node_action.py; the node is acquired and then started.)
2534 if self.status == NODE_STATUS.ALLOCATED:2536 if self.status == NODE_STATUS.ALLOCATED:
2535 event = EVENT_TYPES.REQUEST_NODE_START_DEPLOYMENT2537 event = EVENT_TYPES.REQUEST_NODE_START_DEPLOYMENT
2536 self._register_request_event(2538 self._register_request_event(
@@ -2575,6 +2577,8 @@
25752577
2576 if self.status == NODE_STATUS.ALLOCATED:2578 if self.status == NODE_STATUS.ALLOCATED:
2577 # Claim AUTO IP addresses for the node if it's ALLOCATED.2579 # Claim AUTO IP addresses for the node if it's ALLOCATED.
2580 # The current state being ALLOCATED is our indication that the node
2581 # is being deployed for the first time.
2578 self.claim_auto_ips()2582 self.claim_auto_ips()
2579 transition_monitor = (2583 transition_monitor = (
2580 TransitionMonitor.fromNode(self)2584 TransitionMonitor.fromNode(self)
25812585
=== modified file 'src/maasserver/models/nodegroupinterface.py'
--- src/maasserver/models/nodegroupinterface.py 2015-10-29 19:10:30 +0000
+++ src/maasserver/models/nodegroupinterface.py 2015-12-03 19:55:35 +0000
@@ -172,6 +172,40 @@
172 else:172 else:
173 return None173 return None
174174
175 find_by_managed_range_for_subnet_query = dedent("""\
176 SELECT ngi.*
177 FROM
178 maasserver_subnet AS subnet,
179 maasserver_nodegroupinterface AS ngi,
180 maasserver_nodegroup AS ng
181 WHERE
182 ngi.nodegroup_id = ng.id AND
183 ng.status = 1 AND /* NodeGroup must be ENABLED */
184 ((inet(ngi.ip_range_low) << network(subnet.cidr) AND
185 inet(ngi.ip_range_high) << network(subnet.cidr))
186 OR (inet(ngi.static_ip_range_low) << network(subnet.cidr) AND
187 inet(ngi.static_ip_range_high) << network(subnet.cidr)))
188 AND subnet.id = %s
189 /* Prefer static ranges, since that's how we'll allocate addresses. */
190 ORDER BY ngi.static_ip_range_low DESC NULLS LAST, ngi.id
191 """)
192
193 def get_by_managed_range_for_subnet(self, subnet):
194 """Return the first interface that could contain `address` in its
195 dynamic or static range. (Prefer interfaces static ranges.)
196 """
197 # Circular imports
198 from maasserver.models import Subnet
199 assert isinstance(subnet, Subnet), (
200 "%r is not a Subnet" % (subnet,))
201 interfaces = self.raw(
202 self.find_by_managed_range_for_subnet_query + " LIMIT 1",
203 [subnet.id])
204 for interface in interfaces:
205 return interface # This is stable because the query is ordered.
206 else:
207 return None
208
175209
176def get_default_vlan():210def get_default_vlan():
177 from maasserver.models.vlan import VLAN211 from maasserver.models.vlan import VLAN
@@ -437,7 +471,7 @@
437 exclude = []471 exclude = []
438 self.check_for_network_interface_clashes(exclude)472 self.check_for_network_interface_clashes(exclude)
439473
440 def has_dyanamic_ip_range(self):474 def has_dynamic_ip_range(self):
441 """Returns `True` if this `NodeGroupInterface` has a dynamic IP475 """Returns `True` if this `NodeGroupInterface` has a dynamic IP
442 range specified."""476 range specified."""
443 return self.ip_range_low and self.ip_range_high477 return self.ip_range_low and self.ip_range_high
@@ -445,7 +479,7 @@
445 def get_dynamic_ip_range(self):479 def get_dynamic_ip_range(self):
446 """Returns a `MAASIPRange` for this `NodeGroupInterface`, if a dynamic480 """Returns a `MAASIPRange` for this `NodeGroupInterface`, if a dynamic
447 range is specified. Otherwise, returns `None`."""481 range is specified. Otherwise, returns `None`."""
448 if self.has_dyanamic_ip_range():482 if self.has_dynamic_ip_range():
449 return make_iprange(483 return make_iprange(
450 self.ip_range_low, self.ip_range_high,484 self.ip_range_low, self.ip_range_high,
451 purpose='dynamic-range')485 purpose='dynamic-range')
@@ -645,7 +679,7 @@
645 assert isinstance(ipnetwork, IPNetwork)679 assert isinstance(ipnetwork, IPNetwork)
646680
647 ranges = set()681 ranges = set()
648 if self.has_dyanamic_ip_range():682 if self.has_dynamic_ip_range():
649 dynamic_range = self.get_dynamic_ip_range()683 dynamic_range = self.get_dynamic_ip_range()
650 if dynamic_range in ipnetwork:684 if dynamic_range in ipnetwork:
651 ranges.add(dynamic_range)685 ranges.add(dynamic_range)
652686
=== modified file 'src/maasserver/models/staticipaddress.py'
--- src/maasserver/models/staticipaddress.py 2015-11-17 00:33:35 +0000
+++ src/maasserver/models/staticipaddress.py 2015-12-03 19:55:35 +0000
@@ -166,7 +166,7 @@
166 dynamic_range_low, dynamic_range_high,166 dynamic_range_low, dynamic_range_high,
167 alloc_type=IPADDRESS_TYPE.AUTO, user=None,167 alloc_type=IPADDRESS_TYPE.AUTO, user=None,
168 requested_address=None, hostname=None, subnet=None,168 requested_address=None, hostname=None, subnet=None,
169 exclude_addresses=[]):169 exclude_addresses=[], in_use_ipset=set()):
170 """Return a new StaticIPAddress.170 """Return a new StaticIPAddress.
171171
172 :param network: The network the address should be allocated in.172 :param network: The network the address should be allocated in.
@@ -226,7 +226,8 @@
226 requested_address = self._async_find_free_ip(226 requested_address = self._async_find_free_ip(
227 static_range_low, static_range_high, static_range,227 static_range_low, static_range_high, static_range,
228 alloc_type, user,228 alloc_type, user,
229 exclude_addresses=exclude_addresses).wait(30)229 exclude_addresses=exclude_addresses,
230 in_use_ipset=in_use_ipset).wait(30)
230 try:231 try:
231 return self._attempt_allocation(232 return self._attempt_allocation(
232 requested_address, alloc_type, user,233 requested_address, alloc_type, user,
@@ -272,7 +273,7 @@
272273
273 def _find_free_ip(274 def _find_free_ip(
274 self, range_low, range_high, static_range, alloc_type,275 self, range_low, range_high, static_range, alloc_type,
275 user, exclude_addresses):276 user, exclude_addresses, in_use_ipset=set()):
276 """Helper function that finds a free IP address using a lock."""277 """Helper function that finds a free IP address using a lock."""
277 # The set of _allocated_ addresses in the range is going to be278 # The set of _allocated_ addresses in the range is going to be
278 # smaller or at least no bigger than the set of addresses in the279 # smaller or at least no bigger than the set of addresses in the
@@ -297,7 +298,8 @@
297 })298 })
298 # Now find the first free address in the range.299 # Now find the first free address in the range.
299 for requested_address in static_range:300 for requested_address in static_range:
300 if requested_address not in existing:301 if (requested_address not in existing and
302 requested_address not in in_use_ipset):
301 return requested_address303 return requested_address
302 else:304 else:
303 raise StaticIPAddressExhaustion(305 raise StaticIPAddressExhaustion(
@@ -840,4 +842,7 @@
840 else:842 else:
841 # (2) and (3): the Subnet has changed (could be to None)843 # (2) and (3): the Subnet has changed (could be to None)
842 subnet = Subnet.objects.get_best_subnet_for_ip(ipaddr)844 subnet = Subnet.objects.get_best_subnet_for_ip(ipaddr)
845 # We must save here, otherwise it's possible that we can't
846 # traverse the interface_set many-to-many.
847 self.save()
843 self._set_subnet(subnet, interfaces=self.interface_set.all())848 self._set_subnet(subnet, interfaces=self.interface_set.all())
844849
=== modified file 'src/maasserver/models/subnet.py'
--- src/maasserver/models/subnet.py 2015-11-09 22:20:01 +0000
+++ src/maasserver/models/subnet.py 2015-12-03 19:55:35 +0000
@@ -389,11 +389,18 @@
389389
390 def get_managed_cluster_interface(self):390 def get_managed_cluster_interface(self):
391 """Return the cluster interface that manages this subnet."""391 """Return the cluster interface that manages this subnet."""
392 # Prefer enabled, non-UNMANAGED networks.
392 interfaces = self.nodegroupinterface_set.filter(393 interfaces = self.nodegroupinterface_set.filter(
393 nodegroup__status=NODEGROUP_STATUS.ENABLED)394 nodegroup__status=NODEGROUP_STATUS.ENABLED)
394 interfaces = interfaces.exclude(395 interfaces = interfaces.exclude(
395 management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED)396 management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED)
396 return interfaces.first()397 ngi = interfaces.first()
398 if ngi is None:
399 # Circular imports
400 from maasserver.models import NodeGroupInterface
401 ngi = NodeGroupInterface.objects.get_by_managed_range_for_subnet(
402 self)
403 return ngi
397404
398 def clean(self, *args, **kwargs):405 def clean(self, *args, **kwargs):
399 self.validate_gateway_ip()406 self.validate_gateway_ip()
400407
=== modified file 'src/maasserver/models/tests/test_interface.py'
--- src/maasserver/models/tests/test_interface.py 2015-11-11 01:04:18 +0000
+++ src/maasserver/models/tests/test_interface.py 2015-12-03 19:55:35 +0000
@@ -31,6 +31,7 @@
31 NODEGROUPINTERFACE_MANAGEMENT,31 NODEGROUPINTERFACE_MANAGEMENT,
32)32)
33from maasserver.exceptions import (33from maasserver.exceptions import (
34 StaticIPAddressExhaustion,
34 StaticIPAddressOutOfRange,35 StaticIPAddressOutOfRange,
35 StaticIPAddressUnavailable,36 StaticIPAddressUnavailable,
36)37)
@@ -80,6 +81,7 @@
80 MatchesDict,81 MatchesDict,
81 MatchesListwise,82 MatchesListwise,
82 MatchesStructure,83 MatchesStructure,
84 Not,
83)85)
8486
8587
@@ -2224,9 +2226,93 @@
2224 self.assertEquals(subnet, observed[0].subnet)2226 self.assertEquals(subnet, observed[0].subnet)
2225 self.assertTrue(2227 self.assertTrue(
2226 IPAddress(observed[0].ip) in (2228 IPAddress(observed[0].ip) in (
2227 IPRange(ngi.static_ip_range_low, ngi.static_ip_range_low)),2229 IPRange(ngi.static_ip_range_low, ngi.static_ip_range_high)),
2228 "Assigned IP address should be inside the static range "2230 "Assigned IP address %s should be inside the static range "
2229 "on the cluster.")2231 "on the cluster (%s - %s)." % (
2232 observed[0].ip, ngi.static_ip_range_low,
2233 ngi.static_ip_range_high))
2234
2235 def test__claims_ip_address_in_static_ip_range_skips_gateway_ip(self):
2236 from maasserver.dns import config
2237 self.patch_autospec(interface_module, "update_host_maps")
2238 self.patch_autospec(config, "dns_update_zones")
2239 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
2240 subnet = factory.make_Subnet(vlan=interface.vlan)
2241 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
2242 ngi = factory.make_NodeGroupInterface(
2243 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS,
2244 subnet=subnet)
2245 # Make it a really small range, just to be safe.
2246 ngi.static_ip_range_high = unicode(
2247 IPAddress(ngi.static_ip_range_low) + 1)
2248 ngi.save()
2249 ngi.subnet.gateway_ip = ngi.static_ip_range_low
2250 ngi.subnet.dns_servers = []
2251 ngi.subnet.save()
2252 factory.make_StaticIPAddress(
2253 alloc_type=IPADDRESS_TYPE.AUTO, ip="",
2254 subnet=subnet, interface=interface)
2255 observed = interface.claim_auto_ips()
2256 self.assertEquals(
2257 1, len(observed),
2258 "Should have 1 AUTO IP addresses with an IP address assigned.")
2259 self.assertEquals(subnet, observed[0].subnet)
2260 self.assertTrue(
2261 IPAddress(observed[0].ip) in (
2262 IPRange(ngi.static_ip_range_low, ngi.static_ip_range_high)),
2263 "Assigned IP address %s should be inside the static range "
2264 "on the cluster (%s - %s)." % (
2265 observed[0].ip, ngi.static_ip_range_low,
2266 ngi.static_ip_range_high))
2267 self.assertThat(
2268 IPAddress(observed[0].ip), Not(Equals(IPAddress(
2269 ngi.subnet.gateway_ip))))
2270
2271 def test__claim_fails_if_subnet_missing(self):
2272 from maasserver.dns import config
2273 self.patch_autospec(interface_module, "update_host_maps")
2274 self.patch_autospec(config, "dns_update_zones")
2275 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
2276 subnet = factory.make_Subnet(vlan=interface.vlan)
2277 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
2278 factory.make_NodeGroupInterface(
2279 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS,
2280 subnet=subnet)
2281 ip = factory.make_StaticIPAddress(
2282 alloc_type=IPADDRESS_TYPE.AUTO, ip="",
2283 subnet=subnet, interface=interface)
2284 ip.subnet = None
2285 ip.save()
2286 maaslog = self.patch_autospec(interface_module, "maaslog")
2287 with ExpectedException(StaticIPAddressUnavailable):
2288 interface.claim_auto_ips()
2289 self.expectThat(maaslog.error, MockCalledOnceWith(
2290 "Could not find subnet for interface %s." %
2291 interface.get_log_string()))
2292
2293 def test__claim_fails_if_no_static_range(self):
2294 from maasserver.dns import config
2295 self.patch_autospec(interface_module, "update_host_maps")
2296 self.patch_autospec(config, "dns_update_zones")
2297 interface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL)
2298 subnet = factory.make_Subnet(vlan=interface.vlan)
2299 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
2300 ngi = factory.make_NodeGroupInterface(
2301 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP_AND_DNS,
2302 subnet=subnet)
2303 ngi.static_ip_range_low = ""
2304 ngi.static_ip_range_high = ""
2305 ngi.save()
2306 factory.make_StaticIPAddress(
2307 alloc_type=IPADDRESS_TYPE.AUTO, ip="",
2308 subnet=subnet, interface=interface)
2309 maaslog = self.patch_autospec(interface_module, "maaslog")
2310 with ExpectedException(StaticIPAddressUnavailable):
2311 interface.claim_auto_ips()
2312 self.expectThat(maaslog.error, MockCalledOnceWith(
2313 "Found matching NodeGroupInterface, but no static range has "
2314 "been defined for %s. (did you mean to configure DHCP?) " %
2315 interface.get_log_string()))
22302316
2231 def test__calls_update_host_maps(self):2317 def test__calls_update_host_maps(self):
2232 from maasserver.dns import config2318 from maasserver.dns import config
@@ -2518,7 +2604,7 @@
2518 StaticIPAddressOutOfRange, interface.claim_static_ips, ip_v6)2604 StaticIPAddressOutOfRange, interface.claim_static_ips, ip_v6)
2519 self.assertEquals(2605 self.assertEquals(
2520 "requested_address '%s' is not in a managed subnet for "2606 "requested_address '%s' is not in a managed subnet for "
2521 "this interface '%s'" % (ip_v6, interface.name),2607 "interface '%s'." % (ip_v6, interface.name),
2522 error.message)2608 error.message)
25232609
2524 def test__with_address_calls_link_subnet_with_ip_address(self):2610 def test__with_address_calls_link_subnet_with_ip_address(self):
@@ -2610,3 +2696,27 @@
2610 self.patch_autospec(iface, "link_subnet")2696 self.patch_autospec(iface, "link_subnet")
2611 claimed_ips = iface.claim_static_ips()2697 claimed_ips = iface.claim_static_ips()
2612 self.assertThat(claimed_ips, HasLength(1))2698 self.assertThat(claimed_ips, HasLength(1))
2699
2700 def test__claim_static_fails_if_parent_subnet_cannot_be_found(self):
2701 from maasserver.dns import config
2702 self.patch_autospec(interface_module, "update_host_maps")
2703 self.patch_autospec(config, "dns_update_zones")
2704 subnet = factory.make_Subnet()
2705 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
2706 factory.make_NodeGroupInterface(
2707 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED,
2708 subnet=subnet)
2709 node = factory.make_Node_with_Interface_on_Subnet(
2710 subnet=subnet, unmanaged=True, status=NODE_STATUS.READY)
2711 # Simulate an unmanaged network without association to a subnet.
2712 # (this could happen after a migration)
2713 StaticIPAddress.objects.all().delete()
2714 interface = node.get_boot_interface()
2715 maaslog = self.patch_autospec(interface_module, "maaslog")
2716 with ExpectedException(StaticIPAddressExhaustion):
2717 interface.claim_static_ips()
2718 self.expectThat(maaslog.warning, MockCalledOnceWith(
2719 "%s: Attempted to claim a static IP address, but no associated "
2720 "subnet could be found. (Recommission node '%s' in order for "
2721 "MAAS to discover the subnet.)" %
2722 (interface.get_log_string(), node.hostname)))
26132723
=== modified file 'src/maasserver/models/tests/test_nodegroupinterface.py'
--- src/maasserver/models/tests/test_nodegroupinterface.py 2015-10-29 19:10:30 +0000
+++ src/maasserver/models/tests/test_nodegroupinterface.py 2015-12-03 19:55:35 +0000
@@ -217,6 +217,41 @@
217 self.assertEqual(if2, get_by_address(address))217 self.assertEqual(if2, get_by_address(address))
218218
219219
220class TestGetManagedRangeForSubnet(MAASServerTestCase):
221
222 def test__finds_interface_using_static_range(self):
223 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
224 factory.make_NodeGroupInterface(nodegroup, network=IPNetwork(
225 '192.168.2.0/24'))
226 ngi = factory.make_NodeGroupInterface(
227 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED,
228 ip='192.168.0.1', subnet_mask='', ip_range_low='',
229 ip_range_high='', static_ip_range_low='192.168.1.10',
230 static_ip_range_high='192.168.1.20')
231 factory.make_NodeGroupInterface(nodegroup, network=IPNetwork(
232 '192.168.3.0/24'))
233 subnet = factory.make_Subnet(cidr='192.168.1.0/24')
234 self.assertEqual(
235 ngi, NodeGroupInterface.objects.get_by_managed_range_for_subnet(
236 subnet))
237
238 def test__finds_interface_using_dynamic_range(self):
239 nodegroup = factory.make_NodeGroup(status=NODEGROUP_STATUS.ENABLED)
240 factory.make_NodeGroupInterface(nodegroup, network=IPNetwork(
241 '192.168.2.0/24'))
242 ngi = factory.make_NodeGroupInterface(
243 nodegroup, management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED,
244 ip='192.168.0.1', subnet_mask='', static_ip_range_low='',
245 static_ip_range_high='', ip_range_low='192.168.1.10',
246 ip_range_high='192.168.1.20')
247 factory.make_NodeGroupInterface(nodegroup, network=IPNetwork(
248 '192.168.3.0/24'))
249 subnet = factory.make_Subnet(cidr=IPNetwork('192.168.1.0/24'))
250 result = NodeGroupInterface.objects.get_by_managed_range_for_subnet(
251 subnet)
252 self.assertEqual(ngi, result)
253
254
220class TestNodeGroupInterface(MAASServerTestCase):255class TestNodeGroupInterface(MAASServerTestCase):
221256
222 def test_network(self):257 def test_network(self):
223258
=== modified file 'src/maasserver/testing/factory.py'
--- src/maasserver/testing/factory.py 2015-11-18 18:14:30 +0000
+++ src/maasserver/testing/factory.py 2015-12-03 19:55:35 +0000
@@ -565,7 +565,7 @@
565 def make_Node_with_Interface_on_Subnet(565 def make_Node_with_Interface_on_Subnet(
566 self, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,566 self, management=NODEGROUPINTERFACE_MANAGEMENT.DHCP,
567 interface_count=1, nodegroup=None, vlan=None, subnet=None,567 interface_count=1, nodegroup=None, vlan=None, subnet=None,
568 cidr=None, fabric=None, ifname=None, **kwargs):568 cidr=None, fabric=None, ifname=None, unmanaged=False, **kwargs):
569 """Create a Node that has a Interface which is on a Subnet that has a569 """Create a Node that has a Interface which is on a Subnet that has a
570 NodeGroupInterface.570 NodeGroupInterface.
571571
@@ -592,7 +592,7 @@
592 ngis = subnet.nodegroupinterface_set.filter(nodegroup=nodegroup)592 ngis = subnet.nodegroupinterface_set.filter(nodegroup=nodegroup)
593 ngis = ngis.exclude(management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED)593 ngis = ngis.exclude(management=NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED)
594 ngi = ngis.first()594 ngi = ngis.first()
595 if ngi is None:595 if ngi is None and not unmanaged:
596 self.make_NodeGroupInterface(596 self.make_NodeGroupInterface(
597 nodegroup, vlan=vlan, management=management, subnet=subnet)597 nodegroup, vlan=vlan, management=management, subnet=subnet)
598 boot_interface = self.make_Interface(598 boot_interface = self.make_Interface(
@@ -765,8 +765,18 @@
765 self, iftype=INTERFACE_TYPE.PHYSICAL, node=None, mac_address=None,765 self, iftype=INTERFACE_TYPE.PHYSICAL, node=None, mac_address=None,
766 vlan=None, parents=None, name=None, cluster_interface=None,766 vlan=None, parents=None, name=None, cluster_interface=None,
767 ip=None, enabled=True, fabric=None):767 ip=None, enabled=True, fabric=None):
768 if name is None and iftype != INTERFACE_TYPE.VLAN:768 if name is None:
769 name = self.make_name('name')769 if iftype in (INTERFACE_TYPE.PHYSICAL, INTERFACE_TYPE.UNKNOWN):
770 name = self.make_name('eth')
771 elif iftype == INTERFACE_TYPE.ALIAS:
772 name = self.make_name('eth', sep=':')
773 elif iftype == INTERFACE_TYPE.BOND:
774 name = self.make_name('bond')
775 elif iftype == INTERFACE_TYPE.UNKNOWN:
776 name = self.make_name('eth')
777 elif iftype == INTERFACE_TYPE.VLAN:
778 # This will be determined by the VLAN's VID.
779 name = None
770 if iftype is None:780 if iftype is None:
771 iftype = INTERFACE_TYPE.PHYSICAL781 iftype = INTERFACE_TYPE.PHYSICAL
772 if vlan is None:782 if vlan is None:
773783
=== added file 'utilities/remote-reinstall'
--- utilities/remote-reinstall 1970-01-01 00:00:00 +0000
+++ utilities/remote-reinstall 2015-12-03 19:55:35 +0000
@@ -0,0 +1,70 @@
1#!/bin/bash
2cd $(dirname $0)
3cd ..
4
5get_ip() {
6 ping -c 1 "$1" | head -1 | tr '(' ')' | cut -d')' -f 2
7}
8
9trim () {
10 read -rd '' $1 <<<"${!1}"
11}
12
13die () {
14 echo "$@"
15 exit 1
16}
17
18if [ "$1" == "" ]; then
19 die "You must supply a hostname."
20fi
21
22hostname="$1"
23shift
24
25remote_basedir=/usr/lib/python2.7/dist-packages
26directories="maascli maasserver provisioningserver metadataserver"
27rsync_options=rlptv
28ssh_run="ssh -oBatchMode=yes -l root $hostname"
29
30echo "Checking $hostname..."
31maas_version=$($ssh_run "dpkg -s maas-region-controller-min | grep ^Version") \
32 || die "Cannot SSH to root@$hostname."
33ip_address=$(get_ip $hostname)
34maas_version=$(echo $maas_version | cut -d':' -f 2)
35trim maas_version
36trim hostname
37trim ip_address
38echo ""
39echo "Current MAAS version is: $maas_version"
40echo ""
41echo "WARNING: This will LIVE UPDATE the MAAS server at:"
42if [ $hostname == $ip_address ]; then
43 echo " $hostname"
44else
45 echo " $hostname ($ip_address)"
46fi
47echo ""
48echo "This is a DESTRUCTIVE script that will OVERWRITE files installed by the"
49echo "MAAS packages, and DELETE any extra files found on the server."
50echo ""
51echo "Destination directory:"
52echo " $remote_basedir"
53echo ""
54echo "The following directories (under src/ in this sandbox) will be copied:"
55echo " $directories"
56echo ""
57echo "Press <enter> to continue, ^C to cancel."
58read
59
60echo "Synchronizing files..."
61for dir in $directories; do
62 remote_dir=${remote_basedir}/${dir}
63 rsync -${rsync_options} --delete-after --exclude 'tests/' src/${dir}/ \
64 root@${hostname}:${remote_dir} \
65 && echo "Success." || die "Syncrhonization failed."
66 $ssh_run "python -c \"import compileall; compileall.compile_dir('$remote_dir', force=True)\""
67done
68$ssh_run service maas-regiond restart
69$ssh_run service apache2 restart
70$ssh_run service maas-clusterd restart

Subscribers

People subscribed via source and target branches