Merge lp:~trapnine/maas/fix-1564971 into lp:~maas-committers/maas/trunk

Proposed by Jeffrey C Jones
Status: Merged
Approved by: Jeffrey C Jones
Approved revision: no longer in the source branch.
Merged at revision: 4898
Proposed branch: lp:~trapnine/maas/fix-1564971
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 895 lines (+545/-67)
8 files modified
src/maasserver/api/tests/test_ipranges.py (+5/-5)
src/maasserver/api/tests/test_subnets.py (+32/-8)
src/maasserver/models/iprange.py (+71/-2)
src/maasserver/models/subnet.py (+32/-24)
src/maasserver/models/tests/test_iprange.py (+389/-17)
src/maasserver/models/tests/test_staticipaddress.py (+1/-1)
src/maasserver/testing/factory.py (+4/-1)
src/maasserver/tests/test_dhcp.py (+11/-9)
To merge this branch: bzr merge lp:~trapnine/maas/fix-1564971
Reviewer Review Type Date Requested Status
Mike Pontillo (community) Approve
Review via email: mp+291229@code.launchpad.net

Commit message

When creating a new IPRange, make sure no other conflicting ranges or IP addresses are in use.

Description of the change

When creating a new IPRange, make sure no other conflicting ranges or IP addresses are in use.

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

This looks good in general to me. A few comments below.

I just have one question: I see that you are doing the validation in a signal handler. Normally we would put it in the model or in the form. Was there any particular reason it was done this way? (Is there already a precedent in MAAS code somewhere? I have to admit I haven't done much with signals.)

Did you do any manual testing with the API for this change?

review: Needs Information
Revision history for this message
Jeffrey C Jones (trapnine) wrote :

> This looks good in general to me. A few comments below.
>
> I just have one question: I see that you are doing the validation in a signal
> handler. Normally we would put it in the model or in the form. Was there any
> particular reason it was done this way? (Is there already a precedent in MAAS
> code somewhere? I have to admit I haven't done much with signals.)
>
> Did you do any manual testing with the API for this change?

I've actually be directed towards signals and away from complex clean() override processing in the past, most notably for the BMC validation work. There are lots of examples in the signals package this new signal handler lives in. This is better than a form clean() because it will be run for any IPRange model save(), not just a form save().

In this case, it was 'almost' necessary because I actually delete the to-be-saved instance, then do some testing, then recreate it. I was not sure I could get away with that in a model's actual save() override (maybe it'd be OK). Also, signals make it easy to store pre-modification state in the post-init signal, then read them in the post-save signal to see what was changed. Basically, after all the other normal form and model checking has been done, this will test for range conflicts.

I did not manually test the API for the change, just the unit tests. This was written as a 'make model save() self-protect against dupes and overlapped ranges' perspective, not a 'fix the API or UI' perspective. Is there a gotcha I'm missing in the API (like maybe it doesn't error to the API cleanly when an Exception is thrown from the model)?

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

Sounds good.

Yeah, I asked if you tested the API because I was curious if the errors propagate correctly.

Given your explanation, I'm good with this branch (assuming you address the comments I mentioned).

review: Approve
Revision history for this message
Jeffrey C Jones (trapnine) wrote :

Per sprint discussion, will move login to model clean().

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

Looks good; I see some minor issues that I won't block you on, mostly regarding docstrings and comments. (see comments below.) Also, the major function in this branch is a little long and could stand to be refactored to reduce its complexity, IMO.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/maasserver/api/tests/test_ipranges.py'
--- src/maasserver/api/tests/test_ipranges.py 2016-02-26 18:39:26 +0000
+++ src/maasserver/api/tests/test_ipranges.py 2016-04-11 09:56:14 +0000
@@ -40,7 +40,7 @@
4040
41 def test_read(self):41 def test_read(self):
42 subnet = factory.make_Subnet(cidr="10.0.0.0/24")42 subnet = factory.make_Subnet(cidr="10.0.0.0/24")
43 factory.make_IPRange(subnet, '10.0.0.1', '10.0.0.10')43 factory.make_IPRange(subnet, '10.0.0.2', '10.0.0.10')
44 factory.make_IPRange(subnet, '10.0.0.11', '10.0.0.20')44 factory.make_IPRange(subnet, '10.0.0.11', '10.0.0.20')
45 factory.make_IPRange(subnet, '10.0.0.21', '10.0.0.30')45 factory.make_IPRange(subnet, '10.0.0.21', '10.0.0.30')
46 uri = get_ipranges_uri()46 uri = get_ipranges_uri()
@@ -97,14 +97,14 @@
9797
98 def test_handler_path(self):98 def test_handler_path(self):
99 subnet = factory.make_Subnet(cidr="10.0.0.0/24")99 subnet = factory.make_Subnet(cidr="10.0.0.0/24")
100 iprange = factory.make_IPRange(subnet, '10.0.0.1', '10.0.0.10')100 iprange = factory.make_IPRange(subnet, '10.0.0.2', '10.0.0.10')
101 self.assertEqual(101 self.assertEqual(
102 '/api/2.0/ipranges/%s/' % iprange.id,102 '/api/2.0/ipranges/%s/' % iprange.id,
103 get_iprange_uri(iprange))103 get_iprange_uri(iprange))
104104
105 def test_read(self):105 def test_read(self):
106 subnet = factory.make_Subnet(cidr="10.0.0.0/24")106 subnet = factory.make_Subnet(cidr="10.0.0.0/24")
107 iprange = factory.make_IPRange(subnet, '10.0.0.1', '10.0.0.10')107 iprange = factory.make_IPRange(subnet, '10.0.0.2', '10.0.0.10')
108 factory.make_IPRange(subnet, '10.0.0.11', '10.0.0.20')108 factory.make_IPRange(subnet, '10.0.0.11', '10.0.0.20')
109 factory.make_IPRange(subnet, '10.0.0.21', '10.0.0.30')109 factory.make_IPRange(subnet, '10.0.0.21', '10.0.0.30')
110 uri = get_iprange_uri(iprange)110 uri = get_iprange_uri(iprange)
@@ -131,7 +131,7 @@
131131
132 def test_update(self):132 def test_update(self):
133 subnet = factory.make_Subnet(cidr="10.0.0.0/24")133 subnet = factory.make_Subnet(cidr="10.0.0.0/24")
134 iprange = factory.make_IPRange(subnet, '10.0.0.1', '10.0.0.10')134 iprange = factory.make_IPRange(subnet, '10.0.0.2', '10.0.0.10')
135 uri = get_iprange_uri(iprange)135 uri = get_iprange_uri(iprange)
136 comment = factory.make_name("comment")136 comment = factory.make_name("comment")
137 response = self.client.put(uri, {137 response = self.client.put(uri, {
@@ -147,7 +147,7 @@
147147
148 def test_delete_deletes_iprange(self):148 def test_delete_deletes_iprange(self):
149 subnet = factory.make_Subnet(cidr="10.0.0.0/24")149 subnet = factory.make_Subnet(cidr="10.0.0.0/24")
150 iprange = factory.make_IPRange(subnet, '10.0.0.1', '10.0.0.10')150 iprange = factory.make_IPRange(subnet, '10.0.0.2', '10.0.0.10')
151 uri = get_iprange_uri(iprange)151 uri = get_iprange_uri(iprange)
152 response = self.client.delete(uri)152 response = self.client.delete(uri)
153 self.assertEqual(153 self.assertEqual(
154154
=== modified file 'src/maasserver/api/tests/test_subnets.py'
--- src/maasserver/api/tests/test_subnets.py 2016-03-28 13:54:47 +0000
+++ src/maasserver/api/tests/test_subnets.py 2016-04-11 09:56:14 +0000
@@ -385,14 +385,11 @@
385 "num_addresses": expected_addresses,385 "num_addresses": expected_addresses,
386 }]))386 }]))
387387
388 def test__returns_empty_list_for_full_subnet(self):388 def _unreserved_ip_ranges_empty(self, subnet, first_address, last_address):
389 subnet = factory.make_Subnet()389 """ Used by the succeeding three tests to prevent duplicating the
390 network = subnet.get_ipnetwork()390 boilerplate that creates the requested range, then makes sure the
391 first_address = inet_ntop(network.first + 1)391 unreserved_ip_ranges API call successfully returns an empty list.
392 if network.version == 6:392 """
393 last_address = inet_ntop(network.last)
394 else:
395 last_address = inet_ntop(network.last - 1)
396 factory.make_IPRange(393 factory.make_IPRange(
397 subnet, first_address, last_address,394 subnet, first_address, last_address,
398 type=IPRANGE_TYPE.DYNAMIC)395 type=IPRANGE_TYPE.DYNAMIC)
@@ -406,6 +403,33 @@
406 self.assertThat(403 self.assertThat(
407 result, Equals([]), str(subnet.get_ipranges_in_use()))404 result, Equals([]), str(subnet.get_ipranges_in_use()))
408405
406 def test__returns_empty_list_for_full_ipv4_subnet(self):
407 network = factory.make_ipv4_network()
408 subnet = factory.make_Subnet(cidr=str(network.cidr), dns_servers=[])
409 network = subnet.get_ipnetwork()
410 first_address = inet_ntop(network.first + 2) # Skip gateway, network.
411 last_address = inet_ntop(network.last - 1) # Skip broadcast.
412 self._unreserved_ip_ranges_empty(subnet, first_address, last_address)
413
414 def test__returns_empty_list_for_full_ipv6_subnet(self):
415 network = factory.make_ipv6_network()
416 subnet = factory.make_Subnet(cidr=str(network.cidr), dns_servers=[])
417 network = subnet.get_ipnetwork()
418 first_address = inet_ntop(network.first + 2) # Skip gateway, network.
419 last_address = inet_ntop(network.last)
420 self._unreserved_ip_ranges_empty(subnet, first_address, last_address)
421
422 # Slash-64 ipv6 subnets get a special range put in them - test separately.
423 def test__returns_empty_list_for_full_ipv6_slash_64_subnet(self):
424 network = factory.make_ipv6_network(slash=64)
425 subnet = factory.make_Subnet(cidr=str(network.cidr), dns_servers=[])
426 network = subnet.get_ipnetwork()
427 strip64 = (network.first >> 8) << 8
428 # Skip the automatically reserved range on /64's.
429 first_address = inet_ntop(strip64 + 0x100000000)
430 last_address = inet_ntop(network.last)
431 self._unreserved_ip_ranges_empty(subnet, first_address, last_address)
432
409 def test__accounts_for_reserved_ip_address(self):433 def test__accounts_for_reserved_ip_address(self):
410 subnet = factory.make_ipv4_Subnet_with_IPRanges(434 subnet = factory.make_ipv4_Subnet_with_IPRanges(
411 with_dynamic_range=False, dns_servers=[], with_router=False)435 with_dynamic_range=False, dns_servers=[], with_router=False)
412436
=== modified file 'src/maasserver/models/iprange.py'
--- src/maasserver/models/iprange.py 2016-03-28 13:54:47 +0000
+++ src/maasserver/models/iprange.py 2016-04-11 09:56:14 +0000
@@ -19,11 +19,17 @@
19 PROTECT,19 PROTECT,
20 QuerySet,20 QuerySet,
21)21)
22from maasserver.enum import IPRANGE_TYPE_CHOICES22from maasserver.enum import (
23 IPRANGE_TYPE,
24 IPRANGE_TYPE_CHOICES,
25)
23from maasserver.fields import MAASIPAddressField26from maasserver.fields import MAASIPAddressField
24from maasserver.models.cleansave import CleanSave27from maasserver.models.cleansave import CleanSave
25from maasserver.models.timestampedmodel import TimestampedModel28from maasserver.models.timestampedmodel import TimestampedModel
26from maasserver.utils.orm import MAASQueriesMixin29from maasserver.utils.orm import (
30 MAASQueriesMixin,
31 transactional,
32)
27import netaddr33import netaddr
28from netaddr import (34from netaddr import (
29 AddrFormatError,35 AddrFormatError,
@@ -155,6 +161,7 @@
155 if end_ip not in cidr:161 if end_ip not in cidr:
156 raise ValidationError(162 raise ValidationError(
157 "End IP address must be within subnet: %s." % cidr)163 "End IP address must be within subnet: %s." % cidr)
164 self.clean_prevent_dupes_and_overlaps()
158165
159 @property166 @property
160 def netaddr_iprange(self):167 def netaddr_iprange(self):
@@ -166,3 +173,65 @@
166 # APIs in previous MAAS releases used '-' in range types.173 # APIs in previous MAAS releases used '-' in range types.
167 purpose = purpose.replace('_', '-')174 purpose = purpose.replace('_', '-')
168 return make_iprange(self.start_ip, self.end_ip, purpose=purpose)175 return make_iprange(self.start_ip, self.end_ip, purpose=purpose)
176
177 @transactional
178 def clean_prevent_dupes_and_overlaps(self):
179
180 # A range overlap/conflict could be due to any of these fields.
181 def fail(message, fields=['start_ip', 'end_ip', 'type']):
182 for field in fields:
183 validation_errors[field] = [message]
184 raise ValidationError(validation_errors)
185
186 """Make sure the new or updated range isn't going to cause a conflict.
187 If it will, raise ValidationError.
188 """
189 # If model is incomplete, save() will fail, so don't bother checking.
190 if self.subnet_id is None or self.start_ip is None or (
191 self.end_ip is None) or self.type is None:
192 return
193
194 # The _state.adding flag is False if this instance exists in the DB.
195 # See https://docs.djangoproject.com/en/1.9/ref/models/instances/.
196 if not self._state.adding:
197 orig = IPRange.objects.get(pk=self.pk)
198 if orig.type == self.type and (
199 orig.start_ip == self.start_ip) and (
200 orig.end_ip == self.end_ip):
201 # Range not materially modified, no range dupe check required.
202 return
203 # Pre-existing range moved: remove existing, check, then re-create.
204 if IPRange.objects.filter(pk=self.id).exists():
205 self_id = self.id
206 # Delete will be rolled back if imminent range checks raise.
207 self.delete()
208 # Simulate update by setting the ID back to what it was.
209 self.id = self_id
210
211 # Reserved ranges can overlap allocated IPs but not other ranges.
212 # Dynamic ranges cannot overlap anything (no ranges or IPs).
213 if self.type == IPRANGE_TYPE.RESERVED:
214 unused = self.subnet.get_ipranges_available_for_reserved_range()
215 else:
216 unused = self.subnet.get_ipranges_available_for_dynamic_range()
217
218 validation_errors = {}
219 if len(unused) == 0:
220 fail("There is no room for any %s ranges on this subnet." % (
221 self.type))
222
223 # Find unused range for start_ip
224 for range in unused:
225 if IPAddress(self.start_ip) in range:
226 if IPAddress(self.end_ip) in range:
227 # Success, start and end IP are in an unused range.
228 return
229 else:
230 message = ("Requested %s range conflicts with "
231 "an existing ") % (self.type)
232 if self.type == IPRANGE_TYPE.RESERVED:
233 fail(message + "range.")
234 else:
235 fail(message + "IP address or range.")
236 fail("No %s range can be created at requested start IP." % self.type,
237 ['start_ip', 'type'])
169238
=== modified file 'src/maasserver/models/subnet.py'
--- src/maasserver/models/subnet.py 2016-04-08 12:03:57 +0000
+++ src/maasserver/models/subnet.py 2016-04-11 09:56:14 +0000
@@ -8,7 +8,6 @@
8 'Subnet',8 'Subnet',
9]9]
1010
11
12from django.contrib.postgres.fields import ArrayField11from django.contrib.postgres.fields import ArrayField
13from django.core.exceptions import (12from django.core.exceptions import (
14 PermissionDenied,13 PermissionDenied,
@@ -407,7 +406,7 @@
407 for ip in self.staticipaddress_set.all()406 for ip in self.staticipaddress_set.all()
408 if ip.ip)407 if ip.ip)
409408
410 def get_ipranges_in_use(self, exclude_addresses=[]):409 def get_ipranges_in_use(self, exclude_addresses=[], ranges_only=False):
411 """Returns a `MAASIPSet` of `MAASIPRange` objects which are currently410 """Returns a `MAASIPSet` of `MAASIPRange` objects which are currently
412 in use on this `Subnet`.411 in use on this `Subnet`.
413412
@@ -429,37 +428,46 @@
429 second = str(IPAddress(network.first + 0xFFFFFFFF))428 second = str(IPAddress(network.first + 0xFFFFFFFF))
430 ranges |= {make_iprange(first, second, purpose="reserved")}429 ranges |= {make_iprange(first, second, purpose="reserved")}
431 ipnetwork = self.get_ipnetwork()430 ipnetwork = self.get_ipnetwork()
432 assigned_ip_addresses = self.get_staticipaddresses_in_use()431 if not ranges_only:
433 if (self.gateway_ip is not None and self.gateway_ip != '' and432 assigned_ip_addresses = self.get_staticipaddresses_in_use()
434 self.gateway_ip in ipnetwork):433 if (self.gateway_ip is not None and self.gateway_ip != '' and
435 ranges |= {make_iprange(self.gateway_ip, purpose="gateway-ip")}434 self.gateway_ip in ipnetwork):
436 if self.dns_servers is not None:435 ranges |= {make_iprange(self.gateway_ip, purpose="gateway-ip")}
437 ranges |= set(436 if self.dns_servers is not None:
438 make_iprange(server, purpose="dns-server")437 ranges |= set(
439 for server in self.dns_servers438 make_iprange(server, purpose="dns-server")
440 if server in ipnetwork439 for server in self.dns_servers
441 )440 if server in ipnetwork
442 ranges |= set(441 )
443 make_iprange(ip, purpose="assigned-ip")442 ranges |= set(
444 for ip in assigned_ip_addresses443 make_iprange(ip, purpose="assigned-ip")
445 if ip in ipnetwork444 for ip in assigned_ip_addresses
446 )445 if ip in ipnetwork
447 ranges |= set(446 )
448 make_iprange(address, purpose="excluded")447 ranges |= set(
449 for address in exclude_addresses448 make_iprange(address, purpose="excluded")
450 if address in network449 for address in exclude_addresses
451 )450 if address in network
451 )
452 ranges |= self.get_reserved_maasipset()452 ranges |= self.get_reserved_maasipset()
453 ranges |= self.get_dynamic_maasipset()453 ranges |= self.get_dynamic_maasipset()
454 return MAASIPSet(ranges)454 return MAASIPSet(ranges)
455455
456 def get_ipranges_not_in_use(self, exclude_addresses=[]):456 def get_ipranges_available_for_reserved_range(self):
457 return self.get_ipranges_not_in_use(ranges_only=True)
458
459 def get_ipranges_available_for_dynamic_range(self):
460 return self.get_ipranges_not_in_use()
461
462 def get_ipranges_not_in_use(self, exclude_addresses=[], ranges_only=False):
457 """Returns a `MAASIPSet` of ranges which are currently free on this463 """Returns a `MAASIPSet` of ranges which are currently free on this
458 `Subnet`.464 `Subnet`.
459465
460 :param exclude_addresses: An iterable of addresses not to use.466 :param exclude_addresses: An iterable of addresses not to use.
461 """467 """
462 ranges = self.get_ipranges_in_use(exclude_addresses=exclude_addresses)468 ranges = self.get_ipranges_in_use(
469 exclude_addresses=exclude_addresses,
470 ranges_only=ranges_only)
463 unused = ranges.get_unused_ranges(self.get_ipnetwork())471 unused = ranges.get_unused_ranges(self.get_ipnetwork())
464 return unused472 return unused
465473
466474
=== modified file 'src/maasserver/models/tests/test_iprange.py'
--- src/maasserver/models/tests/test_iprange.py 2016-03-28 13:54:47 +0000
+++ src/maasserver/models/tests/test_iprange.py 2016-04-11 09:56:14 +0000
@@ -6,26 +6,37 @@
6__all__ = []6__all__ = []
77
8from django.core.exceptions import ValidationError8from django.core.exceptions import ValidationError
9from maasserver.enum import IPRANGE_TYPE9from maasserver.enum import (
10 IPADDRESS_TYPE,
11 IPRANGE_TYPE,
12)
10from maasserver.models import IPRange13from maasserver.models import IPRange
11from maasserver.testing.factory import factory14from maasserver.testing.factory import factory
12from maasserver.testing.testcase import MAASServerTestCase15from maasserver.testing.testcase import MAASServerTestCase
16from maasserver.utils.orm import reload_object
13from testtools import ExpectedException17from testtools import ExpectedException
1418
1519
20def make_plain_subnet():
21 return factory.make_Subnet(
22 cidr='192.168.0.0/24',
23 gateway_ip='192.168.0.1',
24 dns_servers=[])
25
26
16class IPRangeTest(MAASServerTestCase):27class IPRangeTest(MAASServerTestCase):
1728
18 def test__create(self):29 def test__create(self):
19 subnet = factory.make_Subnet(cidr='192.168.0.0/24')30 subnet = make_plain_subnet()
20 iprange = IPRange(31 iprange = IPRange(
21 start_ip='192.168.0.1', end_ip='192.168.0.254',32 start_ip='192.168.0.2', end_ip='192.168.0.254',
22 type=IPRANGE_TYPE.RESERVED, user=factory.make_User(),33 type=IPRANGE_TYPE.RESERVED, user=factory.make_User(),
23 comment="The quick brown fox jumps over the lazy dog.",34 comment="The quick brown fox jumps over the lazy dog.",
24 subnet=subnet)35 subnet=subnet)
25 iprange.save()36 iprange.save()
2637
27 def test__requires_valid_ip_addresses(self):38 def test__requires_valid_ip_addresses(self):
28 subnet = factory.make_Subnet(cidr='192.168.0.0/24')39 subnet = make_plain_subnet()
29 iprange = IPRange(40 iprange = IPRange(
30 start_ip='x192.x168.x0.x1', end_ip='y192.y168.y0.y254',41 start_ip='x192.x168.x0.x1', end_ip='y192.y168.y0.y254',
31 type=IPRANGE_TYPE.RESERVED, user=factory.make_User(),42 type=IPRANGE_TYPE.RESERVED, user=factory.make_User(),
@@ -35,7 +46,7 @@
35 iprange.save()46 iprange.save()
3647
37 def test__requires_start_ip_address(self):48 def test__requires_start_ip_address(self):
38 subnet = factory.make_Subnet(cidr='192.168.0.0/24')49 subnet = make_plain_subnet()
39 iprange = IPRange(50 iprange = IPRange(
40 start_ip='192.168.0.1', type=IPRANGE_TYPE.RESERVED,51 start_ip='192.168.0.1', type=IPRANGE_TYPE.RESERVED,
41 user=factory.make_User(), subnet=subnet,52 user=factory.make_User(), subnet=subnet,
@@ -44,7 +55,7 @@
44 iprange.save()55 iprange.save()
4556
46 def test__requires_end_ip_address(self):57 def test__requires_end_ip_address(self):
47 subnet = factory.make_Subnet(cidr='192.168.0.0/24')58 subnet = make_plain_subnet()
48 iprange = IPRange(59 iprange = IPRange(
49 end_ip='192.168.0.1', type=IPRANGE_TYPE.RESERVED,60 end_ip='192.168.0.1', type=IPRANGE_TYPE.RESERVED,
50 user=factory.make_User(), subnet=subnet,61 user=factory.make_User(), subnet=subnet,
@@ -53,7 +64,7 @@
53 iprange.save()64 iprange.save()
5465
55 def test__requires_matching_address_family(self):66 def test__requires_matching_address_family(self):
56 subnet = factory.make_Subnet(cidr='192.168.0.0/24')67 subnet = make_plain_subnet()
57 iprange = IPRange(68 iprange = IPRange(
58 start_ip='192.168.0.1', end_ip='2001:db8::1',69 start_ip='192.168.0.1', end_ip='2001:db8::1',
59 type=IPRANGE_TYPE.RESERVED,70 type=IPRANGE_TYPE.RESERVED,
@@ -71,7 +82,7 @@
71 iprange.save()82 iprange.save()
7283
73 def test__requires_start_ip_and_end_ip(self):84 def test__requires_start_ip_and_end_ip(self):
74 subnet = factory.make_Subnet(cidr='192.168.0.0/24')85 subnet = make_plain_subnet()
75 iprange = IPRange(86 iprange = IPRange(
76 subnet=subnet, type=IPRANGE_TYPE.RESERVED,87 subnet=subnet, type=IPRANGE_TYPE.RESERVED,
77 user=factory.make_User(),88 user=factory.make_User(),
@@ -80,7 +91,7 @@
80 iprange.save()91 iprange.save()
8192
82 def test__requires_start_ip_and_end_ip_to_be_within_subnet(self):93 def test__requires_start_ip_and_end_ip_to_be_within_subnet(self):
83 subnet = factory.make_Subnet(cidr='192.168.0.0/24')94 subnet = make_plain_subnet()
84 iprange = IPRange(95 iprange = IPRange(
85 start_ip='192.168.1.1', end_ip='192.168.1.254', subnet=subnet,96 start_ip='192.168.1.1', end_ip='192.168.1.254', subnet=subnet,
86 type=IPRANGE_TYPE.RESERVED, user=factory.make_User(),97 type=IPRANGE_TYPE.RESERVED, user=factory.make_User(),
@@ -90,7 +101,7 @@
90 iprange.save()101 iprange.save()
91102
92 def test__requires_start_ip_to_be_within_subnet(self):103 def test__requires_start_ip_to_be_within_subnet(self):
93 subnet = factory.make_Subnet(cidr='192.168.0.0/24')104 subnet = make_plain_subnet()
94 iprange = IPRange(105 iprange = IPRange(
95 start_ip='19.168.0.1', end_ip='192.168.0.254', subnet=subnet,106 start_ip='19.168.0.1', end_ip='192.168.0.254', subnet=subnet,
96 type=IPRANGE_TYPE.DYNAMIC, user=factory.make_User(),107 type=IPRANGE_TYPE.DYNAMIC, user=factory.make_User(),
@@ -100,7 +111,7 @@
100 iprange.save()111 iprange.save()
101112
102 def test__requires_end_ip_to_be_within_subnet(self):113 def test__requires_end_ip_to_be_within_subnet(self):
103 subnet = factory.make_Subnet(cidr='192.168.0.0/24')114 subnet = make_plain_subnet()
104 iprange = IPRange(115 iprange = IPRange(
105 start_ip='192.168.0.1', end_ip='193.168.0.254',116 start_ip='192.168.0.1', end_ip='193.168.0.254',
106 subnet=subnet, type=IPRANGE_TYPE.DYNAMIC,117 subnet=subnet, type=IPRANGE_TYPE.DYNAMIC,
@@ -111,7 +122,7 @@
111 iprange.save()122 iprange.save()
112123
113 def test__requires_end_ip_to_be_greater_or_equal_to_start_ip(self):124 def test__requires_end_ip_to_be_greater_or_equal_to_start_ip(self):
114 subnet = factory.make_Subnet(cidr='192.168.0.0/24')125 subnet = make_plain_subnet()
115 iprange = IPRange(126 iprange = IPRange(
116 start_ip='192.168.0.2', end_ip='192.168.0.1',127 start_ip='192.168.0.2', end_ip='192.168.0.1',
117 user=factory.make_User(), subnet=subnet,128 user=factory.make_User(), subnet=subnet,
@@ -122,7 +133,7 @@
122 iprange.save()133 iprange.save()
123134
124 def test__requires_type(self):135 def test__requires_type(self):
125 subnet = factory.make_Subnet(cidr='192.168.0.0/24')136 subnet = make_plain_subnet()
126 iprange = IPRange(137 iprange = IPRange(
127 start_ip='192.168.0.1', end_ip='192.168.0.254',138 start_ip='192.168.0.1', end_ip='192.168.0.254',
128 user=factory.make_User(), subnet=subnet,139 user=factory.make_User(), subnet=subnet,
@@ -131,16 +142,377 @@
131 iprange.save()142 iprange.save()
132143
133 def test__user_optional(self):144 def test__user_optional(self):
134 subnet = factory.make_Subnet(cidr='192.168.0.0/24')145 subnet = make_plain_subnet()
135 iprange = IPRange(146 iprange = IPRange(
136 start_ip='192.168.0.1', end_ip='192.168.0.254',147 start_ip='192.168.0.2', end_ip='192.168.0.254',
137 type=IPRANGE_TYPE.DYNAMIC, subnet=subnet,148 type=IPRANGE_TYPE.DYNAMIC, subnet=subnet,
138 comment="The quick brown owl jumps over the lazy alligator.")149 comment="The quick brown owl jumps over the lazy alligator.")
139 iprange.save()150 iprange.save()
140151
141 def test__comment_optional(self):152 def test__comment_optional(self):
142 subnet = factory.make_Subnet(cidr='192.168.0.0/24')153 subnet = make_plain_subnet()
143 iprange = IPRange(154 iprange = IPRange(
144 start_ip='192.168.0.1', end_ip='192.168.0.254', subnet=subnet,155 start_ip='192.168.0.2', end_ip='192.168.0.254', subnet=subnet,
145 type=IPRANGE_TYPE.RESERVED, user=factory.make_User())156 type=IPRANGE_TYPE.RESERVED, user=factory.make_User())
146 iprange.save()157 iprange.save()
158
159
160class TestIPRangeSavePreventsOverlapping(MAASServerTestCase):
161
162 no_fit = ".*No %s range can be created at requested start IP."
163 dynamic_no_fit = no_fit % IPRANGE_TYPE.DYNAMIC
164 reserved_no_fit = no_fit % IPRANGE_TYPE.RESERVED
165
166 overlaps = ".*Requested %s range conflicts with an existing %srange.*"
167 dynamic_overlaps = overlaps % (IPRANGE_TYPE.DYNAMIC, "IP address or ")
168 reserved_overlaps = overlaps % (IPRANGE_TYPE.RESERVED, "")
169
170 no_room = ".*There is no room for any %s ranges on this subnet.*"
171 dynamic_no_room = no_room % IPRANGE_TYPE.DYNAMIC
172 reserved_no_room = no_room % IPRANGE_TYPE.RESERVED
173
174 def test__no_save_duplicate_ipranges(self):
175 subnet = make_plain_subnet()
176 IPRange(
177 subnet=subnet,
178 type=IPRANGE_TYPE.DYNAMIC,
179 start_ip="192.168.0.100",
180 end_ip="192.168.0.150",
181 ).save()
182 # Make the same range again, should fail to save.
183 iprange = IPRange(
184 subnet=subnet,
185 type=IPRANGE_TYPE.DYNAMIC,
186 start_ip="192.168.0.100",
187 end_ip="192.168.0.150",
188 )
189 with ExpectedException(ValidationError, self.dynamic_no_fit):
190 iprange.save()
191
192 def test__no_save_range_overlap_begin(self):
193 subnet = make_plain_subnet()
194 IPRange(
195 subnet=subnet,
196 type=IPRANGE_TYPE.DYNAMIC,
197 start_ip="192.168.0.100",
198 end_ip="192.168.0.150",
199 ).save()
200 # Make an overlapping range across start_ip, should fail to save.
201 iprange = IPRange(
202 subnet=subnet,
203 type=IPRANGE_TYPE.DYNAMIC,
204 start_ip="192.168.0.90",
205 end_ip="192.168.0.100",
206 )
207 with ExpectedException(ValidationError, self.dynamic_overlaps):
208 iprange.save()
209 # Try as reserved range.
210 iprange.type = IPRANGE_TYPE.RESERVED
211 with ExpectedException(ValidationError, self.reserved_overlaps):
212 iprange.save()
213
214 def test__no_save_range_overlap_end(self):
215 subnet = make_plain_subnet()
216 IPRange(
217 subnet=subnet,
218 type=IPRANGE_TYPE.DYNAMIC,
219 start_ip="192.168.0.100",
220 end_ip="192.168.0.150",
221 ).save()
222 # Make an overlapping range across end_ip, should fail to save.
223 iprange = IPRange(
224 subnet=subnet,
225 type=IPRANGE_TYPE.DYNAMIC,
226 start_ip="192.168.0.140",
227 end_ip="192.168.0.160",
228 )
229 with ExpectedException(ValidationError, self.dynamic_no_fit):
230 iprange.save()
231
232 def test__no_save_range_within_ranges(self):
233 subnet = make_plain_subnet()
234 IPRange(
235 subnet=subnet,
236 type=IPRANGE_TYPE.DYNAMIC,
237 start_ip="192.168.0.100",
238 end_ip="192.168.0.150",
239 ).save()
240 # Make a contained range, should not save.
241 iprange = IPRange(
242 subnet=subnet,
243 type=IPRANGE_TYPE.DYNAMIC,
244 start_ip="192.168.0.110",
245 end_ip="192.168.0.140",
246 )
247 with ExpectedException(ValidationError, self.dynamic_no_fit):
248 iprange.save()
249
250 def test__no_save_range_spanning_existing_range(self):
251 subnet = make_plain_subnet()
252 IPRange(
253 subnet=subnet,
254 type=IPRANGE_TYPE.DYNAMIC,
255 start_ip="192.168.0.100",
256 end_ip="192.168.0.150",
257 ).save()
258 # Make a contained range, should not save.
259 iprange = IPRange(
260 subnet=subnet,
261 type=IPRANGE_TYPE.DYNAMIC,
262 start_ip="192.168.0.10",
263 end_ip="192.168.0.240",
264 )
265 with ExpectedException(ValidationError, self.dynamic_overlaps):
266 iprange.save()
267
268 def test__no_save_range_within_existing_range(self):
269 subnet = make_plain_subnet()
270 IPRange(
271 subnet=subnet,
272 type=IPRANGE_TYPE.DYNAMIC,
273 start_ip="192.168.0.100",
274 end_ip="192.168.0.150",
275 ).save()
276 # Make a contained range, should not save.
277 iprange = IPRange(
278 subnet=subnet,
279 type=IPRANGE_TYPE.DYNAMIC,
280 start_ip="192.168.0.110",
281 end_ip="192.168.0.140",
282 )
283 with ExpectedException(ValidationError, self.dynamic_no_fit):
284 iprange.save()
285
286 def test__no_save_range_within_existing_reserved_range(self):
287 subnet = make_plain_subnet()
288 IPRange(
289 subnet=subnet,
290 type=IPRANGE_TYPE.RESERVED,
291 start_ip="192.168.0.100",
292 end_ip="192.168.0.150",
293 ).save()
294 # Make a contained range, should not save.
295 iprange = IPRange(
296 subnet=subnet,
297 type=IPRANGE_TYPE.DYNAMIC,
298 start_ip="192.168.0.110",
299 end_ip="192.168.0.140",
300 )
301 with ExpectedException(ValidationError, self.dynamic_no_fit):
302 iprange.save()
303
304 def test__no_save_when_no_ranges_available(self):
305 subnet = make_plain_subnet()
306 # Reserve the whole subnet, except gateway.
307 IPRange(
308 subnet=subnet,
309 type=IPRANGE_TYPE.RESERVED,
310 start_ip="192.168.0.2",
311 end_ip="192.168.0.254",
312 ).save()
313 # Try to make dynamic range at gateway (anywhere, actually) = no room!
314 iprange = IPRange(
315 subnet=subnet,
316 type=IPRANGE_TYPE.DYNAMIC,
317 start_ip="192.168.0.1",
318 end_ip="192.168.0.1",
319 )
320 with ExpectedException(ValidationError, self.dynamic_no_room):
321 iprange.save()
322 # We CAN reserve the gateway addr.
323 IPRange(
324 subnet=subnet,
325 type=IPRANGE_TYPE.RESERVED,
326 start_ip="192.168.0.1",
327 end_ip="192.168.0.1",
328 ).save()
329 # But now it's full - trying to save any reserved = no room!
330 iprange = IPRange(
331 subnet=subnet,
332 type=IPRANGE_TYPE.RESERVED,
333 start_ip="192.168.0.25",
334 end_ip="192.168.0.35",
335 )
336 with ExpectedException(ValidationError, self.reserved_no_room):
337 iprange.save()
338
339 def test__modify_existing_performs_validation(self):
340 subnet = make_plain_subnet()
341 IPRange(
342 subnet=subnet,
343 type=IPRANGE_TYPE.DYNAMIC,
344 start_ip="192.168.0.100",
345 end_ip="192.168.0.150",
346 ).save()
347 iprange = IPRange(
348 subnet=subnet,
349 type=IPRANGE_TYPE.DYNAMIC,
350 start_ip="192.168.0.151",
351 end_ip="192.168.0.200",
352 )
353 iprange.save()
354 # Make sure safe modification works.
355 iprange.start_ip = "192.168.0.210"
356 iprange.end_ip = "192.168.0.250"
357 iprange.save()
358 # Modify again, but conflict with first range this time.
359 instance_id = iprange.id
360 iprange.start_ip = "192.168.0.110"
361 iprange.end_ip = "192.168.0.140"
362 with ExpectedException(ValidationError, self.dynamic_no_fit):
363 iprange.save()
364 # Make sure original range isn't deleted after failure to modify.
365 iprange = reload_object(iprange)
366 self.assertEqual(iprange.id, instance_id)
367
368 def test__dynamic_range_cant_overlap_gateway_ip(self):
369 subnet = make_plain_subnet()
370 iprange = IPRange(
371 subnet=subnet,
372 type=IPRANGE_TYPE.DYNAMIC,
373 start_ip="192.168.0.2",
374 end_ip="192.168.0.5",
375 )
376 iprange.save()
377 # A DYNAMIC range cannot overlap the gateway IP.
378 iprange.start_ip = "192.168.0.1"
379 with ExpectedException(ValidationError, self.dynamic_no_fit):
380 iprange.save()
381
382 def test__reserved_range_can_overlap_gateway_ip(self):
383 subnet = make_plain_subnet()
384 iprange = IPRange(
385 subnet=subnet,
386 type=IPRANGE_TYPE.RESERVED,
387 start_ip="192.168.0.2",
388 end_ip="192.168.0.5",
389 )
390 iprange.save()
391 # A RESERVED range can overlap the gateway IP.
392 iprange.start_ip = "192.168.0.1"
393 iprange.save()
394
395 def test__reserved_range_cannot_overlap_dynamic_ranges(self):
396 subnet = factory.make_Subnet(
397 cidr='192.168.0.0/24',
398 gateway_ip='192.168.0.1',
399 dns_servers=['192.168.0.50', '192.168.0.200'])
400 IPRange(
401 subnet=subnet,
402 type=IPRANGE_TYPE.DYNAMIC,
403 start_ip="192.168.0.2",
404 end_ip="192.168.0.49",
405 ).save()
406 iprange = IPRange(
407 subnet=subnet,
408 type=IPRANGE_TYPE.RESERVED,
409 start_ip="192.168.0.25",
410 end_ip="192.168.0.30",
411 )
412 with ExpectedException(ValidationError, self.reserved_no_fit):
413 iprange.save()
414
415 def test__reserved_range_cannot_overlap_reserved_ranges(self):
416 subnet = factory.make_Subnet(
417 cidr='192.168.0.0/24',
418 gateway_ip='192.168.0.1',
419 dns_servers=['192.168.0.50', '192.168.0.200'])
420 IPRange(
421 subnet=subnet,
422 type=IPRANGE_TYPE.RESERVED,
423 start_ip="192.168.0.1",
424 end_ip="192.168.0.250",
425 ).save()
426 iprange = IPRange(
427 subnet=subnet,
428 type=IPRANGE_TYPE.RESERVED,
429 start_ip="192.168.0.250",
430 end_ip="192.168.0.254",
431 )
432 with ExpectedException(ValidationError, self.reserved_no_fit):
433 iprange.save()
434
435 def test__dynamic_range_cannot_overlap_static_ip(self):
436 subnet = make_plain_subnet()
437 factory.make_StaticIPAddress(
438 alloc_type=IPADDRESS_TYPE.USER_RESERVED, subnet=subnet)
439 iprange = IPRange(
440 subnet=subnet,
441 type=IPRANGE_TYPE.DYNAMIC,
442 start_ip="192.168.0.2",
443 end_ip="192.168.0.254",
444 )
445 with ExpectedException(ValidationError, self.dynamic_overlaps):
446 iprange.save()
447
448 def test__reserved_range_can_overlap_static_ip(self):
449 subnet = make_plain_subnet()
450 factory.make_StaticIPAddress(
451 alloc_type=IPADDRESS_TYPE.USER_RESERVED, subnet=subnet)
452 iprange = IPRange(
453 subnet=subnet,
454 type=IPRANGE_TYPE.RESERVED,
455 start_ip="192.168.0.1",
456 end_ip="192.168.0.254",
457 )
458 iprange.save()
459
460 def test__dynamic_range_cannot_overlap_dns_servers(self):
461 subnet = factory.make_Subnet(
462 cidr='192.168.0.0/24',
463 gateway_ip='192.168.0.1',
464 dns_servers=['192.168.0.50', '192.168.0.200'])
465 iprange = IPRange(
466 subnet=subnet,
467 type=IPRANGE_TYPE.DYNAMIC,
468 start_ip="192.168.0.1",
469 end_ip="192.168.0.254",
470 )
471 with ExpectedException(ValidationError, self.dynamic_no_fit):
472 iprange.save()
473
474 def test__reserved_range_can_overlap_dns_servers(self):
475 subnet = factory.make_Subnet(
476 cidr='192.168.0.0/24',
477 gateway_ip='192.168.0.1',
478 dns_servers=['192.168.0.50', '192.168.0.200'])
479 iprange = IPRange(
480 subnet=subnet,
481 type=IPRANGE_TYPE.RESERVED,
482 start_ip="192.168.0.1",
483 end_ip="192.168.0.254",
484 )
485 iprange.save()
486
487 def test__change_reserved_to_dynamic(self):
488 subnet = make_plain_subnet()
489 iprange = IPRange(
490 subnet=subnet,
491 type=IPRANGE_TYPE.RESERVED,
492 start_ip="192.168.0.1",
493 end_ip="192.168.0.5",
494 )
495 # Reserved should save OK overlapping gateway IP.
496 iprange.save()
497
498 # Dynamic should not save overlapping gateway IP.
499 iprange.type = IPRANGE_TYPE.DYNAMIC
500 with ExpectedException(ValidationError, self.dynamic_no_fit):
501 iprange.save()
502 # Fix start_ip and now it should save.
503 iprange.start_ip = "192.168.0.2"
504 iprange.save()
505
506 def test__change_dynamic_to_reserved(self):
507 subnet = make_plain_subnet()
508 iprange = IPRange(
509 subnet=subnet,
510 type=IPRANGE_TYPE.DYNAMIC,
511 start_ip="192.168.0.2",
512 end_ip="192.168.0.5",
513 )
514 iprange.save()
515 # Reserved should save OK overlapping gateway IP.
516 iprange.type = IPRANGE_TYPE.RESERVED
517 iprange.start_ip = "192.168.0.1"
518 iprange.save()
147519
=== modified file 'src/maasserver/models/tests/test_staticipaddress.py'
--- src/maasserver/models/tests/test_staticipaddress.py 2016-03-28 13:54:47 +0000
+++ src/maasserver/models/tests/test_staticipaddress.py 2016-04-11 09:56:14 +0000
@@ -322,7 +322,7 @@
322 with transaction.atomic():322 with transaction.atomic():
323 subnet = factory.make_Subnet(cidr=network)323 subnet = factory.make_Subnet(cidr=network)
324 factory.make_IPRange(324 factory.make_IPRange(
325 subnet, '192.168.230.0', '192.168.230.255',325 subnet, '192.168.230.1', '192.168.230.254',
326 type=IPRANGE_TYPE.RESERVED)326 type=IPRANGE_TYPE.RESERVED)
327 with transaction.atomic():327 with transaction.atomic():
328 e = self.assertRaises(328 e = self.assertRaises(
329329
=== modified file 'src/maasserver/testing/factory.py'
--- src/maasserver/testing/factory.py 2016-04-07 18:58:38 +0000
+++ src/maasserver/testing/factory.py 2016-04-11 09:56:14 +0000
@@ -118,6 +118,7 @@
118 IPNetwork,118 IPNetwork,
119)119)
120from provisioningserver.utils.enum import map_enum120from provisioningserver.utils.enum import map_enum
121from provisioningserver.utils.network import inet_ntop
121122
122# We have a limited number of public keys:123# We have a limited number of public keys:
123# src/maasserver/tests/data/test_rsa{0, 1, 2, 3, 4}.pub124# src/maasserver/tests/data/test_rsa{0, 1, 2, 3, 4}.pub
@@ -750,11 +751,13 @@
750 vlan = factory.make_VLAN(fabric=fabric, vid=vid, dhcp_on=dhcp_on)751 vlan = factory.make_VLAN(fabric=fabric, vid=vid, dhcp_on=dhcp_on)
751 if space is None:752 if space is None:
752 space = factory.make_Space()753 space = factory.make_Space()
754 network = None
753 if cidr is None:755 if cidr is None:
754 network = factory.make_ip4_or_6_network(host_bits=host_bits)756 network = factory.make_ip4_or_6_network(host_bits=host_bits)
755 cidr = str(network.cidr)757 cidr = str(network.cidr)
756 if gateway_ip is None:758 if gateway_ip is None:
757 gateway_ip = factory.pick_ip_in_network(IPNetwork(cidr))759 network = IPNetwork(cidr) if network is None else network
760 gateway_ip = inet_ntop(network.first + 1)
758 if dns_servers is None:761 if dns_servers is None:
759 dns_servers = [762 dns_servers = [
760 self.make_ip_address() for _ in range(random.randint(1, 3))]763 self.make_ip_address() for _ in range(random.randint(1, 3))]
761764
=== modified file 'src/maasserver/tests/test_dhcp.py'
--- src/maasserver/tests/test_dhcp.py 2016-04-01 21:14:24 +0000
+++ src/maasserver/tests/test_dhcp.py 2016-04-11 09:56:14 +0000
@@ -1220,8 +1220,8 @@
1220 ha_subnet = factory.make_Subnet(1220 ha_subnet = factory.make_Subnet(
1221 vlan=ha_vlan, cidr="fd38:c341:27da:c831::/64")1221 vlan=ha_vlan, cidr="fd38:c341:27da:c831::/64")
1222 factory.make_IPRange(1222 factory.make_IPRange(
1223 ha_subnet, "fd38:c341:27da:c831::0001:0000",1223 ha_subnet, "fd38:c341:27da:c831:0:1::",
1224 "fd38:c341:27da:c831::FFFF:0000")1224 "fd38:c341:27da:c831:0:1:ffff:0")
1225 factory.make_Interface(1225 factory.make_Interface(
1226 INTERFACE_TYPE.PHYSICAL, node=primary_rack, vlan=ha_vlan)1226 INTERFACE_TYPE.PHYSICAL, node=primary_rack, vlan=ha_vlan)
1227 secondary_interface = factory.make_Interface(1227 secondary_interface = factory.make_Interface(
@@ -1251,8 +1251,8 @@
1251 vlan=ha_vlan, cidr="fd38:c341:27da:c831::/64")1251 vlan=ha_vlan, cidr="fd38:c341:27da:c831::/64")
1252 ha_network = ha_subnet.get_ipnetwork()1252 ha_network = ha_subnet.get_ipnetwork()
1253 factory.make_IPRange(1253 factory.make_IPRange(
1254 ha_subnet, "fd38:c341:27da:c831::0001:0000",1254 ha_subnet, "fd38:c341:27da:c831:0:1::",
1255 "fd38:c341:27da:c831::FFFF:0000")1255 "fd38:c341:27da:c831:0:1:ffff:0")
1256 ha_dhcp_snippets = [1256 ha_dhcp_snippets = [
1257 factory.make_DHCPSnippet(subnet=ha_subnet, enabled=True)1257 factory.make_DHCPSnippet(subnet=ha_subnet, enabled=True)
1258 for _ in range(3)]1258 for _ in range(3)]
@@ -1375,10 +1375,12 @@
1375 subnet_v4 = factory.make_ipv4_Subnet_with_IPRanges(1375 subnet_v4 = factory.make_ipv4_Subnet_with_IPRanges(
1376 vlan=vlan, unmanaged=(not dhcp_on))1376 vlan=vlan, unmanaged=(not dhcp_on))
1377 subnet_v6 = factory.make_Subnet(1377 subnet_v6 = factory.make_Subnet(
1378 vlan=vlan, cidr="fd38:c341:27da:c831::/64")1378 vlan=vlan, cidr="fd38:c341:27da:c831::/64",
1379 gateway_ip="fd38:c341:27da:c831::1",
1380 dns_servers=[])
1379 factory.make_IPRange(1381 factory.make_IPRange(
1380 subnet_v6, "fd38:c341:27da:c831::0001:0000",1382 subnet_v6, "fd38:c341:27da:c831:0:1::",
1381 "fd38:c341:27da:c831::FFFF:0000")1383 "fd38:c341:27da:c831:0:1:ffff:0")
13821384
1383 factory.make_StaticIPAddress(1385 factory.make_StaticIPAddress(
1384 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet_v4,1386 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet_v4,
@@ -1596,8 +1598,8 @@
1596 subnet_v6 = factory.make_Subnet(1598 subnet_v6 = factory.make_Subnet(
1597 vlan=vlan, cidr="fd38:c341:27da:c831::/64")1599 vlan=vlan, cidr="fd38:c341:27da:c831::/64")
1598 factory.make_IPRange(1600 factory.make_IPRange(
1599 subnet_v6, "fd38:c341:27da:c831::0001:0000",1601 subnet_v6, "fd38:c341:27da:c831:0:1::",
1600 "fd38:c341:27da:c831::FFFF:0000")1602 "fd38:c341:27da:c831:0:1:ffff:0")
16011603
1602 factory.make_StaticIPAddress(1604 factory.make_StaticIPAddress(
1603 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet_v4,1605 alloc_type=IPADDRESS_TYPE.AUTO, subnet=subnet_v4,