Merge lp:~blake-rouse/maas/fix-1441841 into lp:~maas-committers/maas/trunk
- fix-1441841
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Blake Rouse |
Approved revision: | no longer in the source branch. |
Merged at revision: | 3838 |
Proposed branch: | lp:~blake-rouse/maas/fix-1441841 |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
913 lines (+436/-103) 13 files modified
src/maasserver/api/ip_addresses.py (+5/-3) src/maasserver/api/tests/test_ipaddresses.py (+22/-5) src/maasserver/api/tests/test_node.py (+5/-3) src/maasserver/models/macaddress.py (+3/-0) src/maasserver/models/staticipaddress.py (+35/-16) src/maasserver/models/tests/test_staticipaddress.py (+109/-45) src/maasserver/static/js/angular/controllers/add_device.js (+9/-5) src/maasserver/static/js/angular/controllers/tests/test_add_device.js (+13/-0) src/maasserver/static/js/angular/services/tests/test_validation.js (+164/-15) src/maasserver/static/js/angular/services/validation.js (+48/-1) src/maasserver/tests/test_forms_nodegroupinterface.py (+3/-1) src/maasserver/tests/test_forms_validate_new_static_ip_range.py (+18/-8) src/maasserver/tests/test_node_action.py (+2/-1) |
To merge this branch: | bzr merge lp:~blake-rouse/maas/fix-1441841 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Raphaël Badin (community) | Approve | ||
Andres Rodriguez (community) | Needs Information | ||
Review via email: mp+256988@code.launchpad.net |
Commit message
Fix the validation in AddDeviceContro
This allows users to add a static ip address anywhere in the selected network except for the dynamic range.
Description of the change
Blake Rouse (blake-rouse) wrote : | # |
Sorry meant dynamic range. Fixed commit message.
Blake Rouse (blake-rouse) wrote : | # |
This required a fix in the backend not just the frontend. This now allows a requested_address to be anywhere in the network, not just within the static range.
Raphaël Badin (rvb) wrote : | # |
Looks good. Can you please add an API test where the requested IP is *not* in the static range (nor in the dynamic range)?
Blake Rouse (blake-rouse) wrote : | # |
I added that test. While doing so I noticed that I was not actually validating that the requested_address was not inside the dynamic range. I added that logic and tests as well.
Preview Diff
1 | === modified file 'src/maasserver/api/ip_addresses.py' | |||
2 | --- src/maasserver/api/ip_addresses.py 2015-04-17 08:32:15 +0000 | |||
3 | +++ src/maasserver/api/ip_addresses.py 2015-04-23 19:17:12 +0000 | |||
4 | @@ -81,12 +81,14 @@ | |||
5 | 81 | """ | 81 | """ |
6 | 82 | if mac is None: | 82 | if mac is None: |
7 | 83 | sip = StaticIPAddress.objects.allocate_new( | 83 | sip = StaticIPAddress.objects.allocate_new( |
10 | 84 | range_low=interface.static_ip_range_low, | 84 | network=interface.network, |
11 | 85 | range_high=interface.static_ip_range_high, | 85 | static_range_low=interface.static_ip_range_low, |
12 | 86 | static_range_high=interface.static_ip_range_high, | ||
13 | 87 | dynamic_range_low=interface.ip_range_low, | ||
14 | 88 | dynamic_range_high=interface.ip_range_high, | ||
15 | 86 | alloc_type=IPADDRESS_TYPE.USER_RESERVED, | 89 | alloc_type=IPADDRESS_TYPE.USER_RESERVED, |
16 | 87 | requested_address=requested_address, | 90 | requested_address=requested_address, |
17 | 88 | user=user, hostname=hostname) | 91 | user=user, hostname=hostname) |
18 | 89 | commit_within_atomic_block() | ||
19 | 90 | maaslog.info("User %s was allocated IP %s", user.username, sip.ip) | 92 | maaslog.info("User %s was allocated IP %s", user.username, sip.ip) |
20 | 91 | else: | 93 | else: |
21 | 92 | # The user has requested a static IP linked to a MAC | 94 | # The user has requested a static IP linked to a MAC |
22 | 93 | 95 | ||
23 | === modified file 'src/maasserver/api/tests/test_ipaddresses.py' | |||
24 | --- src/maasserver/api/tests/test_ipaddresses.py 2015-04-17 07:26:25 +0000 | |||
25 | +++ src/maasserver/api/tests/test_ipaddresses.py 2015-04-23 19:17:12 +0000 | |||
26 | @@ -213,6 +213,25 @@ | |||
27 | 213 | self.expectThat(returned_address["ip"], Equals(ip_in_network)) | 213 | self.expectThat(returned_address["ip"], Equals(ip_in_network)) |
28 | 214 | self.expectThat(staticipaddress.ip, Equals(ip_in_network)) | 214 | self.expectThat(staticipaddress.ip, Equals(ip_in_network)) |
29 | 215 | 215 | ||
30 | 216 | def test_POST_reserve_creates_address_outside_of_static_range(self): | ||
31 | 217 | interface = self.make_interface() | ||
32 | 218 | interface.ip_range_high = unicode( | ||
33 | 219 | IPAddress(interface.ip_range_high) - 1) | ||
34 | 220 | interface.save() | ||
35 | 221 | net = interface.network | ||
36 | 222 | ip_in_network = unicode( | ||
37 | 223 | IPAddress(interface.ip_range_high) + 1) | ||
38 | 224 | response = self.post_reservation_request( | ||
39 | 225 | net=net, requested_address=ip_in_network) | ||
40 | 226 | self.assertEqual(httplib.OK, response.status_code, response.content) | ||
41 | 227 | returned_address = json.loads(response.content) | ||
42 | 228 | [staticipaddress] = StaticIPAddress.objects.all() | ||
43 | 229 | self.expectThat( | ||
44 | 230 | returned_address["alloc_type"], | ||
45 | 231 | Equals(IPADDRESS_TYPE.USER_RESERVED)) | ||
46 | 232 | self.expectThat(returned_address["ip"], Equals(ip_in_network)) | ||
47 | 233 | self.expectThat(staticipaddress.ip, Equals(ip_in_network)) | ||
48 | 234 | |||
49 | 216 | def test_POST_reserve_requested_address_detects_in_use_address(self): | 235 | def test_POST_reserve_requested_address_detects_in_use_address(self): |
50 | 217 | interface = self.make_interface() | 236 | interface = self.make_interface() |
51 | 218 | net = interface.network | 237 | net = interface.network |
52 | @@ -227,13 +246,11 @@ | |||
53 | 227 | response.content, Equals( | 246 | response.content, Equals( |
54 | 228 | "The IP address %s is already in use." % ip_in_network)) | 247 | "The IP address %s is already in use." % ip_in_network)) |
55 | 229 | 248 | ||
57 | 230 | def test_POST_reserve_requested_address_detects_out_of_range_addr(self): | 249 | def test_POST_reserve_requested_address_rejects_ip_in_dynamic_range(self): |
58 | 231 | interface = self.make_interface() | 250 | interface = self.make_interface() |
59 | 232 | net = interface.network | 251 | net = interface.network |
64 | 233 | ip_not_in_network = unicode( | 252 | ip_in_network = interface.ip_range_low |
65 | 234 | IPAddress(interface.static_ip_range_low) - 1) | 253 | response = self.post_reservation_request(net, ip_in_network) |
62 | 235 | response = self.post_reservation_request( | ||
63 | 236 | net=net, requested_address=ip_not_in_network) | ||
66 | 237 | self.assertEqual( | 254 | self.assertEqual( |
67 | 238 | httplib.FORBIDDEN, response.status_code, response.content) | 255 | httplib.FORBIDDEN, response.status_code, response.content) |
68 | 239 | 256 | ||
69 | 240 | 257 | ||
70 | === modified file 'src/maasserver/api/tests/test_node.py' | |||
71 | --- src/maasserver/api/tests/test_node.py 2015-04-17 08:36:07 +0000 | |||
72 | +++ src/maasserver/api/tests/test_node.py 2015-04-23 19:17:12 +0000 | |||
73 | @@ -1208,11 +1208,12 @@ | |||
74 | 1208 | [observed_static_ip] = StaticIPAddress.objects.all() | 1208 | [observed_static_ip] = StaticIPAddress.objects.all() |
75 | 1209 | self.assertEqual(observed_static_ip.ip, requested_address) | 1209 | self.assertEqual(observed_static_ip.ip, requested_address) |
76 | 1210 | 1210 | ||
78 | 1211 | def test_claim_sticky_ip_address_detects_out_of_range_requested_ip(self): | 1211 | def test_claim_sticky_ip_address_detects_out_of_network_requested_ip(self): |
79 | 1212 | self.become_admin() | 1212 | self.become_admin() |
80 | 1213 | node = factory.make_Node_with_MACAddress_and_NodeGroupInterface() | 1213 | node = factory.make_Node_with_MACAddress_and_NodeGroupInterface() |
81 | 1214 | ngi = node.get_primary_mac().cluster_interface | 1214 | ngi = node.get_primary_mac().cluster_interface |
83 | 1215 | requested_address = IPAddress(ngi.static_ip_range_low) - 1 | 1215 | other_network = factory.make_ipv4_network(but_not=ngi.network) |
84 | 1216 | requested_address = factory.pick_ip_in_network(other_network) | ||
85 | 1216 | 1217 | ||
86 | 1217 | response = self.client.post( | 1218 | response = self.client.post( |
87 | 1218 | self.get_node_uri(node), | 1219 | self.get_node_uri(node), |
88 | @@ -1265,7 +1266,8 @@ | |||
89 | 1265 | ngi.save() | 1266 | ngi.save() |
90 | 1266 | with transaction.atomic(): | 1267 | with transaction.atomic(): |
91 | 1267 | StaticIPAddress.objects.allocate_new( | 1268 | StaticIPAddress.objects.allocate_new( |
93 | 1268 | ngi.static_ip_range_high, ngi.static_ip_range_low) | 1269 | ngi.network, ngi.static_ip_range_low, ngi.static_ip_range_high, |
94 | 1270 | ngi.ip_range_low, ngi.ip_range_high) | ||
95 | 1269 | 1271 | ||
96 | 1270 | response = self.client.post( | 1272 | response = self.client.post( |
97 | 1271 | TestNodeAPI.get_node_uri(node), {'op': 'start'}) | 1273 | TestNodeAPI.get_node_uri(node), {'op': 'start'}) |
98 | 1272 | 1274 | ||
99 | === modified file 'src/maasserver/models/macaddress.py' | |||
100 | --- src/maasserver/models/macaddress.py 2015-04-08 20:14:03 +0000 | |||
101 | +++ src/maasserver/models/macaddress.py 2015-04-23 19:17:12 +0000 | |||
102 | @@ -238,8 +238,11 @@ | |||
103 | 238 | ) | 238 | ) |
104 | 239 | 239 | ||
105 | 240 | new_sip = StaticIPAddress.objects.allocate_new( | 240 | new_sip = StaticIPAddress.objects.allocate_new( |
106 | 241 | cluster_interface.network, | ||
107 | 241 | cluster_interface.static_ip_range_low, | 242 | cluster_interface.static_ip_range_low, |
108 | 242 | cluster_interface.static_ip_range_high, | 243 | cluster_interface.static_ip_range_high, |
109 | 244 | cluster_interface.ip_range_low, | ||
110 | 245 | cluster_interface.ip_range_high, | ||
111 | 243 | alloc_type, requested_address=requested_address, | 246 | alloc_type, requested_address=requested_address, |
112 | 244 | user=user) | 247 | user=user) |
113 | 245 | MACStaticIPAddressLink(mac_address=self, ip_address=new_sip).save() | 248 | MACStaticIPAddressLink(mac_address=self, ip_address=new_sip).save() |
114 | 246 | 249 | ||
115 | === modified file 'src/maasserver/models/staticipaddress.py' | |||
116 | --- src/maasserver/models/staticipaddress.py 2015-04-16 12:15:52 +0000 | |||
117 | +++ src/maasserver/models/staticipaddress.py 2015-04-23 19:17:12 +0000 | |||
118 | @@ -131,13 +131,24 @@ | |||
119 | 131 | ipaddress.save() | 131 | ipaddress.save() |
120 | 132 | return ipaddress | 132 | return ipaddress |
121 | 133 | 133 | ||
125 | 134 | def allocate_new(self, range_low, range_high, | 134 | def allocate_new( |
126 | 135 | alloc_type=IPADDRESS_TYPE.AUTO, user=None, | 135 | self, network, static_range_low, static_range_high, |
127 | 136 | requested_address=None, hostname=None): | 136 | dynamic_range_low, dynamic_range_high, |
128 | 137 | alloc_type=IPADDRESS_TYPE.AUTO, user=None, | ||
129 | 138 | requested_address=None, hostname=None): | ||
130 | 137 | """Return a new StaticIPAddress. | 139 | """Return a new StaticIPAddress. |
131 | 138 | 140 | ||
134 | 139 | :param range_low: The lowest address to allocate in a range | 141 | :param network: The network the address should be allocated in. |
135 | 140 | :param range_high: The highest address to allocate in a range | 142 | :param static_range_low: The lowest static address to allocate in a |
136 | 143 | range. Used if `requested_address` is not passed. | ||
137 | 144 | :param static_range_high: The highest static address to allocate in a | ||
138 | 145 | range. Used if `requested_address` is not passed. | ||
139 | 146 | :param dynamic_range_low: The lowest dynamic address. Used if | ||
140 | 147 | `requested_address` is passed, check that its not inside the | ||
141 | 148 | dynamic range. | ||
142 | 149 | :param dynamic_range_high: The highest dynamic address. Used if | ||
143 | 150 | `requested_address` is passed, check that its not inside the | ||
144 | 151 | dynamic range. | ||
145 | 141 | :param alloc_type: What sort of IP address to allocate in the | 152 | :param alloc_type: What sort of IP address to allocate in the |
146 | 142 | range of choice in IPADDRESS_TYPE. | 153 | range of choice in IPADDRESS_TYPE. |
147 | 143 | :param user: If providing a user, the alloc_type must be | 154 | :param user: If providing a user, the alloc_type must be |
148 | @@ -157,15 +168,15 @@ | |||
149 | 157 | # taken, and so we must first eliminate all other possible causes. | 168 | # taken, and so we must first eliminate all other possible causes. |
150 | 158 | self._verify_alloc_type(alloc_type, user) | 169 | self._verify_alloc_type(alloc_type, user) |
151 | 159 | 170 | ||
152 | 160 | range_low = IPAddress(range_low) | ||
153 | 161 | range_high = IPAddress(range_high) | ||
154 | 162 | static_range = IPRange(range_low, range_high) | ||
155 | 163 | |||
156 | 164 | if requested_address is None: | 171 | if requested_address is None: |
157 | 172 | static_range_low = IPAddress(static_range_low) | ||
158 | 173 | static_range_high = IPAddress(static_range_high) | ||
159 | 174 | static_range = IPRange(static_range_low, static_range_high) | ||
160 | 175 | |||
161 | 165 | with locks.staticip_acquire: | 176 | with locks.staticip_acquire: |
162 | 166 | requested_address = self._async_find_free_ip( | 177 | requested_address = self._async_find_free_ip( |
165 | 167 | range_low, range_high, static_range, alloc_type, user, | 178 | static_range_low, static_range_high, static_range, |
166 | 168 | hostname=hostname).wait(30) | 179 | alloc_type, user, hostname=hostname).wait(30) |
167 | 169 | try: | 180 | try: |
168 | 170 | return self._attempt_allocation( | 181 | return self._attempt_allocation( |
169 | 171 | requested_address, alloc_type, user, | 182 | requested_address, alloc_type, user, |
170 | @@ -176,12 +187,20 @@ | |||
171 | 176 | # let the retry mechanism do its thing. | 187 | # let the retry mechanism do its thing. |
172 | 177 | raise make_serialization_failure() | 188 | raise make_serialization_failure() |
173 | 178 | else: | 189 | else: |
174 | 190 | dynamic_range_low = IPAddress(dynamic_range_low) | ||
175 | 191 | dynamic_range_high = IPAddress(dynamic_range_high) | ||
176 | 192 | dynamic_range = IPRange(dynamic_range_low, dynamic_range_high) | ||
177 | 193 | |||
178 | 179 | requested_address = IPAddress(requested_address) | 194 | requested_address = IPAddress(requested_address) |
184 | 180 | if requested_address not in static_range: | 195 | if requested_address not in network: |
185 | 181 | raise StaticIPAddressOutOfRange( | 196 | raise StaticIPAddressOutOfRange( |
186 | 182 | "%s is not inside the range %s to %s" % ( | 197 | "%s is not inside the network %s" % ( |
187 | 183 | requested_address.format(), range_low.format(), | 198 | requested_address.format(), network)) |
188 | 184 | range_high.format())) | 199 | if requested_address in dynamic_range: |
189 | 200 | raise StaticIPAddressOutOfRange( | ||
190 | 201 | "%s is inside the dynamic range %s to %s" % ( | ||
191 | 202 | requested_address.format(), dynamic_range_low.format(), | ||
192 | 203 | dynamic_range_high.format())) | ||
193 | 185 | return self._attempt_allocation( | 204 | return self._attempt_allocation( |
194 | 186 | requested_address, alloc_type, user=user, hostname=hostname) | 205 | requested_address, alloc_type, user=user, hostname=hostname) |
195 | 187 | 206 | ||
196 | 188 | 207 | ||
197 | === modified file 'src/maasserver/models/tests/test_staticipaddress.py' | |||
198 | --- src/maasserver/models/tests/test_staticipaddress.py 2015-04-16 12:30:02 +0000 | |||
199 | +++ src/maasserver/models/tests/test_staticipaddress.py 2015-04-23 19:17:12 +0000 | |||
200 | @@ -48,108 +48,164 @@ | |||
201 | 48 | 48 | ||
202 | 49 | class TestStaticIPAddressManager(MAASServerTestCase): | 49 | class TestStaticIPAddressManager(MAASServerTestCase): |
203 | 50 | 50 | ||
204 | 51 | def make_ip_ranges(self, network=None): | ||
205 | 52 | interface = factory.make_NodeGroupInterface( | ||
206 | 53 | factory.make_NodeGroup(), network=network) | ||
207 | 54 | return ( | ||
208 | 55 | interface.network, | ||
209 | 56 | interface.static_ip_range_low, | ||
210 | 57 | interface.static_ip_range_high, | ||
211 | 58 | interface.ip_range_low, | ||
212 | 59 | interface.ip_range_low, | ||
213 | 60 | ) | ||
214 | 61 | |||
215 | 51 | def test_allocate_new_returns_ip_in_correct_range(self): | 62 | def test_allocate_new_returns_ip_in_correct_range(self): |
218 | 52 | low, high = factory.make_ip_range() | 63 | network, static_low, static_high, dynamic_low, dynamic_high = ( |
219 | 53 | ipaddress = StaticIPAddress.objects.allocate_new(low, high) | 64 | self.make_ip_ranges()) |
220 | 65 | ipaddress = StaticIPAddress.objects.allocate_new( | ||
221 | 66 | network, static_low, static_high, dynamic_low, dynamic_high) | ||
222 | 54 | self.assertIsInstance(ipaddress, StaticIPAddress) | 67 | self.assertIsInstance(ipaddress, StaticIPAddress) |
224 | 55 | iprange = IPRange(low, high) | 68 | iprange = IPRange(static_low, static_high) |
225 | 56 | self.assertIn(IPAddress(ipaddress.ip), iprange) | 69 | self.assertIn(IPAddress(ipaddress.ip), iprange) |
226 | 57 | 70 | ||
227 | 58 | def test_allocate_new_allocates_IPv6_address(self): | 71 | def test_allocate_new_allocates_IPv6_address(self): |
230 | 59 | low, high = factory.make_ipv6_range() | 72 | network = factory.make_ipv6_network() |
231 | 60 | ipaddress = StaticIPAddress.objects.allocate_new(low, high) | 73 | network, static_low, static_high, dynamic_low, dynamic_high = ( |
232 | 74 | self.make_ip_ranges(network)) | ||
233 | 75 | ipaddress = StaticIPAddress.objects.allocate_new( | ||
234 | 76 | network, static_low, static_high, dynamic_low, dynamic_high) | ||
235 | 61 | self.assertIsInstance(ipaddress, StaticIPAddress) | 77 | self.assertIsInstance(ipaddress, StaticIPAddress) |
237 | 62 | self.assertIn(IPAddress(ipaddress.ip), IPRange(low, high)) | 78 | self.assertIn( |
238 | 79 | IPAddress(ipaddress.ip), IPRange(static_low, static_high)) | ||
239 | 63 | 80 | ||
240 | 64 | def test_allocate_new_sets_user(self): | 81 | def test_allocate_new_sets_user(self): |
242 | 65 | low, high = factory.make_ip_range() | 82 | network, static_low, static_high, dynamic_low, dynamic_high = ( |
243 | 83 | self.make_ip_ranges()) | ||
244 | 66 | user = factory.make_User() | 84 | user = factory.make_User() |
245 | 67 | ipaddress = StaticIPAddress.objects.allocate_new( | 85 | ipaddress = StaticIPAddress.objects.allocate_new( |
247 | 68 | low, high, alloc_type=IPADDRESS_TYPE.USER_RESERVED, user=user) | 86 | network, static_low, static_high, dynamic_low, dynamic_high, |
248 | 87 | alloc_type=IPADDRESS_TYPE.USER_RESERVED, user=user) | ||
249 | 69 | self.assertEqual(user, ipaddress.user) | 88 | self.assertEqual(user, ipaddress.user) |
250 | 70 | 89 | ||
251 | 71 | def test_allocate_new_with_user_disallows_wrong_alloc_types(self): | 90 | def test_allocate_new_with_user_disallows_wrong_alloc_types(self): |
253 | 72 | low, high = factory.make_ip_range() | 91 | network, static_low, static_high, dynamic_low, dynamic_high = ( |
254 | 92 | self.make_ip_ranges()) | ||
255 | 73 | user = factory.make_User() | 93 | user = factory.make_User() |
256 | 74 | alloc_type = factory.pick_enum( | 94 | alloc_type = factory.pick_enum( |
257 | 75 | IPADDRESS_TYPE, but_not=[IPADDRESS_TYPE.USER_RESERVED]) | 95 | IPADDRESS_TYPE, but_not=[IPADDRESS_TYPE.USER_RESERVED]) |
258 | 76 | self.assertRaises( | 96 | self.assertRaises( |
260 | 77 | AssertionError, StaticIPAddress.objects.allocate_new, low, high, | 97 | AssertionError, StaticIPAddress.objects.allocate_new, network, |
261 | 98 | static_low, static_high, dynamic_low, dynamic_high, | ||
262 | 78 | user=user, alloc_type=alloc_type) | 99 | user=user, alloc_type=alloc_type) |
263 | 79 | 100 | ||
264 | 80 | def test_allocate_new_with_reserved_type_requires_a_user(self): | 101 | def test_allocate_new_with_reserved_type_requires_a_user(self): |
266 | 81 | low, high = factory.make_ip_range() | 102 | network, static_low, static_high, dynamic_low, dynamic_high = ( |
267 | 103 | self.make_ip_ranges()) | ||
268 | 82 | self.assertRaises( | 104 | self.assertRaises( |
270 | 83 | AssertionError, StaticIPAddress.objects.allocate_new, low, high, | 105 | AssertionError, StaticIPAddress.objects.allocate_new, network, |
271 | 106 | static_low, static_high, dynamic_low, dynamic_high, | ||
272 | 84 | alloc_type=IPADDRESS_TYPE.USER_RESERVED) | 107 | alloc_type=IPADDRESS_TYPE.USER_RESERVED) |
273 | 85 | 108 | ||
274 | 86 | def test_allocate_new_compares_by_IP_not_alphabetically(self): | 109 | def test_allocate_new_compares_by_IP_not_alphabetically(self): |
275 | 87 | # Django has a bug that casts IP addresses with HOST(), which | 110 | # Django has a bug that casts IP addresses with HOST(), which |
276 | 88 | # results in alphabetical comparisons of strings instead of IP | 111 | # results in alphabetical comparisons of strings instead of IP |
277 | 89 | # addresses. See https://bugs.launchpad.net/maas/+bug/1338452 | 112 | # addresses. See https://bugs.launchpad.net/maas/+bug/1338452 |
280 | 90 | low = "10.0.0.98" | 113 | network = "10.0.0.0/8" |
281 | 91 | high = "10.0.0.100" | 114 | static_low = "10.0.0.98" |
282 | 115 | static_high = "10.0.0.100" | ||
283 | 116 | dynamic_low = "10.0.0.101" | ||
284 | 117 | dynamic_high = "10.0.0.105" | ||
285 | 92 | factory.make_StaticIPAddress("10.0.0.99") | 118 | factory.make_StaticIPAddress("10.0.0.99") |
287 | 93 | ipaddress = StaticIPAddress.objects.allocate_new(low, high) | 119 | ipaddress = StaticIPAddress.objects.allocate_new( |
288 | 120 | network, static_low, static_high, dynamic_low, dynamic_high) | ||
289 | 94 | self.assertEqual(ipaddress.ip, "10.0.0.98") | 121 | self.assertEqual(ipaddress.ip, "10.0.0.98") |
290 | 95 | 122 | ||
291 | 96 | def test_allocate_new_returns_requested_IP_if_available(self): | 123 | def test_allocate_new_returns_requested_IP_if_available(self): |
294 | 97 | low, high = factory.make_ip_range() | 124 | network, static_low, static_high, dynamic_low, dynamic_high = ( |
295 | 98 | requested_address = low + 1 | 125 | self.make_ip_ranges()) |
296 | 126 | requested_address = unicode(IPAddress(static_low) + 1) | ||
297 | 99 | ipaddress = StaticIPAddress.objects.allocate_new( | 127 | ipaddress = StaticIPAddress.objects.allocate_new( |
299 | 100 | low, high, factory.pick_enum( | 128 | network, static_low, static_high, dynamic_low, dynamic_high, |
300 | 129 | factory.pick_enum( | ||
301 | 101 | IPADDRESS_TYPE, but_not=[IPADDRESS_TYPE.USER_RESERVED]), | 130 | IPADDRESS_TYPE, but_not=[IPADDRESS_TYPE.USER_RESERVED]), |
302 | 102 | requested_address=requested_address) | 131 | requested_address=requested_address) |
303 | 103 | self.assertEqual(requested_address.format(), ipaddress.ip) | 132 | self.assertEqual(requested_address.format(), ipaddress.ip) |
304 | 104 | 133 | ||
305 | 105 | def test_allocate_new_raises_when_requested_IP_unavailable(self): | 134 | def test_allocate_new_raises_when_requested_IP_unavailable(self): |
307 | 106 | low, high = factory.make_ip_range() | 135 | network, static_low, static_high, dynamic_low, dynamic_high = ( |
308 | 136 | self.make_ip_ranges()) | ||
309 | 107 | requested_address = StaticIPAddress.objects.allocate_new( | 137 | requested_address = StaticIPAddress.objects.allocate_new( |
311 | 108 | low, high, factory.pick_enum( | 138 | network, static_low, static_high, dynamic_low, dynamic_high, |
312 | 139 | factory.pick_enum( | ||
313 | 109 | IPADDRESS_TYPE, but_not=[IPADDRESS_TYPE.USER_RESERVED])).ip | 140 | IPADDRESS_TYPE, but_not=[IPADDRESS_TYPE.USER_RESERVED])).ip |
314 | 110 | self.assertRaises( | 141 | self.assertRaises( |
315 | 111 | StaticIPAddressUnavailable, StaticIPAddress.objects.allocate_new, | 142 | StaticIPAddressUnavailable, StaticIPAddress.objects.allocate_new, |
317 | 112 | low, high, requested_address=requested_address) | 143 | network, static_low, static_high, dynamic_low, dynamic_high, |
318 | 144 | requested_address=requested_address) | ||
319 | 113 | 145 | ||
320 | 114 | def test_allocate_new_raises_serialization_error_if_ip_taken(self): | 146 | def test_allocate_new_raises_serialization_error_if_ip_taken(self): |
322 | 115 | low, high = factory.make_ip_range() | 147 | network, static_low, static_high, dynamic_low, dynamic_high = ( |
323 | 148 | self.make_ip_ranges()) | ||
324 | 116 | # Simulate a "IP already taken" error. | 149 | # Simulate a "IP already taken" error. |
325 | 117 | mock_attempt_allocation = self.patch( | 150 | mock_attempt_allocation = self.patch( |
326 | 118 | StaticIPAddress.objects, '_attempt_allocation') | 151 | StaticIPAddress.objects, '_attempt_allocation') |
327 | 119 | mock_attempt_allocation.side_effect = StaticIPAddressUnavailable() | 152 | mock_attempt_allocation.side_effect = StaticIPAddressUnavailable() |
328 | 120 | 153 | ||
329 | 121 | error = self.assertRaises( | 154 | error = self.assertRaises( |
331 | 122 | Exception, StaticIPAddress.objects.allocate_new, low, high) | 155 | Exception, StaticIPAddress.objects.allocate_new, |
332 | 156 | network, static_low, static_high, dynamic_low, dynamic_high) | ||
333 | 123 | self.assertTrue(is_serialization_failure(error)) | 157 | self.assertTrue(is_serialization_failure(error)) |
334 | 124 | 158 | ||
335 | 125 | def test_allocate_new_does_not_use_lock_for_requested_ip(self): | 159 | def test_allocate_new_does_not_use_lock_for_requested_ip(self): |
336 | 126 | # When requesting a specific IP address, there's no need to | 160 | # When requesting a specific IP address, there's no need to |
337 | 127 | # acquire the lock. | 161 | # acquire the lock. |
338 | 128 | lock = self.patch(locks, 'staticip_acquire') | 162 | lock = self.patch(locks, 'staticip_acquire') |
341 | 129 | low, high = factory.make_ip_range() | 163 | network, static_low, static_high, dynamic_low, dynamic_high = ( |
342 | 130 | requested_address = low + 1 | 164 | self.make_ip_ranges()) |
343 | 165 | requested_address = unicode(IPAddress(static_low) + 1) | ||
344 | 131 | ipaddress = StaticIPAddress.objects.allocate_new( | 166 | ipaddress = StaticIPAddress.objects.allocate_new( |
346 | 132 | low, high, requested_address=requested_address) | 167 | network, static_low, static_high, dynamic_low, dynamic_high, |
347 | 168 | requested_address=requested_address) | ||
348 | 133 | self.assertIsInstance(ipaddress, StaticIPAddress) | 169 | self.assertIsInstance(ipaddress, StaticIPAddress) |
349 | 134 | self.assertThat(lock.__enter__, MockNotCalled()) | 170 | self.assertThat(lock.__enter__, MockNotCalled()) |
350 | 135 | 171 | ||
362 | 136 | def test_allocate_new_raises_when_requested_IP_out_of_range(self): | 172 | def test_allocate_new_raises_when_requested_IP_out_of_network(self): |
363 | 137 | low, high = factory.make_ip_range() | 173 | network, static_low, static_high, dynamic_low, dynamic_high = ( |
364 | 138 | requested_address = low - 1 | 174 | self.make_ip_ranges()) |
365 | 139 | e = self.assertRaises( | 175 | other_network = factory.make_ipv4_network(but_not=network) |
366 | 140 | StaticIPAddressOutOfRange, StaticIPAddress.objects.allocate_new, | 176 | requested_address = factory.pick_ip_in_network(other_network) |
367 | 141 | low, high, factory.pick_enum( | 177 | e = self.assertRaises( |
368 | 142 | IPADDRESS_TYPE, but_not=[IPADDRESS_TYPE.USER_RESERVED]), | 178 | StaticIPAddressOutOfRange, StaticIPAddress.objects.allocate_new, |
369 | 143 | requested_address=requested_address) | 179 | network, static_low, static_high, dynamic_low, dynamic_high, |
370 | 144 | self.assertEqual( | 180 | factory.pick_enum( |
371 | 145 | "%s is not inside the range %s to %s" % ( | 181 | IPADDRESS_TYPE, but_not=[IPADDRESS_TYPE.USER_RESERVED]), |
372 | 146 | requested_address, low, high), | 182 | requested_address=requested_address) |
373 | 183 | self.assertEqual( | ||
374 | 184 | "%s is not inside the network %s" % ( | ||
375 | 185 | requested_address, network), | ||
376 | 186 | e.message) | ||
377 | 187 | |||
378 | 188 | def test_allocate_new_raises_when_requested_IP_in_dynamic_range(self): | ||
379 | 189 | network, static_low, static_high, dynamic_low, dynamic_high = ( | ||
380 | 190 | self.make_ip_ranges()) | ||
381 | 191 | requested_address = dynamic_low | ||
382 | 192 | e = self.assertRaises( | ||
383 | 193 | StaticIPAddressOutOfRange, StaticIPAddress.objects.allocate_new, | ||
384 | 194 | network, static_low, static_high, dynamic_low, dynamic_high, | ||
385 | 195 | factory.pick_enum( | ||
386 | 196 | IPADDRESS_TYPE, but_not=[IPADDRESS_TYPE.USER_RESERVED]), | ||
387 | 197 | requested_address=requested_address) | ||
388 | 198 | self.assertEqual( | ||
389 | 199 | "%s is inside the dynamic range %s to %s" % ( | ||
390 | 200 | requested_address, dynamic_low, dynamic_high), | ||
391 | 147 | e.message) | 201 | e.message) |
392 | 148 | 202 | ||
393 | 149 | def test_allocate_new_raises_when_alloc_type_is_None(self): | 203 | def test_allocate_new_raises_when_alloc_type_is_None(self): |
394 | 150 | error = self.assertRaises( | 204 | error = self.assertRaises( |
395 | 151 | ValueError, StaticIPAddress.objects.allocate_new, | 205 | ValueError, StaticIPAddress.objects.allocate_new, |
397 | 152 | sentinel.range_low, sentinel.range_high, alloc_type=None) | 206 | sentinel.network, sentinel.static_range_low, |
398 | 207 | sentinel.static_range_low, sentinel.dynamic_range_low, | ||
399 | 208 | sentinel.dynamic_range_high, alloc_type=None) | ||
400 | 153 | self.assertEqual( | 209 | self.assertEqual( |
401 | 154 | "IP address type None is not a member of IPADDRESS_TYPE.", | 210 | "IP address type None is not a member of IPADDRESS_TYPE.", |
402 | 155 | unicode(error)) | 211 | unicode(error)) |
403 | @@ -157,15 +213,19 @@ | |||
404 | 157 | def test_allocate_new_raises_when_alloc_type_is_invalid(self): | 213 | def test_allocate_new_raises_when_alloc_type_is_invalid(self): |
405 | 158 | error = self.assertRaises( | 214 | error = self.assertRaises( |
406 | 159 | ValueError, StaticIPAddress.objects.allocate_new, | 215 | ValueError, StaticIPAddress.objects.allocate_new, |
408 | 160 | sentinel.range_low, sentinel.range_high, alloc_type=12345) | 216 | sentinel.network, sentinel.static_range_low, |
409 | 217 | sentinel.static_range_low, sentinel.dynamic_range_low, | ||
410 | 218 | sentinel.dynamic_range_high, alloc_type=12345) | ||
411 | 161 | self.assertEqual( | 219 | self.assertEqual( |
412 | 162 | "IP address type 12345 is not a member of IPADDRESS_TYPE.", | 220 | "IP address type 12345 is not a member of IPADDRESS_TYPE.", |
413 | 163 | unicode(error)) | 221 | unicode(error)) |
414 | 164 | 222 | ||
415 | 165 | def test_allocate_new_uses_staticip_acquire_lock(self): | 223 | def test_allocate_new_uses_staticip_acquire_lock(self): |
416 | 166 | lock = self.patch(locks, 'staticip_acquire') | 224 | lock = self.patch(locks, 'staticip_acquire') |
419 | 167 | low, high = factory.make_ip_range() | 225 | network, static_low, static_high, dynamic_low, dynamic_high = ( |
420 | 168 | ipaddress = StaticIPAddress.objects.allocate_new(low, high) | 226 | self.make_ip_ranges()) |
421 | 227 | ipaddress = StaticIPAddress.objects.allocate_new( | ||
422 | 228 | network, static_low, static_high, dynamic_low, dynamic_high) | ||
423 | 169 | self.assertIsInstance(ipaddress, StaticIPAddress) | 229 | self.assertIsInstance(ipaddress, StaticIPAddress) |
424 | 170 | self.assertThat(lock.__enter__, MockCalledOnceWith()) | 230 | self.assertThat(lock.__enter__, MockCalledOnceWith()) |
425 | 171 | self.assertThat( | 231 | self.assertThat( |
426 | @@ -273,15 +333,19 @@ | |||
427 | 273 | ''' | 333 | ''' |
428 | 274 | 334 | ||
429 | 275 | def test_allocate_new_raises_when_addresses_exhausted(self): | 335 | def test_allocate_new_raises_when_addresses_exhausted(self): |
431 | 276 | low = high = "192.168.230.1" | 336 | network = "192.168.230.0/24" |
432 | 337 | static_low = static_high = "192.168.230.1" | ||
433 | 338 | dynamic_low = dynamic_high = "192.168.230.2" | ||
434 | 277 | with transaction.atomic(): | 339 | with transaction.atomic(): |
436 | 278 | StaticIPAddress.objects.allocate_new(low, high) | 340 | StaticIPAddress.objects.allocate_new( |
437 | 341 | network, static_low, static_high, dynamic_low, dynamic_high) | ||
438 | 279 | with transaction.atomic(): | 342 | with transaction.atomic(): |
439 | 280 | e = self.assertRaises( | 343 | e = self.assertRaises( |
440 | 281 | StaticIPAddressExhaustion, | 344 | StaticIPAddressExhaustion, |
442 | 282 | StaticIPAddress.objects.allocate_new, low, high) | 345 | StaticIPAddress.objects.allocate_new, |
443 | 346 | network, static_low, static_high, dynamic_low, dynamic_high) | ||
444 | 283 | self.assertEqual( | 347 | self.assertEqual( |
446 | 284 | "No more IPs available in range %s-%s" % (low, high), | 348 | "No more IPs available in range %s-%s" % (static_low, static_high), |
447 | 285 | unicode(e)) | 349 | unicode(e)) |
448 | 286 | 350 | ||
449 | 287 | 351 | ||
450 | 288 | 352 | ||
451 | === modified file 'src/maasserver/static/js/angular/controllers/add_device.js' | |||
452 | --- src/maasserver/static/js/angular/controllers/add_device.js 2015-04-03 18:35:20 +0000 | |||
453 | +++ src/maasserver/static/js/angular/controllers/add_device.js 2015-04-23 19:17:12 +0000 | |||
454 | @@ -139,16 +139,16 @@ | |||
455 | 139 | if(!ValidationService.validateIP($scope.device.ipAddress)) { | 139 | if(!ValidationService.validateIP($scope.device.ipAddress)) { |
456 | 140 | return true; | 140 | return true; |
457 | 141 | } | 141 | } |
459 | 142 | var i, inRange, managedInterfaces = $scope.getManagedInterfaces(); | 142 | var i, inNetwork, managedInterfaces = $scope.getManagedInterfaces(); |
460 | 143 | if(angular.isObject($scope.device.ipAssignment)){ | 143 | if(angular.isObject($scope.device.ipAssignment)){ |
461 | 144 | if($scope.device.ipAssignment.name === "external") { | 144 | if($scope.device.ipAssignment.name === "external") { |
462 | 145 | // External IP address cannot be within a managed interface | 145 | // External IP address cannot be within a managed interface |
463 | 146 | // on one of the clusters. | 146 | // on one of the clusters. |
464 | 147 | for(i = 0; i < managedInterfaces.length; i++) { | 147 | for(i = 0; i < managedInterfaces.length; i++) { |
466 | 148 | inRange = ValidationService.validateIPInRange( | 148 | inNetwork = ValidationService.validateIPInNetwork( |
467 | 149 | $scope.device.ipAddress, | 149 | $scope.device.ipAddress, |
468 | 150 | managedInterfaces[i].network); | 150 | managedInterfaces[i].network); |
470 | 151 | if(inRange) { | 151 | if(inNetwork) { |
471 | 152 | return true; | 152 | return true; |
472 | 153 | } | 153 | } |
473 | 154 | } | 154 | } |
474 | @@ -158,9 +158,13 @@ | |||
475 | 158 | // of the selected clusterInterface. | 158 | // of the selected clusterInterface. |
476 | 159 | var clusterInterface = getInterfaceById( | 159 | var clusterInterface = getInterfaceById( |
477 | 160 | $scope.device.clusterInterfaceId); | 160 | $scope.device.clusterInterfaceId); |
479 | 161 | inRange = ValidationService.validateIPInRange( | 161 | inNetwork = ValidationService.validateIPInNetwork( |
480 | 162 | $scope.device.ipAddress, clusterInterface.network); | 162 | $scope.device.ipAddress, clusterInterface.network); |
482 | 163 | if(!inRange) { | 163 | var inDynamicRange = ValidationService.validateIPInRange( |
483 | 164 | $scope.device.ipAddress, clusterInterface.network, | ||
484 | 165 | clusterInterface.dynamic_range.low, | ||
485 | 166 | clusterInterface.dynamic_range.high); | ||
486 | 167 | if(!inNetwork || inDynamicRange) { | ||
487 | 164 | return true; | 168 | return true; |
488 | 165 | } | 169 | } |
489 | 166 | } | 170 | } |
490 | 167 | 171 | ||
491 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_add_device.js' | |||
492 | --- src/maasserver/static/js/angular/controllers/tests/test_add_device.js 2015-04-03 18:35:20 +0000 | |||
493 | +++ src/maasserver/static/js/angular/controllers/tests/test_add_device.js 2015-04-23 19:17:12 +0000 | |||
494 | @@ -403,6 +403,19 @@ | |||
495 | 403 | }; | 403 | }; |
496 | 404 | expect($scope.ipHasError()).toBe(true); | 404 | expect($scope.ipHasError()).toBe(true); |
497 | 405 | }); | 405 | }); |
498 | 406 | |||
499 | 407 | it("returns true if static ip in dynamic range of network", function() { | ||
500 | 408 | var controller = makeController(); | ||
501 | 409 | var nic = makeManagedClusterInterface(); | ||
502 | 410 | var cluster = makeCluster([nic]); | ||
503 | 411 | $scope.clusters = [cluster]; | ||
504 | 412 | $scope.device.ipAddress = nic.dynamic_range.low; | ||
505 | 413 | $scope.device.clusterInterfaceId = nic.id; | ||
506 | 414 | $scope.device.ipAssignment = { | ||
507 | 415 | name: "static" | ||
508 | 416 | }; | ||
509 | 417 | expect($scope.ipHasError()).toBe(true); | ||
510 | 418 | }); | ||
511 | 406 | }); | 419 | }); |
512 | 407 | 420 | ||
513 | 408 | describe("deviceHasError", function() { | 421 | describe("deviceHasError", function() { |
514 | 409 | 422 | ||
515 | === modified file 'src/maasserver/static/js/angular/services/tests/test_validation.js' | |||
516 | --- src/maasserver/static/js/angular/services/tests/test_validation.js 2015-04-03 18:26:06 +0000 | |||
517 | +++ src/maasserver/static/js/angular/services/tests/test_validation.js 2015-04-23 19:17:12 +0000 | |||
518 | @@ -267,51 +267,200 @@ | |||
519 | 267 | }); | 267 | }); |
520 | 268 | }); | 268 | }); |
521 | 269 | 269 | ||
523 | 270 | describe("validateIPInRange", function() { | 270 | describe("validateIPInNetwork", function() { |
524 | 271 | 271 | ||
525 | 272 | var scenarios = [ | 272 | var scenarios = [ |
526 | 273 | { | 273 | { |
527 | 274 | ip: "192.168.2.1", | 274 | ip: "192.168.2.1", |
529 | 275 | range: "192.168.1.0/24", | 275 | network: "192.168.1.0/24", |
530 | 276 | valid: false | 276 | valid: false |
531 | 277 | }, | 277 | }, |
532 | 278 | { | 278 | { |
533 | 279 | ip: "192.168.1.1", | 279 | ip: "192.168.1.1", |
535 | 280 | range: "192.168.1.0/24", | 280 | network: "192.168.1.0/24", |
536 | 281 | valid: true | 281 | valid: true |
537 | 282 | }, | 282 | }, |
538 | 283 | { | 283 | { |
539 | 284 | ip: "192.168.1.1", | 284 | ip: "192.168.1.1", |
541 | 285 | range: "172.16.0.0/16", | 285 | network: "172.16.0.0/16", |
542 | 286 | valid: false | 286 | valid: false |
543 | 287 | }, | 287 | }, |
544 | 288 | { | 288 | { |
545 | 289 | ip: "172.17.1.1", | 289 | ip: "172.17.1.1", |
547 | 290 | range: "172.16.0.0/16", | 290 | network: "172.16.0.0/16", |
548 | 291 | valid: false | 291 | valid: false |
549 | 292 | }, | 292 | }, |
550 | 293 | { | 293 | { |
551 | 294 | ip: "172.16.1.1", | 294 | ip: "172.16.1.1", |
553 | 295 | range: "172.16.0.0/16", | 295 | network: "172.16.0.0/16", |
554 | 296 | valid: true | 296 | valid: true |
555 | 297 | }, | 297 | }, |
556 | 298 | { | 298 | { |
557 | 299 | ip: "11.1.1.1", | 299 | ip: "11.1.1.1", |
559 | 300 | range: "10.0.0.0/8", | 300 | network: "10.0.0.0/8", |
560 | 301 | valid: false | 301 | valid: false |
561 | 302 | }, | 302 | }, |
562 | 303 | { | 303 | { |
563 | 304 | ip: "10.1.1.1", | 304 | ip: "10.1.1.1", |
571 | 305 | range: "10.0.0.0/8", | 305 | network: "10.0.0.0/8", |
572 | 306 | valid: true | 306 | valid: true |
573 | 307 | } | 307 | }, |
574 | 308 | ]; | 308 | { |
575 | 309 | 309 | ip: "2001:67C:1562::16", | |
576 | 310 | angular.forEach(scenarios, function(scenario) { | 310 | network: "2001:67C:1562::0/32", |
577 | 311 | it("validates: " + scenario.ip + " in range: " + scenario.range, | 311 | valid: true |
578 | 312 | }, | ||
579 | 313 | { | ||
580 | 314 | ip: "2002:67C:1562::16", | ||
581 | 315 | network: "2001:67C:1562::0/32", | ||
582 | 316 | valid: false | ||
583 | 317 | }, | ||
584 | 318 | { | ||
585 | 319 | ip: "2001:67C:1561::16", | ||
586 | 320 | network: "2001:67C:1562::0/64", | ||
587 | 321 | valid: false | ||
588 | 322 | } | ||
589 | 323 | ]; | ||
590 | 324 | |||
591 | 325 | angular.forEach(scenarios, function(scenario) { | ||
592 | 326 | it("validates: " + scenario.ip + " in network: " + scenario.network, | ||
593 | 327 | function() { | ||
594 | 328 | var result = ValidationService.validateIPInNetwork( | ||
595 | 329 | scenario.ip, scenario.network); | ||
596 | 330 | expect(result).toBe(scenario.valid); | ||
597 | 331 | }); | ||
598 | 332 | }); | ||
599 | 333 | }); | ||
600 | 334 | |||
601 | 335 | describe("validateIPInRange", function() { | ||
602 | 336 | |||
603 | 337 | var scenarios = [ | ||
604 | 338 | { | ||
605 | 339 | ip: "192.168.1.1", | ||
606 | 340 | network: "192.168.1.0/24", | ||
607 | 341 | range: { | ||
608 | 342 | low: "192.168.1.2", | ||
609 | 343 | high: "192.168.1.100" | ||
610 | 344 | }, | ||
611 | 345 | valid: false | ||
612 | 346 | }, | ||
613 | 347 | { | ||
614 | 348 | ip: "192.168.1.2", | ||
615 | 349 | network: "192.168.1.0/24", | ||
616 | 350 | range: { | ||
617 | 351 | low: "192.168.1.2", | ||
618 | 352 | high: "192.168.1.100" | ||
619 | 353 | }, | ||
620 | 354 | valid: true | ||
621 | 355 | }, | ||
622 | 356 | { | ||
623 | 357 | ip: "192.168.1.3", | ||
624 | 358 | network: "192.168.1.0/24", | ||
625 | 359 | range: { | ||
626 | 360 | low: "192.168.1.2", | ||
627 | 361 | high: "192.168.1.100" | ||
628 | 362 | }, | ||
629 | 363 | valid: true | ||
630 | 364 | }, | ||
631 | 365 | { | ||
632 | 366 | ip: "192.168.1.100", | ||
633 | 367 | network: "192.168.1.0/24", | ||
634 | 368 | range: { | ||
635 | 369 | low: "192.168.1.2", | ||
636 | 370 | high: "192.168.1.100" | ||
637 | 371 | }, | ||
638 | 372 | valid: true | ||
639 | 373 | }, | ||
640 | 374 | { | ||
641 | 375 | ip: "192.168.1.101", | ||
642 | 376 | network: "192.168.1.0/24", | ||
643 | 377 | range: { | ||
644 | 378 | low: "192.168.1.2", | ||
645 | 379 | high: "192.168.1.100" | ||
646 | 380 | }, | ||
647 | 381 | valid: false | ||
648 | 382 | }, | ||
649 | 383 | { | ||
650 | 384 | ip: "192.168.1.2", | ||
651 | 385 | network: "192.168.2.0/24", | ||
652 | 386 | range: { | ||
653 | 387 | low: "192.168.2.2", | ||
654 | 388 | high: "192.168.2.100" | ||
655 | 389 | }, | ||
656 | 390 | valid: false | ||
657 | 391 | }, | ||
658 | 392 | { | ||
659 | 393 | ip: "2001:67C:1562::1", | ||
660 | 394 | network: "2001:67C:1562::0/32", | ||
661 | 395 | range: { | ||
662 | 396 | low: "2001:67C:1562::2", | ||
663 | 397 | high: "2001:67C:1562::FFFF:FFFF" | ||
664 | 398 | }, | ||
665 | 399 | valid: false | ||
666 | 400 | }, | ||
667 | 401 | { | ||
668 | 402 | ip: "2001:67C:1562::2", | ||
669 | 403 | network: "2001:67C:1562::0/32", | ||
670 | 404 | range: { | ||
671 | 405 | low: "2001:67C:1562::2", | ||
672 | 406 | high: "2001:67C:1562::FFFF:FFFF" | ||
673 | 407 | }, | ||
674 | 408 | valid: true | ||
675 | 409 | }, | ||
676 | 410 | { | ||
677 | 411 | ip: "2001:67C:1562::3", | ||
678 | 412 | network: "2001:67C:1562::0/32", | ||
679 | 413 | range: { | ||
680 | 414 | low: "2001:67C:1562::2", | ||
681 | 415 | high: "2001:67C:1562::FFFF:FFFF" | ||
682 | 416 | }, | ||
683 | 417 | valid: true | ||
684 | 418 | }, | ||
685 | 419 | { | ||
686 | 420 | ip: "2001:67C:1562::FFFF:FFFE", | ||
687 | 421 | network: "2001:67C:1562::0/32", | ||
688 | 422 | range: { | ||
689 | 423 | low: "2001:67C:1562::2", | ||
690 | 424 | high: "2001:67C:1562::FFFF:FFFF" | ||
691 | 425 | }, | ||
692 | 426 | valid: true | ||
693 | 427 | }, | ||
694 | 428 | { | ||
695 | 429 | ip: "2001:67C:1562::FFFF:FFFF", | ||
696 | 430 | network: "2001:67C:1562::0/32", | ||
697 | 431 | range: { | ||
698 | 432 | low: "2001:67C:1562::2", | ||
699 | 433 | high: "2001:67C:1562::FFFF:FFFF" | ||
700 | 434 | }, | ||
701 | 435 | valid: true | ||
702 | 436 | }, | ||
703 | 437 | { | ||
704 | 438 | ip: "2001:67C:1562::1:0:0", | ||
705 | 439 | network: "2001:67C:1562::0/32", | ||
706 | 440 | range: { | ||
707 | 441 | low: "2001:67C:1562::2", | ||
708 | 442 | high: "2001:67C:1562::FFFF:FFFF" | ||
709 | 443 | }, | ||
710 | 444 | valid: false | ||
711 | 445 | }, | ||
712 | 446 | { | ||
713 | 447 | ip: "2001:67C:1562::2", | ||
714 | 448 | network: "2001:67C:1563::0/64", | ||
715 | 449 | range: { | ||
716 | 450 | low: "2001:67C:1563::2", | ||
717 | 451 | high: "2001:67C:1563::FFFF:FFFF" | ||
718 | 452 | }, | ||
719 | 453 | valid: false | ||
720 | 454 | } | ||
721 | 455 | ]; | ||
722 | 456 | |||
723 | 457 | angular.forEach(scenarios, function(scenario) { | ||
724 | 458 | it("validates: " + scenario.ip + " in range: " + | ||
725 | 459 | scenario.range.low + " - " + scenario.range.high, | ||
726 | 312 | function() { | 460 | function() { |
727 | 313 | var result = ValidationService.validateIPInRange( | 461 | var result = ValidationService.validateIPInRange( |
729 | 314 | scenario.ip, scenario.range); | 462 | scenario.ip, scenario.network, |
730 | 463 | scenario.range.low, scenario.range.high); | ||
731 | 315 | expect(result).toBe(scenario.valid); | 464 | expect(result).toBe(scenario.valid); |
732 | 316 | }); | 465 | }); |
733 | 317 | }); | 466 | }); |
734 | 318 | 467 | ||
735 | === modified file 'src/maasserver/static/js/angular/services/validation.js' | |||
736 | --- src/maasserver/static/js/angular/services/validation.js 2015-04-03 18:26:06 +0000 | |||
737 | +++ src/maasserver/static/js/angular/services/validation.js 2015-04-23 19:17:12 +0000 | |||
738 | @@ -155,7 +155,7 @@ | |||
739 | 155 | }; | 155 | }; |
740 | 156 | 156 | ||
741 | 157 | // Return true if the ipAddress is in the network. | 157 | // Return true if the ipAddress is in the network. |
743 | 158 | this.validateIPInRange = function(ipAddress, network) { | 158 | this.validateIPInNetwork = function(ipAddress, network) { |
744 | 159 | var networkSplit = network.split('/'); | 159 | var networkSplit = network.split('/'); |
745 | 160 | var networkAddress = networkSplit[0]; | 160 | var networkAddress = networkSplit[0]; |
746 | 161 | var cidrBits = parseInt(networkSplit[1], 10); | 161 | var cidrBits = parseInt(networkSplit[1], 10); |
747 | @@ -175,4 +175,51 @@ | |||
748 | 175 | } | 175 | } |
749 | 176 | return false; | 176 | return false; |
750 | 177 | }; | 177 | }; |
751 | 178 | |||
752 | 179 | // Return true if the ipAddress is in the network and between the | ||
753 | 180 | // lowAddress and highAddress inclusive. | ||
754 | 181 | this.validateIPInRange = function( | ||
755 | 182 | ipAddress, network, lowAddress, highAddress) { | ||
756 | 183 | // If the ip address is not even in the network then its | ||
757 | 184 | // not in the range. | ||
758 | 185 | if(!this.validateIPInNetwork(ipAddress, network)) { | ||
759 | 186 | return false; | ||
760 | 187 | } | ||
761 | 188 | |||
762 | 189 | var i, ipOctets, lowOctets, highOctets; | ||
763 | 190 | if(this.validateIPv4(ipAddress) && | ||
764 | 191 | this.validateIPv4(lowAddress) && | ||
765 | 192 | this.validateIPv4(highAddress)) { | ||
766 | 193 | |||
767 | 194 | // Check that each octet is of the ip address is more or equal | ||
768 | 195 | // to the low address and less or equal to the high address. | ||
769 | 196 | ipOctets = ipv4ToOctets(ipAddress); | ||
770 | 197 | lowOctets = ipv4ToOctets(lowAddress); | ||
771 | 198 | highOctets = ipv4ToOctets(highAddress); | ||
772 | 199 | for(i = 0; i < 4; i++) { | ||
773 | 200 | if(ipOctets[i] > highOctets[i] || | ||
774 | 201 | ipOctets[i] < lowOctets[i]) { | ||
775 | 202 | return false; | ||
776 | 203 | } | ||
777 | 204 | } | ||
778 | 205 | return true; | ||
779 | 206 | } else if(this.validateIPv6(ipAddress) && | ||
780 | 207 | this.validateIPv6(lowAddress) && | ||
781 | 208 | this.validateIPv6(highAddress)) { | ||
782 | 209 | |||
783 | 210 | // Check that each octet is of the ip address is more or equal | ||
784 | 211 | // to the low address and less or equal to the high address. | ||
785 | 212 | ipOctets = ipv6ToOctets(ipAddress); | ||
786 | 213 | lowOctets = ipv6ToOctets(lowAddress); | ||
787 | 214 | highOctets = ipv6ToOctets(highAddress); | ||
788 | 215 | for(i = 0; i < 8; i++) { | ||
789 | 216 | if(ipOctets[i] > highOctets[i] || | ||
790 | 217 | ipOctets[i] < lowOctets[i]) { | ||
791 | 218 | return false; | ||
792 | 219 | } | ||
793 | 220 | } | ||
794 | 221 | return true; | ||
795 | 222 | } | ||
796 | 223 | return false; | ||
797 | 224 | }; | ||
798 | 178 | }); | 225 | }); |
799 | 179 | 226 | ||
800 | === modified file 'src/maasserver/tests/test_forms_nodegroupinterface.py' | |||
801 | --- src/maasserver/tests/test_forms_nodegroupinterface.py 2015-03-25 15:33:23 +0000 | |||
802 | +++ src/maasserver/tests/test_forms_nodegroupinterface.py 2015-04-23 19:17:12 +0000 | |||
803 | @@ -212,7 +212,9 @@ | |||
804 | 212 | network=network) | 212 | network=network) |
805 | 213 | [interface] = nodegroup.get_managed_interfaces() | 213 | [interface] = nodegroup.get_managed_interfaces() |
806 | 214 | StaticIPAddress.objects.allocate_new( | 214 | StaticIPAddress.objects.allocate_new( |
808 | 215 | interface.static_ip_range_low, interface.static_ip_range_high) | 215 | interface.network, interface.static_ip_range_low, |
809 | 216 | interface.static_ip_range_high, interface.ip_range_low, | ||
810 | 217 | interface.ip_range_high) | ||
811 | 216 | form = NodeGroupInterfaceForm( | 218 | form = NodeGroupInterfaceForm( |
812 | 217 | data={'static_ip_range_low': '', 'static_ip_range_high': ''}, | 219 | data={'static_ip_range_low': '', 'static_ip_range_high': ''}, |
813 | 218 | instance=interface) | 220 | instance=interface) |
814 | 219 | 221 | ||
815 | === modified file 'src/maasserver/tests/test_forms_validate_new_static_ip_range.py' | |||
816 | --- src/maasserver/tests/test_forms_validate_new_static_ip_range.py 2015-03-25 15:33:23 +0000 | |||
817 | +++ src/maasserver/tests/test_forms_validate_new_static_ip_range.py 2015-04-23 19:17:12 +0000 | |||
818 | @@ -49,7 +49,8 @@ | |||
819 | 49 | 49 | ||
820 | 50 | def test_raises_error_when_allocated_ips_fall_outside_new_range(self): | 50 | def test_raises_error_when_allocated_ips_fall_outside_new_range(self): |
821 | 51 | interface = self.make_interface() | 51 | interface = self.make_interface() |
823 | 52 | StaticIPAddress.objects.allocate_new('10.1.0.56', '10.1.0.60') | 52 | StaticIPAddress.objects.allocate_new( |
824 | 53 | '10.1.0.0/16', '10.1.0.56', '10.1.0.60', '10.1.0.1', '10.1.0.10') | ||
825 | 53 | error = self.assertRaises( | 54 | error = self.assertRaises( |
826 | 54 | ValidationError, | 55 | ValidationError, |
827 | 55 | validate_new_static_ip_ranges, | 56 | validate_new_static_ip_ranges, |
828 | @@ -62,7 +63,8 @@ | |||
829 | 62 | 63 | ||
830 | 63 | def test_removing_static_range_raises_error_if_ips_allocated(self): | 64 | def test_removing_static_range_raises_error_if_ips_allocated(self): |
831 | 64 | interface = self.make_interface() | 65 | interface = self.make_interface() |
833 | 65 | StaticIPAddress.objects.allocate_new('10.1.0.56', '10.1.0.60') | 66 | StaticIPAddress.objects.allocate_new( |
834 | 67 | '10.1.0.0/16', '10.1.0.56', '10.1.0.60', '10.1.0.1', '10.1.0.10') | ||
835 | 66 | error = self.assertRaises( | 68 | error = self.assertRaises( |
836 | 67 | ValidationError, | 69 | ValidationError, |
837 | 68 | validate_new_static_ip_ranges, | 70 | validate_new_static_ip_ranges, |
838 | @@ -75,7 +77,8 @@ | |||
839 | 75 | 77 | ||
840 | 76 | def test_allows_range_expansion(self): | 78 | def test_allows_range_expansion(self): |
841 | 77 | interface = self.make_interface() | 79 | interface = self.make_interface() |
843 | 78 | StaticIPAddress.objects.allocate_new('10.1.0.56', '10.1.0.60') | 80 | StaticIPAddress.objects.allocate_new( |
844 | 81 | '10.1.0.0/16', '10.1.0.56', '10.1.0.60', '10.1.0.1', '10.1.0.10') | ||
845 | 79 | is_valid = validate_new_static_ip_ranges( | 82 | is_valid = validate_new_static_ip_ranges( |
846 | 80 | interface, static_ip_range_low='10.1.0.40', | 83 | interface, static_ip_range_low='10.1.0.40', |
847 | 81 | static_ip_range_high='10.1.0.100') | 84 | static_ip_range_high='10.1.0.100') |
848 | @@ -83,7 +86,8 @@ | |||
849 | 83 | 86 | ||
850 | 84 | def test_allows_allocated_ip_as_upper_bound(self): | 87 | def test_allows_allocated_ip_as_upper_bound(self): |
851 | 85 | interface = self.make_interface() | 88 | interface = self.make_interface() |
853 | 86 | StaticIPAddress.objects.allocate_new('10.1.0.55', '10.1.0.55') | 89 | StaticIPAddress.objects.allocate_new( |
854 | 90 | '10.1.0.0/16', '10.1.0.55', '10.1.0.55', '10.1.0.1', '10.1.0.10') | ||
855 | 87 | is_valid = validate_new_static_ip_ranges( | 91 | is_valid = validate_new_static_ip_ranges( |
856 | 88 | interface, | 92 | interface, |
857 | 89 | static_ip_range_low=interface.static_ip_range_low, | 93 | static_ip_range_low=interface.static_ip_range_low, |
858 | @@ -92,7 +96,8 @@ | |||
859 | 92 | 96 | ||
860 | 93 | def test_allows_allocated_ip_as_lower_bound(self): | 97 | def test_allows_allocated_ip_as_lower_bound(self): |
861 | 94 | interface = self.make_interface() | 98 | interface = self.make_interface() |
863 | 95 | StaticIPAddress.objects.allocate_new('10.1.0.55', '10.1.0.55') | 99 | StaticIPAddress.objects.allocate_new( |
864 | 100 | '10.1.0.0/16', '10.1.0.55', '10.1.0.55', '10.1.0.1', '10.1.0.10') | ||
865 | 96 | is_valid = validate_new_static_ip_ranges( | 101 | is_valid = validate_new_static_ip_ranges( |
866 | 97 | interface, static_ip_range_low='10.1.0.55', | 102 | interface, static_ip_range_low='10.1.0.55', |
867 | 98 | static_ip_range_high=interface.static_ip_range_high) | 103 | static_ip_range_high=interface.static_ip_range_high) |
868 | @@ -103,7 +108,9 @@ | |||
869 | 103 | interface.management = NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED | 108 | interface.management = NODEGROUPINTERFACE_MANAGEMENT.UNMANAGED |
870 | 104 | interface.save() | 109 | interface.save() |
871 | 105 | StaticIPAddress.objects.allocate_new( | 110 | StaticIPAddress.objects.allocate_new( |
873 | 106 | interface.static_ip_range_low, interface.static_ip_range_high) | 111 | interface.network, interface.static_ip_range_low, |
874 | 112 | interface.static_ip_range_high, interface.ip_range_low, | ||
875 | 113 | interface.ip_range_high) | ||
876 | 107 | is_valid = validate_new_static_ip_ranges( | 114 | is_valid = validate_new_static_ip_ranges( |
877 | 108 | interface, static_ip_range_low='10.1.0.57', | 115 | interface, static_ip_range_low='10.1.0.57', |
878 | 109 | static_ip_range_high='10.1.0.58') | 116 | static_ip_range_high='10.1.0.58') |
879 | @@ -114,7 +121,8 @@ | |||
880 | 114 | interface.static_ip_range_low = None | 121 | interface.static_ip_range_low = None |
881 | 115 | interface.static_ip_range_high = None | 122 | interface.static_ip_range_high = None |
882 | 116 | interface.save() | 123 | interface.save() |
884 | 117 | StaticIPAddress.objects.allocate_new('10.1.0.56', '10.1.0.60') | 124 | StaticIPAddress.objects.allocate_new( |
885 | 125 | '10.1.0.0/16', '10.1.0.56', '10.1.0.60', '10.1.0.1', '10.1.0.10') | ||
886 | 118 | is_valid = validate_new_static_ip_ranges( | 126 | is_valid = validate_new_static_ip_ranges( |
887 | 119 | interface, static_ip_range_low='10.1.0.57', | 127 | interface, static_ip_range_low='10.1.0.57', |
888 | 120 | static_ip_range_high='10.1.0.58') | 128 | static_ip_range_high='10.1.0.58') |
889 | @@ -123,7 +131,9 @@ | |||
890 | 123 | def test_ignores_unchanged_static_range(self): | 131 | def test_ignores_unchanged_static_range(self): |
891 | 124 | interface = self.make_interface() | 132 | interface = self.make_interface() |
892 | 125 | StaticIPAddress.objects.allocate_new( | 133 | StaticIPAddress.objects.allocate_new( |
894 | 126 | interface.static_ip_range_low, interface.static_ip_range_high) | 134 | interface.network, interface.static_ip_range_low, |
895 | 135 | interface.static_ip_range_high, interface.ip_range_low, | ||
896 | 136 | interface.ip_range_high) | ||
897 | 127 | is_valid = validate_new_static_ip_ranges( | 137 | is_valid = validate_new_static_ip_ranges( |
898 | 128 | interface, | 138 | interface, |
899 | 129 | static_ip_range_low=interface.static_ip_range_low, | 139 | static_ip_range_low=interface.static_ip_range_low, |
900 | 130 | 140 | ||
901 | === modified file 'src/maasserver/tests/test_node_action.py' | |||
902 | --- src/maasserver/tests/test_node_action.py 2015-04-16 13:49:51 +0000 | |||
903 | +++ src/maasserver/tests/test_node_action.py 2015-04-23 19:17:12 +0000 | |||
904 | @@ -485,7 +485,8 @@ | |||
905 | 485 | ngi.save() | 485 | ngi.save() |
906 | 486 | with transaction.atomic(): | 486 | with transaction.atomic(): |
907 | 487 | StaticIPAddress.objects.allocate_new( | 487 | StaticIPAddress.objects.allocate_new( |
909 | 488 | ngi.static_ip_range_high, ngi.static_ip_range_low) | 488 | ngi.network, ngi.static_ip_range_low, ngi.static_ip_range_high, |
910 | 489 | ngi.ip_range_low, ngi.ip_range_high) | ||
911 | 489 | 490 | ||
912 | 490 | e = self.assertRaises(NodeActionError, Deploy(node, user).execute) | 491 | e = self.assertRaises(NodeActionError, Deploy(node, user).execute) |
913 | 491 | self.expectThat( | 492 | self.expectThat( |
Except for the static range? or except for the dynamic range?